Merge "Correctly update the suggestion strip on non-resumable word"
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
new file mode 100644
index 0000000..3f709a6
--- /dev/null
+++ b/java/src/com/android/inputmethod/event/InputTransaction.java
@@ -0,0 +1,66 @@
+/*
+ * 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.event;
+
+import com.android.inputmethod.latin.settings.SettingsValues;
+
+/**
+ * An object encapsulating a single transaction for input.
+ */
+public class InputTransaction {
+    // UPDATE_LATER is stronger than UPDATE_NOW. The reason for this is, if we have to update later,
+    // it's because something will change that we can't evaluate now, which means that even if we
+    // re-evaluate now we'll have to do it again later. The only case where that wouldn't apply
+    // would be if we needed to update now to find out the new state right away, but then we
+    // can't do it with this deferred mechanism anyway.
+    public static final int SHIFT_NO_UPDATE = 0;
+    public static final int SHIFT_UPDATE_NOW = 1;
+    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 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;
+    }
+
+    public void requireShiftUpdate(final int updateType) {
+        mRequiredShiftUpdate = Math.max(mRequiredShiftUpdate, updateType);
+    }
+    public int getRequiredShiftUpdate() {
+        return mRequiredShiftUpdate;
+    }
+}
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 851ecc0..a3a329a 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -40,7 +40,6 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Locale;
-import java.util.Map;
 
 /**
  * Implements a static, compacted, binary dictionary of standard words.
@@ -142,8 +141,6 @@
         JniUtils.loadNativeLibrary();
     }
 
-    private static native boolean createEmptyDictFileNative(String filePath, long dictVersion,
-            String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray);
     private static native long openNative(String sourceDir, long dictOffset, long dictSize,
             boolean isUpdatable);
     private static native void getHeaderInfoNative(long dict, int[] outHeaderSize,
@@ -167,8 +164,6 @@
             int[] suggestOptions, int[] prevWordCodePointArray,
             int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes,
             int[] outputAutoCommitFirstWordConfidence);
-    private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
-    private static native int editDistanceNative(int[] before, int[] after);
     private static native void addUnigramWordNative(long dict, int[] word, int probability,
             int[] shortcutTarget, int shortcutProbability, boolean isNotAWord,
             boolean isBlacklisted, int timestamp);
@@ -179,24 +174,9 @@
             LanguageModelParam[] languageModelParams, int startIndex);
     private static native int calculateProbabilityNative(long dict, int unigramProbability,
             int bigramProbability);
-    private static native int setCurrentTimeForTestNative(int currentTime);
     private static native String getPropertyNative(long dict, String query);
     private static native boolean isCorruptedNative(long dict);
 
-    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()];
-        final String[] valueArray = new String[attributeMap.size()];
-        int index = 0;
-        for (final String key : attributeMap.keySet()) {
-            keyArray[index] = key;
-            valueArray[index] = attributeMap.get(key);
-            index++;
-        }
-        return createEmptyDictFileNative(filePath, dictVersion, locale.toString(), keyArray,
-                valueArray);
-    }
-
     // TODO: Move native dict into session
     private final void loadDictionary(final String path, final long startOffset,
             final long length, final boolean isUpdatable) {
@@ -219,7 +199,6 @@
         return true;
     }
 
-    @UsedForTesting
     public DictionaryHeader getHeader() throws UnsupportedFormatException {
         if (mNativeDict == 0) {
             return null;
@@ -323,20 +302,6 @@
         return getFormatVersionNative(mNativeDict);
     }
 
-    public static float calcNormalizedScore(final String before, final String after,
-            final int score) {
-        return calcNormalizedScoreNative(StringUtils.toCodePointArray(before),
-                StringUtils.toCodePointArray(after), score);
-    }
-
-    public static int editDistance(final String before, final String after) {
-        if (before == null || after == null) {
-            throw new IllegalArgumentException();
-        }
-        return editDistanceNative(StringUtils.toCodePointArray(before),
-                StringUtils.toCodePointArray(after));
-    }
-
     @Override
     public boolean isValidWord(final String word) {
         return getFrequency(word) != NOT_A_PROBABILITY;
@@ -497,22 +462,6 @@
         return calculateProbabilityNative(mNativeDict, unigramProbability, bigramProbability);
     }
 
-    /**
-     * Control the current time to be used in the native code. If currentTime >= 0, this method sets
-     * the current time and gets into test mode.
-     * In test mode, set timestamp is used as the current time in the native code.
-     * If currentTime < 0, quit the test mode and returns to using time() to get the current time.
-     *
-     * @param currentTime seconds since the unix epoch
-     * @return current time got in the native code.
-     */
-    @UsedForTesting
-    public static int setCurrentTimeForTest(final int currentTime) {
-        final int currentNativeTimestamp = setCurrentTimeForTestNative(currentTime);
-        PersonalizationHelper.currentTimeChangedForTesting(currentNativeTimestamp);
-        return currentNativeTimestamp;
-    }
-
     @UsedForTesting
     public String getPropertyForTest(final String query) {
         if (!isValidDictionary()) return "";
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/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 26545ac..7847738 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -27,6 +27,7 @@
 import com.android.inputmethod.latin.makedict.WordProperty;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.utils.AsyncResultHolder;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CombinedFormatUtils;
 import com.android.inputmethod.latin.utils.ExecutorUtils;
@@ -228,7 +229,7 @@
     }
 
     private void createBinaryDictionaryLocked() {
-        BinaryDictionary.createEmptyDictFile(mDictFile.getAbsolutePath(),
+        BinaryDictionaryUtils.createEmptyDictFile(mDictFile.getAbsolutePath(),
                 DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap());
     }
 
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 1747eee..ba64028 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,6 +22,7 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.BoundedTreeSet;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.StringUtils;
@@ -309,7 +310,7 @@
         // than i because we added the typed word to mSuggestions without touching mScores.
         for (int i = 0; i < suggestionsSize - 1; ++i) {
             final SuggestedWordInfo cur = suggestions.get(i + 1);
-            final float normalizedScore = BinaryDictionary.calcNormalizedScore(
+            final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
                     typedWord, cur.toString(), cur.mScore);
             final String scoreInfoString;
             if (normalizedScore > 0) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index f7cf8de..df00682 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -23,12 +23,12 @@
 import android.util.Log;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
-import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.compat.SuggestionSpanUtils;
 import com.android.inputmethod.event.EventInterpreter;
+import com.android.inputmethod.event.InputTransaction;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
@@ -361,40 +361,37 @@
             final SettingsValues settingsValues,
             // TODO: remove these two arguments
             final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) {
+        final InputTransaction inputTransaction = new InputTransaction(settingsValues, code, x, y,
+                SystemClock.uptimeMillis(), mSpaceState,
+                getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()));
         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();
-        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, handler, keyboardSwitcher);
-            LatinImeLogger.logOnDelete(x, y);
+            handleBackspace(inputTransaction, handler);
+            LatinImeLogger.logOnDelete(inputTransaction.mX, inputTransaction.mY);
             break;
         case Constants.CODE_SHIFT:
-            performRecapitalization(settingsValues);
-            keyboardSwitcher.updateShiftState();
+            performRecapitalization(inputTransaction.mSettingsValues);
+            inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
             break;
         case Constants.CODE_CAPSLOCK:
             // Note: Changing keyboard to shift lock state is handled in
@@ -448,32 +445,43 @@
             } 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, keyboardSwitcher, handler);
+                didAutoCorrect = handleNonSpecialCharacter(inputTransaction, handler);
             }
             break;
         case Constants.CODE_SHIFT_ENTER:
-            didAutoCorrect = handleNonSpecialCharacter(settingsValues, Constants.CODE_ENTER,
-                    x, y, spaceState, keyboardSwitcher, 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, keyboardSwitcher, 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
+        }
     }
 
     public void onStartBatchInput(final SettingsValues settingsValues,
@@ -617,31 +625,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,
-            // TODO: remove these arguments
-            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+    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, keyboardSwitcher,
-                    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);
@@ -653,11 +656,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,
-                    keyboardSwitcher, handler);
+            handleNonSeparator(inputTransaction.mSettingsValues, inputTransaction, handler);
         }
         return didAutoCorrect;
     }
@@ -665,15 +667,12 @@
     /**
      * 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,
-            // TODO: Remove these arguments
-            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+            final InputTransaction inputTransaction,
+            // TODO: Remove this argument
+            final LatinIME.UIHandler handler) {
         // TODO: refactor this method to stop flipping isComposingWord around all the time, and
         // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
         // which has the same name as other handle* methods but is not the same.
@@ -681,7 +680,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");
@@ -703,7 +703,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
@@ -714,8 +714,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
@@ -723,26 +723,25 @@
             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
                 // yet, so the word we want is the 1st word before the cursor.
                 mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
-                        getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()),
-                        getNthPreviousWordForSuggestion(
+                        inputTransaction.mShiftState, getNthPreviousWordForSuggestion(
                                 settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */));
             }
             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(keyboardSwitcher);
+                swapSwapperAndSpace(inputTransaction);
                 mSpaceState = SpaceState.WEAK;
             }
             // In case the "add to dictionary" hint was still displayed.
@@ -750,26 +749,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 inputTransaction The transaction in progress.
      * @param isFromSuggestionStrip whether this code point comes from the suggestion strip.
-     * @param spaceState the space state at start of the batch input.
      * @return whether this caused an auto-correction to happen.
      */
-    private boolean handleSeparator(final SettingsValues settingsValues,
-            final int codePoint, final boolean isFromSuggestionStrip, final int spaceState,
-            // TODO: remove these arguments
-            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+    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
@@ -779,48 +778,51 @@
         }
         // 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)) {
-                    keyboardSwitcher.updateShiftState();
+        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()) {
                     mSpaceState = SpaceState.WEAK;
@@ -831,11 +833,12 @@
             handler.postUpdateSuggestionStrip();
         } else {
             if (swapWeakSpace) {
-                swapSwapperAndSpace(keyboardSwitcher);
+                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
@@ -856,25 +859,24 @@
             mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
         }
 
-        keyboardSwitcher.updateShiftState();
+        inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
         return didAutoCorrect;
     }
 
     /**
      * 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,
-            // TODO: remove these arguments
-            final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) {
+    private void handleBackspace(final InputTransaction inputTransaction,
+            // TODO: remove this argument
+            final LatinIME.UIHandler handler) {
         mSpaceState = SpaceState.NONE;
         mDeleteCount++;
 
         // In many cases, we may have to put the keyboard in auto-shift state again. However
         // we want to wait a few milliseconds before doing it to avoid the keyboard flashing
         // during key repeat.
-        handler.postUpdateShiftState();
+        inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_LATER);
 
         if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
             // If we are in the middle of a recorrection, we need to commit the recorrection
@@ -900,14 +902,14 @@
             if (!mWordComposer.isComposingWord()) {
                 // If we just removed the last character, auto-caps mode may have changed so we
                 // need to re-evaluate.
-                keyboardSwitcher.updateShiftState();
+                inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
             }
         } 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)) {
@@ -924,14 +926,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;
@@ -957,8 +959,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
@@ -1004,15 +1006,16 @@
                     }
                 }
             }
-            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.
-            keyboardSwitcher.updateShiftState();
+            inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
         }
     }
 
@@ -1028,9 +1031,9 @@
      *
      * This method will check that there are two characters before the cursor and that the first
      * one is a space before it does the actual swapping.
+     * @param inputTransaction The transaction in progress.
      */
-    // TODO: Remove this argument
-    private void swapSwapperAndSpace(final KeyboardSwitcher keyboardSwitcher) {
+    private void swapSwapperAndSpace(final InputTransaction inputTransaction) {
         final CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
         // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
         if (lastTwo != null && lastTwo.length() == 2 && lastTwo.charAt(0) == Constants.CODE_SPACE) {
@@ -1040,28 +1043,34 @@
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
             }
-            keyboardSwitcher.updateShiftState();
+            inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
         }
     }
 
     /*
      * 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;
@@ -1479,7 +1488,9 @@
      */
     private int getActualCapsMode(final SettingsValues settingsValues,
             final int keyboardShiftMode) {
-        if (keyboardShiftMode != WordComposer.CAPS_MODE_AUTO_SHIFTED) return keyboardShiftMode;
+        if (keyboardShiftMode != WordComposer.CAPS_MODE_AUTO_SHIFTED) {
+            return keyboardShiftMode;
+        }
         final int auto = getCurrentAutoCapsState(settingsValues);
         if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
             return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
@@ -1721,6 +1732,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/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index 1050d1b..a50bad9 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -22,6 +22,7 @@
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
 
 import java.io.File;
@@ -54,7 +55,7 @@
         if (!mDictPlacedDir.isDirectory()) {
             throw new UnsupportedFormatException("Given path is not a directory.");
         }
-        if (!BinaryDictionary.createEmptyDictFile(mDictPlacedDir.getAbsolutePath(),
+        if (!BinaryDictionaryUtils.createEmptyDictFile(mDictPlacedDir.getAbsolutePath(),
                 FormatSpec.VERSION4, LocaleUtils.constructLocaleFromString(
                 dict.mOptions.mAttributes.get(DictionaryHeader.DICTIONARY_LOCALE_KEY)),
                 dict.mOptions.mAttributes)) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index dae36f7..a07e8eb 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -37,6 +37,7 @@
 import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
 import com.android.inputmethod.latin.UserBinaryDictionary;
 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
 import com.android.inputmethod.latin.utils.StringUtils;
@@ -320,7 +321,7 @@
                     hasRecommendedSuggestions = false;
                 } else {
                     gatheredSuggestions = EMPTY_STRING_ARRAY;
-                    final float normalizedScore = BinaryDictionary.calcNormalizedScore(
+                    final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
                             mOriginalText, mBestSuggestion, mBestScore);
                     hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
                 }
@@ -355,7 +356,7 @@
                 final int bestScore = mScores[mLength - 1];
                 final String bestSuggestion = mSuggestions.get(0);
                 final float normalizedScore =
-                        BinaryDictionary.calcNormalizedScore(
+                        BinaryDictionaryUtils.calcNormalizedScore(
                                 mOriginalText, bestSuggestion.toString(), bestScore);
                 hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
                 if (DBG) {
diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index 37c173f..22b9b77 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -40,7 +40,7 @@
             final int autoCorrectionSuggestionScore = suggestion.mScore;
             // TODO: when the normalized score of the first suggestion is nearly equals to
             //       the normalized score of the second suggestion, behave less aggressive.
-            final float normalizedScore = BinaryDictionary.calcNormalizedScore(
+            final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
                     consideredWord, suggestion.mWord, autoCorrectionSuggestionScore);
             if (DBG) {
                 Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + ","
@@ -71,9 +71,8 @@
         if (typedWordLength < MINIMUM_SAFETY_NET_CHAR_LENGTH) {
             return false;
         }
-        final int maxEditDistanceOfNativeDictionary =
-                (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
-        final int distance = BinaryDictionary.editDistance(typedWord, suggestion);
+        final int maxEditDistanceOfNativeDictionary = (typedWordLength / 2) + 1;
+        final int distance = BinaryDictionaryUtils.editDistance(typedWord, suggestion);
         if (DBG) {
             Log.d(TAG, "Autocorrected edit distance = " + distance
                     + ", " + maxEditDistanceOfNativeDictionary);
diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
new file mode 100644
index 0000000..6388300
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
@@ -0,0 +1,110 @@
+/*
+ * 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.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;
+
+public final class BinaryDictionaryUtils {
+    private static final String TAG = BinaryDictionaryUtils.class.getSimpleName();
+
+    private BinaryDictionaryUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    static {
+        JniUtils.loadNativeLibrary();
+    }
+
+    private static native boolean createEmptyDictFileNative(String filePath, long dictVersion,
+            String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray);
+    private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
+    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()];
+        final String[] valueArray = new String[attributeMap.size()];
+        int index = 0;
+        for (final String key : attributeMap.keySet()) {
+            keyArray[index] = key;
+            valueArray[index] = attributeMap.get(key);
+            index++;
+        }
+        return createEmptyDictFileNative(filePath, dictVersion, locale.toString(), keyArray,
+                valueArray);
+    }
+
+    public static float calcNormalizedScore(final String before, final String after,
+            final int score) {
+        return calcNormalizedScoreNative(StringUtils.toCodePointArray(before),
+                StringUtils.toCodePointArray(after), score);
+    }
+
+    public static int editDistance(final String before, final String after) {
+        if (before == null || after == null) {
+            throw new IllegalArgumentException();
+        }
+        return editDistanceNative(StringUtils.toCodePointArray(before),
+                StringUtils.toCodePointArray(after));
+    }
+
+    /**
+     * Control the current time to be used in the native code. If currentTime >= 0, this method sets
+     * the current time and gets into test mode.
+     * In test mode, set timestamp is used as the current time in the native code.
+     * If currentTime < 0, quit the test mode and returns to using time() to get the current time.
+     *
+     * @param currentTime seconds since the unix epoch
+     * @return current time got in the native code.
+     */
+    @UsedForTesting
+    public static int setCurrentTimeForTest(final int currentTime) {
+        final int currentNativeTimestamp = setCurrentTimeForTestNative(currentTime);
+        PersonalizationHelper.currentTimeChangedForTesting(currentNativeTimestamp);
+        return currentNativeTimestamp;
+    }
+}
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/NativeFileList.mk b/native/jni/NativeFileList.mk
index eb24df6..1f58246 100644
--- a/native/jni/NativeFileList.mk
+++ b/native/jni/NativeFileList.mk
@@ -15,6 +15,7 @@
 LATIN_IME_JNI_SRC_FILES := \
     com_android_inputmethod_keyboard_ProximityInfo.cpp \
     com_android_inputmethod_latin_BinaryDictionary.cpp \
+    com_android_inputmethod_latin_BinaryDictionaryUtils.cpp \
     com_android_inputmethod_latin_DicTraverseSession.cpp \
     jni_common.cpp
 
@@ -31,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 \
@@ -40,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/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index bb54cbd..7a7816d 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -27,8 +27,6 @@
 #include "suggest/core/dictionary/word_property.h"
 #include "suggest/core/suggest_options.h"
 #include "suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h"
-#include "suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h"
-#include "utils/autocorrection_threshold_utils.h"
 #include "utils/char_utils.h"
 #include "utils/time_keeper.h"
 
@@ -36,49 +34,6 @@
 
 class ProximityInfo;
 
-// TODO: Move to makedict.
-static jboolean latinime_BinaryDictionary_createEmptyDictFile(JNIEnv *env, jclass clazz,
-        jstring filePath, jlong dictVersion, jstring locale, jobjectArray attributeKeyStringArray,
-        jobjectArray attributeValueStringArray) {
-    const jsize filePathUtf8Length = env->GetStringUTFLength(filePath);
-    char filePathChars[filePathUtf8Length + 1];
-    env->GetStringUTFRegion(filePath, 0, env->GetStringLength(filePath), filePathChars);
-    filePathChars[filePathUtf8Length] = '\0';
-    jsize localeLength = env->GetStringLength(locale);
-    jchar localeCodePoints[localeLength];
-    env->GetStringRegion(locale, 0, localeLength, localeCodePoints);
-    const int keyCount = env->GetArrayLength(attributeKeyStringArray);
-    const int valueCount = env->GetArrayLength(attributeValueStringArray);
-    if (keyCount != valueCount) {
-        return false;
-    }
-
-    DictionaryHeaderStructurePolicy::AttributeMap attributeMap;
-    for (int i = 0; i < keyCount; i++) {
-        jstring keyString = static_cast<jstring>(
-                env->GetObjectArrayElement(attributeKeyStringArray, i));
-        const jsize keyUtf8Length = env->GetStringUTFLength(keyString);
-        char keyChars[keyUtf8Length + 1];
-        env->GetStringUTFRegion(keyString, 0, env->GetStringLength(keyString), keyChars);
-        keyChars[keyUtf8Length] = '\0';
-        DictionaryHeaderStructurePolicy::AttributeMap::key_type key;
-        HeaderReadWriteUtils::insertCharactersIntoVector(keyChars, &key);
-
-        jstring valueString = static_cast<jstring>(
-                env->GetObjectArrayElement(attributeValueStringArray, i));
-        const jsize valueUtf8Length = env->GetStringUTFLength(valueString);
-        char valueChars[valueUtf8Length + 1];
-        env->GetStringUTFRegion(valueString, 0, env->GetStringLength(valueString), valueChars);
-        valueChars[valueUtf8Length] = '\0';
-        DictionaryHeaderStructurePolicy::AttributeMap::mapped_type value;
-        HeaderReadWriteUtils::insertCharactersIntoVector(valueChars, &value);
-        attributeMap[key] = value;
-    }
-
-    return DictFileWritingUtils::createEmptyDictFile(filePathChars, static_cast<int>(dictVersion),
-            CharUtils::convertShortArrayToIntVector(localeCodePoints, localeLength), &attributeMap);
-}
-
 static jlong latinime_BinaryDictionary_open(JNIEnv *env, jclass clazz, jstring sourceDir,
         jlong dictOffset, jlong dictSize, jboolean isUpdatable) {
     PROF_OPEN;
@@ -335,30 +290,6 @@
             outShortcutProbabilities);
 }
 
-static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jclass clazz,
-        jintArray before, jintArray after, jint score) {
-    jsize beforeLength = env->GetArrayLength(before);
-    jsize afterLength = env->GetArrayLength(after);
-    int beforeCodePoints[beforeLength];
-    int afterCodePoints[afterLength];
-    env->GetIntArrayRegion(before, 0, beforeLength, beforeCodePoints);
-    env->GetIntArrayRegion(after, 0, afterLength, afterCodePoints);
-    return AutocorrectionThresholdUtils::calcNormalizedScore(beforeCodePoints, beforeLength,
-            afterCodePoints, afterLength, score);
-}
-
-static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jclass clazz, jintArray before,
-        jintArray after) {
-    jsize beforeLength = env->GetArrayLength(before);
-    jsize afterLength = env->GetArrayLength(after);
-    int beforeCodePoints[beforeLength];
-    int afterCodePoints[afterLength];
-    env->GetIntArrayRegion(before, 0, beforeLength, beforeCodePoints);
-    env->GetIntArrayRegion(after, 0, afterLength, afterCodePoints);
-    return AutocorrectionThresholdUtils::editDistance(beforeCodePoints, beforeLength,
-            afterCodePoints, afterLength);
-}
-
 static void latinime_BinaryDictionary_addUnigramWord(JNIEnv *env, jclass clazz, jlong dict,
         jintArray word, jint probability, jintArray shortcutTarget, jint shortuctProbability,
         jboolean isNotAWord, jboolean isBlacklisted, jint timestamp) {
@@ -518,17 +449,6 @@
     return env->NewStringUTF(resultChars);
 }
 
-static int latinime_BinaryDictionary_setCurrentTimeForTest(JNIEnv *env, jclass clazz,
-        jint currentTime) {
-    if (currentTime >= 0) {
-        TimeKeeper::startTestModeWithForceCurrentTime(currentTime);
-    } else {
-        TimeKeeper::stopTestMode();
-    }
-    TimeKeeper::setCurrentTime();
-    return TimeKeeper::peekCurrentTime();
-}
-
 static bool latinime_BinaryDictionary_isCorruptedNative(JNIEnv *env, jclass clazz, jlong dict) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
     if (!dictionary) {
@@ -539,12 +459,6 @@
 
 static const JNINativeMethod sMethods[] = {
     {
-        const_cast<char *>("createEmptyDictFileNative"),
-        const_cast<char *>(
-                "(Ljava/lang/String;JLjava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)Z"),
-        reinterpret_cast<void *>(latinime_BinaryDictionary_createEmptyDictFile)
-    },
-    {
         const_cast<char *>("openNative"),
         const_cast<char *>("(Ljava/lang/String;JJZ)J"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_open)
@@ -606,16 +520,6 @@
         reinterpret_cast<void *>(latinime_BinaryDictionary_getNextWord)
     },
     {
-        const_cast<char *>("calcNormalizedScoreNative"),
-        const_cast<char *>("([I[II)F"),
-        reinterpret_cast<void *>(latinime_BinaryDictionary_calcNormalizedScore)
-    },
-    {
-        const_cast<char *>("editDistanceNative"),
-        const_cast<char *>("([I[I)I"),
-        reinterpret_cast<void *>(latinime_BinaryDictionary_editDistance)
-    },
-    {
         const_cast<char *>("addUnigramWordNative"),
         const_cast<char *>("(J[II[IIZZI)V"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_addUnigramWord)
@@ -642,11 +546,6 @@
         reinterpret_cast<void *>(latinime_BinaryDictionary_calculateProbabilityNative)
     },
     {
-        const_cast<char *>("setCurrentTimeForTestNative"),
-        const_cast<char *>("(I)I"),
-        reinterpret_cast<void *>(latinime_BinaryDictionary_setCurrentTimeForTest)
-    },
-    {
         const_cast<char *>("getPropertyNative"),
         const_cast<char *>("(JLjava/lang/String;)Ljava/lang/String;"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_getProperty)
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionaryUtils.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionaryUtils.cpp
new file mode 100644
index 0000000..f723664
--- /dev/null
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionaryUtils.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "LatinIME: jni: BinaryDictionaryUtils"
+
+#include "com_android_inputmethod_latin_BinaryDictionaryUtils.h"
+
+#include "defines.h"
+#include "jni.h"
+#include "jni_common.h"
+#include "suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h"
+#include "utils/autocorrection_threshold_utils.h"
+#include "utils/char_utils.h"
+#include "utils/time_keeper.h"
+
+namespace latinime {
+
+static jboolean latinime_BinaryDictionaryUtils_createEmptyDictFile(JNIEnv *env, jclass clazz,
+        jstring filePath, jlong dictVersion, jstring locale, jobjectArray attributeKeyStringArray,
+        jobjectArray attributeValueStringArray) {
+    const jsize filePathUtf8Length = env->GetStringUTFLength(filePath);
+    char filePathChars[filePathUtf8Length + 1];
+    env->GetStringUTFRegion(filePath, 0, env->GetStringLength(filePath), filePathChars);
+    filePathChars[filePathUtf8Length] = '\0';
+    jsize localeLength = env->GetStringLength(locale);
+    jchar localeCodePoints[localeLength];
+    env->GetStringRegion(locale, 0, localeLength, localeCodePoints);
+    const int keyCount = env->GetArrayLength(attributeKeyStringArray);
+    const int valueCount = env->GetArrayLength(attributeValueStringArray);
+    if (keyCount != valueCount) {
+        return false;
+    }
+
+    DictionaryHeaderStructurePolicy::AttributeMap attributeMap;
+    for (int i = 0; i < keyCount; i++) {
+        jstring keyString = static_cast<jstring>(
+                env->GetObjectArrayElement(attributeKeyStringArray, i));
+        const jsize keyUtf8Length = env->GetStringUTFLength(keyString);
+        char keyChars[keyUtf8Length + 1];
+        env->GetStringUTFRegion(keyString, 0, env->GetStringLength(keyString), keyChars);
+        keyChars[keyUtf8Length] = '\0';
+        DictionaryHeaderStructurePolicy::AttributeMap::key_type key;
+        HeaderReadWriteUtils::insertCharactersIntoVector(keyChars, &key);
+
+        jstring valueString = static_cast<jstring>(
+                env->GetObjectArrayElement(attributeValueStringArray, i));
+        const jsize valueUtf8Length = env->GetStringUTFLength(valueString);
+        char valueChars[valueUtf8Length + 1];
+        env->GetStringUTFRegion(valueString, 0, env->GetStringLength(valueString), valueChars);
+        valueChars[valueUtf8Length] = '\0';
+        DictionaryHeaderStructurePolicy::AttributeMap::mapped_type value;
+        HeaderReadWriteUtils::insertCharactersIntoVector(valueChars, &value);
+        attributeMap[key] = value;
+    }
+
+    return DictFileWritingUtils::createEmptyDictFile(filePathChars, static_cast<int>(dictVersion),
+            CharUtils::convertShortArrayToIntVector(localeCodePoints, localeLength), &attributeMap);
+}
+
+static jfloat latinime_BinaryDictionaryUtils_calcNormalizedScore(JNIEnv *env, jclass clazz,
+        jintArray before, jintArray after, jint score) {
+    jsize beforeLength = env->GetArrayLength(before);
+    jsize afterLength = env->GetArrayLength(after);
+    int beforeCodePoints[beforeLength];
+    int afterCodePoints[afterLength];
+    env->GetIntArrayRegion(before, 0, beforeLength, beforeCodePoints);
+    env->GetIntArrayRegion(after, 0, afterLength, afterCodePoints);
+    return AutocorrectionThresholdUtils::calcNormalizedScore(beforeCodePoints, beforeLength,
+            afterCodePoints, afterLength, score);
+}
+
+static jint latinime_BinaryDictionaryUtils_editDistance(JNIEnv *env, jclass clazz, jintArray before,
+        jintArray after) {
+    jsize beforeLength = env->GetArrayLength(before);
+    jsize afterLength = env->GetArrayLength(after);
+    int beforeCodePoints[beforeLength];
+    int afterCodePoints[afterLength];
+    env->GetIntArrayRegion(before, 0, beforeLength, beforeCodePoints);
+    env->GetIntArrayRegion(after, 0, afterLength, afterCodePoints);
+    return AutocorrectionThresholdUtils::editDistance(beforeCodePoints, beforeLength,
+            afterCodePoints, afterLength);
+}
+
+static int latinime_BinaryDictionaryUtils_setCurrentTimeForTest(JNIEnv *env, jclass clazz,
+        jint currentTime) {
+    if (currentTime >= 0) {
+        TimeKeeper::startTestModeWithForceCurrentTime(currentTime);
+    } else {
+        TimeKeeper::stopTestMode();
+    }
+    TimeKeeper::setCurrentTime();
+    return TimeKeeper::peekCurrentTime();
+}
+
+static const JNINativeMethod sMethods[] = {
+    {
+        const_cast<char *>("createEmptyDictFileNative"),
+        const_cast<char *>(
+                "(Ljava/lang/String;JLjava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)Z"),
+        reinterpret_cast<void *>(latinime_BinaryDictionaryUtils_createEmptyDictFile)
+    },
+    {
+        const_cast<char *>("calcNormalizedScoreNative"),
+        const_cast<char *>("([I[II)F"),
+        reinterpret_cast<void *>(latinime_BinaryDictionaryUtils_calcNormalizedScore)
+    },
+    {
+        const_cast<char *>("editDistanceNative"),
+        const_cast<char *>("([I[I)I"),
+        reinterpret_cast<void *>(latinime_BinaryDictionaryUtils_editDistance)
+    },
+    {
+        const_cast<char *>("setCurrentTimeForTestNative"),
+        const_cast<char *>("(I)I"),
+        reinterpret_cast<void *>(latinime_BinaryDictionaryUtils_setCurrentTimeForTest)
+    }
+};
+
+int register_BinaryDictionaryUtils(JNIEnv *env) {
+    const char *const kClassPathName = "com/android/inputmethod/latin/utils/BinaryDictionaryUtils";
+    return registerNativeMethods(env, kClassPathName, sMethods, NELEMS(sMethods));
+}
+} // namespace latinime
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionaryUtils.h b/native/jni/com_android_inputmethod_latin_BinaryDictionaryUtils.h
new file mode 100644
index 0000000..38edcd2
--- /dev/null
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionaryUtils.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_BINARYDICTIONARYUTILS_H
+#define _COM_ANDROID_INPUTMETHOD_LATIN_BINARYDICTIONARYUTILS_H
+
+#include "jni.h"
+
+namespace latinime {
+int register_BinaryDictionaryUtils(JNIEnv *env);
+} // namespace latinime
+#endif // _COM_ANDROID_INPUTMETHOD_LATIN_BINARYDICTIONARYUTILS_H
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index f2867d7..9fa7ef8 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -20,6 +20,7 @@
 
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
+#include "com_android_inputmethod_latin_BinaryDictionaryUtils.h"
 #include "com_android_inputmethod_latin_DicTraverseSession.h"
 #include "defines.h"
 
@@ -42,6 +43,10 @@
         AKLOGE("ERROR: BinaryDictionary native registration failed");
         return -1;
     }
+    if (!latinime::register_BinaryDictionaryUtils(env)) {
+        AKLOGE("ERROR: BinaryDictionaryUtils native registration failed");
+        return -1;
+    }
     if (!latinime::register_DicTraverseSession(env)) {
         AKLOGE("ERROR: DicTraverseSession native registration failed");
         return -1;
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 0e28560..bc8d5bc 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_utils.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_utils.h
@@ -164,6 +164,9 @@
             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;
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 99%
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..e9fb3b8 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,7 @@
  * limitations under the License.
  */
 
-#include "suggest/core/dictionary/suggestions_output_utils.h"
+#include "suggest/core/result/suggestions_output_utils.h"
 
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
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/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index fa51236..69420d6 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -27,6 +27,7 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.FileUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
 
@@ -111,7 +112,7 @@
                 DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
         attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY,
                 DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
-        if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4,
+        if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4,
                 LocaleUtils.constructLocaleFromString(TEST_LOCALE), attributeMap)) {
             return file;
         } else {
@@ -121,11 +122,11 @@
     }
 
     private static int setCurrentTimeForTestMode(final int currentTime) {
-        return BinaryDictionary.setCurrentTimeForTest(currentTime);
+        return BinaryDictionaryUtils.setCurrentTimeForTest(currentTime);
     }
 
     private static int stopTestModeInNativeCode() {
-        return BinaryDictionary.setCurrentTimeForTest(-1);
+        return BinaryDictionaryUtils.setCurrentTimeForTest(-1);
     }
 
     public void testReadDictInJavaSide() {
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index c1adf65..4f9245c 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -25,6 +25,7 @@
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.makedict.WordProperty;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.FileUtils;
 import com.android.inputmethod.latin.utils.LanguageModelParam;
 
@@ -59,7 +60,7 @@
         file.delete();
         file.mkdir();
         Map<String, String> attributeMap = new HashMap<String, String>();
-        if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4,
+        if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4,
                 Locale.ENGLISH, attributeMap)) {
             return file;
         } else {
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
index 0ee0fb5..80978df 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
@@ -30,6 +30,7 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.ByteArrayDictBuffer;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
@@ -77,7 +78,7 @@
 
     public BinaryDictDecoderEncoderTests(final long seed, final int maxUnigrams) {
         super();
-        BinaryDictionary.setCurrentTimeForTest(0);
+        BinaryDictionaryUtils.setCurrentTimeForTest(0);
         Log.e(TAG, "Testing dictionary: seed is " + seed);
         final Random random = new Random(seed);
         sWords.clear();
@@ -112,13 +113,13 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        BinaryDictionary.setCurrentTimeForTest(0);
+        BinaryDictionaryUtils.setCurrentTimeForTest(0);
     }
 
     @Override
     protected void tearDown() throws Exception {
         // Quit test mode.
-        BinaryDictionary.setCurrentTimeForTest(-1);
+        BinaryDictionaryUtils.setCurrentTimeForTest(-1);
         super.tearDown();
     }
 
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index 6ace2de..04840d6 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -20,8 +20,8 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
-import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.FileUtils;
 
@@ -79,11 +79,11 @@
     }
 
     private static int setCurrentTimeForTestMode(final int currentTime) {
-        return BinaryDictionary.setCurrentTimeForTest(currentTime);
+        return BinaryDictionaryUtils.setCurrentTimeForTest(currentTime);
     }
 
     private static int stopTestModeInNativeCode() {
-        return BinaryDictionary.setCurrentTimeForTest(-1);
+        return BinaryDictionaryUtils.setCurrentTimeForTest(-1);
     }
 
     /**
diff --git a/tests/src/com/android/inputmethod/latin/EditDistanceTests.java b/tests/src/com/android/inputmethod/latin/utils/EditDistanceTests.java
similarity index 79%
rename from tests/src/com/android/inputmethod/latin/EditDistanceTests.java
rename to tests/src/com/android/inputmethod/latin/utils/EditDistanceTests.java
index ffec20a..5831226 100644
--- a/tests/src/com/android/inputmethod/latin/EditDistanceTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/EditDistanceTests.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -29,7 +29,7 @@
      * sitting
      */
     public void testExample1() {
-        final int dist = BinaryDictionary.editDistance("kitten", "sitting");
+        final int dist = BinaryDictionaryUtils.editDistance("kitten", "sitting");
         assertEquals("edit distance between 'kitten' and 'sitting' is 3",
                 3, dist);
     }
@@ -42,26 +42,26 @@
      * S--unday
      */
     public void testExample2() {
-        final int dist = BinaryDictionary.editDistance("Saturday", "Sunday");
+        final int dist = BinaryDictionaryUtils.editDistance("Saturday", "Sunday");
         assertEquals("edit distance between 'Saturday' and 'Sunday' is 3",
                 3, dist);
     }
 
     public void testBothEmpty() {
-        final int dist = BinaryDictionary.editDistance("", "");
+        final int dist = BinaryDictionaryUtils.editDistance("", "");
         assertEquals("when both string are empty, no edits are needed",
                 0, dist);
     }
 
     public void testFirstArgIsEmpty() {
-        final int dist = BinaryDictionary.editDistance("", "aaaa");
+        final int dist = BinaryDictionaryUtils.editDistance("", "aaaa");
         assertEquals("when only one string of the arguments is empty,"
                  + " the edit distance is the length of the other.",
                  4, dist);
     }
 
     public void testSecoondArgIsEmpty() {
-        final int dist = BinaryDictionary.editDistance("aaaa", "");
+        final int dist = BinaryDictionaryUtils.editDistance("aaaa", "");
         assertEquals("when only one string of the arguments is empty,"
                  + " the edit distance is the length of the other.",
                  4, dist);
@@ -70,27 +70,27 @@
     public void testSameStrings() {
         final String arg1 = "The quick brown fox jumps over the lazy dog.";
         final String arg2 = "The quick brown fox jumps over the lazy dog.";
-        final int dist = BinaryDictionary.editDistance(arg1, arg2);
+        final int dist = BinaryDictionaryUtils.editDistance(arg1, arg2);
         assertEquals("when same strings are passed, distance equals 0.",
                 0, dist);
     }
 
     public void testSameReference() {
         final String arg = "The quick brown fox jumps over the lazy dog.";
-        final int dist = BinaryDictionary.editDistance(arg, arg);
+        final int dist = BinaryDictionaryUtils.editDistance(arg, arg);
         assertEquals("when same string references are passed, the distance equals 0.",
                 0, dist);
     }
 
     public void testNullArg() {
         try {
-            BinaryDictionary.editDistance(null, "aaa");
+            BinaryDictionaryUtils.editDistance(null, "aaa");
             fail("IllegalArgumentException should be thrown.");
         } catch (Exception e) {
             assertTrue(e instanceof IllegalArgumentException);
         }
         try {
-            BinaryDictionary.editDistance("aaa", null);
+            BinaryDictionaryUtils.editDistance("aaa", null);
             fail("IllegalArgumentException should be thrown.");
         } catch (Exception e) {
             assertTrue(e instanceof IllegalArgumentException);
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index b83ce57..b1dd7f6 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -41,6 +41,7 @@
         $(LATINIME_CORE_SOURCE_DIRECTORY)/SuggestedWords.java \
         $(LATINIME_CORE_SOURCE_DIRECTORY)/WordComposer.java \
         $(LATINIME_CORE_SOURCE_DIRECTORY)/settings/NativeSuggestOptions.java \
+        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/BinaryDictionaryUtils.java \
         $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/ByteArrayDictBuffer.java \
         $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/CollectionUtils.java \
         $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/CombinedFormatUtils.java \
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>