diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index d18be4c718faf092a30e0376b578b0f5b1e5ddee..e941cc7fcd10a6db09c57f38848769c07dc8202b 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -32,9 +32,11 @@ import android.util.Log;
 import android.util.Xml;
 
 import com.android.inputmethod.keyboard.internal.KeySpecParser;
-import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec;
-import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
+import com.android.inputmethod.keyboard.internal.KeyStyle;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.KeyboardRow;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResourceUtils;
 import com.android.inputmethod.latin.StringUtils;
@@ -166,8 +168,8 @@ public class Key {
     /**
      * This constructor is being used only for keys in more keys keyboard.
      */
-    public Key(Keyboard.Params params, MoreKeySpec moreKeySpec, int x, int y, int width, int height,
-            int labelFlags) {
+    public Key(final KeyboardParams params, final MoreKeySpec moreKeySpec, final int x, final int y,
+            final int width, final int height, final int labelFlags) {
         this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode,
                 moreKeySpec.mOutputText, x, y, width, height, labelFlags);
     }
@@ -175,8 +177,9 @@ public class Key {
     /**
      * 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, String outputText, int x, int y, int width, int height, int labelFlags) {
+    public Key(final KeyboardParams params, final String label, final String hintLabel,
+            final int iconId, final int code, final String outputText, final int x, final int y,
+            final int width, final int height, final int labelFlags) {
         mHeight = height - params.mVerticalGap;
         mWidth = width - params.mHorizontalGap;
         mHintLabel = hintLabel;
@@ -213,8 +216,8 @@ public class Key {
      * @param parser the XML parser containing the attributes for this key
      * @throws XmlPullParserException
      */
-    public Key(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
-            XmlPullParser parser) throws XmlPullParserException {
+    public Key(final Resources res, final KeyboardParams params, final KeyboardRow row,
+            final XmlPullParser parser) throws XmlPullParserException {
         final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
         final int keyHeight = row.mRowHeight;
         mHeight = keyHeight - params.mVerticalGap;
@@ -364,7 +367,7 @@ public class Key {
         }
     }
 
-    private static boolean needsToUpperCase(int labelFlags, int keyboardElementId) {
+    private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) {
         if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
         switch (keyboardElementId) {
         case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
@@ -377,7 +380,7 @@ public class Key {
         }
     }
 
-    private static int computeHashCode(Key key) {
+    private static int computeHashCode(final Key key) {
         return Arrays.hashCode(new Object[] {
                 key.mX,
                 key.mY,
@@ -404,7 +407,7 @@ public class Key {
         });
     }
 
-    private boolean equals(Key o) {
+    private boolean equals(final Key o) {
         if (this == o) return true;
         return o.mX == mX
                 && o.mY == mY
@@ -427,7 +430,7 @@ public class Key {
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(final Object o) {
         return o instanceof Key && equals((Key)o);
     }
 
@@ -444,7 +447,7 @@ public class Key {
                 KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
     }
 
-    private static String backgroundName(int backgroundType) {
+    private static String backgroundName(final int backgroundType) {
         switch (backgroundType) {
         case BACKGROUND_TYPE_NORMAL: return "normal";
         case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
@@ -455,19 +458,19 @@ public class Key {
         }
     }
 
-    public void markAsLeftEdge(Keyboard.Params params) {
+    public void markAsLeftEdge(final KeyboardParams params) {
         mHitBox.left = params.mHorizontalEdgesPadding;
     }
 
-    public void markAsRightEdge(Keyboard.Params params) {
+    public void markAsRightEdge(final KeyboardParams params) {
         mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding;
     }
 
-    public void markAsTopEdge(Keyboard.Params params) {
+    public void markAsTopEdge(final KeyboardParams params) {
         mHitBox.top = params.mTopPadding;
     }
 
-    public void markAsBottomEdge(Keyboard.Params params) {
+    public void markAsBottomEdge(final KeyboardParams params) {
         mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
     }
 
@@ -501,7 +504,7 @@ public class Key {
                 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
     }
 
-    public Typeface selectTypeface(Typeface defaultTypeface) {
+    public Typeface selectTypeface(final Typeface defaultTypeface) {
         // TODO: Handle "bold" here too?
         if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
             return Typeface.DEFAULT;
@@ -512,8 +515,8 @@ public class Key {
         }
     }
 
-    public int selectTextSize(int letterSize, int largeLetterSize, int labelSize,
-            int largeLabelSize, int hintLabelSize) {
+    public int selectTextSize(final int letterSize, final int largeLetterSize, final int labelSize,
+            final int largeLabelSize, final int hintLabelSize) {
         switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) {
         case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO:
             return letterSize;
@@ -606,7 +609,7 @@ public class Key {
         return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
     }
 
-    public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
+    public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
         final OptionalAttributes attrs = mOptionalAttributes;
         final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
         final int iconId = mEnabled ? mIconId : disabledIconId;
@@ -617,7 +620,7 @@ public class Key {
         return icon;
     }
 
-    public Drawable getPreviewIcon(KeyboardIconsSet iconSet) {
+    public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) {
         final OptionalAttributes attrs = mOptionalAttributes;
         final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED;
         return previewIconId != ICON_UNDEFINED
@@ -657,7 +660,7 @@ public class Key {
         return mEnabled;
     }
 
-    public void setEnabled(boolean enabled) {
+    public void setEnabled(final boolean enabled) {
         mEnabled = enabled;
     }
 
@@ -667,9 +670,9 @@ public class Key {
      * @param y the y-coordinate of the point
      * @return whether or not the point falls on the key. If the key is attached to an edge, it
      * will assume that all points between the key and the edge are considered to be on the key.
-     * @see #markAsLeftEdge(Keyboard.Params) etc.
+     * @see #markAsLeftEdge(KeyboardParams) etc.
      */
-    public boolean isOnKey(int x, int y) {
+    public boolean isOnKey(final int x, final int y) {
         return mHitBox.contains(x, y);
     }
 
@@ -679,7 +682,7 @@ public class Key {
      * @param y the y-coordinate of the point
      * @return the square of the distance of the point from the nearest edge of the key
      */
-    public int squaredDistanceToEdge(int x, int y) {
+    public int squaredDistanceToEdge(final int x, final int y) {
         final int left = mX;
         final int right = left + mWidth;
         final int top = mY;
@@ -761,15 +764,16 @@ public class Key {
     }
 
     public static class Spacer extends Key {
-        public Spacer(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
-                XmlPullParser parser) throws XmlPullParserException {
+        public Spacer(final Resources res, final KeyboardParams params, final KeyboardRow row,
+                final XmlPullParser parser) throws XmlPullParserException {
             super(res, params, row, parser);
         }
 
         /**
          * This constructor is being used only for divider in more keys keyboard.
          */
-        protected Spacer(Keyboard.Params params, int x, int y, int width, int height) {
+        protected Spacer(final KeyboardParams params, final int x, final int y, final int width,
+                final int height) {
             super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED,
                     null, x, y, width, height, 0);
         }
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index b8e5e95236e8fe736b887534f244a6ae34ce6779..c1b007d2e015fb41a6dd177c6a734b95d86b62a8 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -16,41 +16,15 @@
 
 package com.android.inputmethod.keyboard;
 
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
 import android.graphics.Typeface;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.util.TypedValue;
-import android.util.Xml;
-import android.view.InflateException;
 
-import com.android.inputmethod.keyboard.internal.KeyStyles;
-import com.android.inputmethod.keyboard.internal.KeyboardCodesSet;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
-import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
-import com.android.inputmethod.latin.StringUtils;
-import com.android.inputmethod.latin.SubtypeLocale;
-import com.android.inputmethod.latin.XmlParseUtils;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Locale;
 
 /**
  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -149,7 +123,7 @@ public class Keyboard {
     private final ProximityInfo mProximityInfo;
     private final boolean mProximityCharsCorrectionEnabled;
 
-    public Keyboard(Params params) {
+    public Keyboard(final KeyboardParams params) {
         mId = params.mId;
         mThemeId = params.mThemeId;
         mOccupiedHeight = params.mOccupiedHeight;
@@ -180,7 +154,7 @@ public class Keyboard {
         mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
     }
 
-    public boolean hasProximityCharsCorrection(int code) {
+    public boolean hasProximityCharsCorrection(final int code) {
         if (!mProximityCharsCorrectionEnabled) {
             return false;
         }
@@ -196,7 +170,7 @@ public class Keyboard {
         return mProximityInfo;
     }
 
-    public Key getKey(int code) {
+    public Key getKey(final int code) {
         if (code == CODE_UNSPECIFIED) {
             return null;
         }
@@ -217,7 +191,7 @@ public class Keyboard {
         }
     }
 
-    public boolean hasKey(Key aKey) {
+    public boolean hasKey(final Key aKey) {
         if (mKeyCache.indexOfValue(aKey) >= 0) {
             return true;
         }
@@ -231,7 +205,7 @@ public class Keyboard {
         return false;
     }
 
-    public static boolean isLetterCode(int code) {
+    public static boolean isLetterCode(final int code) {
         return code >= CODE_SPACE;
     }
 
@@ -240,178 +214,6 @@ public class Keyboard {
         return mId.toString();
     }
 
-    // TODO: Move this class to internal package
-    public static class Params {
-        public KeyboardId mId;
-        public int mThemeId;
-
-        /** Total height and width of the keyboard, including the paddings and keys */
-        public int mOccupiedHeight;
-        public int mOccupiedWidth;
-
-        /** Base height and width of the keyboard used to calculate rows' or keys' heights and
-         *  widths
-         */
-        public int mBaseHeight;
-        public int mBaseWidth;
-
-        public int mTopPadding;
-        public int mBottomPadding;
-        public int mHorizontalEdgesPadding;
-        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;
-        public int mHorizontalGap;
-        public int mVerticalGap;
-
-        public int mMoreKeysTemplate;
-        public int mMaxMoreKeysKeyboardColumn;
-
-        public int GRID_WIDTH;
-        public int GRID_HEIGHT;
-
-        public final HashSet<Key> mKeys = CollectionUtils.newHashSet();
-        public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
-        public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
-        public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
-        public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
-        public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
-        public final KeyStyles mKeyStyles = new KeyStyles(mTextsSet);
-
-        public KeyboardLayoutSet.KeysCache mKeysCache;
-
-        public int mMostCommonKeyHeight = 0;
-        public int mMostCommonKeyWidth = 0;
-
-        public boolean mProximityCharsCorrectionEnabled;
-
-        public final TouchPositionCorrection mTouchPositionCorrection =
-                new TouchPositionCorrection();
-
-        public static class TouchPositionCorrection {
-            private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
-
-            public boolean mEnabled;
-            public float[] mXs;
-            public float[] mYs;
-            public float[] mRadii;
-
-            public void load(String[] data) {
-                final int dataLength = data.length;
-                if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
-                    if (LatinImeLogger.sDBG) {
-                        throw new RuntimeException(
-                                "the size of touch position correction data is invalid");
-                    }
-                    return;
-                }
-
-                final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
-                mXs = new float[length];
-                mYs = new float[length];
-                mRadii = new float[length];
-                try {
-                    for (int i = 0; i < dataLength; ++i) {
-                        final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
-                        final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
-                        final float value = Float.parseFloat(data[i]);
-                        if (type == 0) {
-                            mXs[index] = value;
-                        } else if (type == 1) {
-                            mYs[index] = value;
-                        } else {
-                            mRadii[index] = value;
-                        }
-                    }
-                } catch (NumberFormatException e) {
-                    if (LatinImeLogger.sDBG) {
-                        throw new RuntimeException(
-                                "the number format for touch position correction data is invalid");
-                    }
-                    mXs = null;
-                    mYs = null;
-                    mRadii = null;
-                }
-            }
-
-            // TODO: Remove this method.
-            public void setEnabled(boolean enabled) {
-                mEnabled = enabled;
-            }
-
-            public boolean isValid() {
-                return mEnabled && mXs != null && mYs != null && mRadii != null
-                        && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
-            }
-        }
-
-        protected void clearKeys() {
-            mKeys.clear();
-            mShiftKeys.clear();
-            clearHistogram();
-        }
-
-        public void onAddKey(Key newKey) {
-            final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
-            final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0;
-            if (!zeroWidthSpacer) {
-                mKeys.add(key);
-                updateHistogram(key);
-            }
-            if (key.mCode == Keyboard.CODE_SHIFT) {
-                mShiftKeys.add(key);
-            }
-            if (key.altCodeWhileTyping()) {
-                mAltCodeKeysWhileTyping.add(key);
-            }
-        }
-
-        private int mMaxHeightCount = 0;
-        private int mMaxWidthCount = 0;
-        private final SparseIntArray mHeightHistogram = new SparseIntArray();
-        private final SparseIntArray mWidthHistogram = new SparseIntArray();
-
-        private void clearHistogram() {
-            mMostCommonKeyHeight = 0;
-            mMaxHeightCount = 0;
-            mHeightHistogram.clear();
-
-            mMaxWidthCount = 0;
-            mMostCommonKeyWidth = 0;
-            mWidthHistogram.clear();
-        }
-
-        private static int updateHistogramCounter(SparseIntArray histogram, int key) {
-            final int index = histogram.indexOfKey(key);
-            final int count = (index >= 0 ? histogram.get(key) : 0) + 1;
-            histogram.put(key, count);
-            return count;
-        }
-
-        private void updateHistogram(Key key) {
-            final int height = key.mHeight + mVerticalGap;
-            final int heightCount = updateHistogramCounter(mHeightHistogram, height);
-            if (heightCount > mMaxHeightCount) {
-                mMaxHeightCount = heightCount;
-                mMostCommonKeyHeight = height;
-            }
-
-            final int width = key.mWidth + mHorizontalGap;
-            final int widthCount = updateHistogramCounter(mWidthHistogram, width);
-            if (widthCount > mMaxWidthCount) {
-                mMaxWidthCount = widthCount;
-                mMostCommonKeyWidth = width;
-            }
-        }
-    }
-
     /**
      * Returns the array of the keys that are closest to the given point.
      * @param x the x-coordinate of the point
@@ -419,14 +221,14 @@ public class Keyboard {
      * @return the array of the nearest keys to the given point. If the given
      * point is out of range, then an array of size zero is returned.
      */
-    public Key[] getNearestKeys(int x, int y) {
+    public Key[] getNearestKeys(final int x, final int y) {
         // Avoid dead pixels at edges of the keyboard
         final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
         final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
         return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
     }
 
-    public static String printableCode(int code) {
+    public static String printableCode(final int code) {
         switch (code) {
         case CODE_SHIFT: return "shift";
         case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
@@ -448,912 +250,4 @@ public class Keyboard {
             return String.format("'\\u%04x'", code);
         }
     }
-
-   /**
-     * Keyboard Building helper.
-     *
-     * This class parses Keyboard XML file and eventually build a Keyboard.
-     * The Keyboard XML file looks like:
-     * <pre>
-     *   &lt;!-- xml/keyboard.xml --&gt;
-     *   &lt;Keyboard keyboard_attributes*&gt;
-     *     &lt;!-- Keyboard Content --&gt;
-     *     &lt;Row row_attributes*&gt;
-     *       &lt;!-- Row Content --&gt;
-     *       &lt;Key key_attributes* /&gt;
-     *       &lt;Spacer horizontalGap="32.0dp" /&gt;
-     *       &lt;include keyboardLayout="@xml/other_keys"&gt;
-     *       ...
-     *     &lt;/Row&gt;
-     *     &lt;include keyboardLayout="@xml/other_rows"&gt;
-     *     ...
-     *   &lt;/Keyboard&gt;
-     * </pre>
-     * The XML file which is included in other file must have &lt;merge&gt; as root element,
-     * such as:
-     * <pre>
-     *   &lt;!-- xml/other_keys.xml --&gt;
-     *   &lt;merge&gt;
-     *     &lt;Key key_attributes* /&gt;
-     *     ...
-     *   &lt;/merge&gt;
-     * </pre>
-     * and
-     * <pre>
-     *   &lt;!-- xml/other_rows.xml --&gt;
-     *   &lt;merge&gt;
-     *     &lt;Row row_attributes*&gt;
-     *       &lt;Key key_attributes* /&gt;
-     *     &lt;/Row&gt;
-     *     ...
-     *   &lt;/merge&gt;
-     * </pre>
-     * You can also use switch-case-default tags to select Rows and Keys.
-     * <pre>
-     *   &lt;switch&gt;
-     *     &lt;case case_attribute*&gt;
-     *       &lt;!-- Any valid tags at switch position --&gt;
-     *     &lt;/case&gt;
-     *     ...
-     *     &lt;default&gt;
-     *       &lt;!-- Any valid tags at switch position --&gt;
-     *     &lt;/default&gt;
-     *   &lt;/switch&gt;
-     * </pre>
-     * You can declare Key style and specify styles within Key tags.
-     * <pre>
-     *     &lt;switch&gt;
-     *       &lt;case mode="email"&gt;
-     *         &lt;key-style styleName="f1-key" parentStyle="modifier-key"
-     *           keyLabel=".com"
-     *         /&gt;
-     *       &lt;/case&gt;
-     *       &lt;case mode="url"&gt;
-     *         &lt;key-style styleName="f1-key" parentStyle="modifier-key"
-     *           keyLabel="http://"
-     *         /&gt;
-     *       &lt;/case&gt;
-     *     &lt;/switch&gt;
-     *     ...
-     *     &lt;Key keyStyle="shift-key" ... /&gt;
-     * </pre>
-     */
-
-    // TODO: Move this class to internal package.
-    public static class Builder<KP extends Params> {
-        private static final String BUILDER_TAG = "Keyboard.Builder";
-        private static final boolean DEBUG = false;
-
-        // Keyboard XML Tags
-        private static final String TAG_KEYBOARD = "Keyboard";
-        private static final String TAG_ROW = "Row";
-        private static final String TAG_KEY = "Key";
-        private static final String TAG_SPACER = "Spacer";
-        private static final String TAG_INCLUDE = "include";
-        private static final String TAG_MERGE = "merge";
-        private static final String TAG_SWITCH = "switch";
-        private static final String TAG_CASE = "case";
-        private static final String TAG_DEFAULT = "default";
-        public static final String TAG_KEY_STYLE = "key-style";
-
-        private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
-        private static final int DEFAULT_KEYBOARD_ROWS = 4;
-
-        protected final KP mParams;
-        protected final Context mContext;
-        protected final Resources mResources;
-        private final DisplayMetrics mDisplayMetrics;
-
-        private int mCurrentY = 0;
-        private Row mCurrentRow = null;
-        private boolean mLeftEdge;
-        private boolean mTopEdge;
-        private Key mRightEdgeKey = null;
-
-        /**
-         * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
-         * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
-         * defines.
-         */
-        public static class Row {
-            // keyWidth enum constants
-            private static final int KEYWIDTH_NOT_ENUM = 0;
-            private static final int KEYWIDTH_FILL_RIGHT = -1;
-
-            private final Params mParams;
-            /** Default width of a key in this row. */
-            private float mDefaultKeyWidth;
-            /** Default height of a key in this row. */
-            public final int mRowHeight;
-            /** Default keyLabelFlags in this row. */
-            private int mDefaultKeyLabelFlags;
-            /** Default backgroundType for this row */
-            private int mDefaultBackgroundType;
-
-            private final int mCurrentY;
-            // Will be updated by {@link Key}'s constructor.
-            private float mCurrentX;
-
-            public Row(Resources res, Params params, XmlPullParser parser, int y) {
-                mParams = params;
-                TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
-                        R.styleable.Keyboard);
-                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 = ResourceUtils.getDimensionOrFraction(keyAttr,
-                        R.styleable.Keyboard_Key_keyWidth,
-                        params.mBaseWidth, params.mDefaultKeyWidth);
-                mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
-                        Key.BACKGROUND_TYPE_NORMAL);
-                keyAttr.recycle();
-
-                // TODO: Initialize this with <Row> attribute as backgroundType is done.
-                mDefaultKeyLabelFlags = 0;
-                mCurrentY = y;
-                mCurrentX = 0.0f;
-            }
-
-            public float getDefaultKeyWidth() {
-                return mDefaultKeyWidth;
-            }
-
-            public void setDefaultKeyWidth(float defaultKeyWidth) {
-                mDefaultKeyWidth = defaultKeyWidth;
-            }
-
-            public int getDefaultKeyLabelFlags() {
-                return mDefaultKeyLabelFlags;
-            }
-
-            public void setDefaultKeyLabelFlags(int keyLabelFlags) {
-                mDefaultKeyLabelFlags = keyLabelFlags;
-            }
-
-            public int getDefaultBackgroundType() {
-                return mDefaultBackgroundType;
-            }
-
-            public void setDefaultBackgroundType(int backgroundType) {
-                mDefaultBackgroundType = backgroundType;
-            }
-
-            public void setXPos(float keyXPos) {
-                mCurrentX = keyXPos;
-            }
-
-            public void advanceXPos(float width) {
-                mCurrentX += width;
-            }
-
-            public int getKeyY() {
-                return mCurrentY;
-            }
-
-            public float getKeyX(TypedArray keyAttr) {
-                final int keyboardRightEdge = mParams.mOccupiedWidth
-                        - mParams.mHorizontalEdgesPadding;
-                if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
-                    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
-                        // keyboardWidth + keyXPos.
-                        // keyXPos shouldn't be less than mCurrentX because drawable area for this
-                        // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
-                        // its left hand side.
-                        return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
-                    } else {
-                        return keyXPos + mParams.mHorizontalEdgesPadding;
-                    }
-                }
-                return mCurrentX;
-            }
-
-            public float getKeyWidth(TypedArray keyAttr) {
-                return getKeyWidth(keyAttr, mCurrentX);
-            }
-
-            public float getKeyWidth(TypedArray keyAttr, float keyXPos) {
-                final int widthType = ResourceUtils.getEnumValue(keyAttr,
-                        R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
-                switch (widthType) {
-                case KEYWIDTH_FILL_RIGHT:
-                    final int keyboardRightEdge =
-                            mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
-                    // If keyWidth is fillRight, the actual key width will be determined to fill
-                    // out the area up to the right edge of the keyboard.
-                    return keyboardRightEdge - keyXPos;
-                default: // KEYWIDTH_NOT_ENUM
-                    return ResourceUtils.getDimensionOrFraction(keyAttr,
-                            R.styleable.Keyboard_Key_keyWidth,
-                            mParams.mBaseWidth, mDefaultKeyWidth);
-                }
-            }
-        }
-
-        public Builder(Context context, KP params) {
-            mContext = context;
-            final Resources res = context.getResources();
-            mResources = res;
-            mDisplayMetrics = res.getDisplayMetrics();
-
-            mParams = params;
-
-            params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
-            params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
-        }
-
-        public void setAutoGenerate(KeyboardLayoutSet.KeysCache keysCache) {
-            mParams.mKeysCache = keysCache;
-        }
-
-        public Builder<KP> load(int xmlId, KeyboardId id) {
-            mParams.mId = id;
-            final XmlResourceParser parser = mResources.getXml(xmlId);
-            try {
-                parseKeyboard(parser);
-            } catch (XmlPullParserException e) {
-                Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
-                throw new IllegalArgumentException(e);
-            } catch (IOException e) {
-                Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
-                throw new RuntimeException(e);
-            } finally {
-                parser.close();
-            }
-            return this;
-        }
-
-        // TODO: Remove this method.
-        public void setTouchPositionCorrectionEnabled(boolean enabled) {
-            mParams.mTouchPositionCorrection.setEnabled(enabled);
-        }
-
-        public void setProximityCharsCorrectionEnabled(boolean enabled) {
-            mParams.mProximityCharsCorrectionEnabled = enabled;
-        }
-
-        public Keyboard build() {
-            return new Keyboard(mParams);
-        }
-
-        private int mIndent;
-        private static final String SPACES = "                                             ";
-
-        private static String spaces(int count) {
-            return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES;
-        }
-
-        private void startTag(String format, Object ... args) {
-            Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
-        }
-
-        private void endTag(String format, Object ... args) {
-            Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args));
-        }
-
-        private void startEndTag(String format, Object ... args) {
-            Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
-            mIndent--;
-        }
-
-        private void parseKeyboard(XmlPullParser parser)
-                throws XmlPullParserException, IOException {
-            if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId);
-            int event;
-            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-                if (event == XmlPullParser.START_TAG) {
-                    final String tag = parser.getName();
-                    if (TAG_KEYBOARD.equals(tag)) {
-                        parseKeyboardAttributes(parser);
-                        startKeyboard();
-                        parseKeyboardContent(parser, false);
-                        break;
-                    } else {
-                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD);
-                    }
-                }
-            }
-        }
-
-        private void parseKeyboardAttributes(XmlPullParser parser) {
-            final int displayWidth = mDisplayMetrics.widthPixels;
-            final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
-                    Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
-                    R.style.Keyboard);
-            final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                    R.styleable.Keyboard_Key);
-            final TypedArray keyboardViewAttr = mResources.obtainAttributes(
-                    Xml.asAttributeSet(parser), R.styleable.KeyboardView);
-            try {
-                final int displayHeight = mDisplayMetrics.heightPixels;
-                final String keyboardHeightString = ResourceUtils.getDeviceOverrideValue(
-                        mResources, R.array.keyboard_heights, null);
-                final float keyboardHeight;
-                if (keyboardHeightString != null) {
-                    keyboardHeight = Float.parseFloat(keyboardHeightString)
-                            * mDisplayMetrics.density;
-                } else {
-                    keyboardHeight = keyboardAttr.getDimension(
-                            R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
-                }
-                final float maxKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
-                        R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
-                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 = -ResourceUtils.getDimensionOrFraction(keyboardAttr,
-                            R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
-                }
-                final Params params = mParams;
-                // Keyboard height will not exceed maxKeyboardHeight and will not be less than
-                // minKeyboardHeight.
-                params.mOccupiedHeight = (int)Math.max(
-                        Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
-                params.mOccupiedWidth = params.mId.mWidth;
-                params.mTopPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
-                        R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
-                params.mBottomPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
-                        R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
-                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)ResourceUtils.getDimensionOrFraction(keyAttr,
-                        R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
-                        params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
-                params.mHorizontalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
-                        R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
-                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)ResourceUtils.getDimensionOrFraction(keyboardAttr,
-                        R.styleable.Keyboard_rowHeight, params.mBaseHeight,
-                        params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
-
-                if (keyboardViewAttr.hasValue(R.styleable.KeyboardView_keyTypeface)) {
-                    params.mKeyTypeface = Typeface.defaultFromStyle(keyboardViewAttr.getInt(
-                            R.styleable.KeyboardView_keyTypeface, Typeface.NORMAL));
-                }
-                params.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);
-                params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt(
-                        R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
-
-                params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
-                params.mIconsSet.loadIcons(keyboardAttr);
-                final String language = params.mId.mLocale.getLanguage();
-                params.mCodesSet.setLanguage(language);
-                params.mTextsSet.setLanguage(language);
-                final RunInLocale<Void> job = new RunInLocale<Void>() {
-                    @Override
-                    protected Void job(Resources res) {
-                        params.mTextsSet.loadStringResources(mContext);
-                        return null;
-                    }
-                };
-                // Null means the current system locale.
-                final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype)
-                        ? null : params.mId.mLocale;
-                job.runInLocale(mResources, locale);
-
-                final int resourceId = keyboardAttr.getResourceId(
-                        R.styleable.Keyboard_touchPositionCorrectionData, 0);
-                params.mTouchPositionCorrection.setEnabled(resourceId != 0);
-                if (resourceId != 0) {
-                    final String[] data = mResources.getStringArray(resourceId);
-                    params.mTouchPositionCorrection.load(data);
-                }
-            } finally {
-                keyboardViewAttr.recycle();
-                keyAttr.recycle();
-                keyboardAttr.recycle();
-            }
-        }
-
-        private void parseKeyboardContent(XmlPullParser parser, boolean skip)
-                throws XmlPullParserException, IOException {
-            int event;
-            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-                if (event == XmlPullParser.START_TAG) {
-                    final String tag = parser.getName();
-                    if (TAG_ROW.equals(tag)) {
-                        Row row = parseRowAttributes(parser);
-                        if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : "");
-                        if (!skip) {
-                            startRow(row);
-                        }
-                        parseRowContent(parser, row, skip);
-                    } else if (TAG_INCLUDE.equals(tag)) {
-                        parseIncludeKeyboardContent(parser, skip);
-                    } else if (TAG_SWITCH.equals(tag)) {
-                        parseSwitchKeyboardContent(parser, skip);
-                    } else if (TAG_KEY_STYLE.equals(tag)) {
-                        parseKeyStyle(parser, skip);
-                    } else {
-                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW);
-                    }
-                } else if (event == XmlPullParser.END_TAG) {
-                    final String tag = parser.getName();
-                    if (DEBUG) endTag("</%s>", tag);
-                    if (TAG_KEYBOARD.equals(tag)) {
-                        endKeyboard();
-                        break;
-                    } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
-                            || TAG_MERGE.equals(tag)) {
-                        break;
-                    } else {
-                        throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW);
-                    }
-                }
-            }
-        }
-
-        private Row parseRowAttributes(XmlPullParser parser) throws XmlPullParserException {
-            final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                    R.styleable.Keyboard);
-            try {
-                if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
-                    throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
-                }
-                if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
-                    throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
-                }
-                return new Row(mResources, mParams, parser, mCurrentY);
-            } finally {
-                a.recycle();
-            }
-        }
-
-        private void parseRowContent(XmlPullParser parser, Row row, boolean skip)
-                throws XmlPullParserException, IOException {
-            int event;
-            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-                if (event == XmlPullParser.START_TAG) {
-                    final String tag = parser.getName();
-                    if (TAG_KEY.equals(tag)) {
-                        parseKey(parser, row, skip);
-                    } else if (TAG_SPACER.equals(tag)) {
-                        parseSpacer(parser, row, skip);
-                    } else if (TAG_INCLUDE.equals(tag)) {
-                        parseIncludeRowContent(parser, row, skip);
-                    } else if (TAG_SWITCH.equals(tag)) {
-                        parseSwitchRowContent(parser, row, skip);
-                    } else if (TAG_KEY_STYLE.equals(tag)) {
-                        parseKeyStyle(parser, skip);
-                    } else {
-                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
-                    }
-                } else if (event == XmlPullParser.END_TAG) {
-                    final String tag = parser.getName();
-                    if (DEBUG) endTag("</%s>", tag);
-                    if (TAG_ROW.equals(tag)) {
-                        if (!skip) {
-                            endRow(row);
-                        }
-                        break;
-                    } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
-                            || TAG_MERGE.equals(tag)) {
-                        break;
-                    } else {
-                        throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
-                    }
-                }
-            }
-        }
-
-        private void parseKey(XmlPullParser parser, Row row, boolean skip)
-                throws XmlPullParserException, IOException {
-            if (skip) {
-                XmlParseUtils.checkEndTag(TAG_KEY, parser);
-                if (DEBUG) {
-                    startEndTag("<%s /> skipped", TAG_KEY);
-                }
-            } else {
-                final Key key = new Key(mResources, mParams, row, parser);
-                if (DEBUG) {
-                    startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY,
-                            (key.isEnabled() ? "" : " disabled"), key,
-                            Arrays.toString(key.mMoreKeys));
-                }
-                XmlParseUtils.checkEndTag(TAG_KEY, parser);
-                endKey(key);
-            }
-        }
-
-        private void parseSpacer(XmlPullParser parser, Row row, boolean skip)
-                throws XmlPullParserException, IOException {
-            if (skip) {
-                XmlParseUtils.checkEndTag(TAG_SPACER, parser);
-                if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
-            } else {
-                final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser);
-                if (DEBUG) startEndTag("<%s />", TAG_SPACER);
-                XmlParseUtils.checkEndTag(TAG_SPACER, parser);
-                endKey(spacer);
-            }
-        }
-
-        private void parseIncludeKeyboardContent(XmlPullParser parser, boolean skip)
-                throws XmlPullParserException, IOException {
-            parseIncludeInternal(parser, null, skip);
-        }
-
-        private void parseIncludeRowContent(XmlPullParser parser, Row row, boolean skip)
-                throws XmlPullParserException, IOException {
-            parseIncludeInternal(parser, row, skip);
-        }
-
-        private void parseIncludeInternal(XmlPullParser parser, Row row, boolean skip)
-                throws XmlPullParserException, IOException {
-            if (skip) {
-                XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
-                if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
-            } else {
-                final AttributeSet attr = Xml.asAttributeSet(parser);
-                final TypedArray keyboardAttr = mResources.obtainAttributes(attr,
-                        R.styleable.Keyboard_Include);
-                final TypedArray keyAttr = mResources.obtainAttributes(attr,
-                        R.styleable.Keyboard_Key);
-                int keyboardLayout = 0;
-                float savedDefaultKeyWidth = 0;
-                int savedDefaultKeyLabelFlags = 0;
-                int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL;
-                try {
-                    XmlParseUtils.checkAttributeExists(keyboardAttr,
-                            R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
-                            TAG_INCLUDE, parser);
-                    keyboardLayout = keyboardAttr.getResourceId(
-                            R.styleable.Keyboard_Include_keyboardLayout, 0);
-                    if (row != null) {
-                        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
-                            // Override current x coordinate.
-                            row.setXPos(row.getKeyX(keyAttr));
-                        }
-                        // TODO: Remove this if-clause and do the same as backgroundType below.
-                        savedDefaultKeyWidth = row.getDefaultKeyWidth();
-                        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) {
-                            // Override default key width.
-                            row.setDefaultKeyWidth(row.getKeyWidth(keyAttr));
-                        }
-                        savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags();
-                        // Bitwise-or default keyLabelFlag if exists.
-                        row.setDefaultKeyLabelFlags(keyAttr.getInt(
-                                R.styleable.Keyboard_Key_keyLabelFlags, 0)
-                                | savedDefaultKeyLabelFlags);
-                        savedDefaultBackgroundType = row.getDefaultBackgroundType();
-                        // Override default backgroundType if exists.
-                        row.setDefaultBackgroundType(keyAttr.getInt(
-                                R.styleable.Keyboard_Key_backgroundType,
-                                savedDefaultBackgroundType));
-                    }
-                } finally {
-                    keyboardAttr.recycle();
-                    keyAttr.recycle();
-                }
-
-                XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
-                if (DEBUG) {
-                    startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
-                            mResources.getResourceEntryName(keyboardLayout));
-                }
-                final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
-                try {
-                    parseMerge(parserForInclude, row, skip);
-                } finally {
-                    if (row != null) {
-                        // Restore default keyWidth, keyLabelFlags, and backgroundType.
-                        row.setDefaultKeyWidth(savedDefaultKeyWidth);
-                        row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags);
-                        row.setDefaultBackgroundType(savedDefaultBackgroundType);
-                    }
-                    parserForInclude.close();
-                }
-            }
-        }
-
-        private void parseMerge(XmlPullParser parser, Row row, boolean skip)
-                throws XmlPullParserException, IOException {
-            if (DEBUG) startTag("<%s>", TAG_MERGE);
-            int event;
-            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-                if (event == XmlPullParser.START_TAG) {
-                    final String tag = parser.getName();
-                    if (TAG_MERGE.equals(tag)) {
-                        if (row == null) {
-                            parseKeyboardContent(parser, skip);
-                        } else {
-                            parseRowContent(parser, row, skip);
-                        }
-                        break;
-                    } else {
-                        throw new XmlParseUtils.ParseException(
-                                "Included keyboard layout must have <merge> root element", parser);
-                    }
-                }
-            }
-        }
-
-        private void parseSwitchKeyboardContent(XmlPullParser parser, boolean skip)
-                throws XmlPullParserException, IOException {
-            parseSwitchInternal(parser, null, skip);
-        }
-
-        private void parseSwitchRowContent(XmlPullParser parser, Row row, boolean skip)
-                throws XmlPullParserException, IOException {
-            parseSwitchInternal(parser, row, skip);
-        }
-
-        private void parseSwitchInternal(XmlPullParser parser, Row row, boolean skip)
-                throws XmlPullParserException, IOException {
-            if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId);
-            boolean selected = false;
-            int event;
-            while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
-                if (event == XmlPullParser.START_TAG) {
-                    final String tag = parser.getName();
-                    if (TAG_CASE.equals(tag)) {
-                        selected |= parseCase(parser, row, selected ? true : skip);
-                    } else if (TAG_DEFAULT.equals(tag)) {
-                        selected |= parseDefault(parser, row, selected ? true : skip);
-                    } else {
-                        throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
-                    }
-                } else if (event == XmlPullParser.END_TAG) {
-                    final String tag = parser.getName();
-                    if (TAG_SWITCH.equals(tag)) {
-                        if (DEBUG) endTag("</%s>", TAG_SWITCH);
-                        break;
-                    } else {
-                        throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
-                    }
-                }
-            }
-        }
-
-        private boolean parseCase(XmlPullParser parser, Row row, boolean skip)
-                throws XmlPullParserException, IOException {
-            final boolean selected = parseCaseCondition(parser);
-            if (row == null) {
-                // Processing Rows.
-                parseKeyboardContent(parser, selected ? skip : true);
-            } else {
-                // Processing Keys.
-                parseRowContent(parser, row, selected ? skip : true);
-            }
-            return selected;
-        }
-
-        private boolean parseCaseCondition(XmlPullParser parser) {
-            final KeyboardId id = mParams.mId;
-            if (id == null) {
-                return true;
-            }
-            final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                    R.styleable.Keyboard_Case);
-            try {
-                final boolean keyboardLayoutSetElementMatched = matchTypedValue(a,
-                        R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId,
-                        KeyboardId.elementIdToName(id.mElementId));
-                final boolean modeMatched = matchTypedValue(a,
-                        R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
-                final boolean navigateNextMatched = matchBoolean(a,
-                        R.styleable.Keyboard_Case_navigateNext, id.navigateNext());
-                final boolean navigatePreviousMatched = matchBoolean(a,
-                        R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious());
-                final boolean passwordInputMatched = matchBoolean(a,
-                        R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
-                final boolean clobberSettingsKeyMatched = matchBoolean(a,
-                        R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
-                final boolean shortcutKeyEnabledMatched = matchBoolean(a,
-                        R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
-                final boolean hasShortcutKeyMatched = matchBoolean(a,
-                        R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
-                final boolean languageSwitchKeyEnabledMatched = matchBoolean(a,
-                        R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
-                        id.mLanguageSwitchKeyEnabled);
-                final boolean isMultiLineMatched = matchBoolean(a,
-                        R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
-                final boolean imeActionMatched = matchInteger(a,
-                        R.styleable.Keyboard_Case_imeAction, id.imeAction());
-                final boolean localeCodeMatched = matchString(a,
-                        R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
-                final boolean languageCodeMatched = matchString(a,
-                        R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
-                final boolean countryCodeMatched = matchString(a,
-                        R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
-                final boolean selected = keyboardLayoutSetElementMatched && modeMatched
-                        && navigateNextMatched && navigatePreviousMatched && passwordInputMatched
-                        && clobberSettingsKeyMatched && shortcutKeyEnabledMatched
-                        && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
-                        && isMultiLineMatched && imeActionMatched && localeCodeMatched
-                        && languageCodeMatched && countryCodeMatched;
-
-                if (DEBUG) {
-                    startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
-                            textAttr(a.getString(
-                                    R.styleable.Keyboard_Case_keyboardLayoutSetElement),
-                                    "keyboardLayoutSetElement"),
-                            textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
-                            textAttr(a.getString(R.styleable.Keyboard_Case_imeAction),
-                                    "imeAction"),
-                            booleanAttr(a, R.styleable.Keyboard_Case_navigateNext,
-                                    "navigateNext"),
-                            booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious,
-                                    "navigatePrevious"),
-                            booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
-                                    "clobberSettingsKey"),
-                            booleanAttr(a, R.styleable.Keyboard_Case_passwordInput,
-                                    "passwordInput"),
-                            booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled,
-                                    "shortcutKeyEnabled"),
-                            booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey,
-                                    "hasShortcutKey"),
-                            booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
-                                    "languageSwitchKeyEnabled"),
-                            booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine,
-                                    "isMultiLine"),
-                            textAttr(a.getString(R.styleable.Keyboard_Case_localeCode),
-                                    "localeCode"),
-                            textAttr(a.getString(R.styleable.Keyboard_Case_languageCode),
-                                    "languageCode"),
-                            textAttr(a.getString(R.styleable.Keyboard_Case_countryCode),
-                                    "countryCode"),
-                            selected ? "" : " skipped");
-                }
-
-                return selected;
-            } finally {
-                a.recycle();
-            }
-        }
-
-        private static boolean matchInteger(TypedArray a, int index, int value) {
-            // If <case> does not have "index" attribute, that means this <case> is wild-card for
-            // the attribute.
-            return !a.hasValue(index) || a.getInt(index, 0) == value;
-        }
-
-        private static boolean matchBoolean(TypedArray a, int index, boolean value) {
-            // If <case> does not have "index" attribute, that means this <case> is wild-card for
-            // the attribute.
-            return !a.hasValue(index) || a.getBoolean(index, false) == value;
-        }
-
-        private static boolean matchString(TypedArray a, int index, String value) {
-            // If <case> does not have "index" attribute, that means this <case> is wild-card for
-            // the attribute.
-            return !a.hasValue(index)
-                    || StringUtils.containsInArray(value, a.getString(index).split("\\|"));
-        }
-
-        private static boolean matchTypedValue(TypedArray a, int index, int intValue,
-                String strValue) {
-            // If <case> does not have "index" attribute, that means this <case> is wild-card for
-            // the attribute.
-            final TypedValue v = a.peekValue(index);
-            if (v == null) {
-                return true;
-            }
-            if (ResourceUtils.isIntegerValue(v)) {
-                return intValue == a.getInt(index, 0);
-            } else if (ResourceUtils.isStringValue(v)) {
-                return StringUtils.containsInArray(strValue, a.getString(index).split("\\|"));
-            }
-            return false;
-        }
-
-        private boolean parseDefault(XmlPullParser parser, Row row, boolean skip)
-                throws XmlPullParserException, IOException {
-            if (DEBUG) startTag("<%s>", TAG_DEFAULT);
-            if (row == null) {
-                parseKeyboardContent(parser, skip);
-            } else {
-                parseRowContent(parser, row, skip);
-            }
-            return true;
-        }
-
-        private void parseKeyStyle(XmlPullParser parser, boolean skip)
-                throws XmlPullParserException, IOException {
-            TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                    R.styleable.Keyboard_KeyStyle);
-            TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
-                    R.styleable.Keyboard_Key);
-            try {
-                if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
-                    throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
-                            + "/> needs styleName attribute", parser);
-                }
-                if (DEBUG) {
-                    startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
-                            keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
-                            skip ? " skipped" : "");
-                }
-                if (!skip) {
-                    mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
-                }
-            } finally {
-                keyStyleAttr.recycle();
-                keyAttrs.recycle();
-            }
-            XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser);
-        }
-
-        private void startKeyboard() {
-            mCurrentY += mParams.mTopPadding;
-            mTopEdge = true;
-        }
-
-        private void startRow(Row row) {
-            addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
-            mCurrentRow = row;
-            mLeftEdge = true;
-            mRightEdgeKey = null;
-        }
-
-        private void endRow(Row row) {
-            if (mCurrentRow == null) {
-                throw new InflateException("orphan end row tag");
-            }
-            if (mRightEdgeKey != null) {
-                mRightEdgeKey.markAsRightEdge(mParams);
-                mRightEdgeKey = null;
-            }
-            addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
-            mCurrentY += row.mRowHeight;
-            mCurrentRow = null;
-            mTopEdge = false;
-        }
-
-        private void endKey(Key key) {
-            mParams.onAddKey(key);
-            if (mLeftEdge) {
-                key.markAsLeftEdge(mParams);
-                mLeftEdge = false;
-            }
-            if (mTopEdge) {
-                key.markAsTopEdge(mParams);
-            }
-            mRightEdgeKey = key;
-        }
-
-        private void endKeyboard() {
-            // nothing to do here.
-        }
-
-        private void addEdgeSpace(float width, Row row) {
-            row.advanceXPos(width);
-            mLeftEdge = false;
-            mRightEdgeKey = null;
-        }
-
-        private static String textAttr(String value, String name) {
-            return value != null ? String.format(" %s=%s", name, value) : "";
-        }
-
-        private static String booleanAttr(TypedArray a, int index, String name) {
-            return a.hasValue(index)
-                    ? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
-        }
-    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 76ac3de22e340ba8bfe60e43f785639a524163a1..aaccf63bab4a329686bd9b116dad52fb181d34a6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -35,7 +35,9 @@ import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
-import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.KeysCache;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.InputTypeUtils;
@@ -78,31 +80,19 @@ public class KeyboardLayoutSet {
     public static class KeyboardLayoutSetException extends RuntimeException {
         public final KeyboardId mKeyboardId;
 
-        public KeyboardLayoutSetException(Throwable cause, KeyboardId keyboardId) {
+        public KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId) {
             super(cause);
             mKeyboardId = keyboardId;
         }
     }
 
-    public static class KeysCache {
-        private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
-
-        public void clear() {
-            mMap.clear();
-        }
-
-        public Key get(Key key) {
-            final Key existingKey = mMap.get(key);
-            if (existingKey != null) {
-                // Reuse the existing element that equals to "key" without adding "key" to the map.
-                return existingKey;
-            }
-            mMap.put(key, key);
-            return key;
-        }
+    private static class ElementParams {
+        int mKeyboardXmlId;
+        boolean mProximityCharsCorrectionEnabled;
+        public ElementParams() {}
     }
 
-    static class Params {
+    private static class Params {
         String mKeyboardLayoutSetName;
         int mMode;
         EditorInfo mEditorInfo;
@@ -118,11 +108,7 @@ public class KeyboardLayoutSet {
         // Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
         final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
                 CollectionUtils.newSparseArray();
-
-        static class ElementParams {
-            int mKeyboardXmlId;
-            boolean mProximityCharsCorrectionEnabled;
-        }
+        public Params() {}
     }
 
     public static void clearKeyboardCache() {
@@ -130,12 +116,12 @@ public class KeyboardLayoutSet {
         sKeysCache.clear();
     }
 
-    private KeyboardLayoutSet(Context context, Params params) {
+    KeyboardLayoutSet(final Context context, final Params params) {
         mContext = context;
         mParams = params;
     }
 
-    public Keyboard getKeyboard(int baseKeyboardLayoutSetElementId) {
+    public Keyboard getKeyboard(final int baseKeyboardLayoutSetElementId) {
         final int keyboardLayoutSetElementId;
         switch (mParams.mMode) {
         case KeyboardId.MODE_PHONE:
@@ -170,12 +156,12 @@ public class KeyboardLayoutSet {
         }
     }
 
-    private Keyboard getKeyboard(ElementParams elementParams, final KeyboardId id) {
+    private Keyboard getKeyboard(final ElementParams elementParams, final KeyboardId id) {
         final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
         Keyboard keyboard = (ref == null) ? null : ref.get();
         if (keyboard == null) {
-            final Keyboard.Builder<Keyboard.Params> builder =
-                    new Keyboard.Builder<Keyboard.Params>(mContext, new Keyboard.Params());
+            final KeyboardBuilder<KeyboardParams> builder =
+                    new KeyboardBuilder<KeyboardParams>(mContext, new KeyboardParams());
             if (id.isAlphabetKeyboard()) {
                 builder.setAutoGenerate(sKeysCache);
             }
@@ -202,7 +188,7 @@ public class KeyboardLayoutSet {
     // KeyboardLayoutSet element id that is a key in keyboard_set.xml.  Also that file specifies
     // which XML layout should be used for each keyboard.  The KeyboardId is an internal key for
     // Keyboard object.
-    private KeyboardId getKeyboardId(int keyboardLayoutSetElementId) {
+    private KeyboardId getKeyboardId(final int keyboardLayoutSetElementId) {
         final Params params = mParams;
         final boolean isSymbols = (keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS
                 || keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED);
@@ -225,7 +211,7 @@ public class KeyboardLayoutSet {
 
         private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
 
-        public Builder(Context context, EditorInfo editorInfo) {
+        public Builder(final Context context, final EditorInfo editorInfo) {
             mContext = context;
             mPackageName = context.getPackageName();
             mResources = context.getResources();
@@ -238,7 +224,8 @@ public class KeyboardLayoutSet {
                     mPackageName, NO_SETTINGS_KEY, mEditorInfo);
         }
 
-        public Builder setScreenGeometry(int deviceFormFactor, int orientation, int widthPixels) {
+        public Builder setScreenGeometry(final int deviceFormFactor, final int orientation,
+                final int widthPixels) {
             final Params params = mParams;
             params.mDeviceFormFactor = deviceFormFactor;
             params.mOrientation = orientation;
@@ -246,7 +233,7 @@ public class KeyboardLayoutSet {
             return this;
         }
 
-        public Builder setSubtype(InputMethodSubtype subtype) {
+        public Builder setSubtype(final InputMethodSubtype subtype) {
             final boolean asciiCapable = subtype.containsExtraValueKey(ASCII_CAPABLE);
             @SuppressWarnings("deprecation")
             final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
@@ -263,8 +250,8 @@ public class KeyboardLayoutSet {
             return this;
         }
 
-        public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain,
-                boolean languageSwitchKeyEnabled) {
+        public Builder setOptions(final boolean voiceKeyEnabled, final boolean voiceKeyOnMain,
+                final boolean languageSwitchKeyEnabled) {
             @SuppressWarnings("deprecation")
             final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
                     null, NO_MICROPHONE_COMPAT, mEditorInfo);
@@ -277,7 +264,7 @@ public class KeyboardLayoutSet {
             return this;
         }
 
-        public void setTouchPositionCorrectionEnabled(boolean enabled) {
+        public void setTouchPositionCorrectionEnabled(final boolean enabled) {
             mParams.mTouchPositionCorrectionEnabled = enabled;
         }
 
@@ -298,7 +285,7 @@ public class KeyboardLayoutSet {
             return new KeyboardLayoutSet(mContext, mParams);
         }
 
-        private void parseKeyboardLayoutSet(Resources res, int resId)
+        private void parseKeyboardLayoutSet(final Resources res, final int resId)
                 throws XmlPullParserException, IOException {
             final XmlResourceParser parser = res.getXml(resId);
             try {
@@ -318,7 +305,7 @@ public class KeyboardLayoutSet {
             }
         }
 
-        private void parseKeyboardLayoutSetContent(XmlPullParser parser)
+        private void parseKeyboardLayoutSetContent(final XmlPullParser parser)
                 throws XmlPullParserException, IOException {
             int event;
             while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -340,7 +327,7 @@ public class KeyboardLayoutSet {
             }
         }
 
-        private void parseKeyboardLayoutSetElement(XmlPullParser parser)
+        private void parseKeyboardLayoutSetElement(final XmlPullParser parser)
                 throws XmlPullParserException, IOException {
             final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
                     R.styleable.KeyboardLayoutSet_Element);
@@ -367,7 +354,7 @@ public class KeyboardLayoutSet {
             }
         }
 
-        private static int getKeyboardMode(EditorInfo editorInfo) {
+        private static int getKeyboardMode(final EditorInfo editorInfo) {
             if (editorInfo == null)
                 return KeyboardId.MODE_TEXT;
 
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index a3741a2d8790aea7df950cc8dc0bd1facb6f1992..51b157c2814a78b4807d7f74adc3354eb2e4750d 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -20,15 +20,17 @@ import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
-import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StringUtils;
 
 public class MoreKeysKeyboard extends Keyboard {
     private final int mDefaultKeyCoordX;
 
-    MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) {
+    MoreKeysKeyboard(final MoreKeysKeyboardParams params) {
         super(params);
         mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
     }
@@ -37,228 +39,231 @@ public class MoreKeysKeyboard extends Keyboard {
         return mDefaultKeyCoordX;
     }
 
-    public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> {
-        private final Key mParentKey;
-        private final Drawable mDivider;
-
-        private static final float LABEL_PADDING_RATIO = 0.2f;
-        private static final float DIVIDER_RATIO = 0.2f;
+    /* package for test */
+    static class MoreKeysKeyboardParams extends KeyboardParams {
+        public boolean mIsFixedOrder;
+        /* package */int mTopRowAdjustment;
+        public int mNumRows;
+        public int mNumColumns;
+        public int mTopKeys;
+        public int mLeftKeys;
+        public int mRightKeys; // includes default key.
+        public int mDividerWidth;
+        public int mColumnWidth;
+
+        public MoreKeysKeyboardParams() {
+            super();
+        }
 
-        public static class MoreKeysKeyboardParams extends Keyboard.Params {
-            public boolean mIsFixedOrder;
-            /* package */int mTopRowAdjustment;
-            public int mNumRows;
-            public int mNumColumns;
-            public int mTopKeys;
-            public int mLeftKeys;
-            public int mRightKeys; // includes default key.
-            public int mDividerWidth;
-            public int mColumnWidth;
-
-            public MoreKeysKeyboardParams() {
-                super();
+        /**
+         * Set keyboard parameters of more keys keyboard.
+         *
+         * @param numKeys number of keys in this more keys keyboard.
+         * @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.
+         * @param dividerWidth width of divider, zero for no dividers.
+         */
+        public void setParameters(final int numKeys, final int maxColumns, final int keyWidth,
+                final int rowHeight, final int coordXInParent, final int parentKeyboardWidth,
+                final boolean isFixedColumnOrder, final int dividerWidth) {
+            mIsFixedOrder = isFixedColumnOrder;
+            if (parentKeyboardWidth / keyWidth < maxColumns) {
+                throw new IllegalArgumentException(
+                        "Keyboard is too small to hold more keys keyboard: "
+                                + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
             }
-
-            /**
-             * Set keyboard parameters of more keys keyboard.
-             *
-             * @param numKeys number of keys in this more keys keyboard.
-             * @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.
-             * @param dividerWidth width of divider, zero for no dividers.
-             */
-            public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
-                    int coordXInParent, int parentKeyboardWidth, boolean isFixedColumnOrder,
-                    int dividerWidth) {
-                mIsFixedOrder = isFixedColumnOrder;
-                if (parentKeyboardWidth / keyWidth < maxColumns) {
-                    throw new IllegalArgumentException(
-                            "Keyboard is too small to hold more keys keyboard: "
-                                    + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
-                }
-                mDefaultKeyWidth = keyWidth;
-                mDefaultRowHeight = rowHeight;
-
-                final int numRows = (numKeys + maxColumns - 1) / maxColumns;
-                mNumRows = numRows;
-                final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
-                        : getOptimizedColumns(numKeys, maxColumns);
-                mNumColumns = numColumns;
-                final int topKeys = numKeys % numColumns;
-                mTopKeys = topKeys == 0 ? numColumns : topKeys;
-
-                final int numLeftKeys = (numColumns - 1) / 2;
-                final int numRightKeys = numColumns - numLeftKeys; // including default key.
-                // Maximum number of keys we can layout both side of the parent key
-                final int maxLeftKeys = coordXInParent / keyWidth;
-                final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
-                int leftKeys, rightKeys;
-                if (numLeftKeys > maxLeftKeys) {
-                    leftKeys = maxLeftKeys;
-                    rightKeys = numColumns - leftKeys;
-                } else if (numRightKeys > maxRightKeys + 1) {
-                    rightKeys = maxRightKeys + 1; // include default key
-                    leftKeys = numColumns - rightKeys;
-                } else {
-                    leftKeys = numLeftKeys;
-                    rightKeys = numRightKeys;
-                }
-                // If the left keys fill the left side of the parent key, entire more keys keyboard
-                // should be shifted to the right unless the parent key is on the left edge.
-                if (maxLeftKeys == leftKeys && leftKeys > 0) {
-                    leftKeys--;
-                    rightKeys++;
-                }
-                // If the right keys fill the right side of the parent key, entire more keys
-                // should be shifted to the left unless the parent key is on the right edge.
-                if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
-                    leftKeys++;
-                    rightKeys--;
-                }
-                mLeftKeys = leftKeys;
-                mRightKeys = rightKeys;
-
-                // Adjustment of the top row.
-                mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
-                        : getAutoOrderTopRowAdjustment();
-                mDividerWidth = dividerWidth;
-                mColumnWidth = mDefaultKeyWidth + mDividerWidth;
-                mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
-                // Need to subtract the bottom row's gutter only.
-                mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
-                        + mTopPadding + mBottomPadding;
+            mDefaultKeyWidth = keyWidth;
+            mDefaultRowHeight = rowHeight;
+
+            final int numRows = (numKeys + maxColumns - 1) / maxColumns;
+            mNumRows = numRows;
+            final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
+                    : getOptimizedColumns(numKeys, maxColumns);
+            mNumColumns = numColumns;
+            final int topKeys = numKeys % numColumns;
+            mTopKeys = topKeys == 0 ? numColumns : topKeys;
+
+            final int numLeftKeys = (numColumns - 1) / 2;
+            final int numRightKeys = numColumns - numLeftKeys; // including default key.
+            // Maximum number of keys we can layout both side of the parent key
+            final int maxLeftKeys = coordXInParent / keyWidth;
+            final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
+            int leftKeys, rightKeys;
+            if (numLeftKeys > maxLeftKeys) {
+                leftKeys = maxLeftKeys;
+                rightKeys = numColumns - leftKeys;
+            } else if (numRightKeys > maxRightKeys + 1) {
+                rightKeys = maxRightKeys + 1; // include default key
+                leftKeys = numColumns - rightKeys;
+            } else {
+                leftKeys = numLeftKeys;
+                rightKeys = numRightKeys;
             }
-
-            private int getFixedOrderTopRowAdjustment() {
-                if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
-                        || mLeftKeys == 0  || mRightKeys == 1) {
-                    return 0;
-                }
-                return -1;
+            // If the left keys fill the left side of the parent key, entire more keys keyboard
+            // should be shifted to the right unless the parent key is on the left edge.
+            if (maxLeftKeys == leftKeys && leftKeys > 0) {
+                leftKeys--;
+                rightKeys++;
             }
-
-            private int getAutoOrderTopRowAdjustment() {
-                if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
-                        || mLeftKeys == 0 || mRightKeys == 1) {
-                    return 0;
-                }
-                return -1;
+            // If the right keys fill the right side of the parent key, entire more keys
+            // should be shifted to the left unless the parent key is on the right edge.
+            if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
+                leftKeys++;
+                rightKeys--;
             }
+            mLeftKeys = leftKeys;
+            mRightKeys = rightKeys;
+
+            // Adjustment of the top row.
+            mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
+                    : getAutoOrderTopRowAdjustment();
+            mDividerWidth = dividerWidth;
+            mColumnWidth = mDefaultKeyWidth + mDividerWidth;
+            mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
+            // Need to subtract the bottom row's gutter only.
+            mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
+                    + mTopPadding + mBottomPadding;
+        }
 
-            // Return key position according to column count (0 is default).
-            /* package */int getColumnPos(int n) {
-                return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
+        private int getFixedOrderTopRowAdjustment() {
+            if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
+                    || mLeftKeys == 0  || mRightKeys == 1) {
+                return 0;
             }
+            return -1;
+        }
 
-            private int getFixedOrderColumnPos(int n) {
-                final int col = n % mNumColumns;
-                final int row = n / mNumColumns;
-                if (!isTopRow(row)) {
-                    return col - mLeftKeys;
-                }
-                final int rightSideKeys = mTopKeys / 2;
-                final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
-                final int pos = col - leftSideKeys;
-                final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
-                final int numRightKeys = mRightKeys - 1;
-                if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
-                    return pos;
-                } else if (numRightKeys < rightSideKeys) {
-                    return pos - (rightSideKeys - numRightKeys);
-                } else { // numLeftKeys < leftSideKeys
-                    return pos + (leftSideKeys - numLeftKeys);
-                }
+        private int getAutoOrderTopRowAdjustment() {
+            if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
+                    || mLeftKeys == 0 || mRightKeys == 1) {
+                return 0;
             }
+            return -1;
+        }
 
-            private int getAutomaticColumnPos(int n) {
-                final int col = n % mNumColumns;
-                final int row = n / mNumColumns;
-                int leftKeys = mLeftKeys;
-                if (isTopRow(row)) {
-                    leftKeys += mTopRowAdjustment;
-                }
-                if (col == 0) {
-                    // default position.
-                    return 0;
-                }
+        // Return key position according to column count (0 is default).
+        /* package */int getColumnPos(final int n) {
+            return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
+        }
 
-                int pos = 0;
-                int right = 1; // include default position key.
-                int left = 0;
-                int i = 0;
-                while (true) {
-                    // Assign right key if available.
-                    if (right < mRightKeys) {
-                        pos = right;
-                        right++;
-                        i++;
-                    }
-                    if (i >= col)
-                        break;
-                    // Assign left key if available.
-                    if (left < leftKeys) {
-                        left++;
-                        pos = -left;
-                        i++;
-                    }
-                    if (i >= col)
-                        break;
-                }
+        private int getFixedOrderColumnPos(final int n) {
+            final int col = n % mNumColumns;
+            final int row = n / mNumColumns;
+            if (!isTopRow(row)) {
+                return col - mLeftKeys;
+            }
+            final int rightSideKeys = mTopKeys / 2;
+            final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
+            final int pos = col - leftSideKeys;
+            final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
+            final int numRightKeys = mRightKeys - 1;
+            if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
                 return pos;
+            } else if (numRightKeys < rightSideKeys) {
+                return pos - (rightSideKeys - numRightKeys);
+            } else { // numLeftKeys < leftSideKeys
+                return pos + (leftSideKeys - numLeftKeys);
             }
+        }
 
-            private static int getTopRowEmptySlots(int numKeys, int numColumns) {
-                final int remainings = numKeys % numColumns;
-                return remainings == 0 ? 0 : numColumns - remainings;
+        private int getAutomaticColumnPos(final int n) {
+            final int col = n % mNumColumns;
+            final int row = n / mNumColumns;
+            int leftKeys = mLeftKeys;
+            if (isTopRow(row)) {
+                leftKeys += mTopRowAdjustment;
+            }
+            if (col == 0) {
+                // default position.
+                return 0;
             }
 
-            private int getOptimizedColumns(int numKeys, int maxColumns) {
-                int numColumns = Math.min(numKeys, maxColumns);
-                while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
-                    numColumns--;
+            int pos = 0;
+            int right = 1; // include default position key.
+            int left = 0;
+            int i = 0;
+            while (true) {
+                // Assign right key if available.
+                if (right < mRightKeys) {
+                    pos = right;
+                    right++;
+                    i++;
                 }
-                return numColumns;
+                if (i >= col)
+                    break;
+                // Assign left key if available.
+                if (left < leftKeys) {
+                    left++;
+                    pos = -left;
+                    i++;
+                }
+                if (i >= col)
+                    break;
             }
+            return pos;
+        }
 
-            public int getDefaultKeyCoordX() {
-                return mLeftKeys * mColumnWidth;
-            }
+        private static int getTopRowEmptySlots(final int numKeys, final int numColumns) {
+            final int remainings = numKeys % numColumns;
+            return remainings == 0 ? 0 : numColumns - remainings;
+        }
 
-            public int getX(int n, int row) {
-                final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
-                if (isTopRow(row)) {
-                    return x + mTopRowAdjustment * (mColumnWidth / 2);
-                }
-                return x;
+        private int getOptimizedColumns(final int numKeys, final int maxColumns) {
+            int numColumns = Math.min(numKeys, maxColumns);
+            while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
+                numColumns--;
             }
+            return numColumns;
+        }
 
-            public int getY(int row) {
-                return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
-            }
+        public int getDefaultKeyCoordX() {
+            return mLeftKeys * mColumnWidth;
+        }
 
-            public void markAsEdgeKey(Key key, int row) {
-                if (row == 0)
-                    key.markAsTopEdge(this);
-                if (isTopRow(row))
-                    key.markAsBottomEdge(this);
+        public int getX(final int n, final int row) {
+            final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
+            if (isTopRow(row)) {
+                return x + mTopRowAdjustment * (mColumnWidth / 2);
             }
+            return x;
+        }
 
-            private boolean isTopRow(int rowCount) {
-                return mNumRows > 1 && rowCount == mNumRows - 1;
-            }
+        public int getY(final int row) {
+            return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
         }
 
+        public void markAsEdgeKey(final Key key, final int row) {
+            if (row == 0)
+                key.markAsTopEdge(this);
+            if (isTopRow(row))
+                key.markAsBottomEdge(this);
+        }
+
+        private boolean isTopRow(final int rowCount) {
+            return mNumRows > 1 && rowCount == mNumRows - 1;
+        }
+    }
+
+    public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> {
+        private final Key mParentKey;
+        private final Drawable mDivider;
+
+        private static final float LABEL_PADDING_RATIO = 0.2f;
+        private static final float DIVIDER_RATIO = 0.2f;
+
+
         /**
          * The builder of MoreKeysKeyboard.
          * @param containerView the container of {@link MoreKeysKeyboardView}.
          * @param parentKey the {@link Key} that invokes more keys keyboard.
          * @param parentKeyboardView the {@link KeyboardView} that contains the parentKey.
          */
-        public Builder(View containerView, Key parentKey, KeyboardView parentKeyboardView) {
+        public Builder(final View containerView, final Key parentKey,
+                final KeyboardView parentKeyboardView) {
             super(containerView.getContext(), new MoreKeysKeyboardParams());
             final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
             load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId);
@@ -300,7 +305,8 @@ public class MoreKeysKeyboard extends Keyboard {
                     dividerWidth);
         }
 
-        private static int getMaxKeyWidth(KeyboardView view, Key parentKey, int minKeyWidth) {
+        private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey,
+                final int minKeyWidth) {
             final int padding = (int)(view.getResources()
                     .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
                     + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0));
@@ -322,24 +328,6 @@ public class MoreKeysKeyboard extends Keyboard {
             return maxWidth;
         }
 
-        private static class MoreKeyDivider extends Key.Spacer {
-            private final Drawable mIcon;
-
-            public MoreKeyDivider(MoreKeysKeyboardParams params, Drawable icon, int x, int y) {
-                super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
-                mIcon = icon;
-            }
-
-            @Override
-            public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
-                // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
-                // constructor.
-                // TODO: Drawable itself should have an alpha value.
-                mIcon.setAlpha(128);
-                return mIcon;
-            }
-        }
-
         @Override
         public MoreKeysKeyboard build() {
             final MoreKeysKeyboardParams params = mParams;
@@ -368,4 +356,23 @@ public class MoreKeysKeyboard extends Keyboard {
             return new MoreKeysKeyboard(params);
         }
     }
+
+    private static class MoreKeyDivider extends Key.Spacer {
+        private final Drawable mIcon;
+
+        public MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon,
+                final int x, final int y) {
+            super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
+            mIcon = icon;
+        }
+
+        @Override
+        public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
+            // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
+            // constructor.
+            // TODO: Drawable itself should have an alpha value.
+            mIcon.setAlpha(128);
+            return mIcon;
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 71bf31faaab357cf3f30691d8a2da81c7d426c02..e1b082c1687aa9f5a937b32f645d4efcf53c4640 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.keyboard;
 import android.graphics.Rect;
 import android.text.TextUtils;
 
-import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
+import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.JniUtils;
 
@@ -48,9 +48,10 @@ public class ProximityInfo {
     private final Key[][] mGridNeighbors;
     private final String mLocaleStr;
 
-    ProximityInfo(String localeStr, int gridWidth, int gridHeight, int minWidth, int height,
-            int mostCommonKeyWidth, int mostCommonKeyHeight, final Key[] keys,
-            TouchPositionCorrection touchPositionCorrection) {
+    ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight,
+            final int minWidth, final int height, final int mostCommonKeyWidth,
+            final int mostCommonKeyHeight, final Key[] keys,
+            final TouchPositionCorrection touchPositionCorrection) {
         if (TextUtils.isEmpty(localeStr)) {
             mLocaleStr = "";
         } else {
@@ -81,7 +82,7 @@ public class ProximityInfo {
     }
 
     public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity,
-            int rowSize, int gridWidth, int gridHeight) {
+            final int rowSize, final int gridWidth, final int gridHeight) {
         final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
         spellCheckerProximityInfo.mNativeProximityInfo =
                 spellCheckerProximityInfo.setProximityInfoNative("",
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 13214bb9f11201baeb2a5192af5a48602c581de0..2a57caa5f59e25a7af8f52d4fd5214f31c8fa762 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -56,59 +56,20 @@ public class KeySpecParser {
     private static final char ESCAPE_CHAR = '\\';
     private static final char LABEL_END = '|';
     private static final String PREFIX_TEXT = "!text/";
-    private static final String PREFIX_ICON = "!icon/";
+    static final String PREFIX_ICON = "!icon/";
     private static final String PREFIX_CODE = "!code/";
     private static final String PREFIX_HEX = "0x";
     private static final String ADDITIONAL_MORE_KEY_MARKER = "%";
 
-    public static class MoreKeySpec {
-        public final int mCode;
-        public final String mLabel;
-        public final String mOutputText;
-        public final int mIconId;
-
-        public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, Locale locale,
-                final KeyboardCodesSet codesSet) {
-            mLabel = toUpperCaseOfStringForLocale(getLabel(moreKeySpec),
-                    needsToUpperCase, locale);
-            final int code = toUpperCaseOfCodeForLocale(getCode(moreKeySpec, codesSet),
-                    needsToUpperCase, locale);
-            if (code == Keyboard.CODE_UNSPECIFIED) {
-                // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
-                // upper case representation ("SS").
-                mCode = Keyboard.CODE_OUTPUT_TEXT;
-                mOutputText = mLabel;
-            } else {
-                mCode = code;
-                mOutputText = toUpperCaseOfStringForLocale(getOutputText(moreKeySpec),
-                        needsToUpperCase, locale);
-            }
-            mIconId = getIconId(moreKeySpec);
-        }
-
-        @Override
-        public String toString() {
-            final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel
-                    : PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
-            final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText
-                    : Keyboard.printableCode(mCode));
-            if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
-                return output;
-            } else {
-                return label + "|" + output;
-            }
-        }
-    }
-
     private KeySpecParser() {
         // Intentional empty constructor for utility class.
     }
 
-    private static boolean hasIcon(String moreKeySpec) {
+    private static boolean hasIcon(final String moreKeySpec) {
         return moreKeySpec.startsWith(PREFIX_ICON);
     }
 
-    private static boolean hasCode(String moreKeySpec) {
+    private static boolean hasCode(final String moreKeySpec) {
         final int end = indexOfLabelEnd(moreKeySpec, 0);
         if (end > 0 && end + 1 < moreKeySpec.length() && moreKeySpec.startsWith(
                 PREFIX_CODE, end + 1)) {
@@ -117,7 +78,7 @@ public class KeySpecParser {
         return false;
     }
 
-    private static String parseEscape(String text) {
+    private static String parseEscape(final String text) {
         if (text.indexOf(ESCAPE_CHAR) < 0) {
             return text;
         }
@@ -136,7 +97,7 @@ public class KeySpecParser {
         return sb.toString();
     }
 
-    private static int indexOfLabelEnd(String moreKeySpec, int start) {
+    private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
         if (moreKeySpec.indexOf(ESCAPE_CHAR, start) < 0) {
             final int end = moreKeySpec.indexOf(LABEL_END, start);
             if (end == 0) {
@@ -157,7 +118,7 @@ public class KeySpecParser {
         return -1;
     }
 
-    public static String getLabel(String moreKeySpec) {
+    public static String getLabel(final String moreKeySpec) {
         if (hasIcon(moreKeySpec)) {
             return null;
         }
@@ -170,7 +131,7 @@ public class KeySpecParser {
         return label;
     }
 
-    private static String getOutputTextInternal(String moreKeySpec) {
+    private static String getOutputTextInternal(final String moreKeySpec) {
         final int end = indexOfLabelEnd(moreKeySpec, 0);
         if (end <= 0) {
             return null;
@@ -181,7 +142,7 @@ public class KeySpecParser {
         return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1));
     }
 
-    static String getOutputText(String moreKeySpec) {
+    static String getOutputText(final String moreKeySpec) {
         if (hasCode(moreKeySpec)) {
             return null;
         }
@@ -205,7 +166,7 @@ public class KeySpecParser {
         return (StringUtils.codePointCount(label) == 1) ? null : label;
     }
 
-    static int getCode(String moreKeySpec, KeyboardCodesSet codesSet) {
+    static int getCode(final String moreKeySpec, final KeyboardCodesSet codesSet) {
         if (hasCode(moreKeySpec)) {
             final int end = indexOfLabelEnd(moreKeySpec, 0);
             if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
@@ -230,7 +191,8 @@ public class KeySpecParser {
         return Keyboard.CODE_OUTPUT_TEXT;
     }
 
-    public static int parseCode(String text, KeyboardCodesSet codesSet, int defCode) {
+    public static int parseCode(final String text, final KeyboardCodesSet codesSet,
+            final int defCode) {
         if (text == null) return defCode;
         if (text.startsWith(PREFIX_CODE)) {
             return codesSet.getCode(text.substring(PREFIX_CODE.length()));
@@ -241,7 +203,7 @@ public class KeySpecParser {
         }
     }
 
-    public static int getIconId(String moreKeySpec) {
+    public static int getIconId(final String moreKeySpec) {
         if (moreKeySpec != null && hasIcon(moreKeySpec)) {
             final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length());
             final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length())
@@ -251,7 +213,7 @@ public class KeySpecParser {
         return KeyboardIconsSet.ICON_UNDEFINED;
     }
 
-    private static <T> ArrayList<T> arrayAsList(T[] array, int start, int end) {
+    private static <T> ArrayList<T> arrayAsList(final T[] array, final int start, final int end) {
         if (array == null) {
             throw new NullPointerException();
         }
@@ -268,7 +230,7 @@ public class KeySpecParser {
 
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
-    private static String[] filterOutEmptyString(String[] array) {
+    private static String[] filterOutEmptyString(final String[] array) {
         if (array == null) {
             return EMPTY_STRING_ARRAY;
         }
@@ -289,8 +251,8 @@ public class KeySpecParser {
         return out.toArray(new String[out.size()]);
     }
 
-    public static String[] insertAdditionalMoreKeys(String[] moreKeySpecs,
-            String[] additionalMoreKeySpecs) {
+    public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
+            final String[] additionalMoreKeySpecs) {
         final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
         final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
         final int moreKeysCount = moreKeys.length;
@@ -357,12 +319,13 @@ public class KeySpecParser {
 
     @SuppressWarnings("serial")
     public static class KeySpecParserError extends RuntimeException {
-        public KeySpecParserError(String message) {
+        public KeySpecParserError(final String message) {
             super(message);
         }
     }
 
-    public static String resolveTextReference(String rawText, KeyboardTextsSet textsSet) {
+    public static String resolveTextReference(final String rawText,
+            final KeyboardTextsSet textsSet) {
         int level = 0;
         String text = rawText;
         StringBuilder sb;
@@ -408,7 +371,7 @@ public class KeySpecParser {
         return text;
     }
 
-    private static int searchTextNameEnd(String text, int start) {
+    private static int searchTextNameEnd(final String text, final int start) {
         final int size = text.length();
         for (int pos = start; pos < size; pos++) {
             final char c = text.charAt(pos);
@@ -421,7 +384,7 @@ public class KeySpecParser {
         return size;
     }
 
-    public static String[] parseCsvString(String rawText, KeyboardTextsSet textsSet) {
+    public static String[] parseCsvString(final String rawText, final KeyboardTextsSet textsSet) {
         final String text = resolveTextReference(rawText, textsSet);
         final int size = text.length();
         if (size == 0) {
@@ -460,7 +423,8 @@ public class KeySpecParser {
         return list.toArray(new String[list.size()]);
     }
 
-    public static int getIntValue(String[] moreKeys, String key, int defaultValue) {
+    public static int getIntValue(final String[] moreKeys, final String key,
+            final int defaultValue) {
         if (moreKeys == null) {
             return defaultValue;
         }
@@ -486,7 +450,7 @@ public class KeySpecParser {
         return value;
     }
 
-    public static boolean getBooleanValue(String[] moreKeys, String key) {
+    public static boolean getBooleanValue(final String[] moreKeys, final String key) {
         if (moreKeys == null) {
             return false;
         }
@@ -502,8 +466,8 @@ public class KeySpecParser {
         return value;
     }
 
-    public static int toUpperCaseOfCodeForLocale(int code, boolean needsToUpperCase,
-            Locale locale) {
+    public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase,
+            final Locale locale) {
         if (!Keyboard.isLetterCode(code) || !needsToUpperCase) return code;
         final String text = new String(new int[] { code } , 0, 1);
         final String casedText = KeySpecParser.toUpperCaseOfStringForLocale(
@@ -512,8 +476,8 @@ public class KeySpecParser {
                 ? casedText.codePointAt(0) : CODE_UNSPECIFIED;
     }
 
-    public static String toUpperCaseOfStringForLocale(String text, boolean needsToUpperCase,
-            Locale locale) {
+    public static String toUpperCaseOfStringForLocale(final String text,
+            final boolean needsToUpperCase, final Locale locale) {
         if (text == null || !needsToUpperCase) return text;
         return text.toUpperCase(locale);
     }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
new file mode 100644
index 0000000000000000000000000000000000000000..e8cacf9e75672e654d6a0a7d56221b9fb8eee578
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+
+public abstract class KeyStyle {
+    private final KeyboardTextsSet mTextsSet;
+
+    public abstract String[] getStringArray(TypedArray a, int index);
+    public abstract String getString(TypedArray a, int index);
+    public abstract int getInt(TypedArray a, int index, int defaultValue);
+    public abstract int getFlag(TypedArray a, int index);
+
+    protected KeyStyle(final KeyboardTextsSet textsSet) {
+        mTextsSet = textsSet;
+    }
+
+    protected String parseString(final TypedArray a, final int index) {
+        if (a.hasValue(index)) {
+            return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
+        }
+        return null;
+    }
+
+    protected String[] parseStringArray(final TypedArray a, final int index) {
+        if (a.hasValue(index)) {
+            return KeySpecParser.parseCsvString(a.getString(index), mTextsSet);
+        }
+        return null;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
similarity index 73%
rename from java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
rename to java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index e40cf45ccd3b7b98eebe8791417a17e882897e58..71fd3056321a29c015b3ffa9de0d3e16fc2c688f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -20,7 +20,6 @@ import android.content.res.TypedArray;
 import android.util.Log;
 import android.util.SparseArray;
 
-import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.XmlParseUtils;
@@ -30,75 +29,62 @@ import org.xmlpull.v1.XmlPullParserException;
 
 import java.util.HashMap;
 
-public class KeyStyles {
-    private static final String TAG = KeyStyles.class.getSimpleName();
+public class KeyStylesSet {
+    private static final String TAG = KeyStylesSet.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
+    private final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
 
-    final KeyboardTextsSet mTextsSet;
+    private final KeyboardTextsSet mTextsSet;
     private final KeyStyle mEmptyKeyStyle;
     private static final String EMPTY_STYLE_NAME = "<empty>";
 
-    public KeyStyles(KeyboardTextsSet textsSet) {
+    public KeyStylesSet(final KeyboardTextsSet textsSet) {
         mTextsSet = textsSet;
-        mEmptyKeyStyle = new EmptyKeyStyle();
+        mEmptyKeyStyle = new EmptyKeyStyle(textsSet);
         mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle);
     }
 
-    public abstract class KeyStyle {
-        public abstract String[] getStringArray(TypedArray a, int index);
-        public abstract String getString(TypedArray a, int index);
-        public abstract int getInt(TypedArray a, int index, int defaultValue);
-        public abstract int getFlag(TypedArray a, int index);
-
-        protected String parseString(TypedArray a, int index) {
-            if (a.hasValue(index)) {
-                return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
-            }
-            return null;
-        }
-
-        protected String[] parseStringArray(TypedArray a, int index) {
-            if (a.hasValue(index)) {
-                return KeySpecParser.parseCsvString(a.getString(index), mTextsSet);
-            }
-            return null;
+    private static class EmptyKeyStyle extends KeyStyle {
+        EmptyKeyStyle(final KeyboardTextsSet textsSet) {
+            super(textsSet);
         }
-    }
 
-    class EmptyKeyStyle extends KeyStyle {
         @Override
-        public String[] getStringArray(TypedArray a, int index) {
+        public String[] getStringArray(final TypedArray a, final int index) {
             return parseStringArray(a, index);
         }
 
         @Override
-        public String getString(TypedArray a, int index) {
+        public String getString(final TypedArray a, final int index) {
             return parseString(a, index);
         }
 
         @Override
-        public int getInt(TypedArray a, int index, int defaultValue) {
+        public int getInt(final TypedArray a, final int index, final int defaultValue) {
             return a.getInt(index, defaultValue);
         }
 
         @Override
-        public int getFlag(TypedArray a, int index) {
+        public int getFlag(final TypedArray a, final int index) {
             return a.getInt(index, 0);
         }
     }
 
-    private class DeclaredKeyStyle extends KeyStyle {
+    private static class DeclaredKeyStyle extends KeyStyle {
+        private final HashMap<String, KeyStyle> mStyles;
         private final String mParentStyleName;
         private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
 
-        public DeclaredKeyStyle(String parentStyleName) {
+        public DeclaredKeyStyle(final String parentStyleName, final KeyboardTextsSet textsSet,
+                final HashMap<String, KeyStyle> styles) {
+            super(textsSet);
             mParentStyleName = parentStyleName;
+            mStyles = styles;
         }
 
         @Override
-        public String[] getStringArray(TypedArray a, int index) {
+        public String[] getStringArray(final TypedArray a, final int index) {
             if (a.hasValue(index)) {
                 return parseStringArray(a, index);
             }
@@ -111,7 +97,7 @@ public class KeyStyles {
         }
 
         @Override
-        public String getString(TypedArray a, int index) {
+        public String getString(final TypedArray a, final int index) {
             if (a.hasValue(index)) {
                 return parseString(a, index);
             }
@@ -124,7 +110,7 @@ public class KeyStyles {
         }
 
         @Override
-        public int getInt(TypedArray a, int index, int defaultValue) {
+        public int getInt(final TypedArray a, final int index, final int defaultValue) {
             if (a.hasValue(index)) {
                 return a.getInt(index, defaultValue);
             }
@@ -137,7 +123,7 @@ public class KeyStyles {
         }
 
         @Override
-        public int getFlag(TypedArray a, int index) {
+        public int getFlag(final TypedArray a, final int index) {
             int flags = a.getInt(index, 0);
             final Object value = mStyleAttributes.get(index);
             if (value != null) {
@@ -147,7 +133,7 @@ public class KeyStyles {
             return flags | parentStyle.getFlag(a, index);
         }
 
-        void readKeyAttributes(TypedArray keyAttr) {
+        public void readKeyAttributes(final TypedArray keyAttr) {
             // TODO: Currently not all Key attributes can be declared as style.
             readString(keyAttr, R.styleable.Keyboard_Key_code);
             readString(keyAttr, R.styleable.Keyboard_Key_altCode);
@@ -165,38 +151,38 @@ public class KeyStyles {
             readFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
         }
 
-        private void readString(TypedArray a, int index) {
+        private void readString(final TypedArray a, final int index) {
             if (a.hasValue(index)) {
                 mStyleAttributes.put(index, parseString(a, index));
             }
         }
 
-        private void readInt(TypedArray a, int index) {
+        private void readInt(final TypedArray a, final int index) {
             if (a.hasValue(index)) {
                 mStyleAttributes.put(index, a.getInt(index, 0));
             }
         }
 
-        private void readFlag(TypedArray a, int index) {
+        private void readFlag(final TypedArray a, final int index) {
             if (a.hasValue(index)) {
                 final Integer value = (Integer)mStyleAttributes.get(index);
                 mStyleAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0));
             }
         }
 
-        private void readStringArray(TypedArray a, int index) {
+        private void readStringArray(final TypedArray a, final int index) {
             if (a.hasValue(index)) {
                 mStyleAttributes.put(index, parseStringArray(a, index));
             }
         }
     }
 
-    public void parseKeyStyleAttributes(TypedArray keyStyleAttr, TypedArray keyAttrs,
-            XmlPullParser parser) throws XmlPullParserException {
+    public void parseKeyStyleAttributes(final TypedArray keyStyleAttr, final TypedArray keyAttrs,
+            final XmlPullParser parser) throws XmlPullParserException {
         final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName);
         if (DEBUG) {
             Log.d(TAG, String.format("<%s styleName=%s />",
-                    Keyboard.Builder.TAG_KEY_STYLE, styleName));
+                    KeyboardBuilder.TAG_KEY_STYLE, styleName));
             if (mStyles.containsKey(styleName)) {
                 Log.d(TAG, "key-style " + styleName + " is overridden at "
                         + parser.getPositionDescription());
@@ -211,12 +197,12 @@ public class KeyStyles {
                         "Unknown parentStyle " + parentStyleName, parser);
             }
         }
-        final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName);
+        final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName, mTextsSet, mStyles);
         style.readKeyAttributes(keyAttrs);
         mStyles.put(styleName, style);
     }
 
-    public KeyStyle getKeyStyle(TypedArray keyAttr, XmlPullParser parser)
+    public KeyStyle getKeyStyle(final TypedArray keyAttr, final XmlPullParser parser)
             throws XmlParseUtils.ParseException {
         if (!keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) {
             return mEmptyKeyStyle;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..c20b455347af8cafe8c0f3de136411beea1fa552
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -0,0 +1,829 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.InflateException;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+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;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Keyboard Building helper.
+ *
+ * This class parses Keyboard XML file and eventually build a Keyboard.
+ * The Keyboard XML file looks like:
+ * <pre>
+ *   &lt;!-- xml/keyboard.xml --&gt;
+ *   &lt;Keyboard keyboard_attributes*&gt;
+ *     &lt;!-- Keyboard Content --&gt;
+ *     &lt;Row row_attributes*&gt;
+ *       &lt;!-- Row Content --&gt;
+ *       &lt;Key key_attributes* /&gt;
+ *       &lt;Spacer horizontalGap="32.0dp" /&gt;
+ *       &lt;include keyboardLayout="@xml/other_keys"&gt;
+ *       ...
+ *     &lt;/Row&gt;
+ *     &lt;include keyboardLayout="@xml/other_rows"&gt;
+ *     ...
+ *   &lt;/Keyboard&gt;
+ * </pre>
+ * The XML file which is included in other file must have &lt;merge&gt; as root element,
+ * such as:
+ * <pre>
+ *   &lt;!-- xml/other_keys.xml --&gt;
+ *   &lt;merge&gt;
+ *     &lt;Key key_attributes* /&gt;
+ *     ...
+ *   &lt;/merge&gt;
+ * </pre>
+ * and
+ * <pre>
+ *   &lt;!-- xml/other_rows.xml --&gt;
+ *   &lt;merge&gt;
+ *     &lt;Row row_attributes*&gt;
+ *       &lt;Key key_attributes* /&gt;
+ *     &lt;/Row&gt;
+ *     ...
+ *   &lt;/merge&gt;
+ * </pre>
+ * You can also use switch-case-default tags to select Rows and Keys.
+ * <pre>
+ *   &lt;switch&gt;
+ *     &lt;case case_attribute*&gt;
+ *       &lt;!-- Any valid tags at switch position --&gt;
+ *     &lt;/case&gt;
+ *     ...
+ *     &lt;default&gt;
+ *       &lt;!-- Any valid tags at switch position --&gt;
+ *     &lt;/default&gt;
+ *   &lt;/switch&gt;
+ * </pre>
+ * You can declare Key style and specify styles within Key tags.
+ * <pre>
+ *     &lt;switch&gt;
+ *       &lt;case mode="email"&gt;
+ *         &lt;key-style styleName="f1-key" parentStyle="modifier-key"
+ *           keyLabel=".com"
+ *         /&gt;
+ *       &lt;/case&gt;
+ *       &lt;case mode="url"&gt;
+ *         &lt;key-style styleName="f1-key" parentStyle="modifier-key"
+ *           keyLabel="http://"
+ *         /&gt;
+ *       &lt;/case&gt;
+ *     &lt;/switch&gt;
+ *     ...
+ *     &lt;Key keyStyle="shift-key" ... /&gt;
+ * </pre>
+ */
+
+public class KeyboardBuilder<KP extends KeyboardParams> {
+    private static final String BUILDER_TAG = "Keyboard.Builder";
+    private static final boolean DEBUG = false;
+
+    // Keyboard XML Tags
+    private static final String TAG_KEYBOARD = "Keyboard";
+    private static final String TAG_ROW = "Row";
+    private static final String TAG_KEY = "Key";
+    private static final String TAG_SPACER = "Spacer";
+    private static final String TAG_INCLUDE = "include";
+    private static final String TAG_MERGE = "merge";
+    private static final String TAG_SWITCH = "switch";
+    private static final String TAG_CASE = "case";
+    private static final String TAG_DEFAULT = "default";
+    public static final String TAG_KEY_STYLE = "key-style";
+
+    private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
+    private static final int DEFAULT_KEYBOARD_ROWS = 4;
+
+    protected final KP mParams;
+    protected final Context mContext;
+    protected final Resources mResources;
+    private final DisplayMetrics mDisplayMetrics;
+
+    private int mCurrentY = 0;
+    private KeyboardRow mCurrentRow = null;
+    private boolean mLeftEdge;
+    private boolean mTopEdge;
+    private Key mRightEdgeKey = null;
+
+    public KeyboardBuilder(final Context context, final KP params) {
+        mContext = context;
+        final Resources res = context.getResources();
+        mResources = res;
+        mDisplayMetrics = res.getDisplayMetrics();
+
+        mParams = params;
+
+        params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
+        params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
+    }
+
+    public void setAutoGenerate(final KeysCache keysCache) {
+        mParams.mKeysCache = keysCache;
+    }
+
+    public KeyboardBuilder<KP> load(final int xmlId, final KeyboardId id) {
+        mParams.mId = id;
+        final XmlResourceParser parser = mResources.getXml(xmlId);
+        try {
+            parseKeyboard(parser);
+        } catch (XmlPullParserException e) {
+            Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
+            throw new IllegalArgumentException(e);
+        } catch (IOException e) {
+            Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
+            throw new RuntimeException(e);
+        } finally {
+            parser.close();
+        }
+        return this;
+    }
+
+    // TODO: Remove this method.
+    public void setTouchPositionCorrectionEnabled(final boolean enabled) {
+        mParams.mTouchPositionCorrection.setEnabled(enabled);
+    }
+
+    public void setProximityCharsCorrectionEnabled(final boolean enabled) {
+        mParams.mProximityCharsCorrectionEnabled = enabled;
+    }
+
+    public Keyboard build() {
+        return new Keyboard(mParams);
+    }
+
+    private int mIndent;
+    private static final String SPACES = "                                             ";
+
+    private static String spaces(final int count) {
+        return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES;
+    }
+
+    private void startTag(final String format, final Object ... args) {
+        Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
+    }
+
+    private void endTag(final String format, final Object ... args) {
+        Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args));
+    }
+
+    private void startEndTag(final String format, final Object ... args) {
+        Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
+        mIndent--;
+    }
+
+    private void parseKeyboard(final XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId);
+        int event;
+        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (event == XmlPullParser.START_TAG) {
+                final String tag = parser.getName();
+                if (TAG_KEYBOARD.equals(tag)) {
+                    parseKeyboardAttributes(parser);
+                    startKeyboard();
+                    parseKeyboardContent(parser, false);
+                    break;
+                } else {
+                    throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD);
+                }
+            }
+        }
+    }
+
+    private void parseKeyboardAttributes(final XmlPullParser parser) {
+        final int displayWidth = mDisplayMetrics.widthPixels;
+        final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
+                Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
+                R.style.Keyboard);
+        final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                R.styleable.Keyboard_Key);
+        final TypedArray keyboardViewAttr = mResources.obtainAttributes(
+                Xml.asAttributeSet(parser), R.styleable.KeyboardView);
+        try {
+            final int displayHeight = mDisplayMetrics.heightPixels;
+            final String keyboardHeightString = ResourceUtils.getDeviceOverrideValue(
+                    mResources, R.array.keyboard_heights, null);
+            final float keyboardHeight;
+            if (keyboardHeightString != null) {
+                keyboardHeight = Float.parseFloat(keyboardHeightString)
+                        * mDisplayMetrics.density;
+            } else {
+                keyboardHeight = keyboardAttr.getDimension(
+                        R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
+            }
+            final float maxKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
+            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 = -ResourceUtils.getDimensionOrFraction(keyboardAttr,
+                        R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
+            }
+            final KeyboardParams params = mParams;
+            // Keyboard height will not exceed maxKeyboardHeight and will not be less than
+            // minKeyboardHeight.
+            params.mOccupiedHeight = (int)Math.max(
+                    Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
+            params.mOccupiedWidth = params.mId.mWidth;
+            params.mTopPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
+            params.mBottomPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
+            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)ResourceUtils.getDimensionOrFraction(keyAttr,
+                    R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
+                    params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
+            params.mHorizontalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
+            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)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_rowHeight, params.mBaseHeight,
+                    params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
+
+            if (keyboardViewAttr.hasValue(R.styleable.KeyboardView_keyTypeface)) {
+                params.mKeyTypeface = Typeface.defaultFromStyle(keyboardViewAttr.getInt(
+                        R.styleable.KeyboardView_keyTypeface, Typeface.NORMAL));
+            }
+            params.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);
+            params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt(
+                    R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
+
+            params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
+            params.mIconsSet.loadIcons(keyboardAttr);
+            final String language = params.mId.mLocale.getLanguage();
+            params.mCodesSet.setLanguage(language);
+            params.mTextsSet.setLanguage(language);
+            final RunInLocale<Void> job = new RunInLocale<Void>() {
+                @Override
+                protected Void job(Resources res) {
+                    params.mTextsSet.loadStringResources(mContext);
+                    return null;
+                }
+            };
+            // Null means the current system locale.
+            final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype)
+                    ? null : params.mId.mLocale;
+            job.runInLocale(mResources, locale);
+
+            final int resourceId = keyboardAttr.getResourceId(
+                    R.styleable.Keyboard_touchPositionCorrectionData, 0);
+            params.mTouchPositionCorrection.setEnabled(resourceId != 0);
+            if (resourceId != 0) {
+                final String[] data = mResources.getStringArray(resourceId);
+                params.mTouchPositionCorrection.load(data);
+            }
+        } finally {
+            keyboardViewAttr.recycle();
+            keyAttr.recycle();
+            keyboardAttr.recycle();
+        }
+    }
+
+    private void parseKeyboardContent(final XmlPullParser parser, final boolean skip)
+            throws XmlPullParserException, IOException {
+        int event;
+        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (event == XmlPullParser.START_TAG) {
+                final String tag = parser.getName();
+                if (TAG_ROW.equals(tag)) {
+                    final KeyboardRow row = parseRowAttributes(parser);
+                    if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : "");
+                    if (!skip) {
+                        startRow(row);
+                    }
+                    parseRowContent(parser, row, skip);
+                } else if (TAG_INCLUDE.equals(tag)) {
+                    parseIncludeKeyboardContent(parser, skip);
+                } else if (TAG_SWITCH.equals(tag)) {
+                    parseSwitchKeyboardContent(parser, skip);
+                } else if (TAG_KEY_STYLE.equals(tag)) {
+                    parseKeyStyle(parser, skip);
+                } else {
+                    throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW);
+                }
+            } else if (event == XmlPullParser.END_TAG) {
+                final String tag = parser.getName();
+                if (DEBUG) endTag("</%s>", tag);
+                if (TAG_KEYBOARD.equals(tag)) {
+                    endKeyboard();
+                    break;
+                } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
+                        || TAG_MERGE.equals(tag)) {
+                    break;
+                } else {
+                    throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW);
+                }
+            }
+        }
+    }
+
+    private KeyboardRow parseRowAttributes(final XmlPullParser parser)
+            throws XmlPullParserException {
+        final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                R.styleable.Keyboard);
+        try {
+            if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
+                throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
+            }
+            if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
+                throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
+            }
+            return new KeyboardRow(mResources, mParams, parser, mCurrentY);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    private void parseRowContent(final XmlPullParser parser, final KeyboardRow row,
+            final boolean skip) throws XmlPullParserException, IOException {
+        int event;
+        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (event == XmlPullParser.START_TAG) {
+                final String tag = parser.getName();
+                if (TAG_KEY.equals(tag)) {
+                    parseKey(parser, row, skip);
+                } else if (TAG_SPACER.equals(tag)) {
+                    parseSpacer(parser, row, skip);
+                } else if (TAG_INCLUDE.equals(tag)) {
+                    parseIncludeRowContent(parser, row, skip);
+                } else if (TAG_SWITCH.equals(tag)) {
+                    parseSwitchRowContent(parser, row, skip);
+                } else if (TAG_KEY_STYLE.equals(tag)) {
+                    parseKeyStyle(parser, skip);
+                } else {
+                    throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
+                }
+            } else if (event == XmlPullParser.END_TAG) {
+                final String tag = parser.getName();
+                if (DEBUG) endTag("</%s>", tag);
+                if (TAG_ROW.equals(tag)) {
+                    if (!skip) {
+                        endRow(row);
+                    }
+                    break;
+                } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
+                        || TAG_MERGE.equals(tag)) {
+                    break;
+                } else {
+                    throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
+                }
+            }
+        }
+    }
+
+    private void parseKey(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+            throws XmlPullParserException, IOException {
+        if (skip) {
+            XmlParseUtils.checkEndTag(TAG_KEY, parser);
+            if (DEBUG) {
+                startEndTag("<%s /> skipped", TAG_KEY);
+            }
+        } else {
+            final Key key = new Key(mResources, mParams, row, parser);
+            if (DEBUG) {
+                startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY,
+                        (key.isEnabled() ? "" : " disabled"), key,
+                        Arrays.toString(key.mMoreKeys));
+            }
+            XmlParseUtils.checkEndTag(TAG_KEY, parser);
+            endKey(key);
+        }
+    }
+
+    private void parseSpacer(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+            throws XmlPullParserException, IOException {
+        if (skip) {
+            XmlParseUtils.checkEndTag(TAG_SPACER, parser);
+            if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
+        } else {
+            final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser);
+            if (DEBUG) startEndTag("<%s />", TAG_SPACER);
+            XmlParseUtils.checkEndTag(TAG_SPACER, parser);
+            endKey(spacer);
+        }
+    }
+
+    private void parseIncludeKeyboardContent(final XmlPullParser parser, final boolean skip)
+            throws XmlPullParserException, IOException {
+        parseIncludeInternal(parser, null, skip);
+    }
+
+    private void parseIncludeRowContent(final XmlPullParser parser, final KeyboardRow row,
+            final boolean skip) throws XmlPullParserException, IOException {
+        parseIncludeInternal(parser, row, skip);
+    }
+
+    private void parseIncludeInternal(final XmlPullParser parser, final KeyboardRow row,
+            final boolean skip) throws XmlPullParserException, IOException {
+        if (skip) {
+            XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
+            if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
+        } else {
+            final AttributeSet attr = Xml.asAttributeSet(parser);
+            final TypedArray keyboardAttr = mResources.obtainAttributes(attr,
+                    R.styleable.Keyboard_Include);
+            final TypedArray keyAttr = mResources.obtainAttributes(attr,
+                    R.styleable.Keyboard_Key);
+            int keyboardLayout = 0;
+            float savedDefaultKeyWidth = 0;
+            int savedDefaultKeyLabelFlags = 0;
+            int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL;
+            try {
+                XmlParseUtils.checkAttributeExists(keyboardAttr,
+                        R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
+                        TAG_INCLUDE, parser);
+                keyboardLayout = keyboardAttr.getResourceId(
+                        R.styleable.Keyboard_Include_keyboardLayout, 0);
+                if (row != null) {
+                    if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
+                        // Override current x coordinate.
+                        row.setXPos(row.getKeyX(keyAttr));
+                    }
+                    // TODO: Remove this if-clause and do the same as backgroundType below.
+                    savedDefaultKeyWidth = row.getDefaultKeyWidth();
+                    if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) {
+                        // Override default key width.
+                        row.setDefaultKeyWidth(row.getKeyWidth(keyAttr));
+                    }
+                    savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags();
+                    // Bitwise-or default keyLabelFlag if exists.
+                    row.setDefaultKeyLabelFlags(keyAttr.getInt(
+                            R.styleable.Keyboard_Key_keyLabelFlags, 0)
+                            | savedDefaultKeyLabelFlags);
+                    savedDefaultBackgroundType = row.getDefaultBackgroundType();
+                    // Override default backgroundType if exists.
+                    row.setDefaultBackgroundType(keyAttr.getInt(
+                            R.styleable.Keyboard_Key_backgroundType,
+                            savedDefaultBackgroundType));
+                }
+            } finally {
+                keyboardAttr.recycle();
+                keyAttr.recycle();
+            }
+
+            XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
+            if (DEBUG) {
+                startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
+                        mResources.getResourceEntryName(keyboardLayout));
+            }
+            final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
+            try {
+                parseMerge(parserForInclude, row, skip);
+            } finally {
+                if (row != null) {
+                    // Restore default keyWidth, keyLabelFlags, and backgroundType.
+                    row.setDefaultKeyWidth(savedDefaultKeyWidth);
+                    row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags);
+                    row.setDefaultBackgroundType(savedDefaultBackgroundType);
+                }
+                parserForInclude.close();
+            }
+        }
+    }
+
+    private void parseMerge(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+            throws XmlPullParserException, IOException {
+        if (DEBUG) startTag("<%s>", TAG_MERGE);
+        int event;
+        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (event == XmlPullParser.START_TAG) {
+                final String tag = parser.getName();
+                if (TAG_MERGE.equals(tag)) {
+                    if (row == null) {
+                        parseKeyboardContent(parser, skip);
+                    } else {
+                        parseRowContent(parser, row, skip);
+                    }
+                    break;
+                } else {
+                    throw new XmlParseUtils.ParseException(
+                            "Included keyboard layout must have <merge> root element", parser);
+                }
+            }
+        }
+    }
+
+    private void parseSwitchKeyboardContent(final XmlPullParser parser, final boolean skip)
+            throws XmlPullParserException, IOException {
+        parseSwitchInternal(parser, null, skip);
+    }
+
+    private void parseSwitchRowContent(final XmlPullParser parser, final KeyboardRow row,
+            final boolean skip) throws XmlPullParserException, IOException {
+        parseSwitchInternal(parser, row, skip);
+    }
+
+    private void parseSwitchInternal(final XmlPullParser parser, final KeyboardRow row,
+            final boolean skip) throws XmlPullParserException, IOException {
+        if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId);
+        boolean selected = false;
+        int event;
+        while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (event == XmlPullParser.START_TAG) {
+                final String tag = parser.getName();
+                if (TAG_CASE.equals(tag)) {
+                    selected |= parseCase(parser, row, selected ? true : skip);
+                } else if (TAG_DEFAULT.equals(tag)) {
+                    selected |= parseDefault(parser, row, selected ? true : skip);
+                } else {
+                    throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
+                }
+            } else if (event == XmlPullParser.END_TAG) {
+                final String tag = parser.getName();
+                if (TAG_SWITCH.equals(tag)) {
+                    if (DEBUG) endTag("</%s>", TAG_SWITCH);
+                    break;
+                } else {
+                    throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
+                }
+            }
+        }
+    }
+
+    private boolean parseCase(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+            throws XmlPullParserException, IOException {
+        final boolean selected = parseCaseCondition(parser);
+        if (row == null) {
+            // Processing Rows.
+            parseKeyboardContent(parser, selected ? skip : true);
+        } else {
+            // Processing Keys.
+            parseRowContent(parser, row, selected ? skip : true);
+        }
+        return selected;
+    }
+
+    private boolean parseCaseCondition(final XmlPullParser parser) {
+        final KeyboardId id = mParams.mId;
+        if (id == null) {
+            return true;
+        }
+        final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                R.styleable.Keyboard_Case);
+        try {
+            final boolean keyboardLayoutSetElementMatched = matchTypedValue(a,
+                    R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId,
+                    KeyboardId.elementIdToName(id.mElementId));
+            final boolean modeMatched = matchTypedValue(a,
+                    R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
+            final boolean navigateNextMatched = matchBoolean(a,
+                    R.styleable.Keyboard_Case_navigateNext, id.navigateNext());
+            final boolean navigatePreviousMatched = matchBoolean(a,
+                    R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious());
+            final boolean passwordInputMatched = matchBoolean(a,
+                    R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
+            final boolean clobberSettingsKeyMatched = matchBoolean(a,
+                    R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
+            final boolean shortcutKeyEnabledMatched = matchBoolean(a,
+                    R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
+            final boolean hasShortcutKeyMatched = matchBoolean(a,
+                    R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
+            final boolean languageSwitchKeyEnabledMatched = matchBoolean(a,
+                    R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+                    id.mLanguageSwitchKeyEnabled);
+            final boolean isMultiLineMatched = matchBoolean(a,
+                    R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
+            final boolean imeActionMatched = matchInteger(a,
+                    R.styleable.Keyboard_Case_imeAction, id.imeAction());
+            final boolean localeCodeMatched = matchString(a,
+                    R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
+            final boolean languageCodeMatched = matchString(a,
+                    R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
+            final boolean countryCodeMatched = matchString(a,
+                    R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
+            final boolean selected = keyboardLayoutSetElementMatched && modeMatched
+                    && navigateNextMatched && navigatePreviousMatched && passwordInputMatched
+                    && clobberSettingsKeyMatched && shortcutKeyEnabledMatched
+                    && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
+                    && isMultiLineMatched && imeActionMatched && localeCodeMatched
+                    && languageCodeMatched && countryCodeMatched;
+
+            if (DEBUG) {
+                startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+                        textAttr(a.getString(
+                                R.styleable.Keyboard_Case_keyboardLayoutSetElement),
+                                "keyboardLayoutSetElement"),
+                        textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
+                        textAttr(a.getString(R.styleable.Keyboard_Case_imeAction),
+                                "imeAction"),
+                        booleanAttr(a, R.styleable.Keyboard_Case_navigateNext,
+                                "navigateNext"),
+                        booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious,
+                                "navigatePrevious"),
+                        booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
+                                "clobberSettingsKey"),
+                        booleanAttr(a, R.styleable.Keyboard_Case_passwordInput,
+                                "passwordInput"),
+                        booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled,
+                                "shortcutKeyEnabled"),
+                        booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey,
+                                "hasShortcutKey"),
+                        booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+                                "languageSwitchKeyEnabled"),
+                        booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine,
+                                "isMultiLine"),
+                        textAttr(a.getString(R.styleable.Keyboard_Case_localeCode),
+                                "localeCode"),
+                        textAttr(a.getString(R.styleable.Keyboard_Case_languageCode),
+                                "languageCode"),
+                        textAttr(a.getString(R.styleable.Keyboard_Case_countryCode),
+                                "countryCode"),
+                        selected ? "" : " skipped");
+            }
+
+            return selected;
+        } finally {
+            a.recycle();
+        }
+    }
+
+    private static boolean matchInteger(final TypedArray a, final int index, final int value) {
+        // If <case> does not have "index" attribute, that means this <case> is wild-card for
+        // the attribute.
+        return !a.hasValue(index) || a.getInt(index, 0) == value;
+    }
+
+    private static boolean matchBoolean(final TypedArray a, final int index, final boolean value) {
+        // If <case> does not have "index" attribute, that means this <case> is wild-card for
+        // the attribute.
+        return !a.hasValue(index) || a.getBoolean(index, false) == value;
+    }
+
+    private static boolean matchString(final TypedArray a, final int index, final String value) {
+        // If <case> does not have "index" attribute, that means this <case> is wild-card for
+        // the attribute.
+        return !a.hasValue(index)
+                || StringUtils.containsInArray(value, a.getString(index).split("\\|"));
+    }
+
+    private static boolean matchTypedValue(final TypedArray a, final int index, final int intValue,
+            final String strValue) {
+        // If <case> does not have "index" attribute, that means this <case> is wild-card for
+        // the attribute.
+        final TypedValue v = a.peekValue(index);
+        if (v == null) {
+            return true;
+        }
+        if (ResourceUtils.isIntegerValue(v)) {
+            return intValue == a.getInt(index, 0);
+        } else if (ResourceUtils.isStringValue(v)) {
+            return StringUtils.containsInArray(strValue, a.getString(index).split("\\|"));
+        }
+        return false;
+    }
+
+    private boolean parseDefault(final XmlPullParser parser, final KeyboardRow row,
+            final boolean skip) throws XmlPullParserException, IOException {
+        if (DEBUG) startTag("<%s>", TAG_DEFAULT);
+        if (row == null) {
+            parseKeyboardContent(parser, skip);
+        } else {
+            parseRowContent(parser, row, skip);
+        }
+        return true;
+    }
+
+    private void parseKeyStyle(final XmlPullParser parser, final boolean skip)
+            throws XmlPullParserException, IOException {
+        TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                R.styleable.Keyboard_KeyStyle);
+        TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                R.styleable.Keyboard_Key);
+        try {
+            if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
+                throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
+                        + "/> needs styleName attribute", parser);
+            }
+            if (DEBUG) {
+                startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
+                        keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
+                        skip ? " skipped" : "");
+            }
+            if (!skip) {
+                mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
+            }
+        } finally {
+            keyStyleAttr.recycle();
+            keyAttrs.recycle();
+        }
+        XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser);
+    }
+
+    private void startKeyboard() {
+        mCurrentY += mParams.mTopPadding;
+        mTopEdge = true;
+    }
+
+    private void startRow(final KeyboardRow row) {
+        addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
+        mCurrentRow = row;
+        mLeftEdge = true;
+        mRightEdgeKey = null;
+    }
+
+    private void endRow(final KeyboardRow row) {
+        if (mCurrentRow == null) {
+            throw new InflateException("orphan end row tag");
+        }
+        if (mRightEdgeKey != null) {
+            mRightEdgeKey.markAsRightEdge(mParams);
+            mRightEdgeKey = null;
+        }
+        addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
+        mCurrentY += row.mRowHeight;
+        mCurrentRow = null;
+        mTopEdge = false;
+    }
+
+    private void endKey(final Key key) {
+        mParams.onAddKey(key);
+        if (mLeftEdge) {
+            key.markAsLeftEdge(mParams);
+            mLeftEdge = false;
+        }
+        if (mTopEdge) {
+            key.markAsTopEdge(mParams);
+        }
+        mRightEdgeKey = key;
+    }
+
+    private void endKeyboard() {
+        // nothing to do here.
+    }
+
+    private void addEdgeSpace(final float width, final KeyboardRow row) {
+        row.advanceXPos(width);
+        mLeftEdge = false;
+        mRightEdgeKey = null;
+    }
+
+    private static String textAttr(final String value, final String name) {
+        return value != null ? String.format(" %s=%s", name, value) : "";
+    }
+
+    private static String booleanAttr(final TypedArray a, final int index, final String name) {
+        return a.hasValue(index)
+                ? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff5d315280d41782d64b82f747fe69125a3294fa
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.graphics.Typeface;
+import android.util.SparseIntArray;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.ResourceUtils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+public class KeyboardParams {
+    public KeyboardId mId;
+    public int mThemeId;
+
+    /** Total height and width of the keyboard, including the paddings and keys */
+    public int mOccupiedHeight;
+    public int mOccupiedWidth;
+
+    /** Base height and width of the keyboard used to calculate rows' or keys' heights and
+     *  widths
+     */
+    public int mBaseHeight;
+    public int mBaseWidth;
+
+    public int mTopPadding;
+    public int mBottomPadding;
+    public int mHorizontalEdgesPadding;
+    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;
+    public int mHorizontalGap;
+    public int mVerticalGap;
+
+    public int mMoreKeysTemplate;
+    public int mMaxMoreKeysKeyboardColumn;
+
+    public int GRID_WIDTH;
+    public int GRID_HEIGHT;
+
+    public final HashSet<Key> mKeys = CollectionUtils.newHashSet();
+    public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
+    public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
+    public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
+    public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
+    public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
+    public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet);
+
+    public KeysCache mKeysCache;
+
+    public int mMostCommonKeyHeight = 0;
+    public int mMostCommonKeyWidth = 0;
+
+    public boolean mProximityCharsCorrectionEnabled;
+
+    public final TouchPositionCorrection mTouchPositionCorrection =
+            new TouchPositionCorrection();
+
+    protected void clearKeys() {
+        mKeys.clear();
+        mShiftKeys.clear();
+        clearHistogram();
+    }
+
+    public void onAddKey(final Key newKey) {
+        final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
+        final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0;
+        if (!zeroWidthSpacer) {
+            mKeys.add(key);
+            updateHistogram(key);
+        }
+        if (key.mCode == Keyboard.CODE_SHIFT) {
+            mShiftKeys.add(key);
+        }
+        if (key.altCodeWhileTyping()) {
+            mAltCodeKeysWhileTyping.add(key);
+        }
+    }
+
+    private int mMaxHeightCount = 0;
+    private int mMaxWidthCount = 0;
+    private final SparseIntArray mHeightHistogram = new SparseIntArray();
+    private final SparseIntArray mWidthHistogram = new SparseIntArray();
+
+    private void clearHistogram() {
+        mMostCommonKeyHeight = 0;
+        mMaxHeightCount = 0;
+        mHeightHistogram.clear();
+
+        mMaxWidthCount = 0;
+        mMostCommonKeyWidth = 0;
+        mWidthHistogram.clear();
+    }
+
+    private static int updateHistogramCounter(final SparseIntArray histogram, final int key) {
+        final int index = histogram.indexOfKey(key);
+        final int count = (index >= 0 ? histogram.get(key) : 0) + 1;
+        histogram.put(key, count);
+        return count;
+    }
+
+    private void updateHistogram(final Key key) {
+        final int height = key.mHeight + mVerticalGap;
+        final int heightCount = updateHistogramCounter(mHeightHistogram, height);
+        if (heightCount > mMaxHeightCount) {
+            mMaxHeightCount = heightCount;
+            mMostCommonKeyHeight = height;
+        }
+
+        final int width = key.mWidth + mHorizontalGap;
+        final int widthCount = updateHistogramCounter(mWidthHistogram, width);
+        if (widthCount > mMaxWidthCount) {
+            mMaxWidthCount = widthCount;
+            mMostCommonKeyWidth = width;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb17b0ea4b850954fe953d2e28c5914a7b2dbaa9
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.Xml;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
+ * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
+ * defines.
+ */
+public class KeyboardRow {
+    // keyWidth enum constants
+    private static final int KEYWIDTH_NOT_ENUM = 0;
+    private static final int KEYWIDTH_FILL_RIGHT = -1;
+
+    private final KeyboardParams mParams;
+    /** Default width of a key in this row. */
+    private float mDefaultKeyWidth;
+    /** Default height of a key in this row. */
+    public final int mRowHeight;
+    /** Default keyLabelFlags in this row. */
+    private int mDefaultKeyLabelFlags;
+    /** Default backgroundType for this row */
+    private int mDefaultBackgroundType;
+
+    private final int mCurrentY;
+    // Will be updated by {@link Key}'s constructor.
+    private float mCurrentX;
+
+    public KeyboardRow(final Resources res, final KeyboardParams params, final XmlPullParser parser,
+            final int y) {
+        mParams = params;
+        TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
+                R.styleable.Keyboard);
+        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 = ResourceUtils.getDimensionOrFraction(keyAttr,
+                R.styleable.Keyboard_Key_keyWidth,
+                params.mBaseWidth, params.mDefaultKeyWidth);
+        mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
+                Key.BACKGROUND_TYPE_NORMAL);
+        keyAttr.recycle();
+
+        // TODO: Initialize this with <Row> attribute as backgroundType is done.
+        mDefaultKeyLabelFlags = 0;
+        mCurrentY = y;
+        mCurrentX = 0.0f;
+    }
+
+    public float getDefaultKeyWidth() {
+        return mDefaultKeyWidth;
+    }
+
+    public void setDefaultKeyWidth(final float defaultKeyWidth) {
+        mDefaultKeyWidth = defaultKeyWidth;
+    }
+
+    public int getDefaultKeyLabelFlags() {
+        return mDefaultKeyLabelFlags;
+    }
+
+    public void setDefaultKeyLabelFlags(final int keyLabelFlags) {
+        mDefaultKeyLabelFlags = keyLabelFlags;
+    }
+
+    public int getDefaultBackgroundType() {
+        return mDefaultBackgroundType;
+    }
+
+    public void setDefaultBackgroundType(final int backgroundType) {
+        mDefaultBackgroundType = backgroundType;
+    }
+
+    public void setXPos(final float keyXPos) {
+        mCurrentX = keyXPos;
+    }
+
+    public void advanceXPos(final float width) {
+        mCurrentX += width;
+    }
+
+    public int getKeyY() {
+        return mCurrentY;
+    }
+
+    public float getKeyX(final TypedArray keyAttr) {
+        final int keyboardRightEdge = mParams.mOccupiedWidth
+                - mParams.mHorizontalEdgesPadding;
+        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
+            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
+                // keyboardWidth + keyXPos.
+                // keyXPos shouldn't be less than mCurrentX because drawable area for this
+                // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
+                // its left hand side.
+                return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
+            } else {
+                return keyXPos + mParams.mHorizontalEdgesPadding;
+            }
+        }
+        return mCurrentX;
+    }
+
+    public float getKeyWidth(final TypedArray keyAttr) {
+        return getKeyWidth(keyAttr, mCurrentX);
+    }
+
+    public float getKeyWidth(final TypedArray keyAttr, final float keyXPos) {
+        final int widthType = ResourceUtils.getEnumValue(keyAttr,
+                R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
+        switch (widthType) {
+        case KEYWIDTH_FILL_RIGHT:
+            final int keyboardRightEdge =
+                    mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
+            // If keyWidth is fillRight, the actual key width will be determined to fill
+            // out the area up to the right edge of the keyboard.
+            return keyboardRightEdge - keyXPos;
+        default: // KEYWIDTH_NOT_ENUM
+            return ResourceUtils.getDimensionOrFraction(keyAttr,
+                    R.styleable.Keyboard_Key_keyWidth,
+                    mParams.mBaseWidth, mDefaultKeyWidth);
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..f54617c989130a86a47e97f7a59a305aa20140e5
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.HashMap;
+
+public class KeysCache {
+    private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
+
+    public void clear() {
+        mMap.clear();
+    }
+
+    public Key get(final Key key) {
+        final Key existingKey = mMap.get(key);
+        if (existingKey != null) {
+            // Reuse the existing element that equals to "key" without adding "key" to the map.
+            return existingKey;
+        }
+        mMap.put(key, key);
+        return key;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
new file mode 100644
index 0000000000000000000000000000000000000000..5da26543ff921a5d1e7c8d095d51b4658e342692
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.StringUtils;
+
+import java.util.Locale;
+
+public class MoreKeySpec {
+    public final int mCode;
+    public final String mLabel;
+    public final String mOutputText;
+    public final int mIconId;
+
+    public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale,
+            final KeyboardCodesSet codesSet) {
+        mLabel = KeySpecParser.toUpperCaseOfStringForLocale(
+                KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale);
+        final int code = KeySpecParser.toUpperCaseOfCodeForLocale(
+                KeySpecParser.getCode(moreKeySpec, codesSet), needsToUpperCase, locale);
+        if (code == Keyboard.CODE_UNSPECIFIED) {
+            // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
+            // upper case representation ("SS").
+            mCode = Keyboard.CODE_OUTPUT_TEXT;
+            mOutputText = mLabel;
+        } else {
+            mCode = code;
+            mOutputText = KeySpecParser.toUpperCaseOfStringForLocale(
+                    KeySpecParser.getOutputText(moreKeySpec), needsToUpperCase, locale);
+        }
+        mIconId = KeySpecParser.getIconId(moreKeySpec);
+    }
+
+    @Override
+    public String toString() {
+        final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel
+                : KeySpecParser.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
+        final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText
+                : Keyboard.printableCode(mCode));
+        if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
+            return output;
+        } else {
+            return label + "|" + output;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
new file mode 100644
index 0000000000000000000000000000000000000000..69dc01cd6f07306ef4cfcb1f34a43f76802a3338
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+
+public class TouchPositionCorrection {
+    private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
+
+    public boolean mEnabled;
+    public float[] mXs;
+    public float[] mYs;
+    public float[] mRadii;
+
+    public void load(final String[] data) {
+        final int dataLength = data.length;
+        if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
+            if (LatinImeLogger.sDBG) {
+                throw new RuntimeException(
+                        "the size of touch position correction data is invalid");
+            }
+            return;
+        }
+
+        final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+        mXs = new float[length];
+        mYs = new float[length];
+        mRadii = new float[length];
+        try {
+            for (int i = 0; i < dataLength; ++i) {
+                final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+                final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+                final float value = Float.parseFloat(data[i]);
+                if (type == 0) {
+                    mXs[index] = value;
+                } else if (type == 1) {
+                    mYs[index] = value;
+                } else {
+                    mRadii[index] = value;
+                }
+            }
+        } catch (NumberFormatException e) {
+            if (LatinImeLogger.sDBG) {
+                throw new RuntimeException(
+                        "the number format for touch position correction data is invalid");
+            }
+            mXs = null;
+            mYs = null;
+            mRadii = null;
+        }
+    }
+
+    // TODO: Remove this method.
+    public void setEnabled(final boolean enabled) {
+        mEnabled = enabled;
+    }
+
+    public boolean isValid() {
+        return mEnabled && mXs != null && mYs != null && mRadii != null
+                && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 58b01aa557eeeaa7d38fefe91724cda6a0ba6dc8..1f883aa60b67060ea81c5904fde46e06292605a8 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -23,7 +23,9 @@ import android.graphics.drawable.Drawable;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.Utils;
@@ -31,145 +33,149 @@ import com.android.inputmethod.latin.Utils;
 public class MoreSuggestions extends Keyboard {
     public static final int SUGGESTION_CODE_BASE = 1024;
 
-    MoreSuggestions(Builder.MoreSuggestionsParam params) {
+    MoreSuggestions(final MoreSuggestionsParam params) {
         super(params);
     }
 
-    public static class Builder extends Keyboard.Builder<Builder.MoreSuggestionsParam> {
-        private final MoreSuggestionsView mPaneView;
-        private SuggestedWords mSuggestions;
-        private int mFromPos;
-        private int mToPos;
+    private static class MoreSuggestionsParam extends KeyboardParams {
+        private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS];
+        private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS];
+        private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS];
+        private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS];
+        private static final int MAX_COLUMNS_IN_ROW = 3;
+        private int mNumRows;
+        public Drawable mDivider;
+        public int mDividerWidth;
+
+        public MoreSuggestionsParam() {
+            super();
+        }
 
-        public static class MoreSuggestionsParam extends Keyboard.Params {
-            private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS];
-            private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS];
-            private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS];
-            private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS];
-            private static final int MAX_COLUMNS_IN_ROW = 3;
-            private int mNumRows;
-            public Drawable mDivider;
-            public int mDividerWidth;
-
-            public int layout(SuggestedWords suggestions, int fromPos, int maxWidth, int minWidth,
-                    int maxRow, MoreSuggestionsView view) {
-                clearKeys();
-                final Resources res = view.getContext().getResources();
-                mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
-                mDividerWidth = mDivider.getIntrinsicWidth();
-                final int padding = (int) res.getDimension(
-                        R.dimen.more_suggestions_key_horizontal_padding);
-                final Paint paint = view.newDefaultLabelPaint();
-
-                int row = 0;
-                int pos = fromPos, rowStartPos = fromPos;
-                final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS);
-                while (pos < size) {
-                    final String word = suggestions.getWord(pos).toString();
-                    // TODO: Should take care of text x-scaling.
-                    mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
-                    final int numColumn = pos - rowStartPos + 1;
-                    final int columnWidth =
-                            (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
-                    if (numColumn > MAX_COLUMNS_IN_ROW
-                            || !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
-                        if ((row + 1) >= maxRow) {
-                            break;
-                        }
-                        mNumColumnsInRow[row] = pos - rowStartPos;
-                        rowStartPos = pos;
-                        row++;
+        public int layout(final SuggestedWords suggestions, final int fromPos, final int maxWidth,
+                final int minWidth, final int maxRow, final MoreSuggestionsView view) {
+            clearKeys();
+            final Resources res = view.getContext().getResources();
+            mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
+            mDividerWidth = mDivider.getIntrinsicWidth();
+            final int padding = (int) res.getDimension(
+                    R.dimen.more_suggestions_key_horizontal_padding);
+            final Paint paint = view.newDefaultLabelPaint();
+
+            int row = 0;
+            int pos = fromPos, rowStartPos = fromPos;
+            final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS);
+            while (pos < size) {
+                final String word = suggestions.getWord(pos).toString();
+                // TODO: Should take care of text x-scaling.
+                mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
+                final int numColumn = pos - rowStartPos + 1;
+                final int columnWidth =
+                        (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
+                if (numColumn > MAX_COLUMNS_IN_ROW
+                        || !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
+                    if ((row + 1) >= maxRow) {
+                        break;
                     }
-                    mColumnOrders[pos] = pos - rowStartPos;
-                    mRowNumbers[pos] = row;
-                    pos++;
+                    mNumColumnsInRow[row] = pos - rowStartPos;
+                    rowStartPos = pos;
+                    row++;
                 }
-                mNumColumnsInRow[row] = pos - rowStartPos;
-                mNumRows = row + 1;
-                mBaseWidth = mOccupiedWidth = Math.max(
-                        minWidth, calcurateMaxRowWidth(fromPos, pos));
-                mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
-                return pos - fromPos;
+                mColumnOrders[pos] = pos - rowStartPos;
+                mRowNumbers[pos] = row;
+                pos++;
             }
+            mNumColumnsInRow[row] = pos - rowStartPos;
+            mNumRows = row + 1;
+            mBaseWidth = mOccupiedWidth = Math.max(
+                    minWidth, calcurateMaxRowWidth(fromPos, pos));
+            mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
+            return pos - fromPos;
+        }
 
-            private boolean fitInWidth(int startPos, int endPos, int width) {
-                for (int pos = startPos; pos < endPos; pos++) {
-                    if (mWidths[pos] > width)
-                        return false;
-                }
-                return true;
+        private boolean fitInWidth(final int startPos, final int endPos, final int width) {
+            for (int pos = startPos; pos < endPos; pos++) {
+                if (mWidths[pos] > width)
+                    return false;
             }
+            return true;
+        }
 
-            private int calcurateMaxRowWidth(int startPos, int endPos) {
-                int maxRowWidth = 0;
-                int pos = startPos;
-                for (int row = 0; row < mNumRows; row++) {
-                    final int numColumnInRow = mNumColumnsInRow[row];
-                    int maxKeyWidth = 0;
-                    while (pos < endPos && mRowNumbers[pos] == row) {
-                        maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
-                        pos++;
-                    }
-                    maxRowWidth = Math.max(maxRowWidth,
-                            maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
+        private int calcurateMaxRowWidth(final int startPos, final int endPos) {
+            int maxRowWidth = 0;
+            int pos = startPos;
+            for (int row = 0; row < mNumRows; row++) {
+                final int numColumnInRow = mNumColumnsInRow[row];
+                int maxKeyWidth = 0;
+                while (pos < endPos && mRowNumbers[pos] == row) {
+                    maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
+                    pos++;
                 }
-                return maxRowWidth;
+                maxRowWidth = Math.max(maxRowWidth,
+                        maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
             }
+            return maxRowWidth;
+        }
 
-            private static final int[][] COLUMN_ORDER_TO_NUMBER = {
-                { 0, },
-                { 1, 0, },
-                { 2, 0, 1},
-            };
-
-            public int getNumColumnInRow(int pos) {
-                return mNumColumnsInRow[mRowNumbers[pos]];
-            }
+        private static final int[][] COLUMN_ORDER_TO_NUMBER = {
+            { 0, },
+            { 1, 0, },
+            { 2, 0, 1},
+        };
 
-            public int getColumnNumber(int pos) {
-                final int columnOrder = mColumnOrders[pos];
-                final int numColumn = getNumColumnInRow(pos);
-                return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
-            }
+        public int getNumColumnInRow(final int pos) {
+            return mNumColumnsInRow[mRowNumbers[pos]];
+        }
 
-            public int getX(int pos) {
-                final int columnNumber = getColumnNumber(pos);
-                return columnNumber * (getWidth(pos) + mDividerWidth);
-            }
+        public int getColumnNumber(final int pos) {
+            final int columnOrder = mColumnOrders[pos];
+            final int numColumn = getNumColumnInRow(pos);
+            return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
+        }
 
-            public int getY(int pos) {
-                final int row = mRowNumbers[pos];
-                return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
-            }
+        public int getX(final int pos) {
+            final int columnNumber = getColumnNumber(pos);
+            return columnNumber * (getWidth(pos) + mDividerWidth);
+        }
 
-            public int getWidth(int pos) {
-                final int numColumnInRow = getNumColumnInRow(pos);
-                return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
-            }
+        public int getY(final int pos) {
+            final int row = mRowNumbers[pos];
+            return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
+        }
 
-            public void markAsEdgeKey(Key key, int pos) {
-                final int row = mRowNumbers[pos];
-                if (row == 0)
-                    key.markAsBottomEdge(this);
-                if (row == mNumRows - 1)
-                    key.markAsTopEdge(this);
+        public int getWidth(final int pos) {
+            final int numColumnInRow = getNumColumnInRow(pos);
+            return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
+        }
 
-                final int numColumnInRow = mNumColumnsInRow[row];
-                final int column = getColumnNumber(pos);
-                if (column == 0)
-                    key.markAsLeftEdge(this);
-                if (column == numColumnInRow - 1)
-                    key.markAsRightEdge(this);
-            }
+        public void markAsEdgeKey(final Key key, final int pos) {
+            final int row = mRowNumbers[pos];
+            if (row == 0)
+                key.markAsBottomEdge(this);
+            if (row == mNumRows - 1)
+                key.markAsTopEdge(this);
+
+            final int numColumnInRow = mNumColumnsInRow[row];
+            final int column = getColumnNumber(pos);
+            if (column == 0)
+                key.markAsLeftEdge(this);
+            if (column == numColumnInRow - 1)
+                key.markAsRightEdge(this);
         }
+    }
 
-        public Builder(MoreSuggestionsView paneView) {
+    public static class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
+        private final MoreSuggestionsView mPaneView;
+        private SuggestedWords mSuggestions;
+        private int mFromPos;
+        private int mToPos;
+
+        public Builder(final MoreSuggestionsView paneView) {
             super(paneView.getContext(), new MoreSuggestionsParam());
             mPaneView = paneView;
         }
 
-        public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth,
-                int minWidth, int maxRow) {
+        public Builder layout(final SuggestedWords suggestions, final int fromPos,
+                final int maxWidth, final int minWidth, final int maxRow) {
             final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard();
             final int xmlId = R.xml.kbd_suggestions_pane_template;
             load(xmlId, keyboard.mId);
@@ -183,25 +189,6 @@ public class MoreSuggestions extends Keyboard {
             return this;
         }
 
-        private static class Divider extends Key.Spacer {
-            private final Drawable mIcon;
-
-            public Divider(Keyboard.Params params, Drawable icon, int x, int y, int width,
-                    int height) {
-                super(params, x, y, width, height);
-                mIcon = icon;
-            }
-
-            @Override
-            public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
-                // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
-                // constructor.
-                // TODO: Drawable itself should have an alpha value.
-                mIcon.setAlpha(128);
-                return mIcon;
-            }
-        }
-
         @Override
         public MoreSuggestions build() {
             final MoreSuggestionsParam params = mParams;
@@ -228,4 +215,23 @@ public class MoreSuggestions extends Keyboard {
             return new MoreSuggestions(params);
         }
     }
+
+    private static class Divider extends Key.Spacer {
+        private final Drawable mIcon;
+
+        public Divider(final KeyboardParams params, final Drawable icon, final int x,
+                final int y, final int width, final int height) {
+            super(params, x, y, width, height);
+            mIcon = icon;
+        }
+
+        @Override
+        public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
+            // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
+            // constructor.
+            // TODO: Drawable itself should have an alpha value.
+            mIcon.setAlpha(128);
+            return mIcon;
+        }
+    }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
index 5c6c834320dcd1afd9e6e0d9f6c1b57f0771094a..2a244a77238d0b416de3da875cde1ba71a5fbe3f 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
@@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard;
 
 import android.test.AndroidTestCase;
 
-import com.android.inputmethod.keyboard.MoreKeysKeyboard.Builder.MoreKeysKeyboardParams;
+import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams;
 
 public class MoreKeysKeyboardBuilderFixedOrderTests extends AndroidTestCase {
     private static final int WIDTH = 10;
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
index 31f0e0fef8216c92be74cb8689fa881b55f761a0..e6c76db854cbd69c5159819c798bd018702ec913 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
@@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard;
 
 import android.test.AndroidTestCase;
 
-import com.android.inputmethod.keyboard.MoreKeysKeyboard.Builder.MoreKeysKeyboardParams;
+import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams;
 
 public class MoreKeysKeyboardBuilderTests extends AndroidTestCase {
     private static final int WIDTH = 10;
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
index 0b174a7e6668a5365270d18683755f29e9266d19..1ab577557850cb45cd3b73fdf08b728719124268 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
@@ -23,7 +23,6 @@ import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UN
 import android.test.AndroidTestCase;
 
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec;
 
 import java.util.Arrays;
 import java.util.Locale;