Refactor more keys spec handling

Change-Id: Ibd95eff0725724ea35ad58e1d1ec7ec3d02a6e90
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index f839376..8c71237 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -103,11 +103,12 @@
     public final CharSequence mOutputText;
     /** More keys */
     public final String[] mMoreKeys;
-    /** More keys maximum column number */
-    public final int mMaxMoreKeysColumn;
-    public static final int MORE_KEYS_FIXED_COLUMN_ORDER = 0x80000000;
-    private static final String AUTO_COLUMN_ORDER = "!autoColumnOrder!";
-    private static final String FIXED_COLUMN_ORDER = "!fixedColumnOrder!";
+    /** More keys column number and flags */
+    private final int mMoreKeysColumnAndFlags;
+    private static final int MORE_KEYS_COLUMN_MASK = 0x000000ff;
+    private static final int MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER = 0x80000000;
+    private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!";
+    private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!";
 
     /** Background type that represents different key background visual than normal one. */
     public final int mBackgroundType;
@@ -146,7 +147,7 @@
      * This constructor is being used only for key in popup suggestions pane.
      */
     public Key(Keyboard.Params params, String label, String hintLabel, int iconId,
-            int code, CharSequence outputText, int x, int y, int width, int height) {
+            int code, String outputText, int x, int y, int width, int height) {
         mHeight = height - params.mVerticalGap;
         mHorizontalGap = params.mHorizontalGap;
         mVerticalGap = params.mVerticalGap;
@@ -157,7 +158,7 @@
         mBackgroundType = BACKGROUND_TYPE_NORMAL;
         mActionFlags = 0;
         mMoreKeys = null;
-        mMaxMoreKeysColumn = 0;
+        mMoreKeysColumnAndFlags = 0;
         mLabel = label;
         mOutputText = outputText;
         mCode = code;
@@ -236,15 +237,18 @@
         final boolean preserveCase = (mLabelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0;
         int actionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
         String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
-        int column;
-        if ((column = parseMoreKeysColumnOrder(moreKeys, AUTO_COLUMN_ORDER)) > 0) {
-            mMaxMoreKeysColumn = column;
-        } else if ((column = parseMoreKeysColumnOrder(moreKeys, FIXED_COLUMN_ORDER)) > 0) {
-            mMaxMoreKeysColumn = column | MORE_KEYS_FIXED_COLUMN_ORDER;
-        } else {
-            mMaxMoreKeysColumn = style.getInt(keyAttr,
+
+        int moreKeysColumn = style.getInt(keyAttr,
                     R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn);
+        int value;
+        if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
+            moreKeysColumn = value & MORE_KEYS_COLUMN_MASK;
         }
+        if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
+            moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK);
+        }
+        mMoreKeysColumnAndFlags = moreKeysColumn;
+
         final String[] additionalMoreKeys = style.getStringArray(
                 keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys);
         moreKeys = KeySpecParser.insertAddtionalMoreKeys(moreKeys, additionalMoreKeys);
@@ -311,21 +315,6 @@
         }
     }
 
-    private static int parseMoreKeysColumnOrder(String[] moreKeys, String key) {
-        if (moreKeys == null || moreKeys.length == 0 || moreKeys[0] == null
-                || !moreKeys[0].startsWith(key)) {
-            return -1;
-        }
-        try {
-            final int column = Integer.parseInt(moreKeys[0].substring(key.length()));
-            moreKeys[0] = null;
-            return column;
-        } catch (NumberFormatException e) {
-            Log.w(TAG, "column number should follow after " + key);
-            return 0;
-        }
-    }
-
     private static int adjustCaseOfCodeForKeyboardId(int code, boolean preserveCase,
             KeyboardId id) {
         if (!Keyboard.isLetterCode(code) || preserveCase) return code;
@@ -532,6 +521,14 @@
         return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0;
     }
 
+    public int getMoreKeysColumn() {
+        return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK;
+    }
+
+    public boolean isFixedColumnOrderMoreKeys() {
+        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0;
+    }
+
     public Drawable getIcon(KeyboardIconsSet iconSet) {
         return iconSet.getIconDrawable(mIconId);
     }
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 7d8181d..a6dcd88 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -34,7 +34,7 @@
     }
 
     public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> {
-        private final String[] mMoreKeys;
+        private final Key mParentKey;
 
         public static class MoreKeysKeyboardParams extends Keyboard.Params {
             public boolean mIsFixedOrder;
@@ -49,28 +49,20 @@
                 super();
             }
 
-            /* package for test */MoreKeysKeyboardParams(int numKeys, int maxColumns, int keyWidth,
-                    int rowHeight, int coordXInParent, int parentKeyboardWidth) {
-                super();
-                setParameters(numKeys, maxColumns, keyWidth, rowHeight, coordXInParent,
-                        parentKeyboardWidth);
-            }
-
             /**
              * Set keyboard parameters of more keys keyboard.
              *
              * @param numKeys number of keys in this more keys keyboard.
-             * @param maxColumnsAndFlags number of maximum columns of this more keys keyboard.
-             * This might have {@link Key#MORE_KEYS_FIXED_COLUMN_ORDER} flag.
+             * @param maxColumns number of maximum columns of this more keys keyboard.
              * @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
              * @param rowHeight more keys keyboard row height in pixel, including vertical gap.
              * @param coordXInParent coordinate x of the key preview in parent keyboard.
              * @param parentKeyboardWidth parent keyboard width in pixel.
+             * @param isFixedColumnOrder if true, more keys should be laid out in fixed order.
              */
-            public void setParameters(int numKeys, int maxColumnsAndFlags, int keyWidth,
-                    int rowHeight, int coordXInParent, int parentKeyboardWidth) {
-                mIsFixedOrder = (maxColumnsAndFlags & Key.MORE_KEYS_FIXED_COLUMN_ORDER) != 0;
-                final int maxColumns = maxColumnsAndFlags & ~Key.MORE_KEYS_FIXED_COLUMN_ORDER;
+            public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
+                    int coordXInParent, int parentKeyboardWidth, boolean isFixedColumnOrder) {
+                mIsFixedOrder = isFixedColumnOrder;
                 if (parentKeyboardWidth / keyWidth < maxColumns) {
                     throw new IllegalArgumentException(
                             "Keyboard is too small to hold more keys keyboard: "
@@ -253,7 +245,7 @@
             // TODO: More keys keyboard's vertical gap is currently calculated heuristically.
             // Should revise the algorithm.
             mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
-            mMoreKeys = parentKey.mMoreKeys;
+            mParentKey = parentKey;
 
             final int previewWidth = view.mKeyPreviewDrawParams.mPreviewBackgroundWidth;
             final int previewHeight = view.mKeyPreviewDrawParams.mPreviewBackgroundHeight;
@@ -261,20 +253,24 @@
             // Use pre-computed width and height if these values are available and more keys
             // keyboard has only one key to mitigate visual flicker between key preview and more
             // keys keyboard.
-            if (view.isKeyPreviewPopupEnabled() && mMoreKeys.length == 1 && previewWidth > 0
-                    && previewHeight > 0) {
+            final boolean validKeyPreview = view.isKeyPreviewPopupEnabled() && (previewWidth > 0)
+                    && (previewHeight > 0);
+            final boolean singleMoreKeyWithPreview = validKeyPreview
+                    && parentKey.mMoreKeys.length == 1;
+            if (singleMoreKeyWithPreview) {
                 width = previewWidth;
                 height = previewHeight + mParams.mVerticalGap;
             } else {
                 width = getMaxKeyWidth(view, parentKey.mMoreKeys, mParams.mDefaultKeyWidth);
                 height = parentKeyboard.mMostCommonKeyHeight;
             }
-            mParams.setParameters(mMoreKeys.length, parentKey.mMaxMoreKeysColumn, width, height,
-                    parentKey.mX + parentKey.mWidth / 2, view.getMeasuredWidth());
+            mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(),
+                    width, height, parentKey.mX + parentKey.mWidth / 2, view.getMeasuredWidth(),
+                    parentKey.isFixedColumnOrderMoreKeys());
         }
 
         private static int getMaxKeyWidth(KeyboardView view, String[] moreKeys, int minKeyWidth) {
-            final int padding = (int) view.getContext().getResources()
+            final int padding = (int) view.getResources()
                     .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding);
             Paint paint = null;
             int maxWidth = minKeyWidth;
@@ -298,8 +294,9 @@
         @Override
         public MoreKeysKeyboard build() {
             final MoreKeysKeyboardParams params = mParams;
-            for (int n = 0; n < mMoreKeys.length; n++) {
-                final String moreKeySpec = mMoreKeys[n];
+            final String[] moreKeys = mParentKey.mMoreKeys;
+            for (int n = 0; n < moreKeys.length; n++) {
+                final String moreKeySpec = moreKeys[n];
                 final int row = n / params.mNumColumns;
                 final Key key = new Key(mResources, params, moreKeySpec, params.getX(n, row),
                         params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index f61eefd..32f5fbc 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -426,4 +426,28 @@
             return list.toArray(new String[list.size()]);
         }
     }
+
+    public static int getIntValue(String[] moreKeys, String key, int defaultValue) {
+        if (moreKeys == null) {
+            return defaultValue;
+        }
+        boolean foundValue = false;
+        int value = defaultValue;
+        for (int i = 0; i < moreKeys.length; i++) {
+            final String moreKeySpec = moreKeys[i];
+            if (moreKeySpec == null || !moreKeySpec.startsWith(key)) {
+                continue;
+            }
+            moreKeys[i] = null;
+            try {
+                if (!foundValue) {
+                    value = Integer.parseInt(moreKeySpec.substring(key.length()));
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                        "integer should follow after " + key + ": " + moreKeySpec);
+            }
+        }
+        return value;
+    }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
index c0b3445..afd3df4 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
@@ -41,10 +41,12 @@
         super.setUp();
     }
 
-    private static MoreKeysKeyboardParams createParams(int numKeys, int fixColumns,
+    private static MoreKeysKeyboardParams createParams(int numKeys, int columnNum,
             int coordXInParnet) {
-        return new MoreKeysKeyboardParams(numKeys, fixColumns | Key.MORE_KEYS_FIXED_COLUMN_ORDER,
-                WIDTH, HEIGHT, coordXInParnet, KEYBOARD_WIDTH);
+        final MoreKeysKeyboardParams params = new MoreKeysKeyboardParams();
+        params.setParameters(numKeys, columnNum, WIDTH, HEIGHT, coordXInParnet, KEYBOARD_WIDTH,
+                /* isFixedOrderColumn */true);
+        return params;
     }
 
     public void testLayoutError() {
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
index 90bbf7e..456247b 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
@@ -43,8 +43,10 @@
 
     private static MoreKeysKeyboardParams createParams(int numKeys, int maxColumns,
             int coordXInParnet) {
-        return new MoreKeysKeyboardParams(numKeys, maxColumns, WIDTH, HEIGHT, coordXInParnet,
-                KEYBOARD_WIDTH);
+        final MoreKeysKeyboardParams params = new MoreKeysKeyboardParams();
+        params.setParameters(numKeys, maxColumns, WIDTH, HEIGHT, coordXInParnet, KEYBOARD_WIDTH,
+                /* isFixedOrderColumn */false);
+        return params;
     }
 
     public void testLayoutError() {