Merge "Import revised translations."
diff --git a/java/proguard.flags b/java/proguard.flags
index 7ce6f41..44416ec 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -30,3 +30,7 @@
 -keep class com.android.inputmethod.latin.SettingsActivity {
   *;
 }
+
+-keep class com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder$MiniKeyboardLayoutParams {
+  <init>(...);
+}
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index b6adf63..7d6021b 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -36,8 +36,6 @@
     <string name="label_alt_key">ALT</string>
     <!-- Label for "Tab" key.  Must be short to fit on key! -->
     <string name="label_tab_key">Tab</string>
-    <!-- Label for "switch to symbols" key.  Must be short to fit on key! -->
-    <string name="label_to_symbol_key">\?123</string>
     <!-- Label for "switch to phone numeric" key.  Must be short to fit on key! -->
     <string name="label_to_phone_numeric_key">123</string>
     <!-- Label for "switch to phone symbols" key.  Must be short to fit on key! -->
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 2535818..aaea9ad 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -109,17 +109,19 @@
     <!-- Indicates that a word has been added to the dictionary -->
     <string name="added_word"><xliff:g id="word">%s</xliff:g> : Saved</string>
 
-    <!-- Label for soft enter key when it performs GO action.  Must be short to fit on key! -->
+    <!-- Label for soft enter key when it performs GO action.  Must be short to fit on key! [CHAR LIMIT=5] -->
     <string name="label_go_key">Go</string>
-    <!-- Label for soft enter key when it performs NEXT action.  Must be short to fit on key! -->
+    <!-- Label for soft enter key when it performs NEXT action.  Must be short to fit on key! [CHAR LIMIT=5] -->
     <string name="label_next_key">Next</string>
-    <!-- Label for soft enter key when it performs DONE action.  Must be short to fit on key! -->
+    <!-- Label for soft enter key when it performs DONE action.  Must be short to fit on key! [CHAR LIMIT=5] -->
     <string name="label_done_key">Done</string>
-    <!-- Label for soft enter key when it performs SEND action.  Must be short to fit on key! -->
+    <!-- Label for soft enter key when it performs SEND action.  Must be short to fit on key! [CHAR LIMIT=5] -->
     <string name="label_send_key">Send</string>
-    <!-- Label for "switch to alphabetic" key.  Must be short to fit on key! -->
+    <!-- Label for "switch to alphabetic" key.  Must be short to fit on key! [CHAR LIMIT=3] -->
     <string name="label_to_alpha_key">ABC</string>
-    <!-- Label for Shift modifier key of symbol keyboard.  Must be short to fit on key! -->
+    <!-- Label for "switch to symbols" key.  Must be short to fit on key! [CHAR LIMIT=4] -->
+    <string name="label_to_symbol_key">\?123</string>
+    <!-- Label for Shift modifier key of symbol keyboard.  Must be short to fit on key! [CHAR LIMIT=5] -->
     <string name="label_more_key">More</string>
     <!-- Label for "Pause" key of phone number keyboard.  Must be short to fit on key! [CHAR LIMIT=5] -->
     <string name="label_pause_key">Pause</string>
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 5b34dd5..8bc7e43 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -27,6 +27,7 @@
 import com.android.inputmethod.keyboard.internal.KeyStyles;
 import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.keyboard.internal.KeyboardParser;
 import com.android.inputmethod.keyboard.internal.KeyboardParser.ParseException;
 import com.android.inputmethod.keyboard.internal.PopupCharactersParser;
@@ -73,7 +74,9 @@
     /** Height of the key, not including the gap */
     public final int mHeight;
     /** The horizontal gap around this key */
-    public final int mGap;
+    public final int mHorizontalGap;
+    /** The vertical gap below this key */
+    public final int mVerticalGap;
     /** The visual insets */
     public final int mVisualInsetsLeft;
     public final int mVisualInsetsRight;
@@ -102,9 +105,6 @@
     /** Whether this key repeats itself when held down */
     public final boolean mRepeatable;
 
-    /** The Keyboard that this key belongs to */
-    private final Keyboard mKeyboard;
-
     /** The current pressed state of this key */
     private boolean mPressed;
     /** If this is a sticky key, is its highlight on? */
@@ -191,13 +191,13 @@
     /**
      * This constructor is being used only for key in popup mini keyboard.
      */
-    public Key(Resources res, Keyboard keyboard, CharSequence popupCharacter, int x, int y,
+    public Key(Resources res, KeyboardParams params, CharSequence popupCharacter, int x, int y,
             int width, int height, int edgeFlags) {
-        mKeyboard = keyboard;
-        mHeight = height - keyboard.getVerticalGap();
-        mGap = keyboard.getHorizontalGap();
+        mHeight = height - params.mVerticalGap;
+        mHorizontalGap = params.mHorizontalGap;
+        mVerticalGap = params.mVerticalGap;
         mVisualInsetsLeft = mVisualInsetsRight = 0;
-        mWidth = width - mGap;
+        mWidth = width - mHorizontalGap;
         mEdgeFlags = edgeFlags;
         mHintLabel = null;
         mLabelOption = 0;
@@ -210,10 +210,10 @@
         mLabel = PopupCharactersParser.getLabel(popupSpecification);
         mOutputText = PopupCharactersParser.getOutputText(popupSpecification);
         final int code = PopupCharactersParser.getCode(res, popupSpecification);
-        mCode = keyboard.isRtlKeyboard() ? getRtlParenthesisCode(code) : code;
-        mIcon = keyboard.mIconsSet.getIcon(PopupCharactersParser.getIconId(popupSpecification));
+        mCode = params.mIsRtlKeyboard ? getRtlParenthesisCode(code) : code;
+        mIcon = params.mIconsSet.getIcon(PopupCharactersParser.getIconId(popupSpecification));
         // Horizontal gap is divided equally to both sides of the key.
-        mX = x + mGap / 2;
+        mX = x + mHorizontalGap / 2;
         mY = y;
     }
 
@@ -221,16 +221,15 @@
      * Create a key with the given top-left coordinate and extract its attributes from the XML
      * parser.
      * @param res resources associated with the caller's context
-     * @param row the row that this key belongs to. The row must already be attached to
-     * a {@link Keyboard}.
+     * @param params the keyboard building parameters.
+     * @param row the row that this key belongs to.
      * @param x the x coordinate of the top-left
      * @param y the y coordinate of the top-left
      * @param parser the XML parser containing the attributes for this key
      * @param keyStyles active key styles set
      */
-    public Key(Resources res, Row row, int x, int y, XmlResourceParser parser,
-            KeyStyles keyStyles) {
-        mKeyboard = row.getKeyboard();
+    public Key(Resources res, KeyboardParams params, Row row, int x, int y,
+            XmlResourceParser parser, KeyStyles keyStyles) {
 
         final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
                 R.styleable.Keyboard);
@@ -238,13 +237,14 @@
         try {
             mHeight = KeyboardParser.getDimensionOrFraction(keyboardAttr,
                     R.styleable.Keyboard_rowHeight,
-                    mKeyboard.getKeyboardHeight(), row.mDefaultHeight) - row.mVerticalGap;
-            mGap = KeyboardParser.getDimensionOrFraction(keyboardAttr,
+                    params.mHeight, row.mRowHeight) - params.mVerticalGap;
+            mHorizontalGap = KeyboardParser.getDimensionOrFraction(keyboardAttr,
                     R.styleable.Keyboard_horizontalGap,
-                    mKeyboard.getDisplayWidth(), row.mDefaultHorizontalGap);
+                    params.mWidth, params.mHorizontalGap);
+            mVerticalGap = params.mVerticalGap;
             keyWidth = KeyboardParser.getDimensionOrFraction(keyboardAttr,
                     R.styleable.Keyboard_keyWidth,
-                    mKeyboard.getDisplayWidth(), row.mDefaultWidth);
+                    params.mWidth, row.mDefaultKeyWidth);
         } finally {
             keyboardAttr.recycle();
         }
@@ -262,7 +262,7 @@
                 style = keyStyles.getEmptyKeyStyle();
             }
 
-            final int keyboardWidth = mKeyboard.getDisplayWidth();
+            final int keyboardWidth = params.mOccupiedWidth;
             int keyXPos = KeyboardParser.getDimensionOrFraction(keyAttr,
                     R.styleable.Keyboard_Key_keyXPos, keyboardWidth, x);
             if (keyXPos < 0) {
@@ -287,19 +287,19 @@
             }
 
             // Horizontal gap is divided equally to both sides of the key.
-            mX = keyXPos + mGap / 2;
+            mX = keyXPos + mHorizontalGap / 2;
             mY = y;
-            mWidth = keyWidth - mGap;
+            mWidth = keyWidth - mHorizontalGap;
 
             CharSequence[] popupCharacters = style.getTextArray(
                     keyAttr, R.styleable.Keyboard_Key_popupCharacters);
-            if (mKeyboard.mId.mPasswordInput) {
+            if (params.mId.mPasswordInput) {
                 popupCharacters = PopupCharactersParser.filterOut(
                         res, popupCharacters, PopupCharactersParser.NON_ASCII_FILTER);
             }
             // In Arabic symbol layouts, we'd like to keep digits in popup characters regardless of
             // config_digit_popup_characters_enabled.
-            if (mKeyboard.mId.isAlphabetKeyboard() && !res.getBoolean(
+            if (params.mId.isAlphabetKeyboard() && !res.getBoolean(
                     R.bool.config_digit_popup_characters_enabled)) {
                 mPopupCharacters = PopupCharactersParser.filterOut(
                         res, popupCharacters, PopupCharactersParser.DIGIT_FILTER);
@@ -308,7 +308,7 @@
             }
             mMaxPopupColumn = style.getInt(keyboardAttr,
                     R.styleable.Keyboard_Key_maxPopupKeyboardColumn,
-                    mKeyboard.getMaxPopupKeyboardColumn());
+                    params.mMaxPopupColumn);
 
             mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false);
             mFunctional = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isFunctional, false);
@@ -316,7 +316,7 @@
             mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true);
             mEdgeFlags = 0;
 
-            final KeyboardIconsSet iconsSet = mKeyboard.mIconsSet;
+            final KeyboardIconsSet iconsSet = params.mIconsSet;
             mVisualInsetsLeft = KeyboardParser.getDimensionOrFraction(keyAttr,
                     R.styleable.Keyboard_Key_visualInsetsLeft, keyboardWidth, 0);
             mVisualInsetsRight = KeyboardParser.getDimensionOrFraction(keyAttr,
@@ -324,17 +324,14 @@
             mPreviewIcon = iconsSet.getIcon(style.getInt(
                     keyAttr, R.styleable.Keyboard_Key_keyIconPreview,
                     KeyboardIconsSet.ICON_UNDEFINED));
-            Keyboard.setDefaultBounds(mPreviewIcon);
             mIcon = iconsSet.getIcon(style.getInt(
                     keyAttr, R.styleable.Keyboard_Key_keyIcon,
                     KeyboardIconsSet.ICON_UNDEFINED));
-            Keyboard.setDefaultBounds(mIcon);
             final int shiftedIconId = style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconShifted,
                     KeyboardIconsSet.ICON_UNDEFINED);
             if (shiftedIconId != KeyboardIconsSet.ICON_UNDEFINED) {
                 final Drawable shiftedIcon = iconsSet.getIcon(shiftedIconId);
-                Keyboard.setDefaultBounds(shiftedIcon);
-                mKeyboard.addShiftedIcon(this, shiftedIcon);
+                params.addShiftedIcon(this, shiftedIcon);
             }
             mHintLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
 
@@ -347,15 +344,12 @@
                     Keyboard.CODE_UNSPECIFIED);
             if (code == Keyboard.CODE_UNSPECIFIED && !TextUtils.isEmpty(mLabel)) {
                 final int firstChar = mLabel.charAt(0);
-                mCode = mKeyboard.isRtlKeyboard() ? getRtlParenthesisCode(firstChar) : firstChar;
+                mCode = params.mIsRtlKeyboard ? getRtlParenthesisCode(firstChar) : firstChar;
             } else if (code != Keyboard.CODE_UNSPECIFIED) {
                 mCode = code;
             } else {
                 mCode = Keyboard.CODE_DUMMY;
             }
-            if (mCode == Keyboard.CODE_SHIFT) {
-                mKeyboard.addShiftKey(this);
-            }
         } finally {
             keyAttr.recycle();
         }
@@ -365,10 +359,6 @@
         mEdgeFlags |= flags;
     }
 
-    public CharSequence getCaseAdjustedLabel() {
-        return mKeyboard.adjustLabelCase(mLabel);
-    }
-
     public Typeface selectTypeface(Typeface defaultTypeface) {
         // TODO: Handle "bold" here too?
         if ((mLabelOption & LABEL_OPTION_FONT_NORMAL) != 0) {
@@ -460,10 +450,10 @@
      * assume that all points between the key and the edge are considered to be on the key.
      */
     public boolean isOnKey(int x, int y) {
-        final int left = mX - mGap / 2;
-        final int right = left + mWidth + mGap;
+        final int left = mX - mHorizontalGap / 2;
+        final int right = left + mWidth + mHorizontalGap;
         final int top = mY;
-        final int bottom = top + mHeight + mKeyboard.getVerticalGap();
+        final int bottom = top + mHeight + mVerticalGap;
         final int flags = mEdgeFlags;
         if (flags == 0) {
             return x >= left && x <= right && y >= top && y <= bottom;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 6d25025..53d46a3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -57,7 +57,7 @@
         mCorrectionX = (int)correctionX;
         mCorrectionY = (int)correctionY;
         mKeyboard = keyboard;
-        final int threshold = keyboard.getMostCommonKeyWidth();
+        final int threshold = keyboard.mMostCommonKeyWidth;
         mProximityThresholdSquare = threshold * threshold;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 8840c79..809c949 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -16,24 +16,17 @@
 
 package com.android.inputmethod.keyboard;
 
-import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
-import android.util.Log;
 
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
-import com.android.inputmethod.keyboard.internal.KeyboardParser;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.keyboard.internal.KeyboardShiftState;
-import com.android.inputmethod.latin.R;
 
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -54,8 +47,6 @@
  * </pre>
  */
 public class Keyboard {
-    private static final String TAG = Keyboard.class.getSimpleName();
-
     public static final int EDGE_LEFT = 0x01;
     public static final int EDGE_RIGHT = 0x02;
     public static final int EDGE_TOP = 0x04;
@@ -93,212 +84,80 @@
     // Code value representing the code is not specified.
     public static final int CODE_UNSPECIFIED = -99;
 
-    /** Horizontal gap default for all rows */
-    private int mDefaultHorizontalGap;
-
-    /** Default key width */
-    private int mDefaultWidth;
-
-    /** Default key height */
-    private int mDefaultHeight;
-
-    /** Default gap between rows */
-    private int mDefaultVerticalGap;
-
-    /** Popup keyboard template */
-    private int mPopupKeyboardResId;
-
-    /** Maximum column for popup keyboard */
-    private int mMaxPopupColumn;
-
-    /** True if Right-To-Left keyboard */
-    private boolean mIsRtlKeyboard;
-
-    /** List of shift keys in this keyboard and its icons and state */
-    private final List<Key> mShiftKeys = new ArrayList<Key>();
-    private final HashMap<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>();
-    private final HashMap<Key, Drawable> mUnshiftedIcons = new HashMap<Key, Drawable>();
-    private final HashSet<Key> mShiftLockKeys = new HashSet<Key>();
-    private final KeyboardShiftState mShiftState = new KeyboardShiftState();
-
-    /** Total height of the keyboard, including the padding and keys */
-    private int mTotalHeight;
-
-    /**
-     * Total width (minimum width) of the keyboard, including left side gaps and keys, but not any
-     * gaps on the right side.
-     */
-    private int mMinWidth;
-
-    /** List of keys in this keyboard */
-    private final List<Key> mKeys = new ArrayList<Key>();
-
-    /** Width of the screen available to fit the keyboard */
-    private final int mDisplayWidth;
-
-    /** Height of the screen */
-    private final int mDisplayHeight;
-
-    /** Height of keyboard */
-    private int mKeyboardHeight;
-
-    private int mMostCommonKeyWidth = 0;
-
     public final KeyboardId mId;
 
-    public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
+    /** Total height of the keyboard, including the padding and keys */
+    public final int mOccupiedHeight;
+    /** Total width of the keyboard, including the padding and keys */
+    public final int mOccupiedWidth;
 
-    // Variables for pre-computing nearest keys.
+    public final int mHeight;
+    public final int mWidth;
 
-    // TODO: Change GRID_WIDTH and GRID_HEIGHT to private.
-    public final int GRID_WIDTH;
-    public final int GRID_HEIGHT;
+    /** Default row height */
+    public final int mDefaultRowHeight;
+
+    /** Default gap between rows */
+    public final int mVerticalGap;
+
+    public final int mMostCommonKeyWidth;
+
+    /** Popup keyboard template */
+    public final int mPopupKeyboardResId;
+
+    /** Maximum column for popup keyboard */
+    public final int mMaxPopupColumn;
+
+    /** True if Right-To-Left keyboard */
+    public final boolean mIsRtlKeyboard;
+
+    /** List of keys and icons in this keyboard */
+    public final List<Key> mKeys;
+    public final List<Key> mShiftKeys;
+    public final Set<Key> mShiftLockKeys;
+    public final Map<Key, Drawable> mShiftedIcons;
+    public final Map<Key, Drawable> mUnshiftedIcons;
+    public final KeyboardIconsSet mIconsSet;
+
+    private final KeyboardShiftState mShiftState = new KeyboardShiftState();
 
     private final ProximityInfo mProximityInfo;
 
-    /**
-     * Creates a keyboard from the given xml key layout file.
-     * @param context the application or service context
-     * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
-     * @param id keyboard identifier
-     * @param width keyboard width
-     */
+    public Keyboard(KeyboardParams params) {
+        mId = params.mId;
+        mOccupiedHeight = params.mOccupiedHeight;
+        mOccupiedWidth = params.mOccupiedWidth;
+        mHeight = params.mHeight;
+        mWidth = params.mWidth;
+        mMostCommonKeyWidth = params.mMostCommonKeyWidth;
+        mIsRtlKeyboard = params.mIsRtlKeyboard;
+        mPopupKeyboardResId = params.mPopupKeyboardResId;
+        mMaxPopupColumn = params.mMaxPopupColumn;
 
-    public Keyboard(Context context, int xmlLayoutResId, KeyboardId id, int width) {
-        final Resources res = context.getResources();
-        GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
-        GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
+        mDefaultRowHeight = params.mDefaultRowHeight;
+        mVerticalGap = params.mVerticalGap;
 
-        final int horizontalEdgesPadding = (int)res.getDimension(
-                R.dimen.keyboard_horizontal_edges_padding);
-        mDisplayWidth = width - horizontalEdgesPadding * 2;
-        // TODO: Adjust the height by referring to the height of area available for drawing as well.
-        mDisplayHeight = res.getDisplayMetrics().heightPixels;
+        mKeys = Collections.unmodifiableList(params.mKeys);
+        mShiftKeys = Collections.unmodifiableList(params.mShiftKeys);
+        mShiftLockKeys = Collections.unmodifiableSet(params.mShiftLockKeys);
+        mShiftedIcons = Collections.unmodifiableMap(params.mShiftedIcons);
+        mUnshiftedIcons = Collections.unmodifiableMap(params.mUnshiftedIcons);
+        mIconsSet = params.mIconsSet;
 
-        mDefaultHorizontalGap = 0;
-        setKeyWidth(mDisplayWidth / 10);
-        mDefaultVerticalGap = 0;
-        mDefaultHeight = mDefaultWidth;
-        mId = id;
-        loadKeyboard(context, xmlLayoutResId);
         mProximityInfo = new ProximityInfo(
-                GRID_WIDTH, GRID_HEIGHT, getMinWidth(), getHeight(), getKeyWidth(), mKeys);
+                params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
+                mMostCommonKeyWidth, mKeys);
     }
 
     public int getProximityInfo() {
         return mProximityInfo.getNativeProximityInfo();
     }
 
+    // TODO: Access mKeys directly
     public List<Key> getKeys() {
         return mKeys;
     }
 
-    public int getHorizontalGap() {
-        return mDefaultHorizontalGap;
-    }
-
-    public void setHorizontalGap(int gap) {
-        mDefaultHorizontalGap = gap;
-    }
-
-    public int getVerticalGap() {
-        return mDefaultVerticalGap;
-    }
-
-    public void setVerticalGap(int gap) {
-        mDefaultVerticalGap = gap;
-    }
-
-    public int getRowHeight() {
-        return mDefaultHeight;
-    }
-
-    public void setRowHeight(int height) {
-        mDefaultHeight = height;
-    }
-
-    public int getKeyWidth() {
-        return mDefaultWidth;
-    }
-
-    public void setKeyWidth(int width) {
-        mDefaultWidth = width;
-    }
-
-    /**
-     * Returns the total height of the keyboard
-     * @return the total height of the keyboard
-     */
-    public int getHeight() {
-        return mTotalHeight;
-    }
-
-    public void setHeight(int height) {
-        mTotalHeight = height;
-    }
-
-    public int getMinWidth() {
-        return mMinWidth;
-    }
-
-    public void setMinWidth(int minWidth) {
-        mMinWidth = minWidth;
-    }
-
-    public int getDisplayHeight() {
-        return mDisplayHeight;
-    }
-
-    public int getDisplayWidth() {
-        return mDisplayWidth;
-    }
-
-    public int getKeyboardHeight() {
-        return mKeyboardHeight;
-    }
-
-    public void setKeyboardHeight(int height) {
-        mKeyboardHeight = height;
-    }
-
-    public boolean isRtlKeyboard() {
-        return mIsRtlKeyboard;
-    }
-
-    public void setRtlKeyboard(boolean isRtl) {
-        mIsRtlKeyboard = isRtl;
-    }
-
-    public int getPopupKeyboardResId() {
-        return mPopupKeyboardResId;
-    }
-
-    public void setPopupKeyboardResId(int resId) {
-        mPopupKeyboardResId = resId;
-    }
-
-    public int getMaxPopupKeyboardColumn() {
-        return mMaxPopupColumn;
-    }
-
-    public void setMaxPopupKeyboardColumn(int column) {
-        mMaxPopupColumn = column;
-    }
-
-    public void addShiftKey(Key key) {
-        if (key == null) return;
-        mShiftKeys.add(key);
-        if (key.mSticky) {
-            mShiftLockKeys.add(key);
-        }
-    }
-
-    public void addShiftedIcon(Key key, Drawable icon) {
-        if (key == null) return;
-        mUnshiftedIcons.put(key, key.getIcon());
-        mShiftedIcons.put(key, icon);
-    }
-
     public boolean hasShiftLockKey() {
         return !mShiftLockKeys.isEmpty();
     }
@@ -389,52 +248,4 @@
     public int[] getNearestKeys(int x, int y) {
         return mProximityInfo.getNearestKeys(x, y);
     }
-
-    /**
-     * Compute the most common key width in order to use it as proximity key detection threshold.
-     *
-     * @return The most common key width in the keyboard
-     */
-    public int getMostCommonKeyWidth() {
-        if (mMostCommonKeyWidth == 0) {
-            final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
-            int maxCount = 0;
-            int mostCommonWidth = 0;
-            for (final Key key : mKeys) {
-                final Integer width = key.mWidth + key.mGap;
-                Integer count = histogram.get(width);
-                if (count == null)
-                    count = 0;
-                histogram.put(width, ++count);
-                if (count > maxCount) {
-                    maxCount = count;
-                    mostCommonWidth = width;
-                }
-            }
-            mMostCommonKeyWidth = mostCommonWidth;
-        }
-        return mMostCommonKeyWidth;
-    }
-
-    private void loadKeyboard(Context context, int xmlLayoutResId) {
-        try {
-            KeyboardParser parser = new KeyboardParser(this, context);
-            parser.parseKeyboard(xmlLayoutResId);
-            // mMinWidth is the width of this keyboard which is maximum width of row.
-            mMinWidth = parser.getMaxRowWidth();
-            mTotalHeight = parser.getTotalHeight();
-        } catch (XmlPullParserException e) {
-            Log.w(TAG, "keyboard XML parse error: " + e);
-            throw new IllegalArgumentException(e);
-        } catch (IOException e) {
-            Log.w(TAG, "keyboard XML parse error: " + e);
-            throw new RuntimeException(e);
-        }
-    }
-
-    public static void setDefaultBounds(Drawable drawable)  {
-        if (drawable != null)
-            drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
-                    drawable.getIntrinsicHeight());
-    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 9299c6c..d0a2f86 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -42,8 +42,6 @@
     public static final int F2KEY_MODE_SHORTCUT_IME = 2;
     public static final int F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS = 3;
 
-    private static final int MINI_KEYBOARD_ID_MARKER = -1;
-
     public final Locale mLocale;
     public final int mOrientation;
     public final int mWidth;
@@ -110,9 +108,9 @@
         });
     }
 
-    public KeyboardId cloneAsMiniKeyboard() {
-        return new KeyboardId("mini popup keyboard", MINI_KEYBOARD_ID_MARKER, mLocale, mOrientation,
-                mWidth, mMode, mAttribute, false, F2KEY_MODE_NONE, false, false, false);
+    public KeyboardId cloneWithNewXml(String xmlName, int xmlId) {
+        return new KeyboardId(xmlName, xmlId, mLocale, mOrientation, mWidth, mMode, mAttribute,
+                false, F2KEY_MODE_NONE, false, false, false);
     }
 
     public KeyboardId cloneWithNewGeometry(int orientation, int width) {
@@ -127,10 +125,6 @@
         return mXmlId;
     }
 
-    public boolean isMiniKeyboard() {
-        return mXmlId == MINI_KEYBOARD_ID_MARKER;
-    }
-
     public boolean isAlphabetKeyboard() {
         return mXmlId == R.xml.kbd_qwerty;
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 17fdd0c..f45e810 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -29,7 +29,6 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
 import com.android.inputmethod.keyboard.internal.ModifierKeyState;
 import com.android.inputmethod.keyboard.internal.ShiftKeyState;
 import com.android.inputmethod.latin.LatinIME;
@@ -270,14 +269,17 @@
         if (keyboard == null) {
             final Locale savedLocale = Utils.setSystemLocale(
                     mResources, mSubtypeSwitcher.getInputLocale());
-
-            keyboard = new LatinKeyboard(mThemeContext, id, id.mWidth);
+            try {
+                keyboard = new LatinKeyboard.Builder(mThemeContext).load(id).build();
+            } finally {
+                Utils.setSystemLocale(mResources, savedLocale);
+            }
             mKeyboardCache.put(id, new SoftReference<LatinKeyboard>(keyboard));
-            if (DEBUG_CACHE)
+
+            if (DEBUG_CACHE) {
                 Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": "
                         + ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
-
-            Utils.setSystemLocale(mResources, savedLocale);
+            }
         } else if (DEBUG_CACHE) {
             Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": HIT  id=" + id);
         }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 4086a8e..0fb5109 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -357,7 +357,7 @@
         mDirtyRect.set(0, 0, getWidth(), getHeight());
         mBufferNeedsUpdate = true;
         invalidateAllKeys();
-        final int keyHeight = keyboard.getRowHeight() - keyboard.getVerticalGap();
+        final int keyHeight = keyboard.mDefaultRowHeight - keyboard.mVerticalGap;
         mKeyDrawParams.updateKeyHeight(keyHeight);
         mKeyPreviewDrawParams.updateKeyHeight(keyHeight);
     }
@@ -396,7 +396,7 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         if (mKeyboard != null) {
             // The main keyboard expands to the display width.
-            final int height = mKeyboard.getKeyboardHeight() + getPaddingTop() + getPaddingBottom();
+            final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
             setMeasuredDimension(widthMeasureSpec, height);
         } else {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -443,7 +443,8 @@
                     + getPaddingLeft();
             final int keyDrawY = mInvalidatedKey.mY + getPaddingTop();
             canvas.translate(keyDrawX, keyDrawY);
-            onBufferDrawKey(mInvalidatedKey, canvas, mPaint, params, isManualTemporaryUpperCase);
+            onBufferDrawKey(mInvalidatedKey, mKeyboard, canvas, mPaint, params,
+                    isManualTemporaryUpperCase);
             canvas.translate(-keyDrawX, -keyDrawY);
         } else {
             // Draw all keys.
@@ -451,7 +452,7 @@
                 final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
                 final int keyDrawY = key.mY + getPaddingTop();
                 canvas.translate(keyDrawX, keyDrawY);
-                onBufferDrawKey(key, canvas, mPaint, params, isManualTemporaryUpperCase);
+                onBufferDrawKey(key, mKeyboard, canvas, mPaint, params, isManualTemporaryUpperCase);
                 canvas.translate(-keyDrawX, -keyDrawY);
             }
         }
@@ -470,8 +471,8 @@
         return false;
     }
 
-    private static void onBufferDrawKey(final Key key, final Canvas canvas, Paint paint,
-            KeyDrawParams params, boolean isManualTemporaryUpperCase) {
+    private static void onBufferDrawKey(final Key key, final Keyboard keyboard, final Canvas canvas,
+            Paint paint, KeyDrawParams params, boolean isManualTemporaryUpperCase) {
         final boolean debugShowAlign = LatinImeLogger.sVISUALDEBUG;
         // Draw key background.
         final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
@@ -507,7 +508,7 @@
         float positionX = centerX;
         if (key.mLabel != null) {
             // Switch the character to uppercase if shift is pressed
-            final CharSequence label = key.getCaseAdjustedLabel();
+            final CharSequence label = keyboard.adjustLabelCase(key.mLabel);
             // For characters, use large font. For labels like "Done", use smaller font.
             paint.setTypeface(key.selectTypeface(params.mKeyTextStyle));
             final int labelSize = key.selectTextSize(params.mKeyLetterSize,
@@ -798,7 +799,7 @@
                 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize);
                 previewText.setTypeface(params.mKeyTextStyle);
             }
-            previewText.setText(key.getCaseAdjustedLabel());
+            previewText.setText(mKeyboard.adjustLabelCase(key.mLabel));
         } else {
             final Drawable previewIcon = key.getPreviewIcon();
             previewText.setCompoundDrawables(null, null, null,
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index 3c27129..9a13608 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -31,13 +31,14 @@
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.KeyboardParser;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
 
 import java.lang.ref.SoftReference;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Locale;
 
 // TODO: We should remove this class
@@ -73,32 +74,16 @@
     private static final String SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "small";
     private static final String MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "medium";
 
-    public LatinKeyboard(Context context, KeyboardId id, int width) {
-        super(context, id.getXmlId(), id, width);
+    private LatinKeyboard(Context context, LatinKeyboardParams params) {
+        super(params);
         mRes = context.getResources();
         mTheme = context.getTheme();
 
-        final List<Key> keys = getKeys();
-        int spaceKeyIndex = -1;
-        int shortcutKeyIndex = -1;
-        final int keyCount = keys.size();
-        for (int index = 0; index < keyCount; index++) {
-            // For now, assuming there are up to one space key and one shortcut key respectively.
-            switch (keys.get(index).mCode) {
-            case CODE_SPACE:
-                spaceKeyIndex = index;
-                break;
-            case CODE_SHORTCUT:
-                shortcutKeyIndex = index;
-                break;
-            }
-        }
-
         // The index of space key is available only after Keyboard constructor has finished.
-        mSpaceKey = (spaceKeyIndex >= 0) ? keys.get(spaceKeyIndex) : null;
+        mSpaceKey = params.mSpaceKey;
         mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon() : null;
 
-        mShortcutKey = (shortcutKeyIndex >= 0) ? keys.get(shortcutKeyIndex) : null;
+        mShortcutKey = params.mShortcutKey;
         mEnabledShortcutIcon = (mShortcutKey != null) ? mShortcutKey.getIcon() : null;
 
         final TypedArray a = context.obtainStyledAttributes(
@@ -114,6 +99,42 @@
         a.recycle();
     }
 
+    private static class LatinKeyboardParams extends KeyboardParams {
+        public Key mSpaceKey = null;
+        public Key mShortcutKey = null;
+
+        @Override
+        public void onAddKey(Key key) {
+            super.onAddKey(key);
+
+            switch (key.mCode) {
+            case Keyboard.CODE_SPACE:
+                mSpaceKey = key;
+                break;
+            case Keyboard.CODE_SHORTCUT:
+                mShortcutKey = key;
+                break;
+            }
+        }
+    }
+
+    public static class Builder extends KeyboardParser<LatinKeyboardParams> {
+        public Builder(Context context) {
+            super(context, new LatinKeyboardParams());
+        }
+
+        @Override
+        public Builder load(KeyboardId id) {
+            super.load(id);
+            return this;
+        }
+
+        @Override
+        public LatinKeyboard build() {
+            return new LatinKeyboard(mContext, mParams);
+        }
+    }
+
     public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardView view) {
         mSpacebarTextFadeFactor = fadeFactor;
         updateSpacebarForLocale(false);
@@ -294,8 +315,8 @@
     @Override
     public int[] getNearestKeys(int x, int y) {
         // Avoid dead pixels at edges of the keyboard
-        return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)),
-                Math.max(0, Math.min(y, getHeight() - 1)));
+        return super.getNearestKeys(Math.max(0, Math.min(x, mOccupiedWidth - 1)),
+                Math.max(0, Math.min(y, mOccupiedHeight - 1)));
     }
 
     public static int getTextSizeFromTheme(Theme theme, int style, int defValue) {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
index b397ca7..abf28c7 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
@@ -289,7 +289,7 @@
         super.setKeyboard(keyboard);
         mKeyDetector.setKeyboard(
                 keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
-        mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth());
+        mKeyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
         PointerTracker.setKeyDetector(mKeyDetector);
         mPopupPanelCache.clear();
     }
@@ -360,7 +360,7 @@
                 (PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view);
         final Keyboard parentKeyboard = getKeyboard();
         final Keyboard miniKeyboard = new MiniKeyboardBuilder(
-                this, parentKeyboard.getPopupKeyboardResId(), parentKey, parentKeyboard).build();
+                this, parentKeyboard.mPopupKeyboardResId, parentKey, parentKeyboard).build();
         miniKeyboardView.setKeyboard(miniKeyboard);
 
         container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index b78fd94..0409677 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -69,7 +69,7 @@
     public void setKeyboard(Keyboard newKeyboard) {
         super.setKeyboard(newKeyboard);
         // One-seventh of the keyboard width seems like a reasonable threshold
-        final int jumpThreshold = newKeyboard.getMinWidth() / 7;
+        final int jumpThreshold = newKeyboard.mOccupiedWidth / 7;
         mJumpThresholdSquare = jumpThreshold * jumpThreshold;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
index 95e3275..7f5339d 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
@@ -16,30 +16,14 @@
 
 package com.android.inputmethod.keyboard;
 
-import android.content.Context;
+import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder.MiniKeyboardLayoutParams;
 
 public class MiniKeyboard extends Keyboard {
-    private int mDefaultKeyCoordX;
+    private final int mDefaultKeyCoordX;
 
-    public MiniKeyboard(Context context, int xmlLayoutResId, Keyboard parentKeyboard) {
-        super(context, xmlLayoutResId, parentKeyboard.mId.cloneAsMiniKeyboard(),
-                parentKeyboard.getMinWidth());
-        // HACK: Current mini keyboard design totally relies on the 9-patch padding about horizontal
-        // and vertical key spacing. To keep the visual of mini keyboard as is, these hacks are
-        // needed to keep having the same horizontal and vertical key spacing.
-        setHorizontalGap(0);
-        setVerticalGap(parentKeyboard.getVerticalGap() / 2);
-
-        // TODO: When we have correctly padded key background 9-patch drawables for mini keyboard,
-        // revert the above hacks and uncomment the following lines.
-        //setHorizontalGap(parentKeyboard.getHorizontalGap());
-        //setVerticalGap(parentKeyboard.getVerticalGap());
-
-        setRtlKeyboard(parentKeyboard.isRtlKeyboard());
-    }
-
-    public void setDefaultCoordX(int pos) {
-        mDefaultKeyCoordX = pos;
+    public MiniKeyboard(MiniKeyboardLayoutParams params) {
+        super(params);
+        mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
     }
 
     public int getDefaultCoordX() {
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index b25754d..3d8a9cf 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -286,7 +286,7 @@
         mKeyDetector = keyDetector;
         mKeyboard = keyDetector.getKeyboard();
         mKeys = mKeyboard.getKeys();
-        final int keyQuarterWidth = mKeyboard.getKeyWidth() / 4;
+        final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
         mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
index 2741ee8..dfaaa70 100644
--- a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
@@ -108,8 +108,8 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final Keyboard keyboard = getKeyboard();
         if (keyboard != null) {
-            final int width = keyboard.getMinWidth() + getPaddingLeft() + getPaddingRight();
-            final int height = keyboard.getKeyboardHeight() + getPaddingTop() + getPaddingBottom();
+            final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
+            final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
             setMeasuredDimension(width, height);
         } else {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -170,9 +170,9 @@
         final int miniKeyboardLeft = pointX - miniKeyboard.getDefaultCoordX()
                 + parentKeyboardView.getPaddingLeft();
         final int x = Math.max(0, Math.min(miniKeyboardLeft,
-                parentKeyboardView.getWidth() - miniKeyboard.getMinWidth()))
+                parentKeyboardView.getWidth() - miniKeyboard.mOccupiedWidth))
                 - container.getPaddingLeft() + mCoordinates[0];
-        final int y = pointY - parentKeyboard.getVerticalGap()
+        final int y = pointY - parentKeyboard.mVerticalGap
                 - (container.getMeasuredHeight() - container.getPaddingBottom())
                 + parentKeyboardView.getPaddingTop() + mCoordinates[1];
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 02c261b..ed4608b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -21,7 +21,6 @@
 import android.graphics.drawable.Drawable;
 import android.util.Log;
 
-import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.R;
 
 public class KeyboardIconsSet {
@@ -51,7 +50,7 @@
 
     private final Drawable mIcons[] = new Drawable[ICON_LAST + 1];
 
-    private static final int getIconId(int attrIndex) {
+    private static final int getIconId(final int attrIndex) {
         switch (attrIndex) {
         case R.styleable.Keyboard_iconShiftKey:
             return ICON_SHIFT_KEY;
@@ -86,16 +85,14 @@
         }
     }
 
-    public void loadIcons(TypedArray keyboardAttrs) {
+    public void loadIcons(final TypedArray keyboardAttrs) {
         final int count = keyboardAttrs.getIndexCount();
         for (int i = 0; i < count; i++) {
             final int attrIndex = keyboardAttrs.getIndex(i);
             final int iconId = getIconId(attrIndex);
             if (iconId != ICON_UNDEFINED) {
                 try {
-                    final Drawable icon = keyboardAttrs.getDrawable(attrIndex);
-                    Keyboard.setDefaultBounds(icon);
-                    mIcons[iconId] = icon;
+                    mIcons[iconId] = setDefaultBounds(keyboardAttrs.getDrawable(attrIndex));
                 } catch (Resources.NotFoundException e) {
                     Log.w(TAG, "Drawable resource for icon #" + iconId + " not found");
                 }
@@ -103,11 +100,18 @@
         }
     }
 
-    public Drawable getIcon(int iconId) {
+    public Drawable getIcon(final int iconId) {
         if (iconId == ICON_UNDEFINED)
             return null;
         if (iconId < 0 || iconId >= mIcons.length)
             throw new IllegalArgumentException("icon id is out of range: " + iconId);
         return mIcons[iconId];
     }
+
+    private static Drawable setDefaultBounds(final Drawable icon)  {
+        if (icon != null) {
+            icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+        }
+        return icon;
+    }
 }
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 0000000..4ccaa72
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 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.drawable.Drawable;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class KeyboardParams {
+    public KeyboardId mId;
+
+    public int mOccupiedHeight;
+    public int mOccupiedWidth;
+
+    public int mHeight;
+    public int mWidth;
+
+    public int mTopPadding;
+    public int mBottomPadding;
+    public int mHorizontalEdgesPadding;
+    public int mHorizontalCenterPadding;
+
+    public int mDefaultRowHeight;
+    public int mDefaultKeyWidth;
+    public int mHorizontalGap;
+    public int mVerticalGap;
+
+    public boolean mIsRtlKeyboard;
+    public int mPopupKeyboardResId;
+    public int mMaxPopupColumn;
+
+    public int GRID_WIDTH;
+    public int GRID_HEIGHT;
+
+    public final List<Key> mKeys = new ArrayList<Key>();
+    public final List<Key> mShiftKeys = new ArrayList<Key>();
+    public final Set<Key> mShiftLockKeys = new HashSet<Key>();
+    public final Map<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>();
+    public final Map<Key, Drawable> mUnshiftedIcons = new HashMap<Key, Drawable>();
+    public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
+
+    public int mMostCommonKeyWidth = 0;
+
+    public void onAddKey(Key key) {
+        mKeys.add(key);
+        updateHistogram(key);
+        if (key.mCode == Keyboard.CODE_SHIFT) {
+            mShiftKeys.add(key);
+            if (key.mSticky) {
+                mShiftLockKeys.add(key);
+            }
+        }
+    }
+
+    public void addShiftedIcon(Key key, Drawable icon) {
+        mUnshiftedIcons.put(key, key.getIcon());
+        mShiftedIcons.put(key, icon);
+    }
+
+    private int mMaxCount = 0;
+    private final Map<Integer, Integer> mHistogram = new HashMap<Integer, Integer>();
+
+    private void updateHistogram(Key key) {
+        final Integer width = key.mWidth + key.mHorizontalGap;
+        final int count = (mHistogram.containsKey(width) ? mHistogram.get(width) : 0) + 1;
+        mHistogram.put(width, count);
+        if (count > mMaxCount) {
+            mMaxCount = count;
+            mMostCommonKeyWidth = width;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java
index f6f4675..42e290f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java
@@ -20,6 +20,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.TypedValue;
 import android.util.Xml;
@@ -107,7 +108,7 @@
  * </pre>
  */
 
-public class KeyboardParser {
+public class KeyboardParser<KP extends KeyboardParams> {
     private static final String TAG = KeyboardParser.class.getSimpleName();
     private static final boolean DEBUG = false;
 
@@ -123,40 +124,52 @@
     private static final String TAG_DEFAULT = "default";
     public static final String TAG_KEY_STYLE = "key-style";
 
-    private final Keyboard mKeyboard;
-    private final Context mContext;
-    private final Resources mResources;
+    protected final KP mParams;
+    protected final Context mContext;
+    protected final Resources mResources;
+    private final DisplayMetrics mDisplayMetrics;
 
-    private int mKeyboardTopPadding;
-    private int mKeyboardBottomPadding;
-    private int mHorizontalEdgesPadding;
     private int mCurrentX = 0;
     private int mCurrentY = 0;
-    private int mMaxRowWidth = 0;
-    private int mTotalHeight = 0;
     private Row mCurrentRow = null;
     private boolean mLeftEdge;
     private Key mRightEdgeKey = null;
     private final KeyStyles mKeyStyles = new KeyStyles();
 
-    public KeyboardParser(Keyboard keyboard, Context context) {
-        mKeyboard = keyboard;
+    public KeyboardParser(Context context, KP params) {
         mContext = context;
         final Resources res = context.getResources();
         mResources = res;
-        mHorizontalEdgesPadding = (int)res.getDimension(R.dimen.keyboard_horizontal_edges_padding);
+        mDisplayMetrics = res.getDisplayMetrics();
+
+        mParams = params;
+        mParams.mHorizontalEdgesPadding = (int)res.getDimension(
+                R.dimen.keyboard_horizontal_edges_padding);
+
+        mParams.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
+        mParams.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
     }
 
-    public int getMaxRowWidth() {
-        return mMaxRowWidth;
+    public KeyboardParser<KP> load(KeyboardId id) {
+        mParams.mId = id;
+        try {
+            parseKeyboard(id.getXmlId());
+        } catch (XmlPullParserException e) {
+            Log.w(TAG, "keyboard XML parse error: " + e);
+            throw new IllegalArgumentException(e);
+        } catch (IOException e) {
+            Log.w(TAG, "keyboard XML parse error: " + e);
+            throw new RuntimeException(e);
+        }
+        return this;
     }
 
-    public int getTotalHeight() {
-        return mTotalHeight;
+    public Keyboard build() {
+        return new Keyboard(mParams);
     }
 
-    public void parseKeyboard(int resId) throws XmlPullParserException, IOException {
-        if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_KEYBOARD, mKeyboard.mId));
+    private void parseKeyboard(int resId) throws XmlPullParserException, IOException {
+        if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_KEYBOARD, mParams.mId));
         final XmlResourceParser parser = mResources.getXml(resId);
         int event;
         while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -165,7 +178,7 @@
                 if (TAG_KEYBOARD.equals(tag)) {
                     parseKeyboardAttributes(parser);
                     startKeyboard();
-                    parseKeyboardContent(parser, mKeyboard.getKeys());
+                    parseKeyboardContent(parser, false);
                     break;
                 } else {
                     throw new IllegalStartTag(parser, TAG_KEYBOARD);
@@ -196,15 +209,14 @@
     }
 
     private void parseKeyboardAttributes(XmlResourceParser parser) {
-        final Keyboard keyboard = mKeyboard;
-        final int displayWidth = keyboard.getDisplayWidth();
+        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);
         try {
-            final int displayHeight = keyboard.getDisplayHeight();
+            final int displayHeight = mDisplayMetrics.heightPixels;
             final int keyboardHeight = (int)keyboardAttr.getDimension(
                     R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
             final int maxKeyboardHeight = getDimensionOrFraction(keyboardAttr,
@@ -219,61 +231,65 @@
             }
             // Keyboard height will not exceed maxKeyboardHeight and will not be less than
             // minKeyboardHeight.
-            final int height = Math.max(
+            mParams.mOccupiedHeight = Math.max(
                     Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
+            mParams.mOccupiedWidth = mParams.mId.mWidth;
+            mParams.mTopPadding = getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_keyboardTopPadding, mParams.mOccupiedHeight, 0);
+            mParams.mBottomPadding = getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_keyboardBottomPadding, mParams.mOccupiedHeight, 0);
 
-            keyboard.setKeyboardHeight(height);
-            keyboard.setRtlKeyboard(keyboardAttr.getBoolean(
-                    R.styleable.Keyboard_isRtlKeyboard, false));
-            keyboard.setKeyWidth(getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_keyWidth, displayWidth, displayWidth / 10));
-            keyboard.setRowHeight(getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_rowHeight, height, 50));
-            keyboard.setHorizontalGap(getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_horizontalGap, displayWidth, 0));
-            keyboard.setVerticalGap(getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_verticalGap, height, 0));
-            keyboard.setPopupKeyboardResId(keyboardAttr.getResourceId(
-                    R.styleable.Keyboard_popupKeyboardTemplate, 0));
-            keyboard.setMaxPopupKeyboardColumn(keyAttr.getInt(
-                    R.styleable.Keyboard_Key_maxPopupKeyboardColumn, 5));
+            final int height = mParams.mOccupiedHeight;
+            final int width = mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding * 2
+                    - mParams.mHorizontalCenterPadding;
+            mParams.mHeight = height;
+            mParams.mWidth = width;
+            mParams.mDefaultKeyWidth = getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_keyWidth, width, width / 10);
+            mParams.mDefaultRowHeight = getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_rowHeight, height, height / 4);
+            mParams.mHorizontalGap = getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_horizontalGap, width, 0);
+            mParams.mVerticalGap = getDimensionOrFraction(keyboardAttr,
+                    R.styleable.Keyboard_verticalGap, height, 0);
 
-            mKeyboard.mIconsSet.loadIcons(keyboardAttr);
-            mKeyboardTopPadding = getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_keyboardTopPadding, height, 0);
-            mKeyboardBottomPadding = getDimensionOrFraction(keyboardAttr,
-                    R.styleable.Keyboard_keyboardBottomPadding, height, 0);
+            mParams.mIsRtlKeyboard = keyboardAttr.getBoolean(R.styleable.Keyboard_isRtlKeyboard, false);
+            mParams.mPopupKeyboardResId = keyboardAttr.getResourceId(
+                    R.styleable.Keyboard_popupKeyboardTemplate, 0);
+            mParams.mMaxPopupColumn = keyAttr.getInt(R.styleable.Keyboard_Key_maxPopupKeyboardColumn, 5);
+
+            mParams.mIconsSet.loadIcons(keyboardAttr);
         } finally {
             keyAttr.recycle();
             keyboardAttr.recycle();
         }
     }
 
-    private void parseKeyboardContent(XmlResourceParser parser, List<Key> keys)
+    private void parseKeyboardContent(XmlResourceParser 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 = new Row(mResources, mKeyboard, parser);
+                    Row row = parseRowAttributes(parser);
                     if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_ROW));
-                    if (keys != null)
+                    if (!skip)
                         startRow(row);
-                    parseRowContent(parser, row, keys);
+                    parseRowContent(parser, row, skip);
                 } else if (TAG_INCLUDE.equals(tag)) {
-                    parseIncludeKeyboardContent(parser, keys);
+                    parseIncludeKeyboardContent(parser, skip);
                 } else if (TAG_SWITCH.equals(tag)) {
-                    parseSwitchKeyboardContent(parser, keys);
+                    parseSwitchKeyboardContent(parser, skip);
                 } else if (TAG_KEY_STYLE.equals(tag)) {
-                    parseKeyStyle(parser, keys);
+                    parseKeyStyle(parser, skip);
                 } else {
                     throw new IllegalStartTag(parser, TAG_ROW);
                 }
             } else if (event == XmlPullParser.END_TAG) {
                 final String tag = parser.getName();
                 if (TAG_KEYBOARD.equals(tag)) {
-                    endKeyboard(mKeyboard.getVerticalGap());
+                    endKeyboard();
                     break;
                 } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
                         || TAG_MERGE.equals(tag)) {
@@ -288,22 +304,36 @@
         }
     }
 
-    private void parseRowContent(XmlResourceParser parser, Row row, List<Key> keys)
+    private Row parseRowAttributes(XmlResourceParser parser) {
+        final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+                R.styleable.Keyboard);
+        try {
+            if (a.hasValue(R.styleable.Keyboard_horizontalGap))
+                throw new IllegalAttribute(parser, "horizontalGap");
+            if (a.hasValue(R.styleable.Keyboard_verticalGap))
+                throw new IllegalAttribute(parser, "verticalGap");
+            return new Row(mResources, mParams, parser);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    private void parseRowContent(XmlResourceParser 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, keys);
+                    parseKey(parser, row, skip);
                 } else if (TAG_SPACER.equals(tag)) {
-                    parseSpacer(parser, row, keys);
+                    parseSpacer(parser, row, skip);
                 } else if (TAG_INCLUDE.equals(tag)) {
-                    parseIncludeRowContent(parser, row, keys);
+                    parseIncludeRowContent(parser, row, skip);
                 } else if (TAG_SWITCH.equals(tag)) {
-                    parseSwitchRowContent(parser, row, keys);
+                    parseSwitchRowContent(parser, row, skip);
                 } else if (TAG_KEY_STYLE.equals(tag)) {
-                    parseKeyStyle(parser, keys);
+                    parseKeyStyle(parser, skip);
                 } else {
                     throw new IllegalStartTag(parser, TAG_KEY);
                 }
@@ -311,7 +341,7 @@
                 final String tag = parser.getName();
                 if (TAG_ROW.equals(tag)) {
                     if (DEBUG) Log.d(TAG, String.format("</%s>", TAG_ROW));
-                    if (keys != null)
+                    if (!skip)
                         endRow();
                     break;
                 } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
@@ -327,24 +357,24 @@
         }
     }
 
-    private void parseKey(XmlResourceParser parser, Row row, List<Key> keys)
+    private void parseKey(XmlResourceParser parser, Row row, boolean skip)
             throws XmlPullParserException, IOException {
-        if (keys == null) {
+        if (skip) {
             checkEndTag(TAG_KEY, parser);
         } else {
-            Key key = new Key(mResources, row, mCurrentX, mCurrentY, parser, mKeyStyles);
+            Key key = new Key(mResources, mParams, row, mCurrentX, mCurrentY, parser, mKeyStyles);
             if (DEBUG) Log.d(TAG, String.format("<%s%s keyLabel=%s code=%d popupCharacters=%s />",
                     TAG_KEY, (key.isEnabled() ? "" : " disabled"), key.mLabel, key.mCode,
                     Arrays.toString(key.mPopupCharacters)));
             checkEndTag(TAG_KEY, parser);
-            keys.add(key);
+            mParams.onAddKey(key);
             endKey(key);
         }
     }
 
-    private void parseSpacer(XmlResourceParser parser, Row row, List<Key> keys)
+    private void parseSpacer(XmlResourceParser parser, Row row, boolean skip)
             throws XmlPullParserException, IOException {
-        if (keys == null) {
+        if (skip) {
             checkEndTag(TAG_SPACER, parser);
         } else {
             if (DEBUG) Log.d(TAG, String.format("<%s />", TAG_SPACER));
@@ -352,9 +382,9 @@
                     R.styleable.Keyboard);
             if (keyboardAttr.hasValue(R.styleable.Keyboard_horizontalGap))
                 throw new IllegalAttribute(parser, "horizontalGap");
-            final int keyboardWidth = mKeyboard.getDisplayWidth();
+            final int keyboardWidth = mParams.mWidth;
             final int keyWidth = getDimensionOrFraction(keyboardAttr, R.styleable.Keyboard_keyWidth,
-                    keyboardWidth, row.mDefaultWidth);
+                    keyboardWidth, row.mDefaultKeyWidth);
             keyboardAttr.recycle();
 
             final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
@@ -371,19 +401,19 @@
         }
     }
 
-    private void parseIncludeKeyboardContent(XmlResourceParser parser, List<Key> keys)
+    private void parseIncludeKeyboardContent(XmlResourceParser parser, boolean skip)
             throws XmlPullParserException, IOException {
-        parseIncludeInternal(parser, null, keys);
+        parseIncludeInternal(parser, null, skip);
     }
 
-    private void parseIncludeRowContent(XmlResourceParser parser, Row row, List<Key> keys)
+    private void parseIncludeRowContent(XmlResourceParser parser, Row row, boolean skip)
             throws XmlPullParserException, IOException {
-        parseIncludeInternal(parser, row, keys);
+        parseIncludeInternal(parser, row, skip);
     }
 
-    private void parseIncludeInternal(XmlResourceParser parser, Row row, List<Key> keys)
+    private void parseIncludeInternal(XmlResourceParser parser, Row row, boolean skip)
             throws XmlPullParserException, IOException {
-        if (keys == null) {
+        if (skip) {
             checkEndTag(TAG_INCLUDE, parser);
         } else {
             final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
@@ -397,11 +427,11 @@
                 throw new ParseException("No keyboardLayout attribute in <include/>", parser);
             if (DEBUG) Log.d(TAG, String.format("<%s keyboardLayout=%s />",
                     TAG_INCLUDE, mResources.getResourceEntryName(keyboardLayout)));
-            parseMerge(mResources.getLayout(keyboardLayout), row, keys);
+            parseMerge(mResources.getLayout(keyboardLayout), row, skip);
         }
     }
 
-    private void parseMerge(XmlResourceParser parser, Row row, List<Key> keys)
+    private void parseMerge(XmlResourceParser parser, Row row, boolean skip)
             throws XmlPullParserException, IOException {
         int event;
         while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -409,9 +439,9 @@
                 final String tag = parser.getName();
                 if (TAG_MERGE.equals(tag)) {
                     if (row == null) {
-                        parseKeyboardContent(parser, keys);
+                        parseKeyboardContent(parser, skip);
                     } else {
-                        parseRowContent(parser, row, keys);
+                        parseRowContent(parser, row, skip);
                     }
                     break;
                 } else {
@@ -422,28 +452,28 @@
         }
     }
 
-    private void parseSwitchKeyboardContent(XmlResourceParser parser, List<Key> keys)
+    private void parseSwitchKeyboardContent(XmlResourceParser parser, boolean skip)
             throws XmlPullParserException, IOException {
-        parseSwitchInternal(parser, null, keys);
+        parseSwitchInternal(parser, null, skip);
     }
 
-    private void parseSwitchRowContent(XmlResourceParser parser, Row row, List<Key> keys)
+    private void parseSwitchRowContent(XmlResourceParser parser, Row row, boolean skip)
             throws XmlPullParserException, IOException {
-        parseSwitchInternal(parser, row, keys);
+        parseSwitchInternal(parser, row, skip);
     }
 
-    private void parseSwitchInternal(XmlResourceParser parser, Row row, List<Key> keys)
+    private void parseSwitchInternal(XmlResourceParser parser, Row row, boolean skip)
             throws XmlPullParserException, IOException {
-        if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_SWITCH, mKeyboard.mId));
+        if (DEBUG) Log.d(TAG, String.format("<%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 ? null : keys);
+                    selected |= parseCase(parser, row, selected ? true : skip);
                 } else if (TAG_DEFAULT.equals(tag)) {
-                    selected |= parseDefault(parser, row, selected ? null : keys);
+                    selected |= parseDefault(parser, row, selected ? true : skip);
                 } else {
                     throw new IllegalStartTag(parser, TAG_KEY);
                 }
@@ -459,21 +489,21 @@
         }
     }
 
-    private boolean parseCase(XmlResourceParser parser, Row row, List<Key> keys)
+    private boolean parseCase(XmlResourceParser parser, Row row, boolean skip)
             throws XmlPullParserException, IOException {
         final boolean selected = parseCaseCondition(parser);
         if (row == null) {
             // Processing Rows.
-            parseKeyboardContent(parser, selected ? keys : null);
+            parseKeyboardContent(parser, selected ? skip : true);
         } else {
             // Processing Keys.
-            parseRowContent(parser, row, selected ? keys : null);
+            parseRowContent(parser, row, selected ? skip : true);
         }
         return selected;
     }
 
     private boolean parseCaseCondition(XmlResourceParser parser) {
-        final KeyboardId id = mKeyboard.mId;
+        final KeyboardId id = mParams.mId;
         if (id == null)
             return true;
 
@@ -582,18 +612,18 @@
         return false;
     }
 
-    private boolean parseDefault(XmlResourceParser parser, Row row, List<Key> keys)
+    private boolean parseDefault(XmlResourceParser parser, Row row, boolean skip)
             throws XmlPullParserException, IOException {
         if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT));
         if (row == null) {
-            parseKeyboardContent(parser, keys);
+            parseKeyboardContent(parser, skip);
         } else {
-            parseRowContent(parser, row, keys);
+            parseRowContent(parser, row, skip);
         }
         return true;
     }
 
-    private void parseKeyStyle(XmlResourceParser parser, List<Key> keys) {
+    private void parseKeyStyle(XmlResourceParser parser, boolean skip) {
         TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
                 R.styleable.Keyboard_KeyStyle);
         TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
@@ -602,7 +632,7 @@
             if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
                 throw new ParseException("<" + TAG_KEY_STYLE
                         + "/> needs styleName attribute", parser);
-            if (keys != null)
+            if (!skip)
                 mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
         } finally {
             keyStyleAttr.recycle();
@@ -618,12 +648,12 @@
     }
 
     private void startKeyboard() {
-        mCurrentY += mKeyboardTopPadding;
+        mCurrentY += mParams.mTopPadding;
     }
 
     private void startRow(Row row) {
         mCurrentX = 0;
-        setSpacer(mCurrentX, mHorizontalEdgesPadding);
+        setSpacer(mCurrentX, mParams.mHorizontalEdgesPadding);
         mCurrentRow = row;
         mLeftEdge = true;
         mRightEdgeKey = null;
@@ -636,15 +666,13 @@
             mRightEdgeKey.addEdgeFlags(Keyboard.EDGE_RIGHT);
             mRightEdgeKey = null;
         }
-        setSpacer(mCurrentX, mHorizontalEdgesPadding);
-        if (mCurrentX > mMaxRowWidth)
-            mMaxRowWidth = mCurrentX;
-        mCurrentY += mCurrentRow.mDefaultHeight;
+        setSpacer(mCurrentX, mParams.mHorizontalEdgesPadding);
+        mCurrentY += mCurrentRow.mRowHeight;
         mCurrentRow = null;
     }
 
     private void endKey(Key key) {
-        mCurrentX = key.mX - key.mGap / 2 + key.mWidth + key.mGap;
+        mCurrentX = key.mX - key.mHorizontalGap / 2 + key.mWidth + key.mHorizontalGap;
         if (mLeftEdge) {
             key.addEdgeFlags(Keyboard.EDGE_LEFT);
             mLeftEdge = false;
@@ -652,9 +680,7 @@
         mRightEdgeKey = key;
     }
 
-    private void endKeyboard(int defaultVerticalGap) {
-        mCurrentY += mKeyboardBottomPadding;
-        mTotalHeight = mCurrentY - defaultVerticalGap;
+    private void endKeyboard() {
     }
 
     private void setSpacer(int keyXPos, int width) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
index 965c679..bad7a17 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
@@ -16,8 +16,6 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Paint;
 import android.graphics.Rect;
 
@@ -27,26 +25,30 @@
 import com.android.inputmethod.keyboard.MiniKeyboard;
 import com.android.inputmethod.latin.R;
 
-import java.util.List;
-
-public class MiniKeyboardBuilder {
-    private final Resources mRes;
-    private final MiniKeyboard mKeyboard;
+public class MiniKeyboardBuilder extends
+        KeyboardParser<MiniKeyboardBuilder.MiniKeyboardLayoutParams> {
     private final CharSequence[] mPopupCharacters;
-    private final MiniKeyboardLayoutParams mParams;
 
-    /* package */ static class MiniKeyboardLayoutParams {
-        public final int mKeyWidth;
-        public final int mRowHeight;
-        /* package */ final int mTopRowAdjustment;
-        public final int mNumRows;
-        public final int mNumColumns;
-        public final int mLeftKeys;
-        public final int mRightKeys; // includes default key.
-        public int mTopPadding;
+    public static class MiniKeyboardLayoutParams extends KeyboardParams {
+        /* package */ int mTopRowAdjustment;
+        public int mNumRows;
+        public int mNumColumns;
+        public int mLeftKeys;
+        public int mRightKeys; // includes default key.
+
+        public MiniKeyboardLayoutParams() {
+            super();
+        }
+
+        /* package for test */ MiniKeyboardLayoutParams(int numKeys, int maxColumns, int keyWidth,
+                int rowHeight, int coordXInParent, int parentKeyboardWidth) {
+            super();
+            setParameters(
+                    numKeys, maxColumns, keyWidth, rowHeight, coordXInParent, parentKeyboardWidth);
+        }
 
         /**
-         * The object holding mini keyboard layout parameters.
+         * Set keyboard parameters of mini keyboard.
          *
          * @param numKeys number of keys in this mini keyboard.
          * @param maxColumns number of maximum columns of this mini keyboard.
@@ -54,15 +56,15 @@
          * @param rowHeight mini keyboard row height in pixel, including vertical gap.
          * @param coordXInParent coordinate x of the popup key in parent keyboard.
          * @param parentKeyboardWidth parent keyboard width in pixel.
-         * parent keyboard.
          */
-        public MiniKeyboardLayoutParams(int numKeys, int maxColumns, int keyWidth, int rowHeight,
+        public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
                 int coordXInParent, int parentKeyboardWidth) {
-            if (parentKeyboardWidth / keyWidth < maxColumns)
+            if (parentKeyboardWidth / keyWidth < maxColumns) {
                 throw new IllegalArgumentException("Keyboard is too small to hold mini keyboard: "
                         + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
-            mKeyWidth = keyWidth;
-            mRowHeight = rowHeight;
+            }
+            mDefaultKeyWidth = keyWidth;
+            mDefaultRowHeight = rowHeight;
 
             final int numRows = (numKeys + maxColumns - 1) / maxColumns;
             mNumRows = numRows;
@@ -108,6 +110,9 @@
             } else {
                 mTopRowAdjustment = -1;
             }
+
+            mWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth;
+            mHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
         }
 
         // Return key position according to column count (0 is default).
@@ -160,19 +165,19 @@
         }
 
         public int getDefaultKeyCoordX() {
-            return mLeftKeys * mKeyWidth;
+            return mLeftKeys * mDefaultKeyWidth;
         }
 
         public int getX(int n, int row) {
-            final int x = getColumnPos(n) * mKeyWidth + getDefaultKeyCoordX();
+            final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX();
             if (isTopRow(row)) {
-                return x + mTopRowAdjustment * (mKeyWidth / 2);
+                return x + mTopRowAdjustment * (mDefaultKeyWidth / 2);
             }
             return x;
         }
 
         public int getY(int row) {
-            return (mNumRows - 1 - row) * mRowHeight + mTopPadding;
+            return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
         }
 
         public int getRowFlags(int row) {
@@ -185,42 +190,32 @@
         private boolean isTopRow(int rowCount) {
             return rowCount == mNumRows - 1;
         }
-
-        public void setTopPadding (int topPadding) {
-            mTopPadding = topPadding;
-        }
-
-        public int getKeyboardHeight() {
-            return mNumRows * mRowHeight + mTopPadding;
-        }
-
-        public int getKeyboardWidth() {
-            return mNumColumns * mKeyWidth;
-        }
     }
 
-    public MiniKeyboardBuilder(KeyboardView view, int layoutTemplateResId, Key parentKey,
+    public MiniKeyboardBuilder(KeyboardView view, int xmlId, Key parentKey,
             Keyboard parentKeyboard) {
-        final Context context = view.getContext();
-        mRes = context.getResources();
-        final MiniKeyboard keyboard = new MiniKeyboard(
-                context, layoutTemplateResId, parentKeyboard);
-        mKeyboard = keyboard;
+        super(view.getContext(), new MiniKeyboardLayoutParams());
+        load(parentKeyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId));
+
+        // HACK: Current mini keyboard design totally relies on the 9-patch padding about horizontal
+        // and vertical key spacing. To keep the visual of mini keyboard as is, these hacks are
+        // needed to keep having the same horizontal and vertical key spacing.
+        mParams.mHorizontalGap = 0;
+        mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
+        // TODO: When we have correctly padded key background 9-patch drawables for mini keyboard,
+        // revert the above hacks and uncomment the following lines.
+        //mParams.mHorizontalGap = parentKeyboard.mHorizontalGap;
+        //mParams.mVerticalGap = parentKeyboard.mVerticalGap;
+
+        mParams.mIsRtlKeyboard = parentKeyboard.mIsRtlKeyboard;
         mPopupCharacters = parentKey.mPopupCharacters;
 
-        final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, keyboard.getKeyWidth());
-        final MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
+        final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, mParams.mDefaultKeyWidth);
+        mParams.setParameters(
                 mPopupCharacters.length, parentKey.mMaxPopupColumn,
-                keyWidth, parentKeyboard.getRowHeight(),
-                parentKey.mX + (parentKey.mWidth + parentKey.mGap) / 2 - keyWidth / 2,
+                keyWidth, parentKeyboard.mDefaultRowHeight,
+                parentKey.mX + (mParams.mDefaultKeyWidth - keyWidth) / 2,
                 view.getMeasuredWidth());
-        params.setTopPadding(keyboard.getVerticalGap());
-        mParams = params;
-
-        keyboard.setRowHeight(params.mRowHeight);
-        keyboard.setKeyboardHeight(params.getKeyboardHeight());
-        keyboard.setMinWidth(params.getKeyboardWidth());
-        keyboard.setDefaultCoordX(params.getDefaultKeyCoordX() + params.mKeyWidth / 2);
     }
 
     private static int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters,
@@ -249,17 +244,16 @@
         return Math.max(minKeyWidth, maxWidth + horizontalPadding);
     }
 
+    @Override
     public MiniKeyboard build() {
-        final MiniKeyboard keyboard = mKeyboard;
-        final List<Key> keys = keyboard.getKeys();
         final MiniKeyboardLayoutParams params = mParams;
         for (int n = 0; n < mPopupCharacters.length; n++) {
             final CharSequence label = mPopupCharacters[n];
             final int row = n / params.mNumColumns;
-            final Key key = new Key(mRes, keyboard, label, params.getX(n, row), params.getY(row),
-                    params.mKeyWidth, params.mRowHeight, params.getRowFlags(row));
-            keys.add(key);
+            final Key key = new Key(mResources, params, label, params.getX(n, row), params.getY(row),
+                    params.mDefaultKeyWidth, params.mDefaultRowHeight, params.getRowFlags(row));
+            params.onAddKey(key);
         }
-        return keyboard;
+        return new MiniKeyboard(params);
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/Row.java b/java/src/com/android/inputmethod/keyboard/internal/Row.java
index b34d6d0..9299cc2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/Row.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/Row.java
@@ -31,34 +31,19 @@
  */
 public class Row {
     /** Default width of a key in this row. */
-    public final int mDefaultWidth;
+    public final int mDefaultKeyWidth;
     /** Default height of a key in this row. */
-    public final int mDefaultHeight;
-    /** Default horizontal gap between keys in this row. */
-    public final int mDefaultHorizontalGap;
-    /** Vertical gap following this row. */
-    public final int mVerticalGap;
+    public final int mRowHeight;
 
-    private final Keyboard mKeyboard;
-
-    public Row(Resources res, Keyboard keyboard, XmlResourceParser parser) {
-        this.mKeyboard = keyboard;
-        final int keyboardWidth = keyboard.getDisplayWidth();
-        final int keyboardHeight = keyboard.getKeyboardHeight();
+    public Row(Resources res, KeyboardParams params, XmlResourceParser parser) {
+        final int keyboardWidth = params.mWidth;
+        final int keyboardHeight = params.mHeight;
         TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
                 R.styleable.Keyboard);
-        mDefaultWidth = KeyboardParser.getDimensionOrFraction(a,
-                R.styleable.Keyboard_keyWidth, keyboardWidth, keyboard.getKeyWidth());
-        mDefaultHeight = KeyboardParser.getDimensionOrFraction(a,
-                R.styleable.Keyboard_rowHeight, keyboardHeight, keyboard.getRowHeight());
-        mDefaultHorizontalGap = KeyboardParser.getDimensionOrFraction(a,
-                R.styleable.Keyboard_horizontalGap, keyboardWidth, keyboard.getHorizontalGap());
-        mVerticalGap = KeyboardParser.getDimensionOrFraction(a,
-                R.styleable.Keyboard_verticalGap, keyboardHeight, keyboard.getVerticalGap());
+        mDefaultKeyWidth = KeyboardParser.getDimensionOrFraction(a,
+                R.styleable.Keyboard_keyWidth, keyboardWidth, params.mDefaultKeyWidth);
+        mRowHeight = KeyboardParser.getDimensionOrFraction(a,
+                R.styleable.Keyboard_rowHeight, keyboardHeight, params.mDefaultRowHeight);
         a.recycle();
     }
-
-    public Keyboard getKeyboard() {
-        return mKeyboard;
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index bdcbfbc..b2af6f9 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1685,7 +1685,7 @@
             final int rawPrimaryCode = suggestion.charAt(0);
             // Maybe apply the "bidi mirrored" conversions for parentheses
             final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
-            final int primaryCode = keyboard.isRtlKeyboard()
+            final int primaryCode = keyboard.mIsRtlKeyboard
                     ? Key.getRtlParenthesisCode(rawPrimaryCode) : rawPrimaryCode;
 
             final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : "";
diff --git a/native/src/bigram_dictionary.cpp b/native/src/bigram_dictionary.cpp
index 6ed4d09..c340c6c 100644
--- a/native/src/bigram_dictionary.cpp
+++ b/native/src/bigram_dictionary.cpp
@@ -21,13 +21,14 @@
 
 #include "bigram_dictionary.h"
 #include "dictionary.h"
+#include "binary_format.h"
 
 namespace latinime {
 
 BigramDictionary::BigramDictionary(const unsigned char *dict, int maxWordLength,
         int maxAlternatives, const bool isLatestDictVersion, const bool hasBigram,
         Dictionary *parentDictionary)
-    : DICT(dict), MAX_WORD_LENGTH(maxWordLength),
+    : DICT(dict + NEW_DICTIONARY_HEADER_SIZE), MAX_WORD_LENGTH(maxWordLength),
     MAX_ALTERNATIVES(maxAlternatives), IS_LATEST_DICT_VERSION(isLatestDictVersion),
     HAS_BIGRAM(hasBigram), mParentDictionary(parentDictionary) {
     if (DEBUG_DICT) {
@@ -82,169 +83,64 @@
     return false;
 }
 
-int BigramDictionary::getBigramAddress(int *pos, bool advance) {
-    int address = 0;
-
-    address += (DICT[*pos] & 0x3F) << 16;
-    address += (DICT[*pos + 1] & 0xFF) << 8;
-    address += (DICT[*pos + 2] & 0xFF);
-
-    if (advance) {
-        *pos += 3;
-    }
-
-    return address;
-}
-
-int BigramDictionary::getBigramFreq(int *pos) {
-    int freq = DICT[(*pos)++] & FLAG_BIGRAM_FREQ;
-
-    return freq;
-}
-
-
+/* Parameters :
+ * prevWord: the word before, the one for which we need to look up bigrams.
+ * prevWordLength: its length.
+ * codes: what user typed, in the same format as for UnigramDictionary::getSuggestions.
+ * codesSize: the size of the codes array.
+ * bigramChars: an array for output, at the same format as outwords for getSuggestions.
+ * bigramFreq: an array to output frequencies.
+ * maxWordLength: the maximum size of a word.
+ * maxBigrams: the maximum number of bigrams fitting in the bigramChars array.
+ * maxAlteratives: unused.
+ * This method returns the number of bigrams this word has, for backward compatibility.
+ * Note: this is not the number of bigrams output in the array, which is the number of
+ * bigrams this word has WHOSE first letter also matches the letter the user typed.
+ * TODO: this may not be a sensible thing to do. It makes sense when the bigrams are
+ * used to match the first letter of the second word, but once the user has typed more
+ * and the bigrams are used to boost unigram result scores, it makes little sense to
+ * reduce their scope to the ones that match the first letter.
+ */
 int BigramDictionary::getBigrams(unsigned short *prevWord, int prevWordLength, int *codes,
         int codesSize, unsigned short *bigramChars, int *bigramFreq, int maxWordLength,
         int maxBigrams, int maxAlternatives) {
+    // TODO: remove unused arguments, and refrain from storing stuff in members of this class
+    // TODO: have "in" arguments before "out" ones, and make out args explicit in the name
     mBigramFreq = bigramFreq;
     mBigramChars = bigramChars;
     mInputCodes = codes;
-    mInputLength = codesSize;
     mMaxBigrams = maxBigrams;
 
-    if (HAS_BIGRAM && IS_LATEST_DICT_VERSION) {
-        int pos = mParentDictionary->getBigramPosition(prevWord, prevWordLength);
-        if (DEBUG_DICT) {
-            LOGI("Pos -> %d", pos);
-        }
-        if (pos < 0) {
-            return 0;
-        }
+    const uint8_t* const root = DICT;
+    int pos = BinaryFormat::getTerminalPosition(root, prevWord, prevWordLength);
 
-        int bigramCount = 0;
-        int bigramExist = (DICT[pos] & FLAG_BIGRAM_READ);
-        if (bigramExist > 0) {
-            int nextBigramExist = 1;
-            while (nextBigramExist > 0 && bigramCount < maxBigrams) {
-                int bigramAddress = getBigramAddress(&pos, true);
-                int frequency = (FLAG_BIGRAM_FREQ & DICT[pos]);
-                // search for all bigrams and store them
-                searchForTerminalNode(bigramAddress, frequency);
-                nextBigramExist = (DICT[pos++] & FLAG_BIGRAM_CONTINUED);
-                bigramCount++;
-            }
-        }
-
-        return bigramCount;
+    if (NOT_VALID_WORD == pos) return 0;
+    const int flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
+    if (0 == (flags & UnigramDictionary::FLAG_HAS_BIGRAMS)) return 0;
+    if (0 == (flags & UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS)) {
+        BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
+    } else {
+        pos = BinaryFormat::skipOtherCharacters(root, pos);
     }
-    return 0;
-}
+    pos = BinaryFormat::skipChildrenPosition(flags, pos);
+    pos = BinaryFormat::skipFrequency(flags, pos);
+    int bigramFlags;
+    int bigramCount = 0;
+    do {
+        bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
+        uint16_t bigramBuffer[MAX_WORD_LENGTH];
+        const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags,
+                &pos);
+        const int length = BinaryFormat::getWordAtAddress(root, bigramPos, MAX_WORD_LENGTH,
+                bigramBuffer);
 
-void BigramDictionary::searchForTerminalNode(int addressLookingFor, int frequency) {
-    // track word with such address and store it in an array
-    unsigned short word[MAX_WORD_LENGTH];
-
-    int pos;
-    int followDownBranchAddress = DICTIONARY_HEADER_SIZE;
-    bool found = false;
-    char followingChar = ' ';
-    int depth = -1;
-
-    while(!found) {
-        bool followDownAddressSearchStop = false;
-        bool firstAddress = true;
-        bool haveToSearchAll = true;
-
-        if (depth < MAX_WORD_LENGTH && depth >= 0) {
-            word[depth] = (unsigned short) followingChar;
+        if (checkFirstCharacter(bigramBuffer)) {
+            const int frequency = UnigramDictionary::MASK_ATTRIBUTE_FREQUENCY & bigramFlags;
+            addWordBigram(bigramBuffer, length, frequency);
         }
-        pos = followDownBranchAddress; // pos start at count
-        int count = DICT[pos] & 0xFF;
-        if (DEBUG_DICT) {
-            LOGI("count - %d",count);
-        }
-        pos++;
-        for (int i = 0; i < count; i++) {
-            // pos at data
-            pos++;
-            // pos now at flag
-            if (!getFirstBitOfByte(&pos)) { // non-terminal
-                if (!followDownAddressSearchStop) {
-                    int addr = getBigramAddress(&pos, false);
-                    if (addr > addressLookingFor) {
-                        followDownAddressSearchStop = true;
-                        if (firstAddress) {
-                            firstAddress = false;
-                            haveToSearchAll = true;
-                        } else if (!haveToSearchAll) {
-                            break;
-                        }
-                    } else {
-                        followDownBranchAddress = addr;
-                        followingChar = (char)(0xFF & DICT[pos-1]);
-                        if (firstAddress) {
-                            firstAddress = false;
-                            haveToSearchAll = false;
-                        }
-                    }
-                }
-                pos += 3;
-            } else if (getFirstBitOfByte(&pos)) { // terminal
-                if (addressLookingFor == (pos-1)) { // found !!
-                    depth++;
-                    word[depth] = (0xFF & DICT[pos-1]);
-                    found = true;
-                    break;
-                }
-                if (getSecondBitOfByte(&pos)) { // address + freq (4 byte)
-                    if (!followDownAddressSearchStop) {
-                        int addr = getBigramAddress(&pos, false);
-                        if (addr > addressLookingFor) {
-                            followDownAddressSearchStop = true;
-                            if (firstAddress) {
-                                firstAddress = false;
-                                haveToSearchAll = true;
-                            } else if (!haveToSearchAll) {
-                                break;
-                            }
-                        } else {
-                            followDownBranchAddress = addr;
-                            followingChar = (char)(0xFF & DICT[pos-1]);
-                            if (firstAddress) {
-                                firstAddress = false;
-                                haveToSearchAll = true;
-                            }
-                        }
-                    }
-                    pos += 4;
-                } else { // freq only (2 byte)
-                    pos += 2;
-                }
-
-                // skipping bigram
-                int bigramExist = (DICT[pos] & FLAG_BIGRAM_READ);
-                if (bigramExist > 0) {
-                    int nextBigramExist = 1;
-                    while (nextBigramExist > 0) {
-                        pos += 3;
-                        nextBigramExist = (DICT[pos++] & FLAG_BIGRAM_CONTINUED);
-                    }
-                } else {
-                    pos++;
-                }
-            }
-        }
-        depth++;
-        if (followDownBranchAddress == 0) {
-            if (DEBUG_DICT) {
-                LOGI("ERROR!!! Cannot find bigram!!");
-            }
-            break;
-        }
-    }
-    if (checkFirstCharacter(word)) {
-        addWordBigram(word, depth, frequency);
-    }
+        ++bigramCount;
+    } while (0 != (UnigramDictionary::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags));
+    return bigramCount;
 }
 
 bool BigramDictionary::checkFirstCharacter(unsigned short *word) {
diff --git a/native/src/binary_format.h b/native/src/binary_format.h
index a946b1e..6f65088 100644
--- a/native/src/binary_format.h
+++ b/native/src/binary_format.h
@@ -50,6 +50,8 @@
             int *pos);
     static int getTerminalPosition(const uint8_t* const root, const uint16_t* const inWord,
             const int length);
+    static int getWordAtAddress(const uint8_t* const root, const int address, const int maxDepth,
+            uint16_t* outWord);
 };
 
 inline int BinaryFormat::detectFormat(const uint8_t* const dict) {
@@ -290,6 +292,151 @@
     }
 }
 
+// This function searches for a terminal in the dictionary by its address.
+// Due to the fact that words are ordered in the dictionary in a strict breadth-first order,
+// it is possible to check for this with advantageous complexity. For each node, we search
+// for groups with children and compare the children address with the address we look for.
+// When we shoot the address we look for, it means the word we look for is in the children
+// of the previous group. The only tricky part is the fact that if we arrive at the end of a
+// node with the last group's children address still less than what we are searching for, we
+// must descend the last group's children (for example, if the word we are searching for starts
+// with a z, it's the last group of the root node, so all children addresses will be smaller
+// than the address we look for, and we have to descend the z node).
+/* Parameters :
+ * root: the dictionary buffer
+ * address: the byte position of the last chargroup of the word we are searching for (this is
+ *   what is stored as the "bigram address" in each bigram)
+ * outword: an array to write the found word, with MAX_WORD_LENGTH size.
+ * Return value : the length of the word, of 0 if the word was not found.
+ */
+inline int BinaryFormat::getWordAtAddress(const uint8_t* const root, const int address,
+        const int maxDepth, uint16_t* outWord) {
+    int pos = 0;
+    int wordPos = 0;
+
+    // One iteration of the outer loop iterates through nodes. As stated above, we will only
+    // traverse nodes that are actually a part of the terminal we are searching, so each time
+    // we enter this loop we are one depth level further than last time.
+    // The only reason we count nodes is because we want to reduce the probability of infinite
+    // looping in case there is a bug. Since we know there is an upper bound to the depth we are
+    // supposed to traverse, it does not hurt to count iterations.
+    for (int loopCount = maxDepth; loopCount > 0; --loopCount) {
+        int lastCandidateGroupPos = 0;
+        // Let's loop through char groups in this node searching for either the terminal
+        // or one of its ascendants.
+        for (int charGroupCount = getGroupCountAndForwardPointer(root, &pos); charGroupCount > 0;
+                 --charGroupCount) {
+            const int startPos = pos;
+            const uint8_t flags = getFlagsAndForwardPointer(root, &pos);
+            const int32_t character = getCharCodeAndForwardPointer(root, &pos);
+            if (address == startPos) {
+                // We found the address. Copy the rest of the word in the buffer and return
+                // the length.
+                outWord[wordPos] = character;
+                if (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & flags) {
+                    int32_t nextChar = getCharCodeAndForwardPointer(root, &pos);
+                    // We count chars in order to avoid infinite loops if the file is broken or
+                    // if there is some other bug
+                    int charCount = maxDepth;
+                    while (-1 != nextChar && --charCount > 0) {
+                        outWord[++wordPos] = nextChar;
+                        nextChar = getCharCodeAndForwardPointer(root, &pos);
+                    }
+                }
+                return ++wordPos;
+            }
+            // We need to skip past this char group, so skip any remaining chars after the
+            // first and possibly the frequency.
+            if (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & flags) {
+                pos = skipOtherCharacters(root, pos);
+            }
+            pos = skipFrequency(flags, pos);
+
+            // The fact that this group has children is very important. Since we already know
+            // that this group does not match, if it has no children we know it is irrelevant
+            // to what we are searching for.
+            const bool hasChildren = (UnigramDictionary::FLAG_GROUP_ADDRESS_TYPE_NOADDRESS !=
+                    (UnigramDictionary::MASK_GROUP_ADDRESS_TYPE & flags));
+            // We will write in `found' whether we have passed the children address we are
+            // searching for. For example if we search for "beer", the children of b are less
+            // than the address we are searching for and the children of c are greater. When we
+            // come here for c, we realize this is too big, and that we should descend b.
+            bool found;
+            if (hasChildren) {
+                // Here comes the tricky part. First, read the children position.
+                const int childrenPos = readChildrenPosition(root, flags, pos);
+                if (childrenPos > address) {
+                    // If the children pos is greater than address, it means the previous chargroup,
+                    // which address is stored in lastCandidateGroupPos, was the right one.
+                    found = true;
+                } else if (1 >= charGroupCount) {
+                    // However if we are on the LAST group of this node, and we have NOT shot the
+                    // address we should descend THIS node. So we trick the lastCandidateGroupPos
+                    // so that we will descend this node, not the previous one.
+                    lastCandidateGroupPos = startPos;
+                    found = true;
+                } else {
+                    // Else, we should continue looking.
+                    found = false;
+                }
+            } else {
+                // Even if we don't have children here, we could still be on the last group of this
+                // node. If this is the case, we should descend the last group that had children,
+                // and their address is already in lastCandidateGroup.
+                found = (1 >= charGroupCount);
+            }
+
+            if (found) {
+                // Okay, we found the group we should descend. Its address is in
+                // the lastCandidateGroupPos variable, so we just re-read it.
+                if (0 != lastCandidateGroupPos) {
+                    const uint8_t lastFlags =
+                            getFlagsAndForwardPointer(root, &lastCandidateGroupPos);
+                    const int32_t lastChar =
+                            getCharCodeAndForwardPointer(root, &lastCandidateGroupPos);
+                    // We copy all the characters in this group to the buffer
+                    outWord[wordPos] = lastChar;
+                    if (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & lastFlags) {
+                        int32_t nextChar =
+                                getCharCodeAndForwardPointer(root, &lastCandidateGroupPos);
+                        int charCount = maxDepth;
+                        while (-1 != nextChar && --charCount > 0) {
+                            outWord[++wordPos] = nextChar;
+                            nextChar = getCharCodeAndForwardPointer(root, &lastCandidateGroupPos);
+                        }
+                    }
+                    ++wordPos;
+                    // Now we only need to branch to the children address. Skip the frequency if
+                    // it's there, read pos, and break to resume the search at pos.
+                    lastCandidateGroupPos = skipFrequency(lastFlags, lastCandidateGroupPos);
+                    pos = readChildrenPosition(root, lastFlags, lastCandidateGroupPos);
+                    break;
+                } else {
+                    // Here is a little tricky part: we come here if we found out that all children
+                    // addresses in this group are bigger than the address we are searching for.
+                    // Should we conclude the word is not in the dictionary? No! It could still be
+                    // one of the remaining chargroups in this node, so we have to keep looking in
+                    // this node until we find it (or we realize it's not there either, in which
+                    // case it's actually not in the dictionary). Pass the end of this group, ready
+                    // to start the next one.
+                    pos = skipChildrenPosAndAttributes(root, flags, pos);
+                }
+            } else {
+                // If we did not find it, we should record the last children address for the next
+                // iteration.
+                if (hasChildren) lastCandidateGroupPos = startPos;
+                // Now skip the end of this group (children pos and the attributes if any) so that
+                // our pos is after the end of this char group, at the start of the next one.
+                pos = skipChildrenPosAndAttributes(root, flags, pos);
+            }
+
+        }
+    }
+    // If we have looked through all the chargroups and found no match, the address is
+    // not the address of a terminal in this dictionary.
+    return 0;
+}
+
 } // namespace latinime
 
 #endif // LATINIME_BINARY_FORMAT_H
diff --git a/native/src/correction_state.cpp b/native/src/correction_state.cpp
index aa5efce..fba947e 100644
--- a/native/src/correction_state.cpp
+++ b/native/src/correction_state.cpp
@@ -21,18 +21,26 @@
 #define LOG_TAG "LatinIME: correction_state.cpp"
 
 #include "correction_state.h"
+#include "proximity_info.h"
 
 namespace latinime {
 
-CorrectionState::CorrectionState() {
+CorrectionState::CorrectionState(const int typedLetterMultiplier, const int fullWordMultiplier)
+        : TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier) {
 }
 
-void CorrectionState::setCorrectionParams(const ProximityInfo *pi, const int inputLength,
-        const int skipPos, const int excessivePos, const int transposedPos) {
+void CorrectionState::initCorrectionState(const ProximityInfo *pi, const int inputLength) {
     mProximityInfo = pi;
+    mInputLength = inputLength;
+}
+
+void CorrectionState::setCorrectionParams(const int skipPos, const int excessivePos,
+        const int transposedPos, const int spaceProximityPos, const int missingSpacePos) {
     mSkipPos = skipPos;
     mExcessivePos = excessivePos;
     mTransposedPos = transposedPos;
+    mSpaceProximityPos = spaceProximityPos;
+    mMissingSpacePos = missingSpacePos;
 }
 
 void CorrectionState::checkState() {
@@ -46,7 +54,203 @@
     }
 }
 
+int CorrectionState::getFreqForSplitTwoWords(const int firstFreq, const int secondFreq) {
+    return CorrectionState::RankingAlgorithm::calcFreqForSplitTwoWords(firstFreq, secondFreq, this);
+}
+
+int CorrectionState::getFinalFreq(const int inputIndex, const int depth, const int matchWeight,
+        const int freq, const bool sameLength) {
+    return CorrectionState::RankingAlgorithm::calculateFinalFreq(inputIndex, depth, matchWeight,
+            freq, sameLength, this);
+}
+
 CorrectionState::~CorrectionState() {
 }
 
+/////////////////////////
+// static inline utils //
+/////////////////////////
+
+static const int TWO_31ST_DIV_255 = S_INT_MAX / 255;
+static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
+    return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
+}
+
+static const int TWO_31ST_DIV_2 = S_INT_MAX / 2;
+inline static void multiplyIntCapped(const int multiplier, int *base) {
+    const int temp = *base;
+    if (temp != S_INT_MAX) {
+        // Branch if multiplier == 2 for the optimization
+        if (multiplier == 2) {
+            *base = TWO_31ST_DIV_2 >= temp ? temp << 1 : S_INT_MAX;
+        } else {
+            const int tempRetval = temp * multiplier;
+            *base = tempRetval >= temp ? tempRetval : S_INT_MAX;
+        }
+    }
+}
+
+inline static int powerIntCapped(const int base, const int n) {
+    if (n == 0) return 1;
+    if (base == 2) {
+        return n < 31 ? 1 << n : S_INT_MAX;
+    } else {
+        int ret = base;
+        for (int i = 1; i < n; ++i) multiplyIntCapped(base, &ret);
+        return ret;
+    }
+}
+
+inline static void multiplyRate(const int rate, int *freq) {
+    if (*freq != S_INT_MAX) {
+        if (*freq > 1000000) {
+            *freq /= 100;
+            multiplyIntCapped(rate, freq);
+        } else {
+            multiplyIntCapped(rate, freq);
+            *freq /= 100;
+        }
+    }
+}
+
+//////////////////////
+// RankingAlgorithm //
+//////////////////////
+
+int CorrectionState::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const int depth,
+        const int matchCount, const int freq, const bool sameLength,
+        const CorrectionState* correctionState) {
+    const int skipPos = correctionState->getSkipPos();
+    const int excessivePos = correctionState->getExcessivePos();
+    const int transposedPos = correctionState->getTransposedPos();
+    const int inputLength = correctionState->mInputLength;
+    const int typedLetterMultiplier = correctionState->TYPED_LETTER_MULTIPLIER;
+    const int fullWordMultiplier = correctionState->FULL_WORD_MULTIPLIER;
+    const ProximityInfo *proximityInfo = correctionState->mProximityInfo;
+    const int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount);
+
+    // TODO: Demote by edit distance
+    int finalFreq = freq * matchWeight;
+    if (skipPos >= 0) {
+        if (inputLength >= 2) {
+            const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE
+                    * (10 * inputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
+                    / (10 * inputLength
+                            - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10);
+            if (DEBUG_DICT_FULL) {
+                LOGI("Demotion rate for missing character is %d.", demotionRate);
+            }
+            multiplyRate(demotionRate, &finalFreq);
+        } else {
+            finalFreq = 0;
+        }
+    }
+    if (transposedPos >= 0) multiplyRate(
+            WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq);
+    if (excessivePos >= 0) {
+        multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq);
+        if (!proximityInfo->existsAdjacentProximityChars(inputIndex)) {
+            // If an excessive character is not adjacent to the left char or the right char,
+            // we will demote this word.
+            multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE, &finalFreq);
+        }
+    }
+    int lengthFreq = typedLetterMultiplier;
+    multiplyIntCapped(powerIntCapped(typedLetterMultiplier, depth), &lengthFreq);
+    if (lengthFreq == matchWeight) {
+        // Full exact match
+        if (depth > 1) {
+            if (DEBUG_DICT) {
+                LOGI("Found full matched word.");
+            }
+            multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq);
+        }
+        if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0) {
+            finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq);
+        }
+    } else if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0 && depth > 0) {
+        // A word with proximity corrections
+        if (DEBUG_DICT) {
+            LOGI("Found one proximity correction.");
+        }
+        multiplyIntCapped(typedLetterMultiplier, &finalFreq);
+        multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
+    }
+    if (DEBUG_DICT) {
+        LOGI("calc: %d, %d", depth, sameLength);
+    }
+    if (sameLength) multiplyIntCapped(fullWordMultiplier, &finalFreq);
+    return finalFreq;
+}
+
+int CorrectionState::RankingAlgorithm::calcFreqForSplitTwoWords(
+        const int firstFreq, const int secondFreq, const CorrectionState* correctionState) {
+    const int spaceProximityPos = correctionState->mSpaceProximityPos;
+    const int missingSpacePos = correctionState->mMissingSpacePos;
+    if (DEBUG_DICT) {
+        int inputCount = 0;
+        if (spaceProximityPos >= 0) ++inputCount;
+        if (missingSpacePos >= 0) ++inputCount;
+        assert(inputCount <= 1);
+    }
+    const bool isSpaceProximity = spaceProximityPos >= 0;
+    const int inputLength = correctionState->mInputLength;
+    const int firstWordLength = isSpaceProximity ? spaceProximityPos : missingSpacePos;
+    const int secondWordLength = isSpaceProximity
+            ? (inputLength - spaceProximityPos - 1)
+            : (inputLength - missingSpacePos);
+    const int typedLetterMultiplier = correctionState->TYPED_LETTER_MULTIPLIER;
+
+    if (firstWordLength == 0 || secondWordLength == 0) {
+        return 0;
+    }
+    const int firstDemotionRate = 100 - 100 / (firstWordLength + 1);
+    int tempFirstFreq = firstFreq;
+    multiplyRate(firstDemotionRate, &tempFirstFreq);
+
+    const int secondDemotionRate = 100 - 100 / (secondWordLength + 1);
+    int tempSecondFreq = secondFreq;
+    multiplyRate(secondDemotionRate, &tempSecondFreq);
+
+    const int totalLength = firstWordLength + secondWordLength;
+
+    // Promote pairFreq with multiplying by 2, because the word length is the same as the typed
+    // length.
+    int totalFreq = tempFirstFreq + tempSecondFreq;
+
+    // This is a workaround to try offsetting the not-enough-demotion which will be done in
+    // calcNormalizedScore in Utils.java.
+    // In calcNormalizedScore the score will be demoted by (1 - 1 / length)
+    // but we demoted only (1 - 1 / (length + 1)) so we will additionally adjust freq by
+    // (1 - 1 / length) / (1 - 1 / (length + 1)) = (1 - 1 / (length * length))
+    const int normalizedScoreNotEnoughDemotionAdjustment = 100 - 100 / (totalLength * totalLength);
+    multiplyRate(normalizedScoreNotEnoughDemotionAdjustment, &totalFreq);
+
+    // At this moment, totalFreq is calculated by the following formula:
+    // (firstFreq * (1 - 1 / (firstWordLength + 1)) + secondFreq * (1 - 1 / (secondWordLength + 1)))
+    //        * (1 - 1 / totalLength) / (1 - 1 / (totalLength + 1))
+
+    multiplyIntCapped(powerIntCapped(typedLetterMultiplier, totalLength), &totalFreq);
+
+    // This is another workaround to offset the demotion which will be done in
+    // calcNormalizedScore in Utils.java.
+    // In calcNormalizedScore the score will be demoted by (1 - 1 / length) so we have to promote
+    // the same amount because we already have adjusted the synthetic freq of this "missing or
+    // mistyped space" suggestion candidate above in this method.
+    const int normalizedScoreDemotionRateOffset = (100 + 100 / totalLength);
+    multiplyRate(normalizedScoreDemotionRateOffset, &totalFreq);
+
+    if (isSpaceProximity) {
+        // A word pair with one space proximity correction
+        if (DEBUG_DICT) {
+            LOGI("Found a word pair with space proximity correction.");
+        }
+        multiplyIntCapped(typedLetterMultiplier, &totalFreq);
+        multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &totalFreq);
+    }
+
+    multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq);
+    return totalFreq;
+}
+
 } // namespace latinime
diff --git a/native/src/correction_state.h b/native/src/correction_state.h
index 5b73925..e03b2a1 100644
--- a/native/src/correction_state.h
+++ b/native/src/correction_state.h
@@ -26,10 +26,12 @@
 class ProximityInfo;
 
 class CorrectionState {
+
 public:
-    CorrectionState();
-    void setCorrectionParams(const ProximityInfo *pi, const int inputLength, const int skipPos,
-        const int excessivePos, const int transposedPos);
+    CorrectionState(const int typedLetterMultiplier, const int fullWordMultiplier);
+    void initCorrectionState(const ProximityInfo *pi, const int inputLength);
+    void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
+            const int spaceProximityPos, const int missingSpacePos);
     void checkState();
     virtual ~CorrectionState();
     int getSkipPos() const {
@@ -41,12 +43,36 @@
     int getTransposedPos() const {
         return mTransposedPos;
     }
+    int getSpaceProximityPos() const {
+        return mSpaceProximityPos;
+    }
+    int getMissingSpacePos() const {
+        return mMissingSpacePos;
+    }
+    int getFreqForSplitTwoWords(const int firstFreq, const int secondFreq);
+    int getFinalFreq(const int inputIndex, const int depth, const int matchWeight, const int freq,
+            const bool sameLength);
+
 private:
+
+    const int TYPED_LETTER_MULTIPLIER;
+    const int FULL_WORD_MULTIPLIER;
     const ProximityInfo *mProximityInfo;
     int mInputLength;
     int mSkipPos;
     int mExcessivePos;
     int mTransposedPos;
+    int mSpaceProximityPos;
+    int mMissingSpacePos;
+
+    class RankingAlgorithm {
+    public:
+        static int calculateFinalFreq(const int inputIndex, const int depth,
+                const int matchCount, const int freq, const bool sameLength,
+                const CorrectionState* correctionState);
+        static int calcFreqForSplitTwoWords(const int firstFreq, const int secondFreq,
+                const CorrectionState* correctionState);
+    };
 };
 } // namespace latinime
 #endif // LATINIME_CORRECTION_INFO_H
diff --git a/native/src/dictionary.cpp b/native/src/dictionary.cpp
index 9e32ee8..a49769b 100644
--- a/native/src/dictionary.cpp
+++ b/native/src/dictionary.cpp
@@ -57,12 +57,4 @@
     return mUnigramDictionary->isValidWord(word, length);
 }
 
-int Dictionary::getBigramPosition(unsigned short *word, int length) {
-    if (IS_LATEST_DICT_VERSION) {
-        return mUnigramDictionary->getBigramPosition(DICTIONARY_HEADER_SIZE, word, 0, length);
-    } else {
-        return mUnigramDictionary->getBigramPosition(0, word, 0, length);
-    }
-}
-
 } // namespace latinime
diff --git a/native/src/dictionary.h b/native/src/dictionary.h
index 73e03d8..d5de008 100644
--- a/native/src/dictionary.h
+++ b/native/src/dictionary.h
@@ -64,8 +64,6 @@
             const int pos, unsigned short *c, int *childrenPosition,
             bool *terminal, int *freq);
     static inline unsigned short toBaseLowerCase(unsigned short c);
-    // TODO: delete this
-    int getBigramPosition(unsigned short *word, int length);
 
 private:
     bool hasBigram();
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index f0bb384..eb28538 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -48,7 +48,7 @@
     if (DEBUG_DICT) {
         LOGI("UnigramDictionary - constructor");
     }
-    mCorrectionState = new CorrectionState();
+    mCorrectionState = new CorrectionState(typedLetterMultiplier, fullWordMultiplier);
 }
 
 UnigramDictionary::~UnigramDictionary() {
@@ -187,6 +187,7 @@
     PROF_START(0);
     initSuggestions(
             proximityInfo, xcoordinates, ycoordinates, codes, codesSize, outWords, frequencies);
+    mCorrectionState->initCorrectionState(mProximityInfo, mInputLength);
     if (DEBUG_DICT) assert(codesSize == mInputLength);
 
     const int MAX_DEPTH = min(mInputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
@@ -242,7 +243,7 @@
             if (DEBUG_DICT) {
                 LOGI("--- Suggest missing space characters %d", i);
             }
-            getMissingSpaceWords(mInputLength, i);
+            getMissingSpaceWords(mInputLength, i, mCorrectionState);
         }
     }
     PROF_END(5);
@@ -261,7 +262,7 @@
                         i, x, y, proximityInfo->hasSpaceProximity(x, y));
             }
             if (proximityInfo->hasSpaceProximity(x, y)) {
-                getMistypedSpaceWords(mInputLength, i);
+                getMistypedSpaceWords(mInputLength, i, mCorrectionState);
             }
         }
     }
@@ -355,8 +356,8 @@
         assert(excessivePos < mInputLength);
         assert(missingPos < mInputLength);
     }
-    mCorrectionState->setCorrectionParams(mProximityInfo, mInputLength, skipPos, excessivePos,
-            transposedPos);
+    mCorrectionState->setCorrectionParams(skipPos, excessivePos, transposedPos,
+            -1 /* spaceProximityPos */, -1 /* missingSpacePos */);
     int rootPosition = ROOT_POS;
     // Get the number of children of root, then increment the position
     int childCount = Dictionary::getCount(DICT_ROOT, &rootPosition);
@@ -364,7 +365,7 @@
 
     mStackChildCount[0] = childCount;
     mStackTraverseAll[0] = (mInputLength <= 0);
-    mStackNodeFreq[0] = 1;
+    mStackMatchCount[0] = 0;
     mStackInputIndex[0] = 0;
     mStackDiffs[0] = 0;
     mStackSiblingPos[0] = rootPosition;
@@ -375,7 +376,7 @@
         if (mStackChildCount[depth] > 0) {
             --mStackChildCount[depth];
             bool traverseAllNodes = mStackTraverseAll[depth];
-            int matchWeight = mStackNodeFreq[depth];
+            int matchCount = mStackMatchCount[depth];
             int inputIndex = mStackInputIndex[depth];
             int diffs = mStackDiffs[depth];
             int siblingPos = mStackSiblingPos[depth];
@@ -384,9 +385,9 @@
             // depth will never be greater than maxDepth because in that case,
             // needsToTraverseChildrenNodes should be false
             const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos, outputIndex,
-                    maxDepth, traverseAllNodes, matchWeight, inputIndex, diffs,
+                    maxDepth, traverseAllNodes, matchCount, inputIndex, diffs,
                     nextLetters, nextLettersSize, mCorrectionState, &childCount,
-                    &firstChildPos, &traverseAllNodes, &matchWeight, &inputIndex, &diffs,
+                    &firstChildPos, &traverseAllNodes, &matchCount, &inputIndex, &diffs,
                     &siblingPos, &outputIndex);
             // Update next sibling pos
             mStackSiblingPos[depth] = siblingPos;
@@ -395,7 +396,7 @@
                 ++depth;
                 mStackChildCount[depth] = childCount;
                 mStackTraverseAll[depth] = traverseAllNodes;
-                mStackNodeFreq[depth] = matchWeight;
+                mStackMatchCount[depth] = matchCount;
                 mStackInputIndex[depth] = inputIndex;
                 mStackDiffs[depth] = diffs;
                 mStackSiblingPos[depth] = firstChildPos;
@@ -408,11 +409,6 @@
     }
 }
 
-static const int TWO_31ST_DIV_255 = S_INT_MAX / 255;
-static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
-    return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
-}
-
 static const int TWO_31ST_DIV_2 = S_INT_MAX / 2;
 inline static void multiplyIntCapped(const int multiplier, int *base) {
     const int temp = *base;
@@ -427,153 +423,18 @@
     }
 }
 
-inline static int powerIntCapped(const int base, const int n) {
-    if (base == 2) {
-        return n < 31 ? 1 << n : S_INT_MAX;
-    } else {
-        int ret = base;
-        for (int i = 1; i < n; ++i) multiplyIntCapped(base, &ret);
-        return ret;
-    }
+void UnigramDictionary::getMissingSpaceWords(
+        const int inputLength, const int missingSpacePos, CorrectionState *correctionState) {
+    correctionState->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
+            -1 /* transposedPos */, -1 /* spaceProximityPos */, missingSpacePos);
+    getSplitTwoWordsSuggestion(inputLength, correctionState);
 }
 
-inline static void multiplyRate(const int rate, int *freq) {
-    if (*freq != S_INT_MAX) {
-        if (*freq > 1000000) {
-            *freq /= 100;
-            multiplyIntCapped(rate, freq);
-        } else {
-            multiplyIntCapped(rate, freq);
-            *freq /= 100;
-        }
-    }
-}
-
-inline static int calcFreqForSplitTwoWords(
-        const int typedLetterMultiplier, const int firstWordLength, const int secondWordLength,
-        const int firstFreq, const int secondFreq, const bool isSpaceProximity) {
-    if (firstWordLength == 0 || secondWordLength == 0) {
-        return 0;
-    }
-    const int firstDemotionRate = 100 - 100 / (firstWordLength + 1);
-    int tempFirstFreq = firstFreq;
-    multiplyRate(firstDemotionRate, &tempFirstFreq);
-
-    const int secondDemotionRate = 100 - 100 / (secondWordLength + 1);
-    int tempSecondFreq = secondFreq;
-    multiplyRate(secondDemotionRate, &tempSecondFreq);
-
-    const int totalLength = firstWordLength + secondWordLength;
-
-    // Promote pairFreq with multiplying by 2, because the word length is the same as the typed
-    // length.
-    int totalFreq = tempFirstFreq + tempSecondFreq;
-
-    // This is a workaround to try offsetting the not-enough-demotion which will be done in
-    // calcNormalizedScore in Utils.java.
-    // In calcNormalizedScore the score will be demoted by (1 - 1 / length)
-    // but we demoted only (1 - 1 / (length + 1)) so we will additionally adjust freq by
-    // (1 - 1 / length) / (1 - 1 / (length + 1)) = (1 - 1 / (length * length))
-    const int normalizedScoreNotEnoughDemotionAdjustment = 100 - 100 / (totalLength * totalLength);
-    multiplyRate(normalizedScoreNotEnoughDemotionAdjustment, &totalFreq);
-
-    // At this moment, totalFreq is calculated by the following formula:
-    // (firstFreq * (1 - 1 / (firstWordLength + 1)) + secondFreq * (1 - 1 / (secondWordLength + 1)))
-    //        * (1 - 1 / totalLength) / (1 - 1 / (totalLength + 1))
-
-    multiplyIntCapped(powerIntCapped(typedLetterMultiplier, totalLength), &totalFreq);
-
-    // This is another workaround to offset the demotion which will be done in
-    // calcNormalizedScore in Utils.java.
-    // In calcNormalizedScore the score will be demoted by (1 - 1 / length) so we have to promote
-    // the same amount because we already have adjusted the synthetic freq of this "missing or
-    // mistyped space" suggestion candidate above in this method.
-    const int normalizedScoreDemotionRateOffset = (100 + 100 / totalLength);
-    multiplyRate(normalizedScoreDemotionRateOffset, &totalFreq);
-
-    if (isSpaceProximity) {
-        // A word pair with one space proximity correction
-        if (DEBUG_DICT) {
-            LOGI("Found a word pair with space proximity correction.");
-        }
-        multiplyIntCapped(typedLetterMultiplier, &totalFreq);
-        multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &totalFreq);
-    }
-
-    multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq);
-    return totalFreq;
-}
-
-bool UnigramDictionary::getMissingSpaceWords(const int inputLength, const int missingSpacePos) {
-    return getSplitTwoWordsSuggestion(
-            inputLength, 0, missingSpacePos, missingSpacePos, inputLength - missingSpacePos, false);
-}
-
-bool UnigramDictionary::getMistypedSpaceWords(const int inputLength, const int spaceProximityPos) {
-    return getSplitTwoWordsSuggestion(
-            inputLength, 0, spaceProximityPos, spaceProximityPos + 1,
-            inputLength - spaceProximityPos - 1, true);
-}
-
-inline int UnigramDictionary::calculateFinalFreq(const int inputIndex, const int depth,
-        const int matchWeight, const int freq, const bool sameLength,
-        CorrectionState *correctionState) const {
-    const int skipPos = correctionState->getSkipPos();
-    const int excessivePos = correctionState->getExcessivePos();
-    const int transposedPos = correctionState->getTransposedPos();
-
-    // TODO: Demote by edit distance
-    int finalFreq = freq * matchWeight;
-    if (skipPos >= 0) {
-        if (mInputLength >= 2) {
-            const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE
-                    * (10 * mInputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
-                    / (10 * mInputLength
-                            - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10);
-            if (DEBUG_DICT_FULL) {
-                LOGI("Demotion rate for missing character is %d.", demotionRate);
-            }
-            multiplyRate(demotionRate, &finalFreq);
-        } else {
-            finalFreq = 0;
-        }
-    }
-    if (transposedPos >= 0) multiplyRate(
-            WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq);
-    if (excessivePos >= 0) {
-        multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq);
-        if (!mProximityInfo->existsAdjacentProximityChars(inputIndex)) {
-            // If an excessive character is not adjacent to the left char or the right char,
-            // we will demote this word.
-            multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE, &finalFreq);
-        }
-    }
-    int lengthFreq = TYPED_LETTER_MULTIPLIER;
-    multiplyIntCapped(powerIntCapped(TYPED_LETTER_MULTIPLIER, depth), &lengthFreq);
-    if (lengthFreq == matchWeight) {
-        // Full exact match
-        if (depth > 1) {
-            if (DEBUG_DICT) {
-                LOGI("Found full matched word.");
-            }
-            multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq);
-        }
-        if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0) {
-            finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq);
-        }
-    } else if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0 && depth > 0) {
-        // A word with proximity corrections
-        if (DEBUG_DICT) {
-            LOGI("Found one proximity correction.");
-        }
-        multiplyIntCapped(TYPED_LETTER_MULTIPLIER, &finalFreq);
-        multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
-    }
-    if (DEBUG_DICT) {
-        LOGI("calc: %d, %d", depth, sameLength);
-    }
-    if (sameLength) multiplyIntCapped(FULL_WORD_MULTIPLIER, &finalFreq);
-    return finalFreq;
+void UnigramDictionary::getMistypedSpaceWords(
+        const int inputLength, const int spaceProximityPos, CorrectionState *correctionState) {
+    correctionState->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
+            -1 /* transposedPos */, spaceProximityPos, -1 /* missingSpacePos */);
+    getSplitTwoWordsSuggestion(inputLength, correctionState);
 }
 
 inline bool UnigramDictionary::needsToSkipCurrentNode(const unsigned short c,
@@ -586,7 +447,7 @@
 
 inline void UnigramDictionary::onTerminal(unsigned short int* word, const int depth,
         const uint8_t* const root, const uint8_t flags, const int pos,
-        const int inputIndex, const int matchWeight, const int freq, const bool sameLength,
+        const int inputIndex, const int matchCount, const int freq, const bool sameLength,
         int* nextLetters, const int nextLettersSize, CorrectionState *correctionState) {
     const int skipPos = correctionState->getSkipPos();
 
@@ -594,8 +455,8 @@
     if (isSameAsTyped) return;
 
     if (depth >= MIN_SUGGEST_DEPTH) {
-        const int finalFreq = calculateFinalFreq(inputIndex, depth, matchWeight,
-                freq, sameLength, correctionState);
+        const int finalFreq = correctionState->getFinalFreq(inputIndex, depth, matchCount,
+                freq, sameLength);
         if (!isSameAsTyped)
             addWord(word, depth + 1, finalFreq);
     }
@@ -605,13 +466,29 @@
     }
 }
 
-bool UnigramDictionary::getSplitTwoWordsSuggestion(const int inputLength,
-        const int firstWordStartPos, const int firstWordLength, const int secondWordStartPos,
-        const int secondWordLength, const bool isSpaceProximity) {
-    if (inputLength >= MAX_WORD_LENGTH) return false;
+void UnigramDictionary::getSplitTwoWordsSuggestion(
+        const int inputLength, CorrectionState* correctionState) {
+    const int spaceProximityPos = correctionState->getSpaceProximityPos();
+    const int missingSpacePos = correctionState->getMissingSpacePos();
+    if (DEBUG_DICT) {
+        int inputCount = 0;
+        if (spaceProximityPos >= 0) ++inputCount;
+        if (missingSpacePos >= 0) ++inputCount;
+        assert(inputCount <= 1);
+    }
+    const bool isSpaceProximity = spaceProximityPos >= 0;
+    const int firstWordStartPos = 0;
+    const int secondWordStartPos = isSpaceProximity ? (spaceProximityPos + 1) : missingSpacePos;
+    const int firstWordLength = isSpaceProximity ? spaceProximityPos : missingSpacePos;
+    const int secondWordLength = isSpaceProximity
+            ? (inputLength - spaceProximityPos - 1)
+            : (inputLength - missingSpacePos);
+
+    if (inputLength >= MAX_WORD_LENGTH) return;
     if (0 >= firstWordLength || 0 >= secondWordLength || firstWordStartPos >= secondWordStartPos
             || firstWordStartPos < 0 || secondWordStartPos + secondWordLength > inputLength)
-        return false;
+        return;
+
     const int newWordLength = firstWordLength + secondWordLength + 1;
     // Allocating variable length array on stack
     unsigned short word[newWordLength];
@@ -619,7 +496,7 @@
     if (DEBUG_DICT) {
         LOGI("First freq: %d", firstFreq);
     }
-    if (firstFreq <= 0) return false;
+    if (firstFreq <= 0) return;
 
     for (int i = 0; i < firstWordLength; ++i) {
         word[i] = mWord[i];
@@ -629,21 +506,19 @@
     if (DEBUG_DICT) {
         LOGI("Second  freq:  %d", secondFreq);
     }
-    if (secondFreq <= 0) return false;
+    if (secondFreq <= 0) return;
 
     word[firstWordLength] = SPACE;
     for (int i = (firstWordLength + 1); i < newWordLength; ++i) {
         word[i] = mWord[i - firstWordLength - 1];
     }
 
-    int pairFreq = calcFreqForSplitTwoWords(TYPED_LETTER_MULTIPLIER, firstWordLength,
-            secondWordLength, firstFreq, secondFreq, isSpaceProximity);
+    const int pairFreq = mCorrectionState->getFreqForSplitTwoWords(firstFreq, secondFreq);
     if (DEBUG_DICT) {
-        LOGI("Split two words:  %d, %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength,
-                TYPED_LETTER_MULTIPLIER);
+        LOGI("Split two words:  %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength);
     }
     addWord(word, newWordLength, pairFreq);
-    return true;
+    return;
 }
 
 // Wrapper for getMostFrequentWordLikeInner, which matches it to the previous
@@ -803,7 +678,7 @@
 // the current node in nextSiblingPosition. Thus, the caller must keep count of the nodes at any
 // given level, as output into newCount when traversing this level's parent.
 inline bool UnigramDictionary::processCurrentNode(const int initialPos, const int initialDepth,
-        const int maxDepth, const bool initialTraverseAllNodes, int matchWeight, int inputIndex,
+        const int maxDepth, const bool initialTraverseAllNodes, int matchCount, int inputIndex,
         const int initialDiffs, int *nextLetters, const int nextLettersSize,
         CorrectionState *correctionState, int *newCount, int *newChildrenPosition,
         bool *newTraverseAllNodes, int *newMatchRate, int *newInputIndex, int *newDiffs,
@@ -868,7 +743,7 @@
                 // The frequency should be here, because we come here only if this is actually
                 // a terminal node, and we are on its last char.
                 const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos);
-                onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchWeight,
+                onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchCount,
                         freq, false, nextLetters, nextLettersSize, mCorrectionState);
             }
             if (!hasChildren) {
@@ -913,13 +788,13 @@
             // If inputIndex is greater than mInputLength, that means there is no
             // proximity chars. So, we don't need to check proximity.
             if (ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) {
-                multiplyIntCapped(TYPED_LETTER_MULTIPLIER, &matchWeight);
+                ++matchCount;
             }
             const bool isSameAsUserTypedLength = mInputLength == inputIndex + 1
                     || (excessivePos == mInputLength - 1 && inputIndex == mInputLength - 2);
             if (isSameAsUserTypedLength && isTerminal) {
                 const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos);
-                onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchWeight,
+                onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchCount,
                         freq, true, nextLetters, nextLettersSize, mCorrectionState);
             }
             // This character matched the typed character (enough to traverse the node at least)
@@ -975,7 +850,7 @@
     // All the output values that are purely computation by this function are held in local
     // variables. Output them to the caller.
     *newTraverseAllNodes = traverseAllNodes;
-    *newMatchRate = matchWeight;
+    *newMatchRate = matchCount;
     *newDiffs = diffs;
     *newInputIndex = inputIndex;
     *newOutputIndex = depth;
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index 41e3818..f18ed68 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -74,6 +74,7 @@
     virtual ~UnigramDictionary();
 
 private:
+
     void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
             const int *ycoordinates, const int *codes, const int codesSize,
             unsigned short *outWords, int *frequencies);
@@ -89,13 +90,11 @@
             const int transposedPos, int *nextLetters, const int nextLettersSize,
             const int maxDepth);
     bool addWord(unsigned short *word, int length, int frequency);
-    bool getSplitTwoWordsSuggestion(const int inputLength,
-            const int firstWordStartPos, const int firstWordLength,
-            const int secondWordStartPos, const int secondWordLength, const bool isSpaceProximity);
-    bool getMissingSpaceWords(const int inputLength, const int missingSpacePos);
-    bool getMistypedSpaceWords(const int inputLength, const int spaceProximityPos);
-    int calculateFinalFreq(const int inputIndex, const int depth, const int snr,
-            const int freq, const bool sameLength, CorrectionState *correctionState) const;
+    void getSplitTwoWordsSuggestion(const int inputLength, CorrectionState *correctionState);
+    void getMissingSpaceWords(
+            const int inputLength, const int missingSpacePos, CorrectionState *correctionState);
+    void getMistypedSpaceWords(
+            const int inputLength, const int spaceProximityPos, CorrectionState *correctionState);
     void onTerminal(unsigned short int* word, const int depth,
             const uint8_t* const root, const uint8_t flags, const int pos,
             const int inputIndex, const int matchWeight, const int freq, const bool sameLength,
@@ -145,7 +144,7 @@
 
     int mStackChildCount[MAX_WORD_LENGTH_INTERNAL];
     bool mStackTraverseAll[MAX_WORD_LENGTH_INTERNAL];
-    int mStackNodeFreq[MAX_WORD_LENGTH_INTERNAL];
+    int mStackMatchCount[MAX_WORD_LENGTH_INTERNAL];
     int mStackInputIndex[MAX_WORD_LENGTH_INTERNAL];
     int mStackDiffs[MAX_WORD_LENGTH_INTERNAL];
     int mStackSiblingPos[MAX_WORD_LENGTH_INTERNAL];
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index 07d0e5d..f9ea180 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -36,7 +36,7 @@
         // Use null as the locale for Suggest so as to force it to use the internal dictionary
         // (and not try to find a dictionary provider for a specified locale)
         mSuggest = new Suggest(context, dictionaryId, null);
-        mKeyboard = new LatinKeyboard(context, keyboardId, keyboardId.mWidth);
+        mKeyboard = new LatinKeyboard.Builder(context).load(keyboardId).build();
         mKeyDetector = new KeyDetector(0);
         init();
     }
@@ -44,7 +44,7 @@
     protected SuggestHelper(Context context, File dictionaryPath, long startOffset, long length,
             KeyboardId keyboardId) {
         mSuggest = new Suggest(context, dictionaryPath, startOffset, length, null);
-        mKeyboard = new LatinKeyboard(context, keyboardId, keyboardId.mWidth);
+        mKeyboard = new LatinKeyboard.Builder(context).load(keyboardId).build();
         mKeyDetector = new KeyDetector(0);
         init();
     }
@@ -54,7 +54,7 @@
         mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL);
         mKeyDetector.setKeyboard(mKeyboard, 0, 0);
         mKeyDetector.setProximityCorrectionEnabled(true);
-        mKeyDetector.setProximityThreshold(mKeyboard.getMostCommonKeyWidth());
+        mKeyDetector.setProximityThreshold(mKeyboard.mMostCommonKeyWidth);
     }
 
     public void setCorrectionMode(int correctionMode) {