Refactor currency and quotes keys tests

Bug: 13017434
Change-Id: I38dff3c8b9b28eff4397c7cdbad623fb43cbc312
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java b/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java
index be3ed12..b81061a 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java
@@ -28,13 +28,12 @@
  * The generic upper case alphabet keyboard layout.
  */
 public final class AlphabetShifted extends LayoutBase {
-    public static ExpectedKey[][] getAlphabet(final ExpectedKey[][] lowerCaseKeyboard,
+    public static ExpectedKey[][] getDefaultLayout(final ExpectedKey[][] lowerCaseKeyboard,
             final Locale locale) {
-        final ExpectedKey[][] upperCaseKeyboard = ExpectedKeyboardBuilder.toUpperCase(
-                lowerCaseKeyboard, locale);
-        return new ExpectedKeyboardBuilder(upperCaseKeyboard)
-                .replaceKeyOfAll(SHIFT_KEY, SHIFTED_SHIFT_KEY)
-                .build();
+        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(lowerCaseKeyboard);
+        builder.toUpperCase(locale);
+        builder.replaceKeysOfAll(SHIFT_KEY, SHIFTED_SHIFT_KEY);
+        return builder.build();
     }
 
     // Icon id.
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java b/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java
index f7179b7..8b35e3f 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java
@@ -24,7 +24,7 @@
  * The QWERTY alphabet keyboard.
  */
 public final class Qwerty extends LayoutBase {
-    public static ExpectedKey[][] getAlphabet(final boolean isPhone) {
+    public static ExpectedKey[][] getLayout(final boolean isPhone) {
         return toCommonAlphabet(ALPHABET_COMMON, isPhone);
     }
 
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
index 03d7f07..bf46d5d 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
@@ -25,15 +25,88 @@
  * The symbols keyboard layout.
  */
 public final class Symbols extends LayoutBase {
-    public static ExpectedKey[][] getSymbols(final boolean isPhone) {
+    public static ExpectedKey[][] getLayout(final boolean isPhone) {
         return isPhone ? toPhoneSymbol(SYMBOLS_COMMON) : toTabletSymbols(SYMBOLS_COMMON);
     }
 
+    public static ExpectedKey[][] getDefaultLayout(final boolean isPhone) {
+        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(SYMBOLS_COMMON);
+        builder.replaceKeyOfLabel(CURRENCY, Symbols.CURRENCY_DOLLAR);
+        builder.replaceKeyOfLabel(DOUBLE_QUOTE,
+                key("\"", join(Symbols.DOUBLE_QUOTES_9LR, Symbols.DOUBLE_ANGLE_QUOTES_LR)));
+        builder.replaceKeyOfLabel(SINGLE_QUOTE,
+                key("'", join(Symbols.SINGLE_QUOTES_9LR, Symbols.SINGLE_ANGLE_QUOTES_LR)));
+        final ExpectedKey[][] symbolsCommon = builder.build();
+        return isPhone ? toPhoneSymbol(symbolsCommon) : toTabletSymbols(symbolsCommon);
+    }
+
     // Functional keys.
     public static final ExpectedKey ALPHABET_KEY = key("ABC", Constants.CODE_SWITCH_ALPHA_SYMBOL);
     public static final ExpectedKey SYMBOLS_SHIFT_KEY = key("= \\ <", Constants.CODE_SHIFT);
     public static final ExpectedKey TABLET_SYMBOLS_SHIFT_KEY = key("~ [ <", Constants.CODE_SHIFT);
 
+    // Variations of the "currency" key on the 2nd row.
+    public static final String CURRENCY = "currency";
+    // U+00A2: "¢" CENT SIGN
+    // U+00A3: "£" POUND SIGN
+    // U+00A5: "¥" YEN SIGN
+    // U+20AC: "€" EURO SIGN
+    // U+20B1: "₱" PESO SIGN
+    public static final ExpectedKey DOLLAR_SIGN = key("$");
+    public static final ExpectedKey CENT_SIGN = key("\u00A2");
+    public static final ExpectedKey POUND_SIGN = key("\u00A3");
+    public static final ExpectedKey YEN_SIGN = key("\u00A5");
+    public static final ExpectedKey EURO_SIGN = key("\u20AC");
+    public static final ExpectedKey PESO_SIGN = key("\u20B1");
+    public static final ExpectedKey CURRENCY_DOLLAR = key("$",
+            CENT_SIGN, POUND_SIGN, EURO_SIGN, YEN_SIGN, PESO_SIGN);
+    public static final ExpectedKey CURRENCY_EURO = key("\u20AC",
+            CENT_SIGN, POUND_SIGN, DOLLAR_SIGN, YEN_SIGN, PESO_SIGN);
+
+    // Variations of the "double quote" key's "more keys" on the 3rd row.
+    public static final String DOUBLE_QUOTE = "double_quote";
+    // U+201C: "“" LEFT DOUBLE QUOTATION MARK
+    // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+    // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+    static final ExpectedKey DQUOTE_LEFT = key("\u201C");
+    static final ExpectedKey DQUOTE_RIGHT = key("\u201D");
+    static final ExpectedKey DQUOTE_LOW9 = key("\u201E");
+    public static ExpectedKey[] DOUBLE_QUOTES_9LR = { DQUOTE_LOW9, DQUOTE_LEFT, DQUOTE_RIGHT };
+    public static ExpectedKey[] DOUBLE_QUOTES_R9L = { DQUOTE_RIGHT, DQUOTE_LOW9, DQUOTE_LEFT };
+    public static ExpectedKey[] DOUBLE_QUOTES_L9R = { DQUOTE_LEFT, DQUOTE_LOW9, DQUOTE_RIGHT };
+    public static ExpectedKey[] DOUBLE_QUOTES_LR9 = { DQUOTE_LEFT, DQUOTE_RIGHT, DQUOTE_LOW9 };
+    // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+    // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+    private static final ExpectedKey DAQUOTE_LEFT = key("\u00AB");
+    private static final ExpectedKey DAQUOTE_RIGHT = key("\u00BB");
+    private static final ExpectedKey DAQUOTE_LEFT_RTL = key("\u00AB", "\u00BB");
+    private static final ExpectedKey DAQUOTE_RIGHT_RTL = key("\u00BB", "\u00AB");
+    public static ExpectedKey[] DOUBLE_ANGLE_QUOTES_LR = { DAQUOTE_LEFT, DAQUOTE_RIGHT };
+    public static ExpectedKey[] DOUBLE_ANGLE_QUOTES_RL = { DAQUOTE_RIGHT, DAQUOTE_LEFT };
+    public static ExpectedKey[] DOUBLE_ANGLE_QUOTES_RTL = { DAQUOTE_LEFT_RTL, DAQUOTE_RIGHT_RTL };
+
+    // Variations of the "single quote" key's "more keys" on the 3rd row.
+    public static final String SINGLE_QUOTE = "single_quote";
+    // U+2018: "‘" LEFT SINGLE QUOTATION MARK
+    // U+2019: "’" RIGHT SINGLE QUOTATION MARK
+    // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+    static final ExpectedKey SQUOTE_LEFT = key("\u2018");
+    static final ExpectedKey SQUOTE_RIGHT = key("\u2019");
+    static final ExpectedKey SQUOTE_LOW9 = key("\u201A");
+    public static ExpectedKey[] SINGLE_QUOTES_9LR = { SQUOTE_LOW9, SQUOTE_LEFT, SQUOTE_RIGHT };
+    public static ExpectedKey[] SINGLE_QUOTES_R9L = { SQUOTE_RIGHT, SQUOTE_LOW9, SQUOTE_LEFT };
+    public static ExpectedKey[] SINGLE_QUOTES_L9R = { SQUOTE_LEFT, SQUOTE_LOW9, SQUOTE_RIGHT };
+    public static ExpectedKey[] SINGLE_QUOTES_LR9 = { SQUOTE_LEFT, SQUOTE_RIGHT, SQUOTE_LOW9 };
+    // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+    // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+    private static final ExpectedKey SAQUOTE_LEFT = key("\u2039");
+    private static final ExpectedKey SAQUOTE_RIGHT = key("\u203A");
+    private static final ExpectedKey SAQUOTE_LEFT_RTL = key("\u2039", "\u203A");
+    private static final ExpectedKey SAQUOTE_RIGHT_RTL = key("\u203A", "\u2039");
+    public static ExpectedKey[] SINGLE_ANGLE_QUOTES_LR = { SAQUOTE_LEFT, SAQUOTE_RIGHT };
+    public static ExpectedKey[] SINGLE_ANGLE_QUOTES_RL = { SAQUOTE_RIGHT, SAQUOTE_LEFT };
+    public static ExpectedKey[] SINGLE_ANGLE_QUOTES_RTL = { SAQUOTE_LEFT_RTL, SAQUOTE_RIGHT_RTL };
+
     // Common symbols keyboard layout.
     public static final ExpectedKey[][] SYMBOLS_COMMON = new ExpectedKeyboardBuilder(10, 9, 7, 5)
             .setLabelsOfRow(1, "1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
@@ -59,13 +132,7 @@
             // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
             // U+2205: "∅" EMPTY SET
             .setMoreKeysOf("0", "\u207F", "\u2205")
-            .setLabelsOfRow(2, "@", "#", "$", "%", "&", "-", "+", "(", ")")
-            // U+00A2: "¢" CENT SIGN
-            // U+00A3: "£" POUND SIGN
-            // U+20AC: "€" EURO SIGN
-            // U+00A5: "¥" YEN SIGN
-            // U+20B1: "₱" PESO SIGN
-            .setMoreKeysOf("$", "\u00A2", "\u00A3", "\u20AC", "\u00A5", "\u20B1")
+            .setLabelsOfRow(2, "@", "#", CURRENCY, "%", "&", "-", "+", "(", ")")
             // U+2030: "‰" PER MILLE SIGN
             .setMoreKeysOf("%", "\u2030")
             // U+2013: "–" EN DASH
@@ -76,23 +143,11 @@
             .setMoreKeysOf("+", "\u00B1")
             .setMoreKeysOf("(", "<", "{", "[")
             .setMoreKeysOf(")", ">", "}", "]")
-            .setLabelsOfRow(3, "*", "\"", "'", ":", ";", "!", "?")
+            .setLabelsOfRow(3, "*", DOUBLE_QUOTE, SINGLE_QUOTE, ":", ";", "!", "?")
             // U+2020: "†" DAGGER
             // U+2021: "‡" DOUBLE DAGGER
             // U+2605: "★" BLACK STAR
             .setMoreKeysOf("*", "\u2020", "\u2021", "\u2605")
-            // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-            // U+201C: "“" LEFT DOUBLE QUOTATION MARK
-            // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-            // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
-            // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-            .setMoreKeysOf("\"", "\u201E", "\u201C", "\u201D", "\u00AB", "\u00BB")
-            // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-            // U+2018: "‘" LEFT SINGLE QUOTATION MARK
-            // U+2019: "’" RIGHT SINGLE QUOTATION MARK
-            // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
-            // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-            .setMoreKeysOf("'", "\u201A", "\u2018", "\u2019", "\u2039", "\u203A")
             // U+00A1: "¡" INVERTED EXCLAMATION MARK
             .setMoreKeysOf("!", "\u00A1")
             // U+00BF: "¿" INVERTED QUESTION MARK
@@ -123,65 +178,4 @@
                 .addKeysOnTheRightOfRow(4, EMOJI_KEY)
                 .build();
     }
-
-    // Helper method to add currency symbols for Euro.
-    public static ExpectedKeyboardBuilder euro(final ExpectedKeyboardBuilder builder) {
-        return builder
-                // U+20AC: "€" EURO SIGN
-                // U+00A2: "¢" CENT SIGN
-                // U+00A3: "£" POUND SIGN
-                // U+00A5: "¥" YEN SIGN
-                // U+20B1: "₱" PESO SIGN
-                .replaceKeyOfLabel("$", key("\u20AC",
-                        moreKey("\u00A2"), moreKey("\u00A3"), moreKey("$"),
-                        moreKey("\u00A5"), moreKey("\u20B1")));
-    }
-
-    // Helper method to add single quotes "more keys".
-    // "9LLR" means "9-low/Left quotation marks, Left/Right-pointing angle quotation marks".
-    public static ExpectedKeyboardBuilder singleQuotes9LLR(final ExpectedKeyboardBuilder builder) {
-        return builder
-                // U+2019: "’" RIGHT SINGLE QUOTATION MARK
-                // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-                // U+2018: "‘" LEFT SINGLE QUOTATION MARK
-                // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
-                // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-                .setMoreKeysOf("'", "\u2019", "\u201A", "\u2018", "\u2039", "\u203A");
-    }
-
-    // Helper method to add single quotes "more keys".
-    // "9LLR" means "9-low/Left quotation marks, Right/Left-pointing angle quotation marks".
-    public static ExpectedKeyboardBuilder singleQuotes9LRL(final ExpectedKeyboardBuilder builder) {
-        return builder
-                // U+2019: "’" RIGHT SINGLE QUOTATION MARK
-                // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-                // U+2018: "‘" LEFT SINGLE QUOTATION MARK
-                // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-                // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
-                .setMoreKeysOf("'", "\u2019", "\u201A", "\u2018", "\u203A", "\u2039");
-    }
-
-    // Helper method to add double quotes "more keys".
-    // "9LLR" means "9-low/Left quotation marks, Left/Right-pointing angle quotation marks".
-    public static ExpectedKeyboardBuilder doubleQuotes9LLR(final ExpectedKeyboardBuilder builder) {
-        return builder
-                // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-                // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-                // U+201C: "“" LEFT DOUBLE QUOTATION MARK
-                // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
-                // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-                .setMoreKeysOf("\"", "\u201D", "\u201E", "\u201C", "\u00AB", "\u00BB");
-    }
-
-    // Helper method to add double quotes "more keys".
-    // "9LLR" means "9-low/Left quotation marks, Right/Left-pointing angle quotation marks".
-    public static ExpectedKeyboardBuilder doubleQuotes9LRL(final ExpectedKeyboardBuilder builder) {
-        return builder
-                // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-                // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-                // U+201C: "“" LEFT DOUBLE QUOTATION MARK
-                // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-                // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
-                .setMoreKeysOf("\"", "\u201D", "\u201E", "\u201C", "\u00BB", "\u00AB");
-    }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java b/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java
index 368f9db..d04ebf0 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java
@@ -25,17 +25,35 @@
  * The symbols shifted keyboard layout.
  */
 public final class SymbolsShifted extends LayoutBase {
-    public static ExpectedKey[][] getSymbolsShifted(final boolean isPhone) {
+    public static ExpectedKey[][] getLayout(final boolean isPhone) {
         return isPhone ? toPhoneSymbolsShifted(SYMBOLS_SHIFTED_COMMON)
                 : toTabletSymbolsShifted(SYMBOLS_SHIFTED_COMMON);
     }
 
+    public static ExpectedKey[][] getDefaultLayout(final boolean isPhone) {
+        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(SYMBOLS_SHIFTED_COMMON);
+        builder.replaceKeyOfLabel(OTHER_CURRENCIES, SymbolsShifted.CURRENCIES_OTHER_THAN_DOLLAR);
+        final ExpectedKey[][] symbolsShiftedCommon = builder.build();
+        return isPhone ? toPhoneSymbolsShifted(symbolsShiftedCommon)
+                : toTabletSymbolsShifted(symbolsShiftedCommon);
+    }
+
     // Functional key.
     public static final ExpectedKey BACK_TO_SYMBOLS_KEY = key("?123", Constants.CODE_SHIFT);
 
+    // Variations of the "other currencies" keys on the 2rd row.
+    public static final String OTHER_CURRENCIES = "other_currencies";
+    public static final ExpectedKey[] CURRENCIES_OTHER_THAN_DOLLAR = {
+        Symbols.POUND_SIGN, Symbols.CENT_SIGN, Symbols.EURO_SIGN, Symbols.YEN_SIGN
+    };
+    public static final ExpectedKey[] CURRENCIES_OTHER_THAN_EURO = {
+        Symbols.POUND_SIGN, Symbols.YEN_SIGN, key(Symbols.DOLLAR_SIGN, Symbols.CENT_SIGN),
+        Symbols.CENT_SIGN
+    };
+
     // Common symbols shifted keyboard layout.
     public static final ExpectedKey[][] SYMBOLS_SHIFTED_COMMON =
-            new ExpectedKeyboardBuilder(10, 9, 7, 5)
+            new ExpectedKeyboardBuilder(10, 1 /* other_currencies */ + 5, 7, 5)
             // U+0060: "`" GRAVE ACCENT
             // U+2022: "•" BULLET
             // U+221A: "√" SQUARE ROOT
@@ -60,14 +78,8 @@
             // U+00B6: "¶" PILCROW SIGN
             // U+00A7: "§" SECTION SIGN
             .setMoreKeysOf("\u00B6", "\u00A7")
-            // U+00A3: "£" POUND SIGN
-            // U+00A2: "¢" CENT SIGN
-            // U+20AC: "€" EURO SIGN
-            // U+00A5: "¥" YEN SIGN
             // U+00B0: "°" DEGREE SIGN
-            .setLabelsOfRow(2,
-                    "\u00A3", "\u00A2", "\u20AC", "\u00A5", "^",
-                    "\u00B0", "=", "{", "}")
+            .setLabelsOfRow(2, OTHER_CURRENCIES, "^", "\u00B0", "=", "{", "}")
             // U+2191: "↑" UPWARDS ARROW
             // U+2193: "↓" DOWNWARDS ARROW
             // U+2190: "←" LEFTWARDS ARROW
@@ -125,18 +137,4 @@
                 .addKeysOnTheRightOfRow(4, EMOJI_KEY)
                 .build();
     }
-
-    // Helper method to add currency symbols for Euro.
-    public static ExpectedKeyboardBuilder euro(final ExpectedKeyboardBuilder builder) {
-        return builder
-                // U+00A5: "¥" YEN SIGN
-                // U+00A2: "¢" CENT SIGN
-                .replaceKeyOfLabel("\u00A5", key("\u00A2"))
-                // U+20AC: "€" EURO SIGN
-                // U+00A2: "¢" CENT SIGN
-                .replaceKeyOfLabel("\u20AC", key("$", moreKey("\u00A2")))
-                // U+00A2: "¢" CENT SIGN
-                // U+00A5: "¥" YEN SIGN
-                .replaceKeyOfLabel("\u00A2", key("\u00A5"));
-    }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
index 45449b7..682ac20 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
@@ -73,6 +73,14 @@
     }
 
     /**
+     * Return the number of rows.
+     * @return the number of rows being constructed.
+     */
+    int getRowCount() {
+        return mRows.length;
+    }
+
+    /**
      * Get the current contents of the specified row.
      * @param row the row number to get the contents.
      * @return the array of elements at row number <code>row</code>.
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
index 61288f0..57f842b 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
@@ -52,32 +52,56 @@
     }
 
     // A replacement job to be performed.
-    interface ReplaceJob {
-        // Returns a {@link ExpectedKey} object to replace.
-        ExpectedKey replace(final ExpectedKey oldKey);
+    private interface ReplaceJob {
+        // Returns a {@link ExpectedKey} objects to replace.
+        ExpectedKey[] replacingKeys(final ExpectedKey oldKey);
         // Return true if replacing should be stopped at first occurrence.
         boolean stopAtFirstOccurrence();
     }
 
+    private static ExpectedKey[] replaceKeyAt(final ExpectedKey[] keys, final int columnIndex,
+            final ExpectedKey[] replacingKeys) {
+        // Optimization for replacing a key with another key.
+        if (replacingKeys.length == 1) {
+            keys[columnIndex] = replacingKeys[0];
+            return keys;
+        }
+        final int newLength = keys.length - 1 + replacingKeys.length;
+        // Remove the key at columnIndex.
+        final ExpectedKey[] newKeys = Arrays.copyOf(keys, newLength);
+        System.arraycopy(keys, columnIndex + 1, newKeys, columnIndex + replacingKeys.length,
+                keys.length - 1 - columnIndex);
+        // Insert replacing keys at columnIndex.
+        System.arraycopy(replacingKeys, 0, newKeys, columnIndex, replacingKeys.length);
+        return newKeys;
+
+    }
+
     // Replace key(s) that has the specified visual.
     private void replaceKeyOf(final ExpectedKeyVisual visual, final ReplaceJob job) {
         int replacedCount = 0;
-        final ExpectedKey[][] rows = build();
-        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
-            final ExpectedKey[] keys = rows[rowIndex];
-            for (int columnIndex = 0; columnIndex < keys.length; columnIndex++) {
-                if (keys[columnIndex].getVisual().equalsTo(visual)) {
-                    keys[columnIndex] = job.replace(keys[columnIndex]);
-                    replacedCount++;
-                    if (job.stopAtFirstOccurrence()) {
-                        return;
-                    }
+        final int rowCount = getRowCount();
+        for (int row = 1; row <= rowCount; row++) {
+            ExpectedKey[] keys = getRowAt(row);
+            for (int columnIndex = 0; columnIndex < keys.length; /* nothing */) {
+                final ExpectedKey currentKey = keys[columnIndex];
+                if (!currentKey.getVisual().equalsTo(visual)) {
+                    columnIndex++;
+                    continue;
+                }
+                final ExpectedKey[] replacingKeys = job.replacingKeys(currentKey);
+                keys = replaceKeyAt(keys, columnIndex, replacingKeys);
+                columnIndex += replacingKeys.length;
+                setRowAt(row, keys);
+                replacedCount++;
+                if (job.stopAtFirstOccurrence()) {
+                    return;
                 }
             }
         }
         if (replacedCount == 0) {
             throw new RuntimeException(
-                    "Can't find key that has visual: " + visual + " in\n" + toString(rows));
+                    "Can't find key that has visual: " + visual + " in\n" + this);
         }
     }
 
@@ -137,8 +161,10 @@
     private void setMoreKeysOf(final ExpectedKeyVisual visual, final ExpectedKey[] moreKeys) {
         replaceKeyOf(visual, new ReplaceJob() {
             @Override
-            public ExpectedKey replace(final ExpectedKey oldKey) {
-                return ExpectedKey.newInstance(oldKey.getVisual(), oldKey.getOutput(), moreKeys);
+            public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) {
+                return new ExpectedKey[] {
+                    ExpectedKey.newInstance(oldKey.getVisual(), oldKey.getOutput(), moreKeys)
+                };
             }
             @Override
             public boolean stopAtFirstOccurrence() {
@@ -194,17 +220,18 @@
     }
 
     /**
-     * Replace the most top-left key that has the specified label with the new key.
-     * @param label the label of the key to set <code>newKey</code>.
-     * @param newKey the key to be set.
+     * Replace the most top-left key that has the specified label with the new keys.
+     * @param label the label of the key to set <code>newKeys</code>.
+     * @param newKeys the keys to be set.
      * @return this builder.
      */
-    public ExpectedKeyboardBuilder replaceKeyOfLabel(final String label, final ExpectedKey newKey) {
+    public ExpectedKeyboardBuilder replaceKeyOfLabel(final String label,
+            final ExpectedKey ... newKeys) {
         final ExpectedKeyVisual visual = ExpectedKeyVisual.newInstance(label);
         replaceKeyOf(visual, new ReplaceJob() {
             @Override
-            public ExpectedKey replace(final ExpectedKey oldKey) {
-                return newKey;
+            public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) {
+                return newKeys;
             }
             @Override
             public boolean stopAtFirstOccurrence() {
@@ -215,17 +242,17 @@
     }
 
     /**
-     * Replace the all specified keys  with the new key.
-     * @param key the key to be replaced by <code>newKey</code>.
-     * @param newKey the key to be set.
+     * Replace the all specified keys with the new keys.
+     * @param key the key to be replaced by <code>newKeys</code>.
+     * @param newKeys the keys to be set.
      * @return this builder.
      */
-    public ExpectedKeyboardBuilder replaceKeyOfAll(final ExpectedKey key,
-            final ExpectedKey newKey) {
+    public ExpectedKeyboardBuilder replaceKeysOfAll(final ExpectedKey key,
+            final ExpectedKey ... newKeys) {
         replaceKeyOf(key.getVisual(), new ReplaceJob() {
             @Override
-            public ExpectedKey replace(final ExpectedKey oldKey) {
-                return newKey;
+            public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) {
+                return newKeys;
             }
             @Override
             public boolean stopAtFirstOccurrence() {
@@ -236,22 +263,26 @@
     }
 
     /**
-     * Returns new keyboard instance that has upper case keys of the specified keyboard.
-     * @param rows the lower case keyboard.
+     * Convert all keys of this keyboard builder to upper case keys.
      * @param locale the locale used to convert cases.
-     * @return the upper case keyboard.
+     * @return this builder
      */
-    public static ExpectedKey[][] toUpperCase(final ExpectedKey[][] rows, final Locale locale) {
-        final ExpectedKey[][] upperCaseRows = new ExpectedKey[rows.length][];
-        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
-            final ExpectedKey[] lowerCaseKeys = rows[rowIndex];
+    public ExpectedKeyboardBuilder toUpperCase(final Locale locale) {
+        final int rowCount = getRowCount();
+        for (int row = 1; row <= rowCount; row++) {
+            final ExpectedKey[] lowerCaseKeys = getRowAt(row);
             final ExpectedKey[] upperCaseKeys = new ExpectedKey[lowerCaseKeys.length];
             for (int columnIndex = 0; columnIndex < lowerCaseKeys.length; columnIndex++) {
                 upperCaseKeys[columnIndex] = lowerCaseKeys[columnIndex].toUpperCase(locale);
             }
-            upperCaseRows[rowIndex] = upperCaseKeys;
+            setRowAt(row, upperCaseKeys);
         }
-        return upperCaseRows;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return toString(build());
     }
 
     /**
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java
index 1aeb8c0..329f704 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java
@@ -18,6 +18,10 @@
 
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Base class to create an expected keyboard for unit test.
@@ -71,6 +75,24 @@
         return ExpectedKey.newInstance(label, code);
     }
 
+    // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey},
+    // {@link ExpectedKey} array, and {@link String}.
+    public static ExpectedKey[] join(final Object ... keys) {
+        final ArrayList<ExpectedKey> list = CollectionUtils.newArrayList();
+        for (final Object key : keys) {
+            if (key instanceof ExpectedKey) {
+                list.add((ExpectedKey)key);
+            } else if (key instanceof ExpectedKey[]) {
+                list.addAll(Arrays.asList((ExpectedKey[])key));
+            } else if (key instanceof String) {
+                list.add(key((String)key));
+            } else {
+                throw new RuntimeException("Unknown expected key type: " + key);
+            }
+        }
+        return list.toArray(new ExpectedKey[list.size()]);
+    }
+
     // Icon ids.
     private static final int ICON_SHIFT = KeyboardIconsSet.getIconId("shift_key");
     private static final int ICON_DELETE = KeyboardIconsSet.getIconId("delete_key");
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
index 427e7de..5c51d08 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
@@ -68,6 +68,11 @@
         return LayoutBase.key(label, outputText, moreKeys);
     }
 
+    // Helper method to create {@link ExpectedKey} object that has new "more keys".
+    static ExpectedKey key(final ExpectedKey key, final ExpectedKey ... moreKeys) {
+        return LayoutBase.key(key, moreKeys);
+    }
+
     // Helper method to create {@link ExpectedKey} object for "more key" that has the label.
     static ExpectedKey moreKey(final String label) {
         return LayoutBase.moreKey(label);
@@ -79,6 +84,12 @@
         return LayoutBase.moreKey(label, outputText);
     }
 
+    // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey},
+    // {@link ExpectedKey} array, and {@link String}.
+    static ExpectedKey[] join(final Object ... keys) {
+        return LayoutBase.join(keys);
+    }
+
     // Locale for testing subtype.
     abstract Locale getTestLocale();
 
@@ -86,73 +97,73 @@
     abstract String getTestKeyboardLayout();
 
     // Alphabet keyboard for testing subtype.
-    abstract ExpectedKey[][] getAlphabet(final boolean isPhone);
+    abstract ExpectedKey[][] getAlphabetLayout(final boolean isPhone);
 
     // Alphabet automatic shifted keyboard for testing subtype.
-    ExpectedKey[][] getAlphabetAutomaticShifted(final boolean isPhone) {
-        return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale());
+    ExpectedKey[][] getAlphabetAutomaticShiftedLayout(final boolean isPhone) {
+        return AlphabetShifted.getDefaultLayout(getAlphabetLayout(isPhone), getTestLocale());
     }
 
     // Alphabet manual shifted  keyboard for testing subtype.
-    ExpectedKey[][] getAlphabetManualShifted(final boolean isPhone) {
-        return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale());
+    ExpectedKey[][] getAlphabetManualShiftedLayout(final boolean isPhone) {
+        return AlphabetShifted.getDefaultLayout(getAlphabetLayout(isPhone), getTestLocale());
     }
 
     // Alphabet shift locked keyboard for testing subtype.
-    ExpectedKey[][] getAlphabetShiftLocked(final boolean isPhone) {
-        return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale());
+    ExpectedKey[][] getAlphabetShiftLockedLayout(final boolean isPhone) {
+        return AlphabetShifted.getDefaultLayout(getAlphabetLayout(isPhone), getTestLocale());
     }
 
     // Alphabet shift lock shifted keyboard for testing subtype.
-    ExpectedKey[][] getAlphabetShiftLockShifted(final boolean isPhone) {
-        return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale());
+    ExpectedKey[][] getAlphabetShiftLockShiftedLayout(final boolean isPhone) {
+        return AlphabetShifted.getDefaultLayout(getAlphabetLayout(isPhone), getTestLocale());
     }
 
     // Symbols keyboard for testing subtype.
-    ExpectedKey[][] getSymbols(final boolean isPhone) {
-        return Symbols.getSymbols(isPhone);
+    ExpectedKey[][] getSymbolsLayout(final boolean isPhone) {
+        return Symbols.getDefaultLayout(isPhone);
     }
 
     // Symbols shifted keyboard for testing subtype.
-    ExpectedKey[][] getSymbolsShifted(final boolean isPhone) {
-        return SymbolsShifted.getSymbolsShifted(isPhone);
+    ExpectedKey[][] getSymbolsShiftedLayout(final boolean isPhone) {
+        return SymbolsShifted.getDefaultLayout(isPhone);
     }
 
     // TODO: Add phone, phone symbols, number, number password layout tests.
 
     public final void testAlphabet() {
         final int elementId = KeyboardId.ELEMENT_ALPHABET;
-        doKeyboardTests(elementId, getAlphabet(isPhone()));
+        doKeyboardTests(elementId, getAlphabetLayout(isPhone()));
     }
 
     public final void testAlphabetAutomaticShifted() {
         final int elementId = KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED;
-        doKeyboardTests(elementId, getAlphabetAutomaticShifted(isPhone()));
+        doKeyboardTests(elementId, getAlphabetAutomaticShiftedLayout(isPhone()));
     }
 
     public final void testAlphabetManualShifted() {
         final int elementId = KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED;
-        doKeyboardTests(elementId, getAlphabetManualShifted(isPhone()));
+        doKeyboardTests(elementId, getAlphabetManualShiftedLayout(isPhone()));
     }
 
     public final void testAlphabetShiftLocked() {
         final int elementId = KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED;
-        doKeyboardTests(elementId, getAlphabetShiftLocked(isPhone()));
+        doKeyboardTests(elementId, getAlphabetShiftLockedLayout(isPhone()));
     }
 
     public final void testAlphabetShiftLockShifted() {
         final int elementId = KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED;
-        doKeyboardTests(elementId, getAlphabetShiftLockShifted(isPhone()));
+        doKeyboardTests(elementId, getAlphabetShiftLockShiftedLayout(isPhone()));
     }
 
     public final void testSymbols() {
         final int elementId = KeyboardId.ELEMENT_SYMBOLS;
-        doKeyboardTests(elementId, getSymbols(isPhone()));
+        doKeyboardTests(elementId, getSymbolsLayout(isPhone()));
     }
 
     public final void testSymbolsShifted() {
         final int elementId = KeyboardId.ELEMENT_SYMBOLS_SHIFTED;
-        doKeyboardTests(elementId, getSymbolsShifted(isPhone()));
+        doKeyboardTests(elementId, getSymbolsShiftedLayout(isPhone()));
     }
 
     // Comparing expected keyboard and actual keyboard.
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java
index 0792a57..e160ae8 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java
@@ -40,8 +40,8 @@
     }
 
     @Override
-    ExpectedKey[][] getAlphabet(final boolean isPhone) {
-        final ExpectedKey[][] keyboard = Qwerty.getAlphabet(isPhone);
+    ExpectedKey[][] getAlphabetLayout(final boolean isPhone) {
+        final ExpectedKey[][] keyboard = Qwerty.getLayout(isPhone);
         final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(keyboard);
         setAccentedLetters(builder);
         return builder.build();