Merge "Import translations. DO NOT MERGE"
diff --git a/java/res/xml/rowkeys_east_slavic2.xml b/java/res/xml/rowkeys_east_slavic2.xml
index 20d963c..0601000 100644
--- a/java/res/xml/rowkeys_east_slavic2.xml
+++ b/java/res/xml/rowkeys_east_slavic2.xml
@@ -25,8 +25,8 @@
     <Key
         latin:keySpec="&#x0444;" />
     <Key
-        latin:keySpec="!text/keylabel_for_east_slavic_row2_1"
-        latin:moreKeys="!text/more_keys_for_east_slavic_row2_1" />
+        latin:keySpec="!text/keylabel_for_east_slavic_row2_2"
+        latin:moreKeys="!text/more_keys_for_east_slavic_row2_2" />
     <!-- U+0432: "в" CYRILLIC SMALL LETTER VE -->
     <Key
         latin:keySpec="&#x0432;" />
diff --git a/java/src/com/android/inputmethod/event/InputTransaction.java b/java/src/com/android/inputmethod/event/InputTransaction.java
index 5e046a8..3f709a6 100644
--- a/java/src/com/android/inputmethod/event/InputTransaction.java
+++ b/java/src/com/android/inputmethod/event/InputTransaction.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.event;
 
+import com.android.inputmethod.latin.settings.SettingsValues;
+
 /**
  * An object encapsulating a single transaction for input.
  */
@@ -30,12 +32,28 @@
     public static final int SHIFT_UPDATE_LATER = 2;
 
     // Initial conditions
+    public final SettingsValues mSettingsValues;
+    // If the key inserts a code point, mKeyCode is always equal to the code points. Otherwise,
+    // it's always a code that may not be a code point, typically a negative number.
+    public final int mKeyCode;
+    public final int mX; // Pressed x-coordinate, or one of Constants.*_COORDINATE
+    public final int mY; // Pressed y-coordinate, or one of Constants.*_COORDINATE
+    public final long mTimestamp;
+    public final int mSpaceState;
     public final int mShiftState;
 
     // Outputs
     private int mRequiredShiftUpdate = SHIFT_NO_UPDATE;
 
-    public InputTransaction(final int shiftState) {
+    public InputTransaction(final SettingsValues settingsValues, final int keyCode,
+            final int x, final int y, final long timestamp, final int spaceState,
+            final int shiftState) {
+        mSettingsValues = settingsValues;
+        mKeyCode = keyCode;
+        mX = x;
+        mY = y;
+        mTimestamp = timestamp;
+        mSpaceState = spaceState;
         mShiftState = shiftState;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 59cf64d..3539a87 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -323,7 +323,7 @@
     }
 
     // Note that we need primaryCode argument because the keyboard may in shifted state and the
-    // primaryCode is different from {@link Key#mCode}.
+    // primaryCode is different from {@link Key#mKeyCode}.
     private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
             final int y, final long eventTime) {
         final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
@@ -360,7 +360,7 @@
     }
 
     // Note that we need primaryCode argument because the keyboard may be in shifted state and the
-    // primaryCode is different from {@link Key#mCode}.
+    // primaryCode is different from {@link Key#mKeyCode}.
     private void callListenerOnRelease(final Key key, final int primaryCode,
             final boolean withSliding) {
         // See the comment at {@link #callListenerOnPressAndCheckKeyboardLayoutChange(Key}}.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
index ed2db07..cace069 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -107,149 +107,148 @@
         /*  25: 6 */ "more_keys_for_cyrillic_ie",
         /*  26: 5 */ "more_keys_for_nordic_row2_10",
         /*  27: 5 */ "keylabel_for_east_slavic_row1_9",
-        /*  28: 5 */ "keylabel_for_east_slavic_row1_12",
-        /*  29: 5 */ "keylabel_for_east_slavic_row2_1",
-        /*  30: 5 */ "keylabel_for_east_slavic_row2_11",
-        /*  31: 5 */ "keylabel_for_east_slavic_row3_5",
-        /*  32: 5 */ "more_keys_for_cyrillic_soft_sign",
-        /*  33: 5 */ "more_keys_for_punctuation",
-        /*  34: 4 */ "more_keys_for_nordic_row2_11",
-        /*  35: 4 */ "keylabel_for_symbols_1",
-        /*  36: 4 */ "keylabel_for_symbols_2",
-        /*  37: 4 */ "keylabel_for_symbols_3",
-        /*  38: 4 */ "keylabel_for_symbols_4",
-        /*  39: 4 */ "keylabel_for_symbols_5",
-        /*  40: 4 */ "keylabel_for_symbols_6",
-        /*  41: 4 */ "keylabel_for_symbols_7",
-        /*  42: 4 */ "keylabel_for_symbols_8",
-        /*  43: 4 */ "keylabel_for_symbols_9",
-        /*  44: 4 */ "keylabel_for_symbols_0",
-        /*  45: 4 */ "label_to_symbol_key",
-        /*  46: 4 */ "label_to_symbol_with_microphone_key",
-        /*  47: 4 */ "additional_more_keys_for_symbols_1",
-        /*  48: 4 */ "additional_more_keys_for_symbols_2",
-        /*  49: 4 */ "additional_more_keys_for_symbols_3",
-        /*  50: 4 */ "additional_more_keys_for_symbols_4",
-        /*  51: 4 */ "additional_more_keys_for_symbols_5",
-        /*  52: 4 */ "additional_more_keys_for_symbols_6",
-        /*  53: 4 */ "additional_more_keys_for_symbols_7",
-        /*  54: 4 */ "additional_more_keys_for_symbols_8",
-        /*  55: 4 */ "additional_more_keys_for_symbols_9",
-        /*  56: 4 */ "additional_more_keys_for_symbols_0",
-        /*  57: 3 */ "more_keys_for_star",
-        /*  58: 3 */ "keyspec_left_parenthesis",
-        /*  59: 3 */ "keyspec_right_parenthesis",
-        /*  60: 3 */ "keyspec_left_square_bracket",
-        /*  61: 3 */ "keyspec_right_square_bracket",
-        /*  62: 3 */ "keyspec_left_curly_bracket",
-        /*  63: 3 */ "keyspec_right_curly_bracket",
-        /*  64: 3 */ "keyspec_less_than",
-        /*  65: 3 */ "keyspec_greater_than",
-        /*  66: 3 */ "keyspec_less_than_equal",
-        /*  67: 3 */ "keyspec_greater_than_equal",
-        /*  68: 3 */ "keyspec_left_double_angle_quote",
-        /*  69: 3 */ "keyspec_right_double_angle_quote",
-        /*  70: 3 */ "keyspec_left_single_angle_quote",
-        /*  71: 3 */ "keyspec_right_single_angle_quote",
-        /*  72: 3 */ "keylabel_for_tablet_comma",
-        /*  73: 3 */ "more_keys_for_tablet_period",
-        /*  74: 3 */ "more_keys_for_question",
-        /*  75: 2 */ "more_keys_for_h",
-        /*  76: 2 */ "more_keys_for_w",
-        /*  77: 2 */ "more_keys_for_cyrillic_u",
-        /*  78: 2 */ "more_keys_for_cyrillic_en",
-        /*  79: 2 */ "more_keys_for_cyrillic_ghe",
-        /*  80: 2 */ "more_keys_for_east_slavic_row2_1",
-        /*  81: 2 */ "more_keys_for_cyrillic_o",
-        /*  82: 2 */ "keylabel_for_south_slavic_row1_6",
-        /*  83: 2 */ "keylabel_for_south_slavic_row2_11",
-        /*  84: 2 */ "keylabel_for_south_slavic_row3_1",
-        /*  85: 2 */ "keylabel_for_south_slavic_row3_8",
-        /*  86: 2 */ "more_keys_for_cyrillic_i",
-        /*  87: 2 */ "keylabel_for_swiss_row1_11",
-        /*  88: 2 */ "keylabel_for_swiss_row2_10",
-        /*  89: 2 */ "keylabel_for_swiss_row2_11",
-        /*  90: 2 */ "more_keys_for_swiss_row1_11",
-        /*  91: 2 */ "more_keys_for_swiss_row2_10",
-        /*  92: 2 */ "more_keys_for_swiss_row2_11",
-        /*  93: 2 */ "keylabel_for_spanish_row2_10",
-        /*  94: 2 */ "more_keys_for_bullet",
-        /*  95: 2 */ "more_keys_for_left_parenthesis",
-        /*  96: 2 */ "more_keys_for_right_parenthesis",
-        /*  97: 2 */ "more_keys_for_arabic_diacritics",
-        /*  98: 2 */ "keylabel_for_comma",
-        /*  99: 2 */ "more_keys_for_comma",
-        /* 100: 2 */ "keyhintlabel_for_tablet_comma",
-        /* 101: 2 */ "more_keys_for_tablet_comma",
-        /* 102: 2 */ "keyhintlabel_for_period",
-        /* 103: 2 */ "more_keys_for_period",
-        /* 104: 2 */ "keyhintlabel_for_tablet_period",
-        /* 105: 2 */ "keylabel_for_symbols_question",
-        /* 106: 2 */ "keylabel_for_symbols_semicolon",
-        /* 107: 2 */ "keylabel_for_symbols_percent",
-        /* 108: 2 */ "more_keys_for_symbols_semicolon",
-        /* 109: 2 */ "more_keys_for_symbols_percent",
-        /* 110: 1 */ "more_keys_for_v",
-        /* 111: 1 */ "more_keys_for_j",
-        /* 112: 1 */ "more_keys_for_cyrillic_ka",
-        /* 113: 1 */ "more_keys_for_cyrillic_a",
-        /* 114: 1 */ "more_keys_for_east_slavic_row2_11",
-        /* 115: 1 */ "more_keys_for_currency_dollar",
-        /* 116: 1 */ "more_keys_for_tablet_punctuation",
-        /* 117: 1 */ "more_keys_for_plus",
-        /* 118: 1 */ "more_keys_for_less_than",
-        /* 119: 1 */ "more_keys_for_greater_than",
-        /* 120: 1 */ "keylabel_for_period",
-        /* 121: 1 */ "keylabel_for_tablet_period",
-        /* 122: 1 */ "more_keys_for_exclamation",
-        /* 123: 1 */ "more_keys_for_q",
-        /* 124: 1 */ "more_keys_for_x",
-        /* 125: 1 */ "keylabel_for_q",
-        /* 126: 1 */ "keylabel_for_w",
-        /* 127: 1 */ "keylabel_for_y",
-        /* 128: 1 */ "keylabel_for_x",
-        /* 129: 0 */ "more_keys_for_currency",
-        /* 130: 0 */ "more_keys_for_symbols_1",
-        /* 131: 0 */ "more_keys_for_symbols_2",
-        /* 132: 0 */ "more_keys_for_symbols_3",
-        /* 133: 0 */ "more_keys_for_symbols_4",
-        /* 134: 0 */ "more_keys_for_symbols_5",
-        /* 135: 0 */ "more_keys_for_symbols_6",
-        /* 136: 0 */ "more_keys_for_symbols_7",
-        /* 137: 0 */ "more_keys_for_symbols_8",
-        /* 138: 0 */ "more_keys_for_symbols_9",
-        /* 139: 0 */ "more_keys_for_symbols_0",
-        /* 140: 0 */ "more_keys_for_am_pm",
-        /* 141: 0 */ "settings_as_more_key",
-        /* 142: 0 */ "shortcut_as_more_key",
-        /* 143: 0 */ "action_next_as_more_key",
-        /* 144: 0 */ "action_previous_as_more_key",
-        /* 145: 0 */ "label_to_more_symbol_key",
-        /* 146: 0 */ "label_to_more_symbol_for_tablet_key",
-        /* 147: 0 */ "label_to_phone_numeric_key",
-        /* 148: 0 */ "label_to_phone_symbols_key",
-        /* 149: 0 */ "label_time_am",
-        /* 150: 0 */ "label_time_pm",
-        /* 151: 0 */ "keylabel_for_popular_domain",
-        /* 152: 0 */ "more_keys_for_popular_domain",
-        /* 153: 0 */ "keyspecs_for_left_parenthesis_more_keys",
-        /* 154: 0 */ "keyspecs_for_right_parenthesis_more_keys",
-        /* 155: 0 */ "single_laqm_raqm",
-        /* 156: 0 */ "single_raqm_laqm",
-        /* 157: 0 */ "double_laqm_raqm",
-        /* 158: 0 */ "double_raqm_laqm",
-        /* 159: 0 */ "single_lqm_rqm",
-        /* 160: 0 */ "single_9qm_lqm",
-        /* 161: 0 */ "single_9qm_rqm",
-        /* 162: 0 */ "single_rqm_9qm",
-        /* 163: 0 */ "double_lqm_rqm",
-        /* 164: 0 */ "double_9qm_lqm",
-        /* 165: 0 */ "double_9qm_rqm",
-        /* 166: 0 */ "double_rqm_9qm",
-        /* 167: 0 */ "more_keys_for_single_quote",
-        /* 168: 0 */ "more_keys_for_double_quote",
-        /* 169: 0 */ "more_keys_for_tablet_double_quote",
-        /* 170: 0 */ "emoji_key_as_more_key",
+        /*  28: 5 */ "keylabel_for_east_slavic_row2_2",
+        /*  29: 5 */ "keylabel_for_east_slavic_row2_11",
+        /*  30: 5 */ "keylabel_for_east_slavic_row3_5",
+        /*  31: 5 */ "more_keys_for_cyrillic_soft_sign",
+        /*  32: 5 */ "more_keys_for_punctuation",
+        /*  33: 4 */ "more_keys_for_nordic_row2_11",
+        /*  34: 4 */ "keylabel_for_symbols_1",
+        /*  35: 4 */ "keylabel_for_symbols_2",
+        /*  36: 4 */ "keylabel_for_symbols_3",
+        /*  37: 4 */ "keylabel_for_symbols_4",
+        /*  38: 4 */ "keylabel_for_symbols_5",
+        /*  39: 4 */ "keylabel_for_symbols_6",
+        /*  40: 4 */ "keylabel_for_symbols_7",
+        /*  41: 4 */ "keylabel_for_symbols_8",
+        /*  42: 4 */ "keylabel_for_symbols_9",
+        /*  43: 4 */ "keylabel_for_symbols_0",
+        /*  44: 4 */ "label_to_symbol_key",
+        /*  45: 4 */ "label_to_symbol_with_microphone_key",
+        /*  46: 4 */ "additional_more_keys_for_symbols_1",
+        /*  47: 4 */ "additional_more_keys_for_symbols_2",
+        /*  48: 4 */ "additional_more_keys_for_symbols_3",
+        /*  49: 4 */ "additional_more_keys_for_symbols_4",
+        /*  50: 4 */ "additional_more_keys_for_symbols_5",
+        /*  51: 4 */ "additional_more_keys_for_symbols_6",
+        /*  52: 4 */ "additional_more_keys_for_symbols_7",
+        /*  53: 4 */ "additional_more_keys_for_symbols_8",
+        /*  54: 4 */ "additional_more_keys_for_symbols_9",
+        /*  55: 4 */ "additional_more_keys_for_symbols_0",
+        /*  56: 3 */ "more_keys_for_star",
+        /*  57: 3 */ "keyspec_left_parenthesis",
+        /*  58: 3 */ "keyspec_right_parenthesis",
+        /*  59: 3 */ "keyspec_left_square_bracket",
+        /*  60: 3 */ "keyspec_right_square_bracket",
+        /*  61: 3 */ "keyspec_left_curly_bracket",
+        /*  62: 3 */ "keyspec_right_curly_bracket",
+        /*  63: 3 */ "keyspec_less_than",
+        /*  64: 3 */ "keyspec_greater_than",
+        /*  65: 3 */ "keyspec_less_than_equal",
+        /*  66: 3 */ "keyspec_greater_than_equal",
+        /*  67: 3 */ "keyspec_left_double_angle_quote",
+        /*  68: 3 */ "keyspec_right_double_angle_quote",
+        /*  69: 3 */ "keyspec_left_single_angle_quote",
+        /*  70: 3 */ "keyspec_right_single_angle_quote",
+        /*  71: 3 */ "keylabel_for_tablet_comma",
+        /*  72: 3 */ "more_keys_for_tablet_period",
+        /*  73: 3 */ "more_keys_for_question",
+        /*  74: 2 */ "more_keys_for_h",
+        /*  75: 2 */ "more_keys_for_w",
+        /*  76: 2 */ "more_keys_for_cyrillic_u",
+        /*  77: 2 */ "more_keys_for_cyrillic_en",
+        /*  78: 2 */ "more_keys_for_cyrillic_ghe",
+        /*  79: 2 */ "more_keys_for_east_slavic_row2_2",
+        /*  80: 2 */ "more_keys_for_cyrillic_o",
+        /*  81: 2 */ "keylabel_for_south_slavic_row1_6",
+        /*  82: 2 */ "keylabel_for_south_slavic_row2_11",
+        /*  83: 2 */ "keylabel_for_south_slavic_row3_1",
+        /*  84: 2 */ "keylabel_for_south_slavic_row3_8",
+        /*  85: 2 */ "more_keys_for_cyrillic_i",
+        /*  86: 2 */ "keylabel_for_swiss_row1_11",
+        /*  87: 2 */ "keylabel_for_swiss_row2_10",
+        /*  88: 2 */ "keylabel_for_swiss_row2_11",
+        /*  89: 2 */ "more_keys_for_swiss_row1_11",
+        /*  90: 2 */ "more_keys_for_swiss_row2_10",
+        /*  91: 2 */ "more_keys_for_swiss_row2_11",
+        /*  92: 2 */ "keylabel_for_spanish_row2_10",
+        /*  93: 2 */ "more_keys_for_bullet",
+        /*  94: 2 */ "more_keys_for_left_parenthesis",
+        /*  95: 2 */ "more_keys_for_right_parenthesis",
+        /*  96: 2 */ "more_keys_for_arabic_diacritics",
+        /*  97: 2 */ "keylabel_for_comma",
+        /*  98: 2 */ "more_keys_for_comma",
+        /*  99: 2 */ "keyhintlabel_for_tablet_comma",
+        /* 100: 2 */ "more_keys_for_tablet_comma",
+        /* 101: 2 */ "keyhintlabel_for_period",
+        /* 102: 2 */ "more_keys_for_period",
+        /* 103: 2 */ "keyhintlabel_for_tablet_period",
+        /* 104: 2 */ "keylabel_for_symbols_question",
+        /* 105: 2 */ "keylabel_for_symbols_semicolon",
+        /* 106: 2 */ "keylabel_for_symbols_percent",
+        /* 107: 2 */ "more_keys_for_symbols_semicolon",
+        /* 108: 2 */ "more_keys_for_symbols_percent",
+        /* 109: 1 */ "more_keys_for_v",
+        /* 110: 1 */ "more_keys_for_j",
+        /* 111: 1 */ "more_keys_for_cyrillic_ka",
+        /* 112: 1 */ "more_keys_for_cyrillic_a",
+        /* 113: 1 */ "more_keys_for_east_slavic_row2_11",
+        /* 114: 1 */ "more_keys_for_currency_dollar",
+        /* 115: 1 */ "more_keys_for_tablet_punctuation",
+        /* 116: 1 */ "more_keys_for_plus",
+        /* 117: 1 */ "more_keys_for_less_than",
+        /* 118: 1 */ "more_keys_for_greater_than",
+        /* 119: 1 */ "keylabel_for_period",
+        /* 120: 1 */ "keylabel_for_tablet_period",
+        /* 121: 1 */ "more_keys_for_exclamation",
+        /* 122: 1 */ "more_keys_for_q",
+        /* 123: 1 */ "more_keys_for_x",
+        /* 124: 1 */ "keylabel_for_q",
+        /* 125: 1 */ "keylabel_for_w",
+        /* 126: 1 */ "keylabel_for_y",
+        /* 127: 1 */ "keylabel_for_x",
+        /* 128: 0 */ "more_keys_for_currency",
+        /* 129: 0 */ "more_keys_for_symbols_1",
+        /* 130: 0 */ "more_keys_for_symbols_2",
+        /* 131: 0 */ "more_keys_for_symbols_3",
+        /* 132: 0 */ "more_keys_for_symbols_4",
+        /* 133: 0 */ "more_keys_for_symbols_5",
+        /* 134: 0 */ "more_keys_for_symbols_6",
+        /* 135: 0 */ "more_keys_for_symbols_7",
+        /* 136: 0 */ "more_keys_for_symbols_8",
+        /* 137: 0 */ "more_keys_for_symbols_9",
+        /* 138: 0 */ "more_keys_for_symbols_0",
+        /* 139: 0 */ "more_keys_for_am_pm",
+        /* 140: 0 */ "settings_as_more_key",
+        /* 141: 0 */ "shortcut_as_more_key",
+        /* 142: 0 */ "action_next_as_more_key",
+        /* 143: 0 */ "action_previous_as_more_key",
+        /* 144: 0 */ "label_to_more_symbol_key",
+        /* 145: 0 */ "label_to_more_symbol_for_tablet_key",
+        /* 146: 0 */ "label_to_phone_numeric_key",
+        /* 147: 0 */ "label_to_phone_symbols_key",
+        /* 148: 0 */ "label_time_am",
+        /* 149: 0 */ "label_time_pm",
+        /* 150: 0 */ "keylabel_for_popular_domain",
+        /* 151: 0 */ "more_keys_for_popular_domain",
+        /* 152: 0 */ "keyspecs_for_left_parenthesis_more_keys",
+        /* 153: 0 */ "keyspecs_for_right_parenthesis_more_keys",
+        /* 154: 0 */ "single_laqm_raqm",
+        /* 155: 0 */ "single_raqm_laqm",
+        /* 156: 0 */ "double_laqm_raqm",
+        /* 157: 0 */ "double_raqm_laqm",
+        /* 158: 0 */ "single_lqm_rqm",
+        /* 159: 0 */ "single_9qm_lqm",
+        /* 160: 0 */ "single_9qm_rqm",
+        /* 161: 0 */ "single_rqm_9qm",
+        /* 162: 0 */ "double_lqm_rqm",
+        /* 163: 0 */ "double_9qm_lqm",
+        /* 164: 0 */ "double_9qm_rqm",
+        /* 165: 0 */ "double_rqm_9qm",
+        /* 166: 0 */ "more_keys_for_single_quote",
+        /* 167: 0 */ "more_keys_for_double_quote",
+        /* 168: 0 */ "more_keys_for_tablet_double_quote",
+        /* 169: 0 */ "emoji_key_as_more_key",
     };
 
     private static final String EMPTY = "";
@@ -273,7 +272,7 @@
         /* double_angle_quotes */ "!text/double_laqm_raqm",
         /* keylabel_for_currency */ "$",
         /* more_keys_for_r ~ */
-        EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+        EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
         /* ~ more_keys_for_cyrillic_soft_sign */
         /* more_keys_for_punctuation */ "!fixedColumnOrder!8,;,/,!text/keyspec_left_parenthesis,!text/keyspec_right_parenthesis,#,!,\\,,?,&,\\%,+,\",-,:,',@",
         /* more_keys_for_nordic_row2_11 */ EMPTY,
@@ -533,7 +532,7 @@
         /* label_to_alpha_key */ "\u0623\u200C\u0628\u200C\u062C",
         /* more_keys_for_y ~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null,
+        null, null, null, null, null, null,
         /* ~ more_keys_for_cyrillic_soft_sign */
         /* more_keys_for_punctuation */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
         /* more_keys_for_nordic_row2_11 */ null,
@@ -722,10 +721,8 @@
         /* more_keys_for_nordic_row2_10 */ null,
         // U+045E: "ў" CYRILLIC SMALL LETTER SHORT U
         /* keylabel_for_east_slavic_row1_9 */ "\u045E",
-        // U+0451: "ё" CYRILLIC SMALL LETTER IO
-        /* keylabel_for_east_slavic_row1_12 */ "\u0451",
         // U+044B: "ы" CYRILLIC SMALL LETTER YERU
-        /* keylabel_for_east_slavic_row2_1 */ "\u044B",
+        /* keylabel_for_east_slavic_row2_2 */ "\u044B",
         // U+044D: "э" CYRILLIC SMALL LETTER E
         /* keylabel_for_east_slavic_row2_11 */ "\u044D",
         // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
@@ -813,7 +810,7 @@
         /* more_keys_for_l */ "l\u00B7l,\u0142",
         /* more_keys_for_g ~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null,
+        null,
         /* ~ more_keys_for_cyrillic_soft_sign */
         // U+00B7: "·" MIDDLE DOT
         /* more_keys_for_punctuation */ "!fixedColumnOrder!9,;,/,(,),#,\u00B7,!,\\,,?,&,\\%,+,\",-,:,',@",
@@ -974,7 +971,7 @@
         // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
         /* more_keys_for_nordic_row2_10 */ "\u00E4",
         /* keylabel_for_east_slavic_row1_9 ~ */
-        null, null, null, null, null, null, null,
+        null, null, null, null, null, null,
         /* ~ more_keys_for_punctuation */
         // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
         /* more_keys_for_nordic_row2_11 */ "\u00F6",
@@ -1033,7 +1030,7 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null,
         /* ~ more_keys_for_cyrillic_i */
         // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
         /* keylabel_for_swiss_row1_11 */ "\u00FC",
@@ -1227,7 +1224,7 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null,
         /* ~ more_keys_for_question */
         // U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
         // U+0127: "ħ" LATIN SMALL LETTER H WITH STROKE
@@ -1317,7 +1314,7 @@
         /* more_keys_for_n */ "\u00F1,\u0144",
         /* label_to_alpha_key ~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null,
         /* ~ more_keys_for_cyrillic_soft_sign */
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
         // U+00BF: "¿" INVERTED QUESTION MARK
@@ -1445,7 +1442,7 @@
         // U+FDFC: "﷼" RIAL SIGN
         /* keylabel_for_currency */ "\uFDFC",
         /* more_keys_for_r ~ */
-        null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
         /* ~ more_keys_for_cyrillic_soft_sign */
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
@@ -1620,7 +1617,7 @@
         // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
         /* more_keys_for_nordic_row2_10 */ "\u00F8",
         /* keylabel_for_east_slavic_row1_9 ~ */
-        null, null, null, null, null, null, null,
+        null, null, null, null, null, null,
         /* ~ more_keys_for_punctuation */
         // U+00E6: "æ" LATIN SMALL LETTER AE
         /* more_keys_for_nordic_row2_11 */ "\u00E6",
@@ -1685,7 +1682,7 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         /* ~ more_keys_for_cyrillic_i */
         // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
         /* keylabel_for_swiss_row1_11 */ "\u00E8",
@@ -1717,7 +1714,7 @@
         // U+20B9: "₹" INDIAN RUPEE SIGN
         /* keylabel_for_currency */ "\u20B9",
         /* more_keys_for_r ~ */
-        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         /* ~ more_keys_for_nordic_row2_11 */
         // U+0967: "१" DEVANAGARI DIGIT ONE
         /* keylabel_for_symbols_1 */ "\u0967",
@@ -1853,7 +1850,7 @@
         /* label_to_alpha_key */ "\u0531\u0532\u0533",
         /* more_keys_for_y ~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null,
+        null, null, null, null, null, null,
         /* ~ more_keys_for_cyrillic_soft_sign */
         // U+058A: "֊" ARMENIAN HYPHEN
         // U+055C: "՜" ARMENIAN EXCLAMATION MARK
@@ -2025,7 +2022,7 @@
         /* more_keys_for_r ~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null,
+        null, null, null, null, null, null,
         /* ~ additional_more_keys_for_symbols_0 */
         // U+2605: "★" BLACK STAR
         /* more_keys_for_star */ "\u2605",
@@ -2096,10 +2093,8 @@
         /* more_keys_for_nordic_row2_10 */ null,
         // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
         /* keylabel_for_east_slavic_row1_9 */ "\u0449",
-        // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* keylabel_for_east_slavic_row1_12 */ "\u044A",
         // U+044B: "ы" CYRILLIC SMALL LETTER YERU
-        /* keylabel_for_east_slavic_row2_1 */ "\u044B",
+        /* keylabel_for_east_slavic_row2_2 */ "\u044B",
         // U+044D: "э" CYRILLIC SMALL LETTER E
         /* keylabel_for_east_slavic_row2_11 */ "\u044D",
         // U+0438: "и" CYRILLIC SMALL LETTER I
@@ -2119,7 +2114,7 @@
         // U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE
         /* more_keys_for_cyrillic_ghe */ "\u0493",
         // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
-        /* more_keys_for_east_slavic_row2_1 */ "\u0456",
+        /* more_keys_for_east_slavic_row2_2 */ "\u0456",
         // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
         /* more_keys_for_cyrillic_o */ "\u04E9",
         /* keylabel_for_south_slavic_row1_6 ~ */
@@ -2151,7 +2146,7 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null,
         /* ~ more_keys_for_east_slavic_row2_11 */
         // U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL
         /* more_keys_for_currency_dollar */ "\u17DB,\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
@@ -2175,10 +2170,8 @@
         /* more_keys_for_nordic_row2_10 */ null,
         // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
         /* keylabel_for_east_slavic_row1_9 */ "\u0449",
-        // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* keylabel_for_east_slavic_row1_12 */ "\u044A",
         // U+044B: "ы" CYRILLIC SMALL LETTER YERU
-        /* keylabel_for_east_slavic_row2_1 */ "\u044B",
+        /* keylabel_for_east_slavic_row2_2 */ "\u044B",
         // U+044D: "э" CYRILLIC SMALL LETTER E
         /* keylabel_for_east_slavic_row2_11 */ "\u044D",
         // U+0438: "и" CYRILLIC SMALL LETTER I
@@ -2195,7 +2188,7 @@
         // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
         /* more_keys_for_cyrillic_en */ "\u04A3",
         /* more_keys_for_cyrillic_ghe */ null,
-        /* more_keys_for_east_slavic_row2_1 */ null,
+        /* more_keys_for_east_slavic_row2_2 */ null,
         // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
         /* more_keys_for_cyrillic_o */ "\u04E9",
     };
@@ -2432,7 +2425,7 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null,
         /* ~ more_keys_for_cyrillic_o */
         // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
         /* keylabel_for_south_slavic_row1_6 */ "\u0455",
@@ -2510,7 +2503,7 @@
         // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
         /* more_keys_for_nordic_row2_10 */ "\u00F6",
         /* keylabel_for_east_slavic_row1_9 ~ */
-        null, null, null, null, null, null, null,
+        null, null, null, null, null, null,
         /* ~ more_keys_for_punctuation */
         // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
         /* more_keys_for_nordic_row2_11 */ "\u00E4",
@@ -2532,7 +2525,7 @@
         // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
         /* keylabel_for_currency */ "\u0930\u0941.",
         /* more_keys_for_r ~ */
-        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         /* ~ more_keys_for_nordic_row2_11 */
         // U+0967: "१" DEVANAGARI DIGIT ONE
         /* keylabel_for_symbols_1 */ "\u0967",
@@ -2804,10 +2797,8 @@
         /* more_keys_for_nordic_row2_10 */ null,
         // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
         /* keylabel_for_east_slavic_row1_9 */ "\u0449",
-        // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* keylabel_for_east_slavic_row1_12 */ "\u044A",
         // U+044B: "ы" CYRILLIC SMALL LETTER YERU
-        /* keylabel_for_east_slavic_row2_1 */ "\u044B",
+        /* keylabel_for_east_slavic_row2_2 */ "\u044B",
         // U+044D: "э" CYRILLIC SMALL LETTER E
         /* keylabel_for_east_slavic_row2_11 */ "\u044D",
         // U+0438: "и" CYRILLIC SMALL LETTER I
@@ -2968,7 +2959,7 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null,
         /* ~ more_keys_for_cyrillic_o */
         // TODO: Move these to sr-Latn once we can handle IETF language tag with script name specified.
         // BEGIN: More keys definitions for Serbian (Latin)
@@ -3081,7 +3072,7 @@
         // U+0153: "œ" LATIN SMALL LIGATURE OE
         /* more_keys_for_nordic_row2_10 */ "\u00F8,\u0153",
         /* keylabel_for_east_slavic_row1_9 ~ */
-        null, null, null, null, null, null, null,
+        null, null, null, null, null, null,
         /* ~ more_keys_for_punctuation */
         // U+00E6: "æ" LATIN SMALL LETTER AE
         /* more_keys_for_nordic_row2_11 */ "\u00E6",
@@ -3284,10 +3275,8 @@
         /* ~ more_keys_for_nordic_row2_10 */
         // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
         /* keylabel_for_east_slavic_row1_9 */ "\u0449",
-        // U+0457: "ї" CYRILLIC SMALL LETTER YI
-        /* keylabel_for_east_slavic_row1_12 */ "\u0457",
         // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
-        /* keylabel_for_east_slavic_row2_1 */ "\u0456",
+        /* keylabel_for_east_slavic_row2_2 */ "\u0456",
         // U+0454: "є" CYRILLIC SMALL LETTER UKRAINIAN IE
         /* keylabel_for_east_slavic_row2_11 */ "\u0454",
         // U+0438: "и" CYRILLIC SMALL LETTER I
@@ -3303,7 +3292,7 @@
         // U+0491: "ґ" CYRILLIC SMALL LETTER GHE WITH UPTURN
         /* more_keys_for_cyrillic_ghe */ "\u0491",
         // U+0457: "ї" CYRILLIC SMALL LETTER YI
-        /* more_keys_for_east_slavic_row2_1 */ "\u0457",
+        /* more_keys_for_east_slavic_row2_2 */ "\u0457",
     };
 
     /* Language vi: Vietnamese */
@@ -3565,7 +3554,7 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null,
         /* ~ more_keys_for_question */
         // U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
         /* more_keys_for_h */ "\u0125",
@@ -3584,60 +3573,60 @@
     // Currently we are dropping the region from the key.
     private static final Object[] LANGUAGES_AND_TEXTS = {
     // "locale", TEXT_ARRAY,  /* numberOfNonNullText/lengthOf_TEXT_ARRAY localeName */
-        "DEFAULT", LANGUAGE_DEFAULT, /* 171/171 default */
+        "DEFAULT", LANGUAGE_DEFAULT, /* 170/170 default */
         "af", LANGUAGE_af,    /*   7/ 12 Afrikaans */
-        "ar", LANGUAGE_ar,    /*  58/110 Arabic */
+        "ar", LANGUAGE_ar,    /*  58/109 Arabic */
         "az", LANGUAGE_az_AZ, /*   8/ 17 Azerbaijani (Azerbaijan) */
-        "be", LANGUAGE_be_BY, /*  10/ 33 Belarusian (Belarus) */
+        "be", LANGUAGE_be_BY, /*   9/ 32 Belarusian (Belarus) */
         "bg", LANGUAGE_bg,    /*   2/ 11 Bulgarian */
-        "ca", LANGUAGE_ca,    /*  11/117 Catalan */
+        "ca", LANGUAGE_ca,    /*  11/116 Catalan */
         "cs", LANGUAGE_cs,    /*  17/ 21 Czech */
-        "da", LANGUAGE_da,    /*  19/ 35 Danish */
-        "de", LANGUAGE_de,    /*  16/ 93 German */
+        "da", LANGUAGE_da,    /*  19/ 34 Danish */
+        "de", LANGUAGE_de,    /*  16/ 92 German */
         "el", LANGUAGE_el,    /*   1/ 11 Greek */
         "en", LANGUAGE_en,    /*   8/ 10 English */
-        "eo", LANGUAGE_eo,    /*  26/129 Esperanto */
-        "es", LANGUAGE_es,    /*   8/ 34 Spanish */
+        "eo", LANGUAGE_eo,    /*  26/128 Esperanto */
+        "es", LANGUAGE_es,    /*   8/ 33 Spanish */
         "et", LANGUAGE_et_EE, /*  22/ 27 Estonian (Estonia) */
-        "fa", LANGUAGE_fa,    /*  61/120 Persian */
-        "fi", LANGUAGE_fi,    /*  10/ 35 Finnish */
-        "fr", LANGUAGE_fr,    /*  13/ 93 French */
-        "hi", LANGUAGE_hi,    /*  24/ 57 Hindi */
+        "fa", LANGUAGE_fa,    /*  61/119 Persian */
+        "fi", LANGUAGE_fi,    /*  10/ 34 Finnish */
+        "fr", LANGUAGE_fr,    /*  13/ 92 French */
+        "hi", LANGUAGE_hi,    /*  24/ 56 Hindi */
         "hr", LANGUAGE_hr,    /*   9/ 19 Croatian */
         "hu", LANGUAGE_hu,    /*   9/ 19 Hungarian */
-        "hy", LANGUAGE_hy_AM, /*   8/123 Armenian (Armenia) */
+        "hy", LANGUAGE_hy_AM, /*   8/122 Armenian (Armenia) */
         "is", LANGUAGE_is,    /*  13/ 25 Icelandic */
         "it", LANGUAGE_it,    /*   5/  5 Italian */
-        "iw", LANGUAGE_iw,    /*  20/118 Hebrew */
+        "iw", LANGUAGE_iw,    /*  20/117 Hebrew */
         "ka", LANGUAGE_ka_GE, /*   3/ 11 Georgian (Georgia) */
-        "kk", LANGUAGE_kk,    /*  16/115 Kazakh */
-        "km", LANGUAGE_km_KH, /*   2/116 Khmer (Cambodia) */
-        "ky", LANGUAGE_ky,    /*  11/ 82 Kirghiz */
+        "kk", LANGUAGE_kk,    /*  15/114 Kazakh */
+        "km", LANGUAGE_km_KH, /*   2/115 Khmer (Cambodia) */
+        "ky", LANGUAGE_ky,    /*  10/ 81 Kirghiz */
         "lo", LANGUAGE_lo_LA, /*   2/ 20 Lao (Laos) */
         "lt", LANGUAGE_lt,    /*  18/ 22 Lithuanian */
         "lv", LANGUAGE_lv,    /*  18/ 22 Latvian */
-        "mk", LANGUAGE_mk,    /*   9/ 87 Macedonian */
+        "mk", LANGUAGE_mk,    /*   9/ 86 Macedonian */
         "mn", LANGUAGE_mn_MN, /*   2/ 20 Mongolian (Mongolia) */
-        "nb", LANGUAGE_nb,    /*  11/ 35 Norwegian Bokmål */
-        "ne", LANGUAGE_ne_NP, /*  24/ 57 Nepali (Nepal) */
+        "nb", LANGUAGE_nb,    /*  11/ 34 Norwegian Bokmål */
+        "ne", LANGUAGE_ne_NP, /*  24/ 56 Nepali (Nepal) */
         "nl", LANGUAGE_nl,    /*   9/ 12 Dutch */
         "pl", LANGUAGE_pl,    /*  10/ 16 Polish */
         "pt", LANGUAGE_pt,    /*   6/  8 Portuguese */
         "rm", LANGUAGE_rm,    /*   1/  2 Raeto-Romance */
         "ro", LANGUAGE_ro,    /*   6/ 15 Romanian */
-        "ru", LANGUAGE_ru,    /*  10/ 33 Russian */
+        "ru", LANGUAGE_ru,    /*   9/ 32 Russian */
         "sk", LANGUAGE_sk,    /*  20/ 22 Slovak */
         "sl", LANGUAGE_sl,    /*   8/ 19 Slovenian */
-        "sr", LANGUAGE_sr,    /*  11/ 87 Serbian */
-        "sv", LANGUAGE_sv,    /*  21/ 35 Swedish */
+        "sr", LANGUAGE_sr,    /*  11/ 86 Serbian */
+        "sv", LANGUAGE_sv,    /*  21/ 34 Swedish */
         "sw", LANGUAGE_sw,    /*   9/ 17 Swahili */
         "th", LANGUAGE_th,    /*   2/ 20 Thai */
         "tl", LANGUAGE_tl,    /*   7/ 10 Tagalog */
         "tr", LANGUAGE_tr,    /*   7/ 17 Turkish */
-        "uk", LANGUAGE_uk,    /*  12/ 81 Ukrainian */
+        "uk", LANGUAGE_uk,    /*  11/ 80 Ukrainian */
         "vi", LANGUAGE_vi,    /*   8/ 20 Vietnamese */
         "zu", LANGUAGE_zu,    /*   8/ 10 Zulu */
-        "zz", LANGUAGE_zz,    /*  19/112 Alphabet */
+        "zz", LANGUAGE_zz,    /*  19/111 Alphabet */
     };
 
     static {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 05e6ef0..a3a329a 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -199,7 +199,6 @@
         return true;
     }
 
-    @UsedForTesting
     public DictionaryHeader getHeader() throws UnsupportedFormatException {
         if (mNativeDict == 0) {
             return null;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index acbd919..4c49cb3 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -21,10 +21,9 @@
 import android.content.res.AssetFileDescriptor;
 import android.util.Log;
 
-import com.android.inputmethod.latin.makedict.DictDecoder;
 import com.android.inputmethod.latin.makedict.DictionaryHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
@@ -226,12 +225,10 @@
     // ## HACK ## we prevent usage of a dictionary before version 18. The reason for this is, since
     // those do not include whitelist entries, the new code with an old version of the dictionary
     // would lose whitelist functionality.
-    private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
+    private static boolean hackCanUseDictionaryFile(final Locale locale, final File file) {
         try {
             // Read the version of the file
-            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(f, 0, f.length());
-            final DictionaryHeader header = dictDecoder.readHeader();
-
+            final DictionaryHeader header = BinaryDictionaryUtils.getHeader(file);
             final String version = header.mDictionaryOptions.mAttributes.get(VERSION_KEY);
             if (null == version) {
                 // No version in the options : the format is unexpected
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index d1ff714..e71723a 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -144,6 +144,7 @@
 
     public static final int NOT_A_CODE = -1;
     public static final int NOT_A_CURSOR_POSITION = -1;
+    // TODO: replace the following constants with state in InputTransaction?
     public static final int NOT_A_COORDINATE = -1;
     public static final int SUGGESTION_STRIP_COORDINATE = -2;
     public static final int SPELL_CHECKER_COORDINATE = -3;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 44353ba..a9e5480 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -59,6 +59,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
+import com.android.inputmethod.event.InputTransaction;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardId;
@@ -1265,8 +1266,18 @@
             mSubtypeSwitcher.switchToShortcutIME(this);
             // Still call the *#onCodeInput methods for readability.
         }
-        mInputLogic.onCodeInput(codeToSend, keyX, keyY, mSettings.getCurrent(), mHandler,
-                mKeyboardSwitcher);
+        final InputTransaction completeInputTransaction =
+                mInputLogic.onCodeInput(mSettings.getCurrent(), codeToSend, keyX, keyY,
+                        mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
+        switch (completeInputTransaction.getRequiredShiftUpdate()) {
+            case InputTransaction.SHIFT_UPDATE_LATER:
+                mHandler.postUpdateShiftState();
+                break;
+            case InputTransaction.SHIFT_UPDATE_NOW:
+                mKeyboardSwitcher.updateShiftState();
+                break;
+            default: // SHIFT_NO_UPDATE
+        }
         mKeyboardSwitcher.onCodeInput(codePoint);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index daaac59..dd9d6e8 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -205,9 +205,9 @@
             LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
             // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
             final int primaryCode = suggestion.charAt(0);
-            onCodeInput(primaryCode,
+            onCodeInput(settingsValues, primaryCode,
                     Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
-                    settingsValues, handler, keyboardSwitcher);
+                    keyboardSwitcher.getKeyboardShiftMode(), handler);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
                         false /* isBatchMode */, suggestedWords.mIsPrediction);
@@ -353,49 +353,48 @@
      * Typically, this is called whenever a key is pressed on the software keyboard. This is not
      * the entry point for gesture input; see the onBatchInput* family of functions for this.
      *
+     * @param settingsValues the current settings values.
      * @param code the code to handle. It may be a code point, or an internal key code.
      * @param x the x-coordinate where the user pressed the key, or NOT_A_COORDINATE.
      * @param y the y-coordinate where the user pressed the key, or NOT_A_COORDINATE.
+     * @param keyboardShiftMode the current shift mode of the keyboard, as returned by
+     *     {@link com.android.inputmethod.keyboard.KeyboardSwitcher#getKeyboardShiftMode()}
+     * @return the complete transaction object
      */
-    public void onCodeInput(final int code, final int x, final int y,
-            final SettingsValues settingsValues,
-            // TODO: remove these two arguments
-            final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) {
+    public InputTransaction onCodeInput(final SettingsValues settingsValues, final int code,
+            final int x, final int y, final int keyboardShiftMode,
+            // TODO: remove this argument
+            final LatinIME.UIHandler handler) {
+        final InputTransaction inputTransaction = new InputTransaction(settingsValues, code, x, y,
+                SystemClock.uptimeMillis(), mSpaceState,
+                getActualCapsMode(settingsValues, keyboardShiftMode));
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.latinIME_onCodeInput(code, x, y);
+            ResearchLogger.latinIME_onCodeInput(inputTransaction.mKeyCode,
+                    inputTransaction.mX, inputTransaction.mY);
         }
-        final long when = SystemClock.uptimeMillis();
-        final InputTransaction inputTransaction = new InputTransaction(
-                getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()));
-        if (code != Constants.CODE_DELETE
-                || when > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
+        if (inputTransaction.mKeyCode != Constants.CODE_DELETE
+                || inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
             mDeleteCount = 0;
         }
-        mLastKeyTime = when;
+        mLastKeyTime = inputTransaction.mTimestamp;
         mConnection.beginBatchEdit();
-        // The space state depends only on the last character pressed and its own previous
-        // state. Here, we revert the space state to neutral if the key is actually modifying
-        // the input contents (any non-shift key), which is what we should do for
-        // all inputs that do not result in a special state. Each character handling is then
-        // free to override the state as they see fit.
-        final int spaceState = mSpaceState;
         if (!mWordComposer.isComposingWord()) {
             mIsAutoCorrectionIndicatorOn = false;
         }
 
         // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state.
-        if (code != Constants.CODE_SPACE) {
+        if (inputTransaction.mKeyCode != Constants.CODE_SPACE) {
             handler.cancelDoubleSpacePeriodTimer();
         }
 
         boolean didAutoCorrect = false;
-        switch (code) {
+        switch (inputTransaction.mKeyCode) {
         case Constants.CODE_DELETE:
-            handleBackspace(settingsValues, spaceState, inputTransaction, handler);
-            LatinImeLogger.logOnDelete(x, y);
+            handleBackspace(inputTransaction, handler);
+            LatinImeLogger.logOnDelete(inputTransaction.mX, inputTransaction.mY);
             break;
         case Constants.CODE_SHIFT:
-            performRecapitalization(settingsValues);
+            performRecapitalization(inputTransaction.mSettingsValues);
             inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
             break;
         case Constants.CODE_CAPSLOCK:
@@ -450,41 +449,35 @@
             } else {
                 // No action label, and the action from imeOptions is NONE: this is a regular
                 // enter key that should input a carriage return.
-                didAutoCorrect = handleNonSpecialCharacter(settingsValues, Constants.CODE_ENTER,
-                        x, y, spaceState, inputTransaction, handler);
+                didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
             }
             break;
         case Constants.CODE_SHIFT_ENTER:
-            didAutoCorrect = handleNonSpecialCharacter(settingsValues, Constants.CODE_ENTER,
-                    x, y, spaceState, inputTransaction, handler);
+            // TODO: remove this object
+            final InputTransaction tmpTransaction = new InputTransaction(
+                    inputTransaction.mSettingsValues, inputTransaction.mKeyCode,
+                    inputTransaction.mX, inputTransaction.mY, inputTransaction.mTimestamp,
+                    inputTransaction.mSpaceState, inputTransaction.mShiftState);
+            didAutoCorrect = handleNonSpecialCharacter(tmpTransaction, handler);
             break;
         case Constants.CODE_ALPHA_FROM_EMOJI:
             // Note: Switching back from Emoji keyboard to the main keyboard is being handled in
             // {@link KeyboardState#onCodeInput(int,int)}.
             break;
         default:
-            didAutoCorrect = handleNonSpecialCharacter(settingsValues,
-                    code, x, y, spaceState, inputTransaction, handler);
+            didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
             break;
         }
         // Reset after any single keystroke, except shift, capslock, and symbol-shift
-        if (!didAutoCorrect && code != Constants.CODE_SHIFT
-                && code != Constants.CODE_CAPSLOCK
-                && code != Constants.CODE_SWITCH_ALPHA_SYMBOL)
+        if (!didAutoCorrect && inputTransaction.mKeyCode != Constants.CODE_SHIFT
+                && inputTransaction.mKeyCode != Constants.CODE_CAPSLOCK
+                && inputTransaction.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
             mLastComposedWord.deactivate();
-        if (Constants.CODE_DELETE != code) {
+        if (Constants.CODE_DELETE != inputTransaction.mKeyCode) {
             mEnteredText = null;
         }
         mConnection.endBatchEdit();
-        switch (inputTransaction.getRequiredShiftUpdate()) {
-        case InputTransaction.SHIFT_UPDATE_LATER:
-            mLatinIME.mHandler.postUpdateShiftState();
-            break;
-        case InputTransaction.SHIFT_UPDATE_NOW:
-            keyboardSwitcher.updateShiftState();
-            break;
-        default: // SHIFT_NO_UPDATE
-        }
+        return inputTransaction;
     }
 
     public void onStartBatchInput(final SettingsValues settingsValues,
@@ -628,33 +621,26 @@
      * manage keyboard-related stuff like shift, language switch, settings, layout switch, or
      * any key that results in multiple code points like the ".com" key.
      *
-     * @param settingsValues The current settings values.
-     * @param codePoint the code point associated with the key.
-     * @param x the x-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
-     * @param y the y-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
-     * @param spaceState the space state at start of the batch input.
      * @param inputTransaction The transaction in progress.
      * @return whether this caused an auto-correction to happen.
      */
-    private boolean handleNonSpecialCharacter(final SettingsValues settingsValues,
-            final int codePoint, final int x, final int y, final int spaceState,
-            final InputTransaction inputTransaction,
+    private boolean handleNonSpecialCharacter(final InputTransaction inputTransaction,
             // TODO: remove this argument
             final LatinIME.UIHandler handler) {
         mSpaceState = SpaceState.NONE;
         final boolean didAutoCorrect;
-        if (settingsValues.isWordSeparator(codePoint)
-                || Character.getType(codePoint) == Character.OTHER_SYMBOL) {
-            didAutoCorrect = handleSeparator(settingsValues, codePoint,
-                    Constants.SUGGESTION_STRIP_COORDINATE == x, spaceState, inputTransaction,
-                    handler);
-            if (settingsValues.mIsInternal) {
-                LatinImeLoggerUtils.onSeparator((char)codePoint, x, y);
+        if (inputTransaction.mSettingsValues.isWordSeparator(inputTransaction.mKeyCode)
+                || Character.getType(inputTransaction.mKeyCode) == Character.OTHER_SYMBOL) {
+            didAutoCorrect = handleSeparator(inputTransaction,
+                    Constants.SUGGESTION_STRIP_COORDINATE == inputTransaction.mX, handler);
+            if (inputTransaction.mSettingsValues.mIsInternal) {
+                LatinImeLoggerUtils.onSeparator((char)inputTransaction.mKeyCode,
+                        inputTransaction.mX, inputTransaction.mY);
             }
         } else {
             didAutoCorrect = false;
-            if (SpaceState.PHANTOM == spaceState) {
-                if (settingsValues.mIsInternal) {
+            if (SpaceState.PHANTOM == inputTransaction.mSpaceState) {
+                if (inputTransaction.mSettingsValues.mIsInternal) {
                     if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
                         LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ",
                                 mWordComposer);
@@ -666,11 +652,10 @@
                     resetEntireInputState(mConnection.getExpectedSelectionStart(),
                             mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
                 } else {
-                    commitTyped(settingsValues, LastComposedWord.NOT_A_SEPARATOR);
+                    commitTyped(inputTransaction.mSettingsValues, LastComposedWord.NOT_A_SEPARATOR);
                 }
             }
-            handleNonSeparator(settingsValues, codePoint, x, y, spaceState,
-                    inputTransaction, handler);
+            handleNonSeparator(inputTransaction.mSettingsValues, inputTransaction, handler);
         }
         return didAutoCorrect;
     }
@@ -678,14 +663,9 @@
     /**
      * Handle a non-separator.
      * @param settingsValues The current settings values.
-     * @param codePoint the code point associated with the key.
-     * @param x the x-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
-     * @param y the y-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
-     * @param spaceState the space state at start of the batch input.
      * @param inputTransaction The transaction in progress.
      */
     private void handleNonSeparator(final SettingsValues settingsValues,
-            final int codePoint, final int x, final int y, final int spaceState,
             final InputTransaction inputTransaction,
             // TODO: Remove this argument
             final LatinIME.UIHandler handler) {
@@ -696,7 +676,8 @@
 
         // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
         // See onStartBatchInput() to see how to do it.
-        if (SpaceState.PHANTOM == spaceState && !settingsValues.isWordConnector(codePoint)) {
+        if (SpaceState.PHANTOM == inputTransaction.mSpaceState
+                && !settingsValues.isWordConnector(inputTransaction.mKeyCode)) {
             if (isComposingWord) {
                 // Sanity check
                 throw new RuntimeException("Should not be composing here");
@@ -718,7 +699,7 @@
         if (!isComposingWord
         // We only start composing if this is a word code point. Essentially that means it's a
         // a letter or a word connector.
-                && settingsValues.isWordCodePoint(codePoint)
+                && settingsValues.isWordCodePoint(inputTransaction.mKeyCode)
         // We never go into composing state if suggestions are not requested.
                 && settingsValues.isSuggestionsRequested() &&
         // In languages with spaces, we only start composing a word when we are not already
@@ -729,8 +710,8 @@
             // the character is a single quote or a dash. The idea here is, single quote and dash
             // are not separators and they should be treated as normal characters, except in the
             // first position where they should not start composing a word.
-            isComposingWord = (Constants.CODE_SINGLE_QUOTE != codePoint
-                    && Constants.CODE_DASH != codePoint);
+            isComposingWord = (Constants.CODE_SINGLE_QUOTE != inputTransaction.mKeyCode
+                    && Constants.CODE_DASH != inputTransaction.mKeyCode);
             // Here we don't need to reset the last composed word. It will be reset
             // when we commit this one, if we ever do; if on the other hand we backspace
             // it entirely and resume suggestions on the previous word, we'd like to still
@@ -738,7 +719,7 @@
             resetComposingState(false /* alsoResetLastComposedWord */);
         }
         if (isComposingWord) {
-            mWordComposer.add(codePoint, x, y);
+            mWordComposer.add(inputTransaction.mKeyCode, inputTransaction.mX, inputTransaction.mY);
             // If it's the first letter, make note of auto-caps state
             if (mWordComposer.size() == 1) {
                 // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
@@ -750,10 +731,10 @@
             mConnection.setComposingText(getTextWithUnderline(
                     mWordComposer.getTypedWord()), 1);
         } else {
-            final boolean swapWeakSpace = maybeStripSpace(settingsValues,
-                    codePoint, spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
+            final boolean swapWeakSpace = maybeStripSpace(inputTransaction,
+                    Constants.SUGGESTION_STRIP_COORDINATE == inputTransaction.mX);
 
-            sendKeyCodePoint(settingsValues, codePoint);
+            sendKeyCodePoint(settingsValues, inputTransaction.mKeyCode);
 
             if (swapWeakSpace) {
                 swapSwapperAndSpace(inputTransaction);
@@ -764,28 +745,26 @@
         }
         handler.postUpdateSuggestionStrip();
         if (settingsValues.mIsInternal) {
-            LatinImeLoggerUtils.onNonSeparator((char)codePoint, x, y);
+            LatinImeLoggerUtils.onNonSeparator((char)inputTransaction.mKeyCode, inputTransaction.mX,
+                    inputTransaction.mY);
         }
     }
 
     /**
      * Handle input of a separator code point.
-     * @param settingsValues The current settings values.
-     * @param codePoint the code point associated with the key.
-     * @param isFromSuggestionStrip whether this code point comes from the suggestion strip.
-     * @param spaceState the space state at start of the batch input.
      * @param inputTransaction The transaction in progress.
+     * @param isFromSuggestionStrip whether this code point comes from the suggestion strip.
      * @return whether this caused an auto-correction to happen.
      */
-    private boolean handleSeparator(final SettingsValues settingsValues,
-            final int codePoint, final boolean isFromSuggestionStrip, final int spaceState,
-            final InputTransaction inputTransaction,
+    private boolean handleSeparator(final InputTransaction inputTransaction,
+            final boolean isFromSuggestionStrip,
             // TODO: remove this argument
             final LatinIME.UIHandler handler) {
         boolean didAutoCorrect = false;
         // We avoid sending spaces in languages without spaces if we were composing.
-        final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint
-                && !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
+        final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == inputTransaction.mKeyCode
+                && !inputTransaction.mSettingsValues.mSpacingAndPunctuations
+                        .mCurrentLanguageHasSpaces
                 && mWordComposer.isComposingWord();
         if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
             // If we are in the middle of a recorrection, we need to commit the recorrection
@@ -795,47 +774,50 @@
         }
         // isComposingWord() may have changed since we stored wasComposing
         if (mWordComposer.isComposingWord()) {
-            if (settingsValues.mCorrectionEnabled) {
+            if (inputTransaction.mSettingsValues.mCorrectionEnabled) {
                 final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
-                        : StringUtils.newSingleCodePointString(codePoint);
-                commitCurrentAutoCorrection(settingsValues, separator, handler);
+                        : StringUtils.newSingleCodePointString(inputTransaction.mKeyCode);
+                commitCurrentAutoCorrection(inputTransaction.mSettingsValues, separator, handler);
                 didAutoCorrect = true;
             } else {
-                commitTyped(settingsValues, StringUtils.newSingleCodePointString(codePoint));
+                commitTyped(inputTransaction.mSettingsValues,
+                        StringUtils.newSingleCodePointString(inputTransaction.mKeyCode));
             }
         }
 
-        final boolean swapWeakSpace = maybeStripSpace(settingsValues, codePoint, spaceState,
-                isFromSuggestionStrip);
+        final boolean swapWeakSpace = maybeStripSpace(inputTransaction, isFromSuggestionStrip);
 
-        final boolean isInsideDoubleQuoteOrAfterDigit = Constants.CODE_DOUBLE_QUOTE == codePoint
+        final boolean isInsideDoubleQuoteOrAfterDigit =
+                Constants.CODE_DOUBLE_QUOTE == inputTransaction.mKeyCode
                 && mConnection.isInsideDoubleQuoteOrAfterDigit();
 
         final boolean needsPrecedingSpace;
-        if (SpaceState.PHANTOM != spaceState) {
+        if (SpaceState.PHANTOM != inputTransaction.mSpaceState) {
             needsPrecedingSpace = false;
-        } else if (Constants.CODE_DOUBLE_QUOTE == codePoint) {
+        } else if (Constants.CODE_DOUBLE_QUOTE == inputTransaction.mKeyCode) {
             // Double quotes behave like they are usually preceded by space iff we are
             // not inside a double quote or after a digit.
             needsPrecedingSpace = !isInsideDoubleQuoteOrAfterDigit;
         } else {
-            needsPrecedingSpace = settingsValues.isUsuallyPrecededBySpace(codePoint);
+            needsPrecedingSpace = inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(
+                    inputTransaction.mKeyCode);
         }
 
         if (needsPrecedingSpace) {
-            promotePhantomSpace(settingsValues);
+            promotePhantomSpace(inputTransaction.mSettingsValues);
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.latinIME_handleSeparator(codePoint, mWordComposer.isComposingWord());
+            ResearchLogger.latinIME_handleSeparator(inputTransaction.mKeyCode,
+                    mWordComposer.isComposingWord());
         }
 
         if (!shouldAvoidSendingCode) {
-            sendKeyCodePoint(settingsValues, codePoint);
+            sendKeyCodePoint(inputTransaction.mSettingsValues, inputTransaction.mKeyCode);
         }
 
-        if (Constants.CODE_SPACE == codePoint) {
-            if (settingsValues.isSuggestionsRequested()) {
-                if (maybeDoubleSpacePeriod(settingsValues, handler)) {
+        if (Constants.CODE_SPACE == inputTransaction.mKeyCode) {
+            if (inputTransaction.mSettingsValues.isSuggestionsRequested()) {
+                if (maybeDoubleSpacePeriod(inputTransaction.mSettingsValues, handler)) {
                     inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
                     mSpaceState = SpaceState.DOUBLE;
                 } else if (!mSuggestedWords.isPunctuationSuggestions()) {
@@ -849,9 +831,10 @@
             if (swapWeakSpace) {
                 swapSwapperAndSpace(inputTransaction);
                 mSpaceState = SpaceState.SWAP_PUNCTUATION;
-            } else if ((SpaceState.PHANTOM == spaceState
-                    && settingsValues.isUsuallyFollowedBySpace(codePoint))
-                    || (Constants.CODE_DOUBLE_QUOTE == codePoint
+            } else if ((SpaceState.PHANTOM == inputTransaction.mSpaceState
+                    && inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(
+                            inputTransaction.mKeyCode))
+                    || (Constants.CODE_DOUBLE_QUOTE == inputTransaction.mKeyCode
                             && isInsideDoubleQuoteOrAfterDigit)) {
                 // If we are in phantom space state, and the user presses a separator, we want to
                 // stay in phantom space state so that the next keypress has a chance to add the
@@ -878,12 +861,9 @@
 
     /**
      * Handle a press on the backspace key.
-     * @param settingsValues The current settings values.
-     * @param spaceState The space state at start of this batch edit.
      * @param inputTransaction The transaction in progress.
      */
-    private void handleBackspace(final SettingsValues settingsValues, final int spaceState,
-            final InputTransaction inputTransaction,
+    private void handleBackspace(final InputTransaction inputTransaction,
             // TODO: remove this argument
             final LatinIME.UIHandler handler) {
         mSpaceState = SpaceState.NONE;
@@ -922,10 +902,10 @@
             }
         } else {
             if (mLastComposedWord.canRevertCommit()) {
-                if (settingsValues.mIsInternal) {
+                if (inputTransaction.mSettingsValues.mIsInternal) {
                     LatinImeLoggerUtils.onAutoCorrectionCancellation();
                 }
-                revertCommit(settingsValues, handler);
+                revertCommit(inputTransaction.mSettingsValues, handler);
                 return;
             }
             if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
@@ -942,14 +922,14 @@
                 // reverting any autocorrect at this point. So we can safely return.
                 return;
             }
-            if (SpaceState.DOUBLE == spaceState) {
+            if (SpaceState.DOUBLE == inputTransaction.mSpaceState) {
                 handler.cancelDoubleSpacePeriodTimer();
                 if (mConnection.revertDoubleSpacePeriod()) {
                     // No need to reset mSpaceState, it has already be done (that's why we
                     // receive it as a parameter)
                     return;
                 }
-            } else if (SpaceState.SWAP_PUNCTUATION == spaceState) {
+            } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
                 if (mConnection.revertSwapPunctuation()) {
                     // Likewise
                     return;
@@ -975,8 +955,8 @@
                     // This should never happen.
                     Log.e(TAG, "Backspace when we don't know the selection position");
                 }
-                if (settingsValues.isBeforeJellyBean() ||
-                        settingsValues.mInputAttributes.isTypeNull()) {
+                if (inputTransaction.mSettingsValues.isBeforeJellyBean() ||
+                        inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()) {
                     // There are two possible reasons to send a key event: either the field has
                     // type TYPE_NULL, in which case the keyboard should send events, or we are
                     // running in backward compatibility mode. Before Jelly bean, the keyboard
@@ -1022,11 +1002,12 @@
                     }
                 }
             }
-            if (settingsValues.isSuggestionStripVisible()
-                    && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
+            if (inputTransaction.mSettingsValues.isSuggestionStripVisible()
+                    && inputTransaction.mSettingsValues.mSpacingAndPunctuations
+                            .mCurrentLanguageHasSpaces
                     && !mConnection.isCursorFollowedByWordCharacter(
-                            settingsValues.mSpacingAndPunctuations)) {
-                restartSuggestionsOnWordTouchedByCursor(settingsValues,
+                            inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
+                restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
                         true /* includeResumedWordInSuggestions */);
             }
             // We just removed at least one character. We need to update the auto-caps state.
@@ -1064,22 +1045,28 @@
 
     /*
      * Strip a trailing space if necessary and returns whether it's a swap weak space situation.
-     * @param settingsValues The current settings values.
-     * @param codePoint The code point that is about to be inserted.
-     * @param spaceState The space state at start of this batch edit.
+     * @param inputTransaction The transaction in progress.
      * @param isFromSuggestionStrip Whether this code point is coming from the suggestion strip.
      * @return whether we should swap the space instead of removing it.
      */
-    private boolean maybeStripSpace(final SettingsValues settingsValues,
-            final int code, final int spaceState, final boolean isFromSuggestionStrip) {
-        if (Constants.CODE_ENTER == code && SpaceState.SWAP_PUNCTUATION == spaceState) {
+    private boolean maybeStripSpace(final InputTransaction inputTransaction,
+            final boolean isFromSuggestionStrip) {
+        if (Constants.CODE_ENTER == inputTransaction.mKeyCode &&
+                SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
             mConnection.removeTrailingSpace();
             return false;
         }
-        if ((SpaceState.WEAK == spaceState || SpaceState.SWAP_PUNCTUATION == spaceState)
+        if ((SpaceState.WEAK == inputTransaction.mSpaceState
+                || SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState)
                 && isFromSuggestionStrip) {
-            if (settingsValues.isUsuallyPrecededBySpace(code)) return false;
-            if (settingsValues.isUsuallyFollowedBySpace(code)) return true;
+            if (inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(
+                    inputTransaction.mKeyCode)) {
+                return false;
+            }
+            if (inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(
+                    inputTransaction.mKeyCode)) {
+                return true;
+            }
             mConnection.removeTrailingSpace();
         }
         return false;
@@ -1313,7 +1300,10 @@
                     SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
                     SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
         }
-        if (!isResumableWord(settingsValues, typedWord)) return;
+        if (!isResumableWord(settingsValues, typedWord)) {
+            mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+            return;
+        }
         int i = 0;
         for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) {
             for (final String s : span.getSuggestions()) {
@@ -1738,6 +1728,7 @@
      * @param settingsValues the current values of the settings.
      * @param codePoint the code point to send.
      */
+    // TODO: replace these two parameters with an InputTransaction
     private void sendKeyCodePoint(final SettingsValues settingsValues, final int codePoint) {
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_sendKeyCodePoint(codePoint);
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 90e7400..caf3cf3 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -222,54 +222,6 @@
         return countSize;
     }
 
-    private static final int HEADER_READING_BUFFER_SIZE = 16384;
-    /**
-     * Convenience method to read the header of a binary file.
-     *
-     * This is quite resource intensive - don't call when performance is critical.
-     *
-     * @param file The file to read.
-     * @param offset The offset in the file where to start reading the data.
-     * @param length The length of the data file.
-     * @return the header of the specified dictionary file.
-     */
-    private static DictionaryHeader getDictionaryFileHeader(
-            final File file, final long offset, final long length)
-            throws FileNotFoundException, IOException, UnsupportedFormatException {
-        final byte[] buffer = new byte[HEADER_READING_BUFFER_SIZE];
-        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, offset, length,
-                new DictDecoder.DictionaryBufferFactory() {
-                    @Override
-                    public DictBuffer getDictionaryBuffer(File file)
-                            throws FileNotFoundException, IOException {
-                        final FileInputStream inStream = new FileInputStream(file);
-                        try {
-                            inStream.skip(offset);
-                            inStream.read(buffer);
-                            return new ByteArrayDictBuffer(buffer);
-                        } finally {
-                            inStream.close();
-                        }
-                    }
-                });
-        if (dictDecoder == null) {
-            return null;
-        }
-        return dictDecoder.readHeader();
-    }
-
-    public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file, final long offset,
-            final long length) {
-        try {
-            final DictionaryHeader header = getDictionaryFileHeader(file, offset, length);
-            return header;
-        } catch (UnsupportedFormatException e) {
-            return null;
-        } catch (IOException e) {
-            return null;
-        }
-    }
-
     /**
      * Helper method to hide the actual value of the no children address.
      */
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 9abecbf..484bb4b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -341,6 +341,7 @@
         return null;
     }
 
+    @UsedForTesting
     public static DictDecoder getDictDecoder(final File dictFile, final long offset,
             final long length, final DictionaryBufferFactory factory) {
         if (dictFile.isDirectory()) {
@@ -351,6 +352,7 @@
         return null;
     }
 
+    @UsedForTesting
     public static DictDecoder getDictDecoder(final File dictFile, final long offset,
             final long length) {
         return getDictDecoder(dictFile, offset, length, DictDecoder.USE_READONLY_BYTEBUFFER);
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java
index ae1e443..ab24fbc 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java
@@ -123,6 +123,7 @@
     private final DictionaryBufferFactory mBufferFactory;
     protected DictBuffer mDictBuffer;
 
+    @UsedForTesting
     /* package */ Ver2DictDecoder(final File file, final long offset, final long length,
             final int factoryFlag) {
         mDictionaryBinaryFile = file;
diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
index 6872285..6388300 100644
--- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
@@ -17,8 +17,13 @@
 package com.android.inputmethod.latin.utils;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.personalization.PersonalizationHelper;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.Locale;
 import java.util.Map;
 
@@ -39,6 +44,26 @@
     private static native int editDistanceNative(int[] before, int[] after);
     private static native int setCurrentTimeForTestNative(int currentTime);
 
+    public static DictionaryHeader getHeader(final File dictFile)
+            throws IOException, UnsupportedFormatException {
+        return getHeaderWithOffsetAndLength(dictFile, 0 /* offset */, dictFile.length());
+    }
+
+    public static DictionaryHeader getHeaderWithOffsetAndLength(final File dictFile,
+            final long offset, final long length) throws IOException, UnsupportedFormatException {
+        // dictType is never used for reading the header. Passing an empty string.
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(
+                dictFile.getAbsolutePath(), offset, length,
+                true /* useFullEditDistance */, null /* locale */, "" /* dictType */,
+                false /* isUpdatable */);
+        final DictionaryHeader header = binaryDictionary.getHeader();
+        binaryDictionary.close();
+        if (header == null) {
+            throw new IOException();
+        }
+        return header;
+    }
+
     public static boolean createEmptyDictFile(final String filePath, final long dictVersion,
             final Locale locale, final Map<String, String> attributeMap) {
         final String[] keyArray = new String[attributeMap.size()];
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index a155565..e531d4b 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -30,9 +30,11 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
 import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Locale;
@@ -283,7 +285,20 @@
     }
 
     public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file) {
-        return BinaryDictIOUtils.getDictionaryFileHeaderOrNull(file, 0, file.length());
+        return getDictionaryFileHeaderOrNull(file, 0, file.length());
+    }
+
+    private static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
+            final long offset, final long length) {
+        try {
+            final DictionaryHeader header =
+                    BinaryDictionaryUtils.getHeaderWithOffsetAndLength(file, offset, length);
+            return header;
+        } catch (UnsupportedFormatException e) {
+            return null;
+        } catch (IOException e) {
+            return null;
+        }
     }
 
     /**
@@ -294,7 +309,7 @@
      */
     private static DictionaryInfo createDictionaryInfoFromFileAddress(
             final AssetFileAddress fileAddress) {
-        final DictionaryHeader header = BinaryDictIOUtils.getDictionaryFileHeaderOrNull(
+        final DictionaryHeader header = getDictionaryFileHeaderOrNull(
                 new File(fileAddress.mFilename), fileAddress.mOffset, fileAddress.mLength);
         if (header == null) {
             return null;
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index 3b3da96..9657b9d 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -31,15 +31,12 @@
     -Wwrite-strings -Wfloat-equal -Wpointer-arith -Winit-self -Wredundant-decls \
     -Woverloaded-virtual -Wstrict-null-sentinel -Wsign-promo -Wno-system-headers
 
-ifeq ($(TARGET_ARCH), arm)
-ifeq ($(TARGET_GCC_VERSION), 4.6)
-LOCAL_CFLAGS += -Winline
-endif # TARGET_GCC_VERSION
-endif # TARGET_ARCH
-
 # To suppress compiler warnings for unused variables/functions used for debug features etc.
 LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function
 
+# For C++11
+LOCAL_CFLAGS += -std=c++11
+
 include $(LOCAL_PATH)/NativeFileList.mk
 
 LOCAL_SRC_FILES := \
@@ -64,7 +61,7 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_SDK_VERSION := 14
-LOCAL_NDK_STL_VARIANT := stlport_static
+LOCAL_NDK_STL_VARIANT := gnustl_static
 
 include $(BUILD_STATIC_LIBRARY)
 ######################################
@@ -87,7 +84,7 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_SDK_VERSION := 14
-LOCAL_NDK_STL_VARIANT := stlport_static
+LOCAL_NDK_STL_VARIANT := gnustl_static
 LOCAL_LDFLAGS += -ldl
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/native/jni/NativeFileList.mk b/native/jni/NativeFileList.mk
index 82237dc..1f58246 100644
--- a/native/jni/NativeFileList.mk
+++ b/native/jni/NativeFileList.mk
@@ -32,7 +32,6 @@
         digraph_utils.cpp \
         error_type_utils.cpp \
         multi_bigram_map.cpp \
-        suggestions_output_utils.cpp \
         word_property.cpp) \
     $(addprefix suggest/core/layout/, \
         additional_proximity_chars.cpp \
@@ -41,6 +40,7 @@
         proximity_info_state.cpp \
         proximity_info_state_utils.cpp) \
     suggest/core/policy/weighting.cpp \
+    suggest/core/result/suggestions_output_utils.cpp \
     suggest/core/session/dic_traverse_session.cpp \
     $(addprefix suggest/policyimpl/dictionary/, \
         header/header_policy.cpp \
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 22cc4c0..4c57af0 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -344,21 +344,18 @@
 #define MAX_POINTER_COUNT 1
 #define MAX_POINTER_COUNT_G 2
 
-template<typename T> AK_FORCE_INLINE const T &min(const T &a, const T &b) { return a < b ? a : b; }
-template<typename T> AK_FORCE_INLINE const T &max(const T &a, const T &b) { return a > b ? a : b; }
-
 // DEBUG
 #define INPUTLENGTH_FOR_DEBUG (-1)
 #define MIN_OUTPUT_INDEX_FOR_DEBUG (-1)
 
 #define DISALLOW_DEFAULT_CONSTRUCTOR(TypeName) \
-  TypeName()
+  TypeName() = delete
 
 #define DISALLOW_COPY_CONSTRUCTOR(TypeName) \
-  TypeName(const TypeName&)
+  TypeName(const TypeName&) = delete
 
 #define DISALLOW_ASSIGNMENT_OPERATOR(TypeName) \
-  void operator=(const TypeName&)
+  void operator=(const TypeName&) = delete
 
 #define DISALLOW_COPY_AND_ASSIGN(TypeName) \
   DISALLOW_COPY_CONSTRUCTOR(TypeName);     \
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
index 7461f0c..1f02731 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DIC_NODE_PRIORITY_QUEUE_H
 #define LATINIME_DIC_NODE_PRIORITY_QUEUE_H
 
+#include <algorithm>
 #include <queue>
 #include <vector>
 
@@ -49,7 +50,7 @@
 
     AK_FORCE_INLINE void setMaxSize(const int maxSize) {
         ASSERT(maxSize <= mCapacity);
-        mMaxSize = min(maxSize, mCapacity);
+        mMaxSize = std::min(maxSize, mCapacity);
     }
 
     AK_FORCE_INLINE void clearAndResizeToCapacity() {
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index 71bcab6..a6ea68c 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -16,6 +16,7 @@
 
 #include "suggest/core/dicnode/dic_node_utils.h"
 
+#include <algorithm>
 #include <cstring>
 
 #include "suggest/core/dicnode/dic_node.h"
@@ -117,7 +118,7 @@
         }
         actualLength0 = i + 1;
     }
-    actualLength0 = min(actualLength0, MAX_WORD_LENGTH);
+    actualLength0 = std::min(actualLength0, MAX_WORD_LENGTH);
     memmove(dest, src0, actualLength0 * sizeof(dest[0]));
     if (!src1 || length1 == 0) {
         return actualLength0;
@@ -129,7 +130,7 @@
         }
         actualLength1 = i + 1;
     }
-    actualLength1 = min(actualLength1, MAX_WORD_LENGTH - actualLength0);
+    actualLength1 = std::min(actualLength1, MAX_WORD_LENGTH - actualLength0);
     memmove(&dest[actualLength0], src1, actualLength1 * sizeof(dest[0]));
     return actualLength0 + actualLength1;
 }
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
index 8493b6a..c31c056 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DIC_NODES_CACHE_H
 #define LATINIME_DIC_NODES_CACHE_H
 
+#include <algorithm>
 #include <stdint.h>
 
 #include "defines.h"
@@ -51,7 +52,7 @@
         // We want to use the max capacity for the current active dic node queue.
         mActiveDicNodes->clearAndResizeToCapacity();
         // nextActiveSize is used to limit the next iteration's active dic node size.
-        const int nextActiveSizeFittingToTheCapacity = min(nextActiveSize, getCacheCapacity());
+        const int nextActiveSizeFittingToTheCapacity = std::min(nextActiveSize, getCacheCapacity());
         mNextActiveDicNodes->clearAndResize(nextActiveSizeFittingToTheCapacity);
         mTerminalDicNodes->clearAndResize(terminalSize);
         // We want to use the max capacity for the cached dic nodes that will be used for the
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
index fc68510..abafc0e 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DIC_NODE_STATE_OUTPUT_H
 #define LATINIME_DIC_NODE_STATE_OUTPUT_H
 
+#include <algorithm>
 #include <cstring> // for memmove()
 #include <stdint.h>
 
@@ -49,7 +50,8 @@
     void addMergedNodeCodePoints(const uint16_t mergedNodeCodePointCount,
             const int *const mergedNodeCodePoints) {
         if (mergedNodeCodePoints) {
-            const int additionalCodePointCount = min(static_cast<int>(mergedNodeCodePointCount),
+            const int additionalCodePointCount = std::min(
+                    static_cast<int>(mergedNodeCodePointCount),
                     MAX_WORD_LENGTH - mOutputtedCodePointCount);
             memmove(&mCodePointsBuf[mOutputtedCodePointCount], mergedNodeCodePoints,
                     additionalCodePointCount * sizeof(mCodePointsBuf[0]));
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
index e7108d9..7868f78 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DIC_NODE_STATE_PREVWORD_H
 #define LATINIME_DIC_NODE_STATE_PREVWORD_H
 
+#include <algorithm>
 #include <cstring> // for memset() and memmove()
 #include <stdint.h>
 
@@ -69,7 +70,7 @@
             const int prevWordNodePos, const int *const src0, const int16_t length0,
             const int *const src1, const int16_t length1,
             const int prevWordSecondWordFirstInputIndex, const int lastInputIndex) {
-        mPrevWordCount = min(prevWordCount, static_cast<int16_t>(MAX_RESULTS));
+        mPrevWordCount = std::min(prevWordCount, static_cast<int16_t>(MAX_RESULTS));
         mPrevWordProbability = prevWordProbability;
         mPrevWordPtNodePos = prevWordNodePos;
         int twoWordsLen =
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
index 11c201e..18b7d73 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DIC_NODE_STATE_SCORING_H
 #define LATINIME_DIC_NODE_STATE_SCORING_H
 
+#include <algorithm>
 #include <stdint.h>
 
 #include "defines.h"
@@ -199,7 +200,7 @@
             mNormalizedCompoundDistance = mSpatialDistance + mLanguageDistance;
         } else {
             mNormalizedCompoundDistance = (mSpatialDistance + mLanguageDistance)
-                    / static_cast<float>(max(1, totalInputIndex));
+                    / static_cast<float>(std::max(1, totalInputIndex));
         }
     }
 };
diff --git a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
index d0b96b0..0859df4 100644
--- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-#include <cstring>
-
 #define LOG_TAG "LatinIME: bigram_dictionary.cpp"
 
 #include "bigram_dictionary.h"
 
+#include <algorithm>
+#include <cstring>
+
 #include "defines.h"
 #include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
 #include "suggest/core/dictionary/dictionary.h"
@@ -142,7 +143,7 @@
                 outBigramCodePoints, outputTypes);
         ++bigramCount;
     }
-    return min(bigramCount, MAX_RESULTS);
+    return std::min(bigramCount, MAX_RESULTS);
 }
 
 // Returns a pointer to the start of the bigram list.
diff --git a/native/jni/src/suggest/core/layout/proximity_info.cpp b/native/jni/src/suggest/core/layout/proximity_info.cpp
index ee8e59e..8b3ae4d 100644
--- a/native/jni/src/suggest/core/layout/proximity_info.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info.cpp
@@ -18,6 +18,7 @@
 
 #include "suggest/core/layout/proximity_info.h"
 
+#include <algorithm>
 #include <cstring>
 #include <cmath>
 
@@ -63,7 +64,7 @@
                           static_cast<float>(mostCommonKeyWidth))),
           CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
           CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
-          KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
+          KEY_COUNT(std::min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
           KEYBOARD_WIDTH(keyboardWidth), KEYBOARD_HEIGHT(keyboardHeight),
           KEYBOARD_HYPOTENUSE(hypotf(KEYBOARD_WIDTH, KEYBOARD_HEIGHT)),
           HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.cpp b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
index 40c3448..2919904 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
@@ -18,6 +18,7 @@
 
 #include "suggest/core/layout/proximity_info_state.h"
 
+#include <algorithm>
 #include <cstring> // for memset() and memmove()
 #include <sstream> // for debug prints
 #include <vector>
@@ -171,7 +172,7 @@
     const int keyId = mProximityInfo->getKeyIndexOf(codePoint);
     if (keyId != NOT_AN_INDEX) {
         const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
-        return min(mSampledNormalizedSquaredLengthCache[index], mMaxPointToKeyLength);
+        return std::min(mSampledNormalizedSquaredLengthCache[index], mMaxPointToKeyLength);
     }
     if (CharUtils::isIntentionalOmissionCodePoint(codePoint)) {
         return 0.0f;
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
index bc4ca8e..867f598 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
@@ -16,6 +16,7 @@
 
 #include "suggest/core/layout/proximity_info_state_utils.h"
 
+#include <algorithm>
 #include <cmath>
 #include <cstring> // for memset()
 #include <sstream> // for debug prints
@@ -240,7 +241,7 @@
         // Calculate velocity by using distances and durations of
         // ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION points for both forward and
         // backward.
-        const int forwardNumPoints = min(inputSize - 1,
+        const int forwardNumPoints = std::min(inputSize - 1,
                 index + ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION);
         for (int j = index; j < forwardNumPoints; ++j) {
             if (i < sampledInputSize - 1 && j >= (*sampledInputIndice)[i + 1]) {
@@ -250,7 +251,7 @@
                     xCoordinates[j + 1], yCoordinates[j + 1]);
             duration += times[j + 1] - times[j];
         }
-        const int backwardNumPoints = max(0,
+        const int backwardNumPoints = std::max(0,
                 index - ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION);
         for (int j = index - 1; j >= backwardNumPoints; --j) {
             if (i > 0 && j < (*sampledInputIndice)[i - 1]) {
@@ -272,7 +273,7 @@
 
     // Direction calculation.
     sampledDirections->resize(sampledInputSize - 1);
-    for (int i = max(0, lastSavedInputSize - 1); i < sampledInputSize - 1; ++i) {
+    for (int i = std::max(0, lastSavedInputSize - 1); i < sampledInputSize - 1; ++i) {
         (*sampledDirections)[i] = getDirection(sampledInputXs, sampledInputYs, i, i + 1);
     }
     return averageSpeed;
@@ -609,7 +610,7 @@
         const int inputIndex, const int keyId) {
     if (keyId != NOT_AN_INDEX) {
         const int index = inputIndex * keyCount + keyId;
-        return min((*sampledNormalizedSquaredLengthCache)[index], maxPointToKeyLength);
+        return std::min((*sampledNormalizedSquaredLengthCache)[index], maxPointToKeyLength);
     }
     // If the char is not a key on the keyboard then return the max length.
     return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
@@ -650,13 +651,13 @@
         }
 
         if (i == 0) {
-            skipProbability *= min(1.0f,
+            skipProbability *= std::min(1.0f,
                     nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT
                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS);
             // Promote the first point
             skipProbability *= ProximityInfoParams::SKIP_FIRST_POINT_PROBABILITY;
         } else if (i == sampledInputSize - 1) {
-            skipProbability *= min(1.0f,
+            skipProbability *= std::min(1.0f,
                     nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT_FOR_LAST
                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS_FOR_LAST);
             // Promote the last point
@@ -667,17 +668,17 @@
                     && speedRate
                             < (*sampledSpeedRates)[i + 1] - ProximityInfoParams::SPEED_MARGIN) {
                 if (currentAngle < ProximityInfoParams::CORNER_ANGLE_THRESHOLD) {
-                    skipProbability *= min(1.0f, speedRate
+                    skipProbability *= std::min(1.0f, speedRate
                             * ProximityInfoParams::SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY);
                 } else {
                     // If the angle is small enough, we promote this point more. (e.g. pit vs put)
-                    skipProbability *= min(1.0f,
+                    skipProbability *= std::min(1.0f,
                             speedRate * ProximityInfoParams::SPEED_WEIGHT_FOR_SKIP_PROBABILITY
                                     + ProximityInfoParams::MIN_SPEED_RATE_FOR_SKIP_PROBABILITY);
                 }
             }
 
-            skipProbability *= min(1.0f,
+            skipProbability *= std::min(1.0f,
                     speedRate * nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT
                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS);
 
@@ -707,10 +708,10 @@
         // (1.0f - skipProbability).
         const float inputCharProbability = 1.0f - skipProbability;
 
-        const float speedxAngleRate = min(speedRate * currentAngle / M_PI_F
+        const float speedxAngleRate = std::min(speedRate * currentAngle / M_PI_F
                 * ProximityInfoParams::SPEEDxANGLE_WEIGHT_FOR_STANDARD_DEVIATION,
                         ProximityInfoParams::MAX_SPEEDxANGLE_RATE_FOR_STANDARD_DEVIATION);
-        const float speedxNearestKeyDistanceRate = min(speedRate * nearestKeyDistance
+        const float speedxNearestKeyDistanceRate = std::min(speedRate * nearestKeyDistance
                 * ProximityInfoParams::SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DEVIATION,
                         ProximityInfoParams::MAX_SPEEDxNEAREST_RATE_FOR_STANDARD_DEVIATION);
         const float sigma = speedxAngleRate + speedxNearestKeyDistanceRate
@@ -827,7 +828,7 @@
 
     // Decrease key probabilities of points which don't have the highest probability of that key
     // among nearby points. Probabilities of the first point and the last point are not suppressed.
-    for (int i = max(start, 1); i < sampledInputSize; ++i) {
+    for (int i = std::max(start, 1); i < sampledInputSize; ++i) {
         for (int j = i + 1; j < sampledInputSize; ++j) {
             if (!suppressCharProbabilities(
                     mostCommonKeyWidth, sampledInputSize, sampledLengthCache, i, j,
@@ -835,7 +836,7 @@
                 break;
             }
         }
-        for (int j = i - 1; j >= max(start, 0); --j) {
+        for (int j = i - 1; j >= std::max(start, 0); --j) {
             if (!suppressCharProbabilities(
                     mostCommonKeyWidth, sampledInputSize, sampledLengthCache, i, j,
                     charProbabilities)) {
@@ -878,7 +879,7 @@
         if (i >= lastSavedInputSize) {
             (*sampledSearchKeySets)[i].reset();
         }
-        for (int j = max(i, lastSavedInputSize); j < sampledInputSize; ++j) {
+        for (int j = std::max(i, lastSavedInputSize); j < sampledInputSize; ++j) {
             // TODO: Investigate if this is required. This may not fail.
             if ((*sampledLengthCache)[j] - (*sampledLengthCache)[i] >= readForwordLength) {
                 break;
@@ -929,7 +930,7 @@
             (*charProbabilities)[index0][NOT_AN_INDEX] += suppression;
 
             // Add the probability of the same key nearby index1
-            const float probabilityGain = min(suppression
+            const float probabilityGain = std::min(suppression
                     * ProximityInfoParams::SUPPRESSION_WEIGHT_FOR_PROBABILITY_GAIN,
                     (*charProbabilities)[index1][NOT_AN_INDEX]
                             * ProximityInfoParams::SKIP_PROBABALITY_WEIGHT_FOR_PROBABILITY_GAIN);
diff --git a/native/jni/src/suggest/core/layout/proximity_info_utils.h b/native/jni/src/suggest/core/layout/proximity_info_utils.h
index bc8d5bc..6d2c11b 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_utils.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_utils.h
@@ -164,12 +164,15 @@
             const int gridWidth, const int mostCommonKeyWidth, const int keyCount,
             const int x, const int y, const int primaryKey, const char *const localeStr,
             const hash_map_compat<int, int> *const codeToKeyMap, int *proximities) {
-        if (x == NOT_A_COORDINATE || y == NOT_A_COORDINATE) {
-            return;
-        }
         const int mostCommonKeyWidthSquare = mostCommonKeyWidth * mostCommonKeyWidth;
         int insertPos = 0;
         proximities[insertPos++] = primaryKey;
+        if (x == NOT_A_COORDINATE || y == NOT_A_COORDINATE) {
+            for (int i = insertPos; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+                proximities[i] = NOT_A_CODE_POINT;
+            }
+            return;
+        }
         const int startIndex = getStartIndexFromCoordinates(x, y, cellHeight, cellWidth, gridWidth);
         if (startIndex >= 0) {
             for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
diff --git a/native/jni/src/suggest/core/layout/touch_position_correction_utils.h b/native/jni/src/suggest/core/layout/touch_position_correction_utils.h
index 9130e87..14074c1 100644
--- a/native/jni/src/suggest/core/layout/touch_position_correction_utils.h
+++ b/native/jni/src/suggest/core/layout/touch_position_correction_utils.h
@@ -17,6 +17,8 @@
 #ifndef LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
 #define LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
 
+#include <algorithm>
+
 #include "defines.h"
 #include "suggest/core/layout/proximity_info_params.h"
 
@@ -34,7 +36,7 @@
         static const float R2 = 1.0f;
         const float x = normalizedSquaredDistance;
         if (!isTouchPositionCorrectionEnabled) {
-            return min(C, x);
+            return std::min(C, x);
         }
 
         // factor is a piecewise linear function like:
diff --git a/native/jni/src/suggest/core/dictionary/suggestions_output_utils.cpp b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
similarity index 97%
rename from native/jni/src/suggest/core/dictionary/suggestions_output_utils.cpp
rename to native/jni/src/suggest/core/result/suggestions_output_utils.cpp
index 07c2e6e..19912f2 100644
--- a/native/jni/src/suggest/core/dictionary/suggestions_output_utils.cpp
+++ b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-#include "suggest/core/dictionary/suggestions_output_utils.h"
+#include "suggest/core/result/suggestions_output_utils.h"
+
+#include <algorithm>
 
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
@@ -36,7 +38,7 @@
 #if DEBUG_EVALUATE_MOST_PROBABLE_STRING
     const int terminalSize = 0;
 #else
-    const int terminalSize = min(MAX_RESULTS,
+    const int terminalSize = std::min(MAX_RESULTS,
             static_cast<int>(traverseSession->getDicTraverseCache()->terminalSize()));
 #endif
     DicNode terminals[MAX_RESULTS]; // Avoiding non-POD variable length array
@@ -245,12 +247,12 @@
             // shortcut entry's score == its base entry's score - 1
             shortcutScore = finalScore;
             // Protection against int underflow
-            shortcutScore = max(S_INT_MIN + 1, shortcutScore) - 1;
+            shortcutScore = std::max(S_INT_MIN + 1, shortcutScore) - 1;
             kind = Dictionary::KIND_SHORTCUT;
         }
         outputTypes[outputWordIndex] = kind;
         outputScores[outputWordIndex] = shortcutScore;
-        outputScores[outputWordIndex] = max(S_INT_MIN + 1, shortcutScore) - 1;
+        outputScores[outputWordIndex] = std::max(S_INT_MIN + 1, shortcutScore) - 1;
         const int startIndex2 = outputWordIndex * MAX_WORD_LENGTH;
         DicNodeUtils::appendTwoWords(0, 0, shortcutTarget, shortcutTargetStringLength,
                 &outputCodePoints[startIndex2]);
diff --git a/native/jni/src/suggest/core/dictionary/suggestions_output_utils.h b/native/jni/src/suggest/core/result/suggestions_output_utils.h
similarity index 100%
rename from native/jni/src/suggest/core/dictionary/suggestions_output_utils.h
rename to native/jni/src/suggest/core/result/suggestions_output_utils.h
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 56acc2d..c3b6703 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -21,11 +21,11 @@
 #include "suggest/core/dicnode/dic_node_vector.h"
 #include "suggest/core/dictionary/dictionary.h"
 #include "suggest/core/dictionary/digraph_utils.h"
-#include "suggest/core/dictionary/suggestions_output_utils.h"
 #include "suggest/core/layout/proximity_info.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 #include "suggest/core/policy/traversal.h"
 #include "suggest/core/policy/weighting.h"
+#include "suggest/core/result/suggestions_output_utils.h"
 #include "suggest/core/session/dic_traverse_session.h"
 
 namespace latinime {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
index 7c7b05c..ecc9fda 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
@@ -16,6 +16,8 @@
 
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
 
+#include <algorithm>
+
 namespace latinime {
 
 // Note that these are corresponding definitions in Java side in DictionaryHeader.
@@ -72,7 +74,7 @@
         outValue[1] = '\0';
         return;
     }
-    const int terminalIndex = min(static_cast<int>(it->second.size()), outValueSize - 1);
+    const int terminalIndex = std::min(static_cast<int>(it->second.size()), outValueSize - 1);
     for (int i = 0; i < terminalIndex; ++i) {
         outValue[i] = it->second[i];
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
index 35e05d7..bac4d4e 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
@@ -16,6 +16,7 @@
 
 #include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
 
+#include <algorithm>
 #include <cmath>
 #include <stdlib.h>
 
@@ -72,7 +73,7 @@
             headerPolicy->getForgettingCurveDurationToLevelDown());
     return sProbabilityTable.getProbability(
             headerPolicy->getForgettingCurveProbabilityValuesTableId(), historicalInfo->getLevel(),
-            min(max(elapsedTimeStepCount, 0), MAX_ELAPSED_TIME_STEP_COUNT));
+            std::min(std::max(elapsedTimeStepCount, 0), MAX_ELAPSED_TIME_STEP_COUNT));
 }
 
 /* static */ int ForgettingCurveUtils::getProbability(const int unigramProbability,
@@ -80,11 +81,11 @@
     if (unigramProbability == NOT_A_PROBABILITY) {
         return NOT_A_PROBABILITY;
     } else if (bigramProbability == NOT_A_PROBABILITY) {
-        return min(backoff(unigramProbability), MAX_PROBABILITY);
+        return std::min(backoff(unigramProbability), MAX_PROBABILITY);
     } else {
         // TODO: Investigate better way to handle bigram probability.
-        return min(max(unigramProbability, bigramProbability + MULTIPLIER_TWO_IN_PROBABILITY_SCALE),
-                MAX_PROBABILITY);
+        return std::min(std::max(unigramProbability,
+                bigramProbability + MULTIPLIER_TWO_IN_PROBABILITY_SCALE), MAX_PROBABILITY);
     }
 }
 
@@ -183,7 +184,7 @@
                                 -1.0f * static_cast<float>(timeStepCount)
                                         / static_cast<float>(MAX_ELAPSED_TIME_STEP_COUNT + 1));
                 mTables[tableId][level][timeStepCount] =
-                        min(max(static_cast<int>(probability), 1), MAX_PROBABILITY);
+                        std::min(std::max(static_cast<int>(probability), 1), MAX_PROBABILITY);
             }
         }
     }
diff --git a/native/jni/src/suggest/policyimpl/utils/edit_distance.h b/native/jni/src/suggest/policyimpl/utils/edit_distance.h
index 0871c37..4cfd0b3 100644
--- a/native/jni/src/suggest/policyimpl/utils/edit_distance.h
+++ b/native/jni/src/suggest/policyimpl/utils/edit_distance.h
@@ -17,6 +17,8 @@
 #ifndef LATINIME_EDIT_DISTANCE_H
 #define LATINIME_EDIT_DISTANCE_H
 
+#include <algorithm>
+
 #include "defines.h"
 #include "suggest/policyimpl/utils/edit_distance_policy.h"
 
@@ -38,13 +40,13 @@
 
         for (int i = 0; i < beforeLength; ++i) {
             for (int j = 0; j < afterLength; ++j) {
-                dp[(afterLength + 1) * (i + 1) + (j + 1)] = min(
+                dp[(afterLength + 1) * (i + 1) + (j + 1)] = std::min(
                         dp[(afterLength + 1) * i + (j + 1)] + policy->getInsertionCost(i, j),
-                        min(dp[(afterLength + 1) * (i + 1) + j] + policy->getDeletionCost(i, j),
-                                dp[(afterLength + 1) * i + j]
-                                        + policy->getSubstitutionCost(i, j)));
+                        std::min(
+                                dp[(afterLength + 1) * (i + 1) + j] + policy->getDeletionCost(i, j),
+                                dp[(afterLength + 1) * i + j] + policy->getSubstitutionCost(i, j)));
                 if (policy->allowTransposition(i, j)) {
-                    dp[(afterLength + 1) * (i + 1) + (j + 1)] = min(
+                    dp[(afterLength + 1) * (i + 1) + (j + 1)] = std::min(
                             dp[(afterLength + 1) * (i + 1) + (j + 1)],
                             dp[(afterLength + 1) * (i - 1) + (j - 1)]
                                     + policy->getTranspositionCost(i, j));
diff --git a/native/jni/src/utils/autocorrection_threshold_utils.cpp b/native/jni/src/utils/autocorrection_threshold_utils.cpp
index 1f8ee08..349786a 100644
--- a/native/jni/src/utils/autocorrection_threshold_utils.cpp
+++ b/native/jni/src/utils/autocorrection_threshold_utils.cpp
@@ -16,6 +16,7 @@
 
 #include "utils/autocorrection_threshold_utils.h"
 
+#include <algorithm>
 #include <cmath>
 
 #include "defines.h"
@@ -99,7 +100,7 @@
     const float maxScore = score >= S_INT_MAX ? static_cast<float>(S_INT_MAX)
             : static_cast<float>(MAX_INITIAL_SCORE)
                     * powf(static_cast<float>(TYPED_LETTER_MULTIPLIER),
-                            static_cast<float>(min(beforeLength, afterLength - spaceCount)))
+                            static_cast<float>(std::min(beforeLength, afterLength - spaceCount)))
                     * static_cast<float>(FULL_WORD_MULTIPLIER);
 
     return (static_cast<float>(score) / maxScore) * weight;
diff --git a/native/jni/src/utils/hash_map_compat.h b/native/jni/src/utils/hash_map_compat.h
index a1e982b..7bf35a6 100644
--- a/native/jni/src/utils/hash_map_compat.h
+++ b/native/jni/src/utils/hash_map_compat.h
@@ -17,18 +17,12 @@
 #ifndef LATINIME_HASH_MAP_COMPAT_H
 #define LATINIME_HASH_MAP_COMPAT_H
 
-// TODO: Use std::unordered_map that has been standardized in C++11
+#include <unordered_map>
 
-#ifdef __APPLE__
-#include <ext/hash_map>
-#else // __APPLE__
-#include <hash_map>
-#endif // __APPLE__
+#define hash_map_compat std::unordered_map
 
-#ifdef __SGI_STL_PORT
-#define hash_map_compat stlport::hash_map
-#else // __SGI_STL_PORT
-#define hash_map_compat __gnu_cxx::hash_map
-#endif // __SGI_STL_PORT
+#if 0 // TODO: Use this instead of the above macro.
+template <typename TKey, typename TValue> using hash_map_compat = std::unordered_map<TKey, TValue>;
+#endif
 
 #endif // LATINIME_HASH_MAP_COMPAT_H
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
new file mode 100644
index 0000000..45449b7
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2014 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.layout.expected;
+
+import java.util.Arrays;
+
+/**
+ * This class builds a keyboard that is a two dimensional array of elements <code>E</code>.
+ *
+ * A keyboard consists of array of rows, and a row consists of array of elements. Each row may have
+ * different number of elements. A element of a keyboard can be specified by a row number and a
+ * column number, both numbers starts from 1.
+ *
+ * @param <E> the type of a keyboard element. A keyboard element must be an immutable object.
+ */
+abstract class AbstractKeyboardBuilder<E> {
+    // A building array of rows.
+    private final E[][] mRows;
+
+    // Returns an instance of default element.
+    abstract E defaultElement();
+    // Returns an <code>E</code> array instance of the <code>size</code>.
+    abstract E[] newArray(final int size);
+    // Returns an <code>E[]</code> array instance of the <code>size</code>.
+    abstract E[][] newArrayOfArray(final int size);
+
+    /**
+     * Construct a builder filled with the default element.
+     * @param dimensions the integer array of each row's size.
+     */
+    AbstractKeyboardBuilder(final int ... dimensions) {
+        mRows = newArrayOfArray(dimensions.length);
+        for (int rowIndex = 0; rowIndex < dimensions.length; rowIndex++) {
+            mRows[rowIndex] = newArray(dimensions[rowIndex]);
+            Arrays.fill(mRows[rowIndex], defaultElement());
+        }
+    }
+
+    /**
+     * Construct a builder from template keyboard. This builder has the same dimensions and
+     * elements of <code>rows</rows>.
+     * @param rows the template keyboard rows. The elements of the <code>rows</code> will be
+     *        shared with this builder. Therefore a element must be an immutable object.
+     */
+    AbstractKeyboardBuilder(final E[][] rows) {
+        mRows = newArrayOfArray(rows.length);
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            final E[] row = rows[rowIndex];
+            mRows[rowIndex] = Arrays.copyOf(row, row.length);
+        }
+    }
+
+    /**
+     * Return current constructing keyboard.
+     * @return the array of the array of the element being constructed.
+     */
+    E[][] build() {
+        return mRows;
+    }
+
+    /**
+     * Get the current contents of the specified row.
+     * @param row the row number to get the contents.
+     * @return the array of elements at row number <code>row</code>.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    E[] getRowAt(final int row) {
+        final int rowIndex = row - 1;
+        if (rowIndex < 0 || rowIndex >= mRows.length) {
+            throw new RuntimeException("Illegal row number: " + row);
+        }
+        return mRows[rowIndex];
+    }
+
+    /**
+     * Set an array of elements to the specified row.
+     * @param row the row number to set <code>elements</code>.
+     * @param elements the array of elements to set at row number <code>row</code>.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    void setRowAt(final int row, final E[] elements) {
+        final int rowIndex = row - 1;
+        if (rowIndex < 0 || rowIndex >= mRows.length) {
+            throw new RuntimeException("Illegal row number: " + row);
+        }
+        mRows[rowIndex] = elements;
+    }
+
+    /**
+     * Set or insert an element at specified position.
+     * @param row the row number to set or insert the <code>element</code>.
+     * @param column the column number to set or insert the <code>element</code>.
+     * @param element the element to set or insert at <code>row,column</code>.
+     * @param insert if true, the <code>element</code> is inserted at <code>row,column</code>.
+     *        Otherwise the <code>element</code> replace the element at <code>row,column</code>.
+     * @throws {@link RuntimeException} if <code>row</code> or <code>column</code> is illegal.
+     */
+    void setElementAt(final int row, final int column, final E element, final boolean insert) {
+        final E[] elements = getRowAt(row);
+        final int columnIndex = column - 1;
+        if (insert) {
+            if (columnIndex < 0 || columnIndex >= elements.length + 1) {
+                throw new RuntimeException("Illegal column number: " + column);
+            }
+            final E[] newElements = Arrays.copyOf(elements, elements.length + 1);
+            // Shift the remaining elements.
+            System.arraycopy(newElements, columnIndex, newElements, columnIndex + 1,
+                    elements.length - columnIndex);
+            // Insert the element at <code>row,column</code>.
+            newElements[columnIndex] = element;
+            // Replace the current row with one.
+            setRowAt(row, newElements);
+            return;
+        }
+        if (columnIndex < 0 || columnIndex >= elements.length) {
+            throw new RuntimeException("Illegal column number: " + column);
+        }
+        elements[columnIndex] = element;
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java
new file mode 100644
index 0000000..e22d75c
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2014 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.layout.expected;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * This class represents an expected key.
+ */
+public class ExpectedKey {
+    static ExpectedKey EMPTY_KEY = newInstance("");
+
+    // A key that has a string label and may have "more keys".
+    static ExpectedKey newInstance(final String label, final ExpectedKey ... moreKeys) {
+        return newInstance(label, label, moreKeys);
+    }
+
+    // A key that has a string label and a different output text and may have "more keys".
+    static ExpectedKey newInstance(final String label, final String outputText,
+            final ExpectedKey ... moreKeys) {
+        return newInstance(ExpectedKeyVisual.newInstance(label),
+                ExpectedKeyOutput.newInstance(outputText), moreKeys);
+    }
+
+    // A key that has a string label and a code point output and may have "more keys".
+    static ExpectedKey newInstance(final String label, final int code,
+            final ExpectedKey ... moreKeys) {
+        return newInstance(ExpectedKeyVisual.newInstance(label),
+                ExpectedKeyOutput.newInstance(code), moreKeys);
+    }
+
+    // A key that has an icon and a code point output and may have "more keys".
+    static ExpectedKey newInstance(final int iconId, final int code,
+            final ExpectedKey ... moreKeys) {
+        return newInstance(ExpectedKeyVisual.newInstance(iconId),
+                ExpectedKeyOutput.newInstance(code), moreKeys);
+    }
+
+    static ExpectedKey newInstance(final ExpectedKeyVisual visual, final ExpectedKeyOutput output,
+            final ExpectedKey ... moreKeys) {
+        if (moreKeys.length == 0) {
+            return new ExpectedKey(visual, output);
+        }
+        return new ExpectedKeyWithMoreKeys(visual, output, moreKeys);
+    }
+
+    private static final ExpectedKey[] EMPTY_KEYS = new ExpectedKey[0];
+
+    // The expected visual outlook of this key.
+    private final ExpectedKeyVisual mVisual;
+    // The expected output of this key.
+    private final ExpectedKeyOutput mOutput;
+
+    public final ExpectedKeyVisual getVisual() {
+        return mVisual;
+    }
+
+    public final ExpectedKeyOutput getOutput() {
+        return mOutput;
+    }
+
+    public ExpectedKey[] getMoreKeys() {
+        // This key has no "more keys".
+        return EMPTY_KEYS;
+    }
+
+    protected ExpectedKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) {
+        mVisual = visual;
+        mOutput = output;
+    }
+
+    public ExpectedKey toUpperCase(Locale locale) {
+        return newInstance(mVisual.toUpperCase(locale), mOutput.toUpperCase(locale));
+    }
+
+    public boolean equalsTo(final Key key) {
+        // This key has no "more keys".
+        return mVisual.equalsTo(key) && mOutput.equalsTo(key) && key.getMoreKeys() == null;
+    }
+
+    public boolean equalsTo(final MoreKeySpec moreKeySpec) {
+        return mVisual.equalsTo(moreKeySpec) && mOutput.equalsTo(moreKeySpec);
+    }
+
+    @Override
+    public boolean equals(final Object object) {
+        if (object instanceof ExpectedKey) {
+            final ExpectedKey key = (ExpectedKey)object;
+            return mVisual.equalsTo(key.mVisual) && mOutput.equalsTo(key.mOutput)
+                    && Arrays.equals(getMoreKeys(), key.getMoreKeys());
+        }
+        return false;
+    }
+
+    private static int hashCode(final Object ... objects) {
+        return Arrays.hashCode(objects);
+    }
+
+    @Override
+    public int hashCode() {
+        return hashCode(mVisual, mOutput, getMoreKeys());
+    }
+
+    @Override
+    public String toString() {
+        if (mVisual.equalsTo(mOutput)) {
+            return mVisual.toString();
+        }
+        return mVisual + "|" + mOutput;
+    }
+
+    /**
+     * This class represents an expected key that has "more keys".
+     */
+    private static final class ExpectedKeyWithMoreKeys extends ExpectedKey {
+        private final ExpectedKey[] mMoreKeys;
+
+        ExpectedKeyWithMoreKeys(final ExpectedKeyVisual visual,
+                final ExpectedKeyOutput output, final ExpectedKey ... moreKeys) {
+            super(visual, output);
+            mMoreKeys = moreKeys;
+        }
+
+        @Override
+        public ExpectedKey toUpperCase(final Locale locale) {
+            final ExpectedKey[] upperCaseMoreKeys = new ExpectedKey[mMoreKeys.length];
+            for (int i = 0; i < mMoreKeys.length; i++) {
+                upperCaseMoreKeys[i] = mMoreKeys[i].toUpperCase(locale);
+            }
+            return newInstance(getVisual().toUpperCase(locale), getOutput().toUpperCase(locale),
+                    upperCaseMoreKeys);
+        }
+
+        @Override
+        public ExpectedKey[] getMoreKeys() {
+            return mMoreKeys;
+        }
+
+        @Override
+        public boolean equalsTo(final Key key) {
+            if (getVisual().equalsTo(key) && getOutput().equalsTo(key)) {
+                final MoreKeySpec[] moreKeys = key.getMoreKeys();
+                // This key should have at least one "more key".
+                if (moreKeys == null || moreKeys.length != mMoreKeys.length) {
+                    return false;
+                }
+                for (int index = 0; index < moreKeys.length; index++) {
+                    if (!mMoreKeys[index].equalsTo(moreKeys[index])) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean equalsTo(final MoreKeySpec moreKeySpec) {
+            // MoreKeySpec has no "more keys".
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + "^" + Arrays.toString(mMoreKeys);
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
new file mode 100644
index 0000000..1be51e6
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2014 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.layout.expected;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.StringUtils;
+
+import java.util.Locale;
+
+/**
+ * This class represents an expected output of a key.
+ *
+ * There are two types of expected output, an integer code point and a string output text.
+ */
+abstract class ExpectedKeyOutput {
+    static ExpectedKeyOutput newInstance(final int code) {
+        return new Code(code);
+    }
+
+    static ExpectedKeyOutput newInstance(final String outputText) {
+        // If the <code>outputText</code> is one code point string, use {@link CodePoint} object.
+        if (StringUtils.codePointCount(outputText) == 1) {
+            return new Code(outputText.codePointAt(0));
+        }
+        return new Text(outputText);
+    }
+
+    abstract ExpectedKeyOutput toUpperCase(final Locale locale);
+    abstract boolean equalsTo(final String text);
+    abstract boolean equalsTo(final Key key);
+    abstract boolean equalsTo(final MoreKeySpec moreKeySpec);
+    abstract boolean equalsTo(final ExpectedKeyOutput output);
+
+    /**
+     * This class represents an integer code point.
+     */
+    private static class Code extends ExpectedKeyOutput {
+        // UNICODE code point or a special negative value defined in {@link Constants}.
+        private final int mCode;
+
+        Code(final int code) { mCode = code; }
+
+        @Override
+        ExpectedKeyOutput toUpperCase(final Locale locale) {
+            if (Constants.isLetterCode(mCode)) {
+                final String codeString = StringUtils.newSingleCodePointString(mCode);
+                // A letter may have an upper case counterpart that consists of multiple code
+                // points, for instance the upper case of "ß" is "SS".
+                return newInstance(codeString.toUpperCase(locale));
+            }
+            // A special negative value has no upper case.
+            return this;
+        }
+
+        @Override
+        boolean equalsTo(final String text) {
+            return StringUtils.codePointCount(text) == 1 && text.codePointAt(0) == mCode;
+        }
+
+        @Override
+        boolean equalsTo(final Key key) {
+            return mCode == key.getCode();
+        }
+
+        @Override
+        boolean equalsTo(final MoreKeySpec moreKeySpec) {
+            return mCode == moreKeySpec.mCode;
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyOutput output) {
+            return (output instanceof Code) && mCode == ((Code)output).mCode;
+        }
+
+        @Override
+        public String toString() {
+            return Constants.isLetterCode(mCode) ? StringUtils.newSingleCodePointString(mCode)
+                    : Constants.printableCode(mCode);
+        }
+    }
+
+    /**
+     * This class represents a string output text.
+     */
+    private static class Text extends ExpectedKeyOutput {
+        private final String mText;
+
+        Text(final String text) { mText = text; }
+
+        @Override
+        ExpectedKeyOutput toUpperCase(final Locale locale) {
+            return newInstance(mText.toUpperCase(locale));
+        }
+
+        @Override
+        boolean equalsTo(final String text) {
+            return text.equals(text);
+        }
+
+        @Override
+        boolean equalsTo(final Key key) {
+            return key.getCode() == Constants.CODE_OUTPUT_TEXT
+                    && mText.equals(key.getOutputText());
+        }
+
+        @Override
+        boolean equalsTo(final MoreKeySpec moreKeySpec) {
+            return moreKeySpec.mCode == Constants.CODE_OUTPUT_TEXT
+                    && mText.equals(moreKeySpec.mOutputText);
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyOutput output) {
+            return (output instanceof Text) && mText == ((Text)output).mText;
+        }
+
+        @Override
+        public String toString() {
+            return mText;
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java
new file mode 100644
index 0000000..0a0da32
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 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.layout.expected;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
+
+import java.util.Locale;
+
+/**
+ * This class represents an expected visual outlook of a key.
+ *
+ * There are two types of expected visual, an integer icon id and a string label.
+ */
+abstract class ExpectedKeyVisual {
+    static ExpectedKeyVisual newInstance(final String label) {
+        return new Label(label);
+    }
+
+    static ExpectedKeyVisual newInstance(final int iconId) {
+        return new Icon(iconId);
+    }
+
+    abstract ExpectedKeyVisual toUpperCase(final Locale locale);
+    abstract boolean equalsTo(final String text);
+    abstract boolean equalsTo(final Key key);
+    abstract boolean equalsTo(final MoreKeySpec moreKeySpec);
+    abstract boolean equalsTo(final ExpectedKeyOutput output);
+    abstract boolean equalsTo(final ExpectedKeyVisual visual);
+
+    /**
+     * This class represents an integer icon id.
+     */
+    private static class Icon extends ExpectedKeyVisual {
+        private final int mIconId;
+
+        Icon(final int iconId) {
+            mIconId = iconId;
+        }
+
+        @Override
+        ExpectedKeyVisual toUpperCase(final Locale locale) {
+            return this;
+        }
+
+        @Override
+        boolean equalsTo(final String text) {
+            return false;
+        }
+
+        @Override
+        boolean equalsTo(final Key key) {
+            return mIconId == key.getIconId();
+        }
+
+        @Override
+        boolean equalsTo(final MoreKeySpec moreKeySpec) {
+            return mIconId == moreKeySpec.mIconId;
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyOutput output) {
+            return false;
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyVisual visual) {
+            return (visual instanceof Icon) && mIconId == ((Icon)visual).mIconId;
+        }
+
+        @Override
+        public String toString() {
+            return KeyboardIconsSet.getIconName(mIconId);
+        }
+    }
+
+    /**
+     * This class represents a string label.
+     */
+    private static class Label extends ExpectedKeyVisual {
+        private final String mLabel;
+
+        Label(final String label) { mLabel = label; }
+
+        @Override
+        ExpectedKeyVisual toUpperCase(final Locale locale) {
+            return new Label(mLabel.toUpperCase(locale));
+        }
+
+        @Override
+        boolean equalsTo(final String text) {
+            return mLabel.equals(text);
+        }
+
+        @Override
+        boolean equalsTo(final Key key) {
+            return mLabel.equals(key.getLabel());
+        }
+
+        @Override
+        boolean equalsTo(final MoreKeySpec moreKeySpec) {
+            return mLabel.equals(moreKeySpec.mLabel);
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyOutput output) {
+            return output.equalsTo(mLabel);
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyVisual visual) {
+            return (visual instanceof Label) && mLabel.equals(((Label)visual).mLabel);
+        }
+
+        @Override
+        public String toString() {
+            return mLabel;
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
new file mode 100644
index 0000000..61288f0
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2014 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.layout.expected;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * This class builds an expected keyboard for unit test.
+ */
+public final class ExpectedKeyboardBuilder extends AbstractKeyboardBuilder<ExpectedKey> {
+    public ExpectedKeyboardBuilder(final int ... dimensions) {
+        super(dimensions);
+    }
+
+    public ExpectedKeyboardBuilder(final ExpectedKey[][] rows) {
+        super(rows);
+    }
+
+    @Override
+    protected ExpectedKey defaultElement() {
+        return ExpectedKey.EMPTY_KEY;
+    }
+
+    @Override
+    ExpectedKey[] newArray(final int size) {
+        return new ExpectedKey[size];
+    }
+
+    @Override
+    ExpectedKey[][] newArrayOfArray(final int size) {
+        return new ExpectedKey[size][];
+    }
+
+    @Override
+    public ExpectedKey[][] build() {
+        return super.build();
+    }
+
+    // A replacement job to be performed.
+    interface ReplaceJob {
+        // Returns a {@link ExpectedKey} object to replace.
+        ExpectedKey replace(final ExpectedKey oldKey);
+        // Return true if replacing should be stopped at first occurrence.
+        boolean stopAtFirstOccurrence();
+    }
+
+    // Replace key(s) that has the specified visual.
+    private void replaceKeyOf(final ExpectedKeyVisual visual, final ReplaceJob job) {
+        int replacedCount = 0;
+        final ExpectedKey[][] rows = build();
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            final ExpectedKey[] keys = rows[rowIndex];
+            for (int columnIndex = 0; columnIndex < keys.length; columnIndex++) {
+                if (keys[columnIndex].getVisual().equalsTo(visual)) {
+                    keys[columnIndex] = job.replace(keys[columnIndex]);
+                    replacedCount++;
+                    if (job.stopAtFirstOccurrence()) {
+                        return;
+                    }
+                }
+            }
+        }
+        if (replacedCount == 0) {
+            throw new RuntimeException(
+                    "Can't find key that has visual: " + visual + " in\n" + toString(rows));
+        }
+    }
+
+    /**
+     * Set the row with specified keys that have specified labels.
+     * @param row the row number to set keys.
+     * @param labels the label texts of the keys.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setLabelsOfRow(final int row, final String ... labels) {
+        final ExpectedKey[] keys = new ExpectedKey[labels.length];
+        for (int columnIndex = 0; columnIndex < labels.length; columnIndex++) {
+            keys[columnIndex] = ExpectedKey.newInstance(labels[columnIndex]);
+        }
+        setRowAt(row, keys);
+        return this;
+    }
+
+    /**
+     * Set the "more keys" of the key that has the specified label.
+     * @param label the label of the key to set the "more keys".
+     * @param moreKeys the array of labels of the "more keys" to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setMoreKeysOf(final String label, final String ... moreKeys) {
+        final ExpectedKey[] expectedMoreKeys = new ExpectedKey[moreKeys.length];
+        for (int index = 0; index < moreKeys.length; index++) {
+            expectedMoreKeys[index] = ExpectedKey.newInstance(moreKeys[index]);
+        }
+        setMoreKeysOf(label, expectedMoreKeys);
+        return this;
+    }
+
+    /**
+     * Set the "more keys" of the key that has the specified label.
+     * @param label the label of the key to set the "more keys".
+     * @param moreKeys the array of "more key" to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setMoreKeysOf(final String label,
+            final ExpectedKey ... moreKeys) {
+        setMoreKeysOf(ExpectedKeyVisual.newInstance(label), moreKeys);
+        return this;
+    }
+
+    /**
+     * Set the "more keys" of the key that has the specified icon.
+     * @param iconId the icon id of the key to set the "more keys".
+     * @param moreKeys the array of "more key" to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setMoreKeysOf(final int iconId, final ExpectedKey ... moreKeys) {
+        setMoreKeysOf(ExpectedKeyVisual.newInstance(iconId), moreKeys);
+        return this;
+    }
+
+    private void setMoreKeysOf(final ExpectedKeyVisual visual, final ExpectedKey[] moreKeys) {
+        replaceKeyOf(visual, new ReplaceJob() {
+            @Override
+            public ExpectedKey replace(final ExpectedKey oldKey) {
+                return ExpectedKey.newInstance(oldKey.getVisual(), oldKey.getOutput(), moreKeys);
+            }
+            @Override
+            public boolean stopAtFirstOccurrence() {
+                return true;
+            }
+        });
+    }
+
+    /**
+     * Insert the keys at specified position.
+     * @param row the row number to insert the <code>keys</code>.
+     * @param column the column number to insert the <code>keys</code>.
+     * @param keys the array of keys to insert at <code>row,column</code>.
+     * @return this builder.
+     * @throws {@link RuntimeException} if <code>row</code> or <code>column</code> is illegal.
+     */
+    public ExpectedKeyboardBuilder insertKeysAtRow(final int row, final int column,
+            final ExpectedKey ... keys) {
+        for (int index = 0; index < keys.length; index++) {
+            setElementAt(row, column + index, keys[index], true /* insert */);
+        }
+        return this;
+    }
+
+    /**
+     * Add the keys on the left most of the row.
+     * @param row the row number to add the <code>keys</code>.
+     * @param keys the array of keys to add on the left most of the row.
+     * @return this builder.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    public ExpectedKeyboardBuilder addKeysOnTheLeftOfRow(final int row,
+            final ExpectedKey ... keys) {
+        // Keys should be inserted from the last to preserve the order.
+        for (int index = keys.length - 1; index >= 0; index--) {
+            setElementAt(row, 1, keys[index], true /* insert */);
+        }
+        return this;
+    }
+
+    /**
+     * Add the keys on the right most of the row.
+     * @param row the row number to add the <code>keys</code>.
+     * @param keys the array of keys to add on the right most of the row.
+     * @return this builder.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    public ExpectedKeyboardBuilder addKeysOnTheRightOfRow(final int row,
+            final ExpectedKey ... keys) {
+        final int rightEnd = getRowAt(row).length + 1;
+        insertKeysAtRow(row, rightEnd, keys);
+        return this;
+    }
+
+    /**
+     * Replace the most top-left key that has the specified label with the new key.
+     * @param label the label of the key to set <code>newKey</code>.
+     * @param newKey the key to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder replaceKeyOfLabel(final String label, final ExpectedKey newKey) {
+        final ExpectedKeyVisual visual = ExpectedKeyVisual.newInstance(label);
+        replaceKeyOf(visual, new ReplaceJob() {
+            @Override
+            public ExpectedKey replace(final ExpectedKey oldKey) {
+                return newKey;
+            }
+            @Override
+            public boolean stopAtFirstOccurrence() {
+                return true;
+            }
+        });
+        return this;
+    }
+
+    /**
+     * Replace the all specified keys  with the new key.
+     * @param key the key to be replaced by <code>newKey</code>.
+     * @param newKey the key to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder replaceKeyOfAll(final ExpectedKey key,
+            final ExpectedKey newKey) {
+        replaceKeyOf(key.getVisual(), new ReplaceJob() {
+            @Override
+            public ExpectedKey replace(final ExpectedKey oldKey) {
+                return newKey;
+            }
+            @Override
+            public boolean stopAtFirstOccurrence() {
+                return false;
+            }
+        });
+        return this;
+    }
+
+    /**
+     * Returns new keyboard instance that has upper case keys of the specified keyboard.
+     * @param rows the lower case keyboard.
+     * @param locale the locale used to convert cases.
+     * @return the upper case keyboard.
+     */
+    public static ExpectedKey[][] toUpperCase(final ExpectedKey[][] rows, final Locale locale) {
+        final ExpectedKey[][] upperCaseRows = new ExpectedKey[rows.length][];
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            final ExpectedKey[] lowerCaseKeys = rows[rowIndex];
+            final ExpectedKey[] upperCaseKeys = new ExpectedKey[lowerCaseKeys.length];
+            for (int columnIndex = 0; columnIndex < lowerCaseKeys.length; columnIndex++) {
+                upperCaseKeys[columnIndex] = lowerCaseKeys[columnIndex].toUpperCase(locale);
+            }
+            upperCaseRows[rowIndex] = upperCaseKeys;
+        }
+        return upperCaseRows;
+    }
+
+    /**
+     * Convert the keyboard to human readable string.
+     * @param rows the keyboard to be converted to string.
+     * @return the human readable representation of <code>rows</code>.
+     */
+    public static String toString(final ExpectedKey[][] rows) {
+        final StringBuilder sb = new StringBuilder();
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            if (rowIndex > 0) {
+                sb.append("\n");
+            }
+            sb.append(Arrays.toString(rows[rowIndex]));
+        }
+        return sb.toString();
+    }
+}
diff --git a/tools/Android.mk b/tools/Android.mk
index 91b2fbb..dc14033 100644
--- a/tools/Android.mk
+++ b/tools/Android.mk
@@ -12,4 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-include $(call all-subdir-makefiles)
+# Temporarily excluding dicttool
+#include $(call all-subdir-makefiles)
+include $(call all-named-subdir-makefiles, make-keyboard-text)
diff --git a/tools/dicttool/NativeLib.mk b/tools/dicttool/NativeLib.mk
index 05e5841..26b6774 100644
--- a/tools/dicttool/NativeLib.mk
+++ b/tools/dicttool/NativeLib.mk
@@ -33,6 +33,10 @@
 LOCAL_CFLAGS += -DHOST_TOOL -fPIC -Wno-deprecated
 LOCAL_NO_DEFAULT_COMPILER_FLAGS := true
 
+# For C++11
+# TODO: Change this to -std=c++11
+LOCAL_CFLAGS += -std=gnu++0x
+
 LATINIME_NATIVE_JNI_DIR := $(LATINIME_DIR_RELATIVE_TO_DICTTOOL)/native/jni
 LATINIME_NATIVE_SRC_DIR := $(LATINIME_DIR_RELATIVE_TO_DICTTOOL)/native/jni/src
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(LATINIME_NATIVE_SRC_DIR)
diff --git a/tools/make-keyboard-text/res/values-be-rBY/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-be-rBY/donottranslate-more-keys.xml
index 4723503..4f29f31 100644
--- a/tools/make-keyboard-text/res/values-be-rBY/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-be-rBY/donottranslate-more-keys.xml
@@ -20,10 +20,8 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- U+045E: "ў" CYRILLIC SMALL LETTER SHORT U -->
     <string name="keylabel_for_east_slavic_row1_9">&#x045E;</string>
-    <!-- U+0451: "ё" CYRILLIC SMALL LETTER IO -->
-    <string name="keylabel_for_east_slavic_row1_12">&#x0451;</string>
     <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU -->
-    <string name="keylabel_for_east_slavic_row2_1">&#x044B;</string>
+    <string name="keylabel_for_east_slavic_row2_2">&#x044B;</string>
     <!-- U+044D: "э" CYRILLIC SMALL LETTER E -->
     <string name="keylabel_for_east_slavic_row2_11">&#x044D;</string>
     <!-- U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
diff --git a/tools/make-keyboard-text/res/values-kk/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-kk/donottranslate-more-keys.xml
index 0e953ff..dcf7480 100644
--- a/tools/make-keyboard-text/res/values-kk/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-kk/donottranslate-more-keys.xml
@@ -20,10 +20,8 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
     <string name="keylabel_for_east_slavic_row1_9">&#x0449;</string>
-    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
-    <string name="keylabel_for_east_slavic_row1_12">&#x044A;</string>
     <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU -->
-    <string name="keylabel_for_east_slavic_row2_1">&#x044B;</string>
+    <string name="keylabel_for_east_slavic_row2_2">&#x044B;</string>
     <!-- U+044D: "э" CYRILLIC SMALL LETTER E -->
     <string name="keylabel_for_east_slavic_row2_11">&#x044D;</string>
     <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
@@ -40,7 +38,7 @@
     <!-- U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE -->
     <string name="more_keys_for_cyrillic_ghe">&#x0493;</string>
     <!-- U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
-    <string name="more_keys_for_east_slavic_row2_1">&#x0456;</string>
+    <string name="more_keys_for_east_slavic_row2_2">&#x0456;</string>
     <!-- U+04D9: "ә" CYRILLIC SMALL LETTER SCHWA -->
     <string name="more_keys_for_cyrillic_a">&#x04D9;</string>
     <!-- U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O -->
diff --git a/tools/make-keyboard-text/res/values-ky/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ky/donottranslate-more-keys.xml
index 8d8c5fb..1d3922f 100644
--- a/tools/make-keyboard-text/res/values-ky/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-ky/donottranslate-more-keys.xml
@@ -20,10 +20,8 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
     <string name="keylabel_for_east_slavic_row1_9">&#x0449;</string>
-    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
-    <string name="keylabel_for_east_slavic_row1_12">&#x044A;</string>
     <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU -->
-    <string name="keylabel_for_east_slavic_row2_1">&#x044B;</string>
+    <string name="keylabel_for_east_slavic_row2_2">&#x044B;</string>
     <!-- U+044D: "э" CYRILLIC SMALL LETTER E -->
     <string name="keylabel_for_east_slavic_row2_11">&#x044D;</string>
     <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
diff --git a/tools/make-keyboard-text/res/values-ru/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ru/donottranslate-more-keys.xml
index f62c90f..44244bc 100644
--- a/tools/make-keyboard-text/res/values-ru/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-ru/donottranslate-more-keys.xml
@@ -20,10 +20,8 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
     <string name="keylabel_for_east_slavic_row1_9">&#x0449;</string>
-    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
-    <string name="keylabel_for_east_slavic_row1_12">&#x044A;</string>
     <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU -->
-    <string name="keylabel_for_east_slavic_row2_1">&#x044B;</string>
+    <string name="keylabel_for_east_slavic_row2_2">&#x044B;</string>
     <!-- U+044D: "э" CYRILLIC SMALL LETTER E -->
     <string name="keylabel_for_east_slavic_row2_11">&#x044D;</string>
     <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
diff --git a/tools/make-keyboard-text/res/values-uk/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-uk/donottranslate-more-keys.xml
index 6ee34e3..f797255 100644
--- a/tools/make-keyboard-text/res/values-uk/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-uk/donottranslate-more-keys.xml
@@ -20,10 +20,8 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
     <string name="keylabel_for_east_slavic_row1_9">&#x0449;</string>
-    <!-- U+0457: "ї" CYRILLIC SMALL LETTER YI -->
-    <string name="keylabel_for_east_slavic_row1_12">&#x0457;</string>
     <!-- U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
-    <string name="keylabel_for_east_slavic_row2_1">&#x0456;</string>
+    <string name="keylabel_for_east_slavic_row2_2">&#x0456;</string>
     <!-- U+0454: "є" CYRILLIC SMALL LETTER UKRAINIAN IE -->
     <string name="keylabel_for_east_slavic_row2_11">&#x0454;</string>
     <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
@@ -31,7 +29,7 @@
     <!-- U+0491: "ґ" CYRILLIC SMALL LETTER GHE WITH UPTURN -->
     <string name="more_keys_for_cyrillic_ghe">&#x0491;</string>
     <!-- U+0457: "ї" CYRILLIC SMALL LETTER YI -->
-    <string name="more_keys_for_east_slavic_row2_1">&#x0457;</string>
+    <string name="more_keys_for_east_slavic_row2_2">&#x0457;</string>
     <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
     <string name="more_keys_for_cyrillic_soft_sign">&#x044A;</string>
     <!-- U+20B4: "₴" HRYVNIA SIGN -->
diff --git a/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
index 9cdcb46..1ea3018 100644
--- a/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
@@ -44,15 +44,14 @@
     <string name="more_keys_for_nordic_row2_10"></string>
     <string name="more_keys_for_nordic_row2_11"></string>
     <string name="keylabel_for_east_slavic_row1_9"></string>
-    <string name="keylabel_for_east_slavic_row1_12"></string>
-    <string name="keylabel_for_east_slavic_row2_1"></string>
+    <string name="keylabel_for_east_slavic_row2_2"></string>
     <string name="keylabel_for_east_slavic_row2_11"></string>
     <string name="keylabel_for_east_slavic_row3_5"></string>
     <string name="more_keys_for_cyrillic_u"></string>
     <string name="more_keys_for_cyrillic_ka"></string>
     <string name="more_keys_for_cyrillic_en"></string>
     <string name="more_keys_for_cyrillic_ghe"></string>
-    <string name="more_keys_for_east_slavic_row2_1"></string>
+    <string name="more_keys_for_east_slavic_row2_2"></string>
     <string name="more_keys_for_cyrillic_a"></string>
     <string name="more_keys_for_cyrillic_o"></string>
     <string name="more_keys_for_cyrillic_soft_sign"></string>