Merge "Separate WeightedString from FusionDictionary."
diff --git a/java/res/values-ka-rGE/strings.xml b/java/res/values-ka-rGE/strings.xml
index 056bc35..8fe415d 100644
--- a/java/res/values-ka-rGE/strings.xml
+++ b/java/res/values-ka-rGE/strings.xml
@@ -167,7 +167,7 @@
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"ინსტალაციისათვის აირჩიეთ ლექსიკონის ფაილი"</string>
     <string name="read_external_dictionary_confirm_install_message" msgid="4782116251651288054">"ნამდვილად გსურთ ამ ფაილის <xliff:g id="LANGUAGE_NAME">%s</xliff:g>-ისთვის ინსტალაცია?"</string>
     <string name="error" msgid="8940763624668513648">"წარმოიშვა შეცდომა"</string>
-    <string name="prefs_dump_contacts_dict" msgid="7227327764402323097">"კონტაქტების საქაღალდის ჩამოწერა"</string>
+    <string name="prefs_dump_contacts_dict" msgid="7227327764402323097">"კონტაქტების საქაღალდის ამონაწერი"</string>
     <string name="prefs_dump_user_dict" msgid="294870685041741951">"პერსონალური საქაღალდის ჩამოწერა"</string>
     <string name="prefs_dump_user_history_dict" msgid="6821075152449554628">"მომხმ. ისტორიის საქაღალდის ჩამოწერა"</string>
     <string name="prefs_dump_personalization_dict" msgid="7558387996151745284">"პერსონალიზაციის საქაღალდის ჩამოწერა"</string>
diff --git a/java/res/values-lo-rLA/strings.xml b/java/res/values-lo-rLA/strings.xml
index 9f28cd1..dae75e3 100644
--- a/java/res/values-lo-rLA/strings.xml
+++ b/java/res/values-lo-rLA/strings.xml
@@ -167,7 +167,7 @@
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"ເລືອກໄຟລ໌ວັດຈະນານຸກົມເພື່ອຕິດຕັ້ງ"</string>
     <string name="read_external_dictionary_confirm_install_message" msgid="4782116251651288054">"ຕິດ​ຕັ້ງ​ໄຟ​ລ໌​ນີ້​ສຳ​ລັບ <xliff:g id="LANGUAGE_NAME">%s</xliff:g> ແທ້ບໍ່??"</string>
     <string name="error" msgid="8940763624668513648">"ມີຂໍ້ຜິດພາດເກີດຂຶ້ນ"</string>
-    <string name="prefs_dump_contacts_dict" msgid="7227327764402323097">"ເທຂໍ້ມູນວັດຈະນານຸກົມລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
+    <string name="prefs_dump_contacts_dict" msgid="7227327764402323097">"​​ດຶງ​ຂໍ້​ມູນ​ວັດ​ຈະ​ນາ​ນຸ​ກົມ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່"</string>
     <string name="prefs_dump_user_dict" msgid="294870685041741951">"ເທຂໍ້ມູນວັດຈະນານຸກົມສ່ວນໂຕ"</string>
     <string name="prefs_dump_user_history_dict" msgid="6821075152449554628">"ເທຂໍ້ມູນວັດຈະນານຸກົມປະຫວັດຜູ່ໃຊ້"</string>
     <string name="prefs_dump_personalization_dict" msgid="7558387996151745284">"ເທຂໍ້ມູນວັດຈະນານຸກົມຄວາມເປັນໂຕຕົນ"</string>
diff --git a/java/res/values-mn-rMN/strings.xml b/java/res/values-mn-rMN/strings.xml
index ef8181f..6c97403 100644
--- a/java/res/values-mn-rMN/strings.xml
+++ b/java/res/values-mn-rMN/strings.xml
@@ -167,7 +167,7 @@
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Суулгах толь бичгийн файлыг сонгоно уу"</string>
     <string name="read_external_dictionary_confirm_install_message" msgid="4782116251651288054">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g>-д зориулсан энэ файлыг үнэхээр суулгах уу?"</string>
     <string name="error" msgid="8940763624668513648">"Алдаа гарсан"</string>
-    <string name="prefs_dump_contacts_dict" msgid="7227327764402323097">"Харилцагчдын толь бичгийг хаях"</string>
+    <string name="prefs_dump_contacts_dict" msgid="7227327764402323097">"Харилцагчдын толь бичгийг жагсаах"</string>
     <string name="prefs_dump_user_dict" msgid="294870685041741951">"Хувийн толь бичгийг хаях"</string>
     <string name="prefs_dump_user_history_dict" msgid="6821075152449554628">"Хэрэглэгчийн түүхийн толь бичгийг хаях"</string>
     <string name="prefs_dump_personalization_dict" msgid="7558387996151745284">"Хувийн тохиргоотой толь бичгийг хаях"</string>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index ddff769..5efa733 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -491,8 +491,6 @@
     <string name="prefs_key_popup_dismiss_end_scale_settings" translatable="false">Key popup dismiss end scale</string>
     <!-- Title of the settings for reading an external dictionary file -->
     <string name="prefs_read_external_dictionary">Read external dictionary file</string>
-    <!-- Title of the settings for using only personalization dictionary -->
-    <string name="prefs_use_only_personalization_dictionary" translatable="false">Use only personalization dictionary</string>
     <!-- Message to show when there are no files to install as an external dictionary [CHAR LIMIT=100] -->
     <string name="read_external_dictionary_no_files_message">No dictionary files in the Downloads folder</string>
     <!-- Title of the dialog that selects a file to install as an external dictionary [CHAR LIMIT=50] -->
diff --git a/java/res/xml-sw600dp/key_azerty3_right.xml b/java/res/xml-sw600dp/key_azerty3_right.xml
deleted file mode 100644
index 25b0e52..0000000
--- a/java/res/xml-sw600dp/key_azerty3_right.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <Key
-        latin:keySpec=":"
-        latin:keyHintLabel=";"
-        latin:moreKeys=";"
-        latin:keyStyle="hasShiftedLetterHintStyle" />
-</merge>
diff --git a/java/res/xml/key_azerty3_right.xml b/java/res/xml/key_azerty3_right.xml
deleted file mode 100644
index 85a0666..0000000
--- a/java/res/xml/key_azerty3_right.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <switch>
-        <case
-            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLockShifted"
-        >
-            <Key
-                latin:keySpec="\?" />
-        </case>
-        <default>
-            <Key
-                latin:keySpec="\'"
-                latin:moreKeys="!text/more_keys_for_single_quote" />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index 81a5d98..7b2b8ea 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -65,11 +65,6 @@
         android:key="pref_key_preview_dismiss_duration"
         android:title="@string/prefs_key_popup_dismiss_duration_settings"
         latin:maxValue="100" /> <!-- milliseconds -->
-    <CheckBoxPreference
-        android:defaultValue="false"
-        android:key="use_only_personalization_dictionary_for_debug"
-        android:persistent="true"
-        android:title="@string/prefs_use_only_personalization_dictionary" />
     <PreferenceScreen
         android:key="read_external_dictionary"
         android:title="@string/prefs_read_external_dictionary" />
diff --git a/java/res/xml/rowkeys_azerty3.xml b/java/res/xml/rowkeys_azerty3.xml
index 0aa2153..c955e23 100644
--- a/java/res/xml/rowkeys_azerty3.xml
+++ b/java/res/xml/rowkeys_azerty3.xml
@@ -37,6 +37,17 @@
     <Key
         latin:keySpec="n"
         latin:moreKeys="!text/more_keys_for_n" />
-    <include
-        latin:keyboardLayout="@xml/key_azerty3_right" />
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+        >
+            <Key
+                latin:keySpec="\?" />
+        </case>
+        <default>
+            <Key
+                latin:keySpec="\'"
+                latin:moreKeys="!text/more_keys_for_single_quote" />
+        </default>
+    </switch>
 </merge>
diff --git a/java/res/xml/rowkeys_east_slavic2.xml b/java/res/xml/rowkeys_east_slavic2.xml
index 20d963c..0601000 100644
--- a/java/res/xml/rowkeys_east_slavic2.xml
+++ b/java/res/xml/rowkeys_east_slavic2.xml
@@ -25,8 +25,8 @@
     <Key
         latin:keySpec="&#x0444;" />
     <Key
-        latin:keySpec="!text/keylabel_for_east_slavic_row2_1"
-        latin:moreKeys="!text/more_keys_for_east_slavic_row2_1" />
+        latin:keySpec="!text/keylabel_for_east_slavic_row2_2"
+        latin:moreKeys="!text/more_keys_for_east_slavic_row2_2" />
     <!-- U+0432: "в" CYRILLIC SMALL LETTER VE -->
     <Key
         latin:keySpec="&#x0432;" />
diff --git a/java/src/com/android/inputmethod/event/InputTransaction.java b/java/src/com/android/inputmethod/event/InputTransaction.java
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/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 6c56b8a..3590c48 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -700,12 +700,12 @@
     }
 
     @Override
-    public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {
+    public void onCancelMoreKeysPanel() {
         PointerTracker.dismissAllMoreKeysPanels();
     }
 
     @Override
-    public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {
+    public void onDismissMoreKeysPanel() {
         dimEntireKeyboard(false /* dimmed */);
         if (isShowingMoreKeysPanel()) {
             mMoreKeysPanel.removeFromParent();
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 1891dfc..fc33197 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -122,7 +122,7 @@
         onMoveKeyInternal(x, y, pointerId);
         if (hasOldKey && mCurrentKey == null) {
             // If the pointer has moved too far away from any target then cancel the panel.
-            mController.onCancelMoreKeysPanel(this);
+            mController.onCancelMoreKeysPanel();
         }
     }
 
@@ -184,7 +184,7 @@
         if (!isShowingInParent()) {
             return;
         }
-        mController.onDismissMoreKeysPanel(this);
+        mController.onDismissMoreKeysPanel();
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
index 4a33e65..7bddd09 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
@@ -29,24 +29,22 @@
 
         /**
          * Remove the current {@link MoreKeysPanel} from the target view.
-         * @param panel the panel to be dismissed.
          */
-        public void onDismissMoreKeysPanel(final MoreKeysPanel panel);
+        public void onDismissMoreKeysPanel();
 
         /**
          * Instructs the parent to cancel the panel (e.g., when entering a different input mode).
-         * @param panel the panel to be canceled.
          */
-        public void onCancelMoreKeysPanel(final MoreKeysPanel panel);
+        public void onCancelMoreKeysPanel();
     }
 
     public static final Controller EMPTY_CONTROLLER = new Controller() {
         @Override
         public void onShowMoreKeysPanel(final MoreKeysPanel panel) {}
         @Override
-        public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {}
+        public void onDismissMoreKeysPanel() {}
         @Override
-        public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {}
+        public void onCancelMoreKeysPanel() {}
     };
 
     /**
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/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/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
index cd18a6b..d6178fc 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -55,7 +55,6 @@
 
     private final ConcurrentHashMap<String, Dictionary> mDictionaries =
             CollectionUtils.newConcurrentHashMap();
-    private HashSet<String> mDictionarySubsetForDebug = null;
 
     private Dictionary mMainDictionary;
     private ContactsBinaryDictionary mContactsDictionary;
@@ -85,7 +84,6 @@
         mContext = context;
         mLocale = locale;
         mLatchForWaitingLoadingMainDictionary = new CountDownLatch(1);
-        initForDebug(settingsValues);
         loadMainDict(context, locale, listener);
         setUserDictionary(new UserBinaryDictionary(context, locale));
         resetAdditionalDictionaries(oldDictionaryFacilitator, settingsValues);
@@ -101,7 +99,6 @@
             final DictionaryFacilitatorForSuggest oldDictionaryFacilitator) {
         mContext = oldDictionaryFacilitator.mContext;
         mLocale = oldDictionaryFacilitator.mLocale;
-        mDictionarySubsetForDebug = oldDictionaryFacilitator.mDictionarySubsetForDebug;
         mLatchForWaitingLoadingMainDictionary = new CountDownLatch(1);
         loadMainDict(mContext, mLocale, listener);
         // Transfer user dictionary.
@@ -130,7 +127,6 @@
         mContext = oldDictionaryFacilitator.mContext;
         mLocale = oldDictionaryFacilitator.mLocale;
         mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
-        initForDebug(settingsValues);
         // Transfer main dictionary.
         setMainDictionary(oldDictionaryFacilitator.mMainDictionary);
         oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_MAIN);
@@ -197,12 +193,12 @@
         }
     }
 
-    // initialize a debug flag for the personalization
-    private void initForDebug(final SettingsValues settingsValues) {
-        if (settingsValues.mUseOnlyPersonalizationDictionaryForDebug) {
-            mDictionarySubsetForDebug = new HashSet<String>();
-            mDictionarySubsetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
-        }
+    public boolean needsToBeRecreated(final Locale newLocale,
+            final SettingsValues newSettingsValues) {
+        return !mLocale.equals(newLocale)
+                || (newSettingsValues.mUseContactsDict != (mContactsDictionary != null))
+                || (newSettingsValues.mUsePersonalizedDicts != (mUserHistoryDictionary != null))
+                || (newSettingsValues.mUsePersonalizedDicts != hasPersonalizationDictionary());
     }
 
     public void close() {
@@ -531,10 +527,6 @@
     }
 
     private void addOrReplaceDictionary(final String key, final Dictionary dict) {
-        if (mDictionarySubsetForDebug != null && !mDictionarySubsetForDebug.contains(key)) {
-            Log.w(TAG, "Ignore add " + key + " dictionary for debug.");
-            return;
-        }
         final Dictionary oldDict;
         if (dict == null) {
             oldDict = mDictionaries.remove(key);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index bfc5780..a9e5480 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -59,6 +59,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
+import com.android.inputmethod.event.InputTransaction;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardId;
@@ -78,7 +79,6 @@
 import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
 import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
-import com.android.inputmethod.latin.utils.CompletionInfoUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
 import com.android.inputmethod.latin.utils.IntentUtils;
@@ -124,9 +124,6 @@
     private View mKeyPreviewBackingView;
     private SuggestionStripView mSuggestionStripView;
 
-    // TODO[IL]: remove this member completely.
-    public CompletionInfo[] mApplicationSpecifiedCompletions;
-
     private RichInputMethodManager mRichImm;
     @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
     private final SubtypeSwitcher mSubtypeSwitcher;
@@ -192,8 +189,9 @@
             final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher;
             switch (msg.what) {
             case MSG_UPDATE_SUGGESTION_STRIP:
+                cancelUpdateSuggestionStrip();
                 latinIme.mInputLogic.performUpdateSuggestionStripSync(
-                        latinIme.mSettings.getCurrent(), this /* handler */);
+                        latinIme.mSettings.getCurrent());
                 break;
             case MSG_UPDATE_SHIFT_STATE:
                 switcher.updateShiftState();
@@ -530,27 +528,31 @@
         final EditorInfo editorInfo = getCurrentInputEditorInfo();
         final InputAttributes inputAttributes = new InputAttributes(editorInfo, isFullscreenMode());
         mSettings.loadSettings(this, locale, inputAttributes);
-        AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(mSettings.getCurrent());
-        // To load the keyboard we need to load all the settings once, but resetting the
-        // contacts dictionary should be deferred until after the new layout has been displayed
-        // to improve responsivity. In the language switching process, we post a reopenDictionaries
-        // message, then come here to read the settings for the new language before we change
-        // the layout; at this time, we need to skip resetting the contacts dictionary. It will
-        // be done later inside {@see #initSuggest()} when the reopenDictionaries message is
-        // processed.
         final SettingsValues currentSettingsValues = mSettings.getCurrent();
-        final Suggest suggest = mInputLogic.mSuggest;
-        if (!mHandler.hasPendingReopenDictionaries() && suggest != null) {
+        AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
+        // This method is called on startup and language switch, before the new layout has
+        // been displayed. Opening dictionaries never affects responsivity as dictionaries are
+        // asynchronously loaded.
+        initOrResetSuggestForSettingsValues(mInputLogic.mSuggest, locale, currentSettingsValues);
+    }
+
+    private void initOrResetSuggestForSettingsValues(final Suggest oldSuggest,
+            final Locale locale, final SettingsValues settingsValues) {
+        if (!mHandler.hasPendingReopenDictionaries() && oldSuggest != null) {
             // May need to reset dictionaries depending on the user settings.
             final DictionaryFacilitatorForSuggest oldDictionaryFacilitator =
-                    suggest.mDictionaryFacilitator;
+                    oldSuggest.mDictionaryFacilitator;
+            if (!oldDictionaryFacilitator.needsToBeRecreated(locale, settingsValues)) {
+                // Continue to use the same dictionary facilitator if no configuration has changed.
+                refreshPersonalizationDictionarySession();
+                return;
+            }
             final DictionaryFacilitatorForSuggest dictionaryFacilitator =
-                    new DictionaryFacilitatorForSuggest(currentSettingsValues,
-                            oldDictionaryFacilitator);
+                    new DictionaryFacilitatorForSuggest(settingsValues, oldDictionaryFacilitator);
             // Create Suggest instance with the new dictionary facilitator.
-            resetSuggest(new Suggest(suggest /* oldSuggest */, dictionaryFacilitator));
-        } else if (suggest == null) {
-            initSuggestForLocale(locale);
+            replaceSuggest(new Suggest(oldSuggest, dictionaryFacilitator));
+        } else if (oldSuggest == null) {
+            initSuggest();
         }
     }
 
@@ -610,13 +612,13 @@
         } else {
             subtypeLocale = switcherSubtypeLocale;
         }
-        initSuggestForLocale(subtypeLocale);
+        initSuggestForLocale(mInputLogic.mSuggest, subtypeLocale);
     }
 
-    private void initSuggestForLocale(final Locale locale) {
+    private void initSuggestForLocale(final Suggest oldSuggest, final Locale locale) {
         final SettingsValues settingsValues = mSettings.getCurrent();
         final DictionaryFacilitatorForSuggest oldDictionaryFacilitator =
-                (mInputLogic.mSuggest == null) ? null : mInputLogic.mSuggest.mDictionaryFacilitator;
+                (oldSuggest == null) ? null : oldSuggest.mDictionaryFacilitator;
         // Creates new dictionary facilitator for the new locale.
         final DictionaryFacilitatorForSuggest dictionaryFacilitator =
                 new DictionaryFacilitatorForSuggest(this /* context */, locale, settingsValues,
@@ -625,7 +627,7 @@
         if (settingsValues.mCorrectionEnabled) {
             newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
         }
-        resetSuggest(newSuggest);
+        replaceSuggest(newSuggest);
     }
 
     /* package private */ void resetSuggestMainDict() {
@@ -633,10 +635,10 @@
                 mInputLogic.mSuggest.mDictionaryFacilitator;
         final DictionaryFacilitatorForSuggest dictionaryFacilitator =
                 new DictionaryFacilitatorForSuggest(this /* listener */, oldDictionaryFacilitator);
-        resetSuggest(new Suggest(mInputLogic.mSuggest /* oldSuggest */, dictionaryFacilitator));
+        replaceSuggest(new Suggest(mInputLogic.mSuggest /* oldSuggest */, dictionaryFacilitator));
     }
 
-    private void resetSuggest(final Suggest newSuggest) {
+    private void replaceSuggest(final Suggest newSuggest) {
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.getInstance().initDictionary(newSuggest.mDictionaryFacilitator);
         }
@@ -808,7 +810,6 @@
         // The EditorInfo might have a flag that affects fullscreen mode.
         // Note: This call should be done by InputMethodService?
         updateFullscreenMode();
-        mApplicationSpecifiedCompletions = null;
 
         // The app calling setText() has the effect of clearing the composing
         // span, so we should reset our state unconditionally, even if restarting is true.
@@ -875,7 +876,7 @@
         }
         // This will set the punctuation suggestions if next word suggestion is off;
         // otherwise it will clear the suggestion strip.
-        setNeutralSuggestionStripInternal();
+        setNeutralSuggestionStrip();
 
         mHandler.cancelUpdateSuggestionStrip();
         mHandler.cancelDoubleSpacePeriodTimer();
@@ -950,8 +951,7 @@
         // NOTE: the test harness subclasses LatinIME and overrides isInputViewShown().
         // TODO: find a better way to simulate actual execution.
         if (isInputViewShown() &&
-                mInputLogic.onUpdateSelection(mSettings.getCurrent(), oldSelStart, oldSelEnd,
-                        newSelStart, newSelEnd, composingSpanStart, composingSpanEnd)) {
+                mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd)) {
             mKeyboardSwitcher.updateShiftState();
         }
 
@@ -1030,8 +1030,6 @@
             }
             return;
         }
-        mApplicationSpecifiedCompletions =
-                CompletionInfoUtils.removeNulls(applicationSpecifiedCompletions);
 
         final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
                 SuggestedWords.getFromApplicationSpecifiedCompletions(
@@ -1046,18 +1044,6 @@
         }
     }
 
-    private void setSuggestionStripShownInternal(final boolean isSuggestionStripVisible) {
-        // TODO: Modify this if we support suggestions with hard keyboard
-        if (!onEvaluateInputViewShown() || !hasSuggestionStripView()) {
-            return;
-        }
-        if (isSuggestionStripVisible) {
-            mSuggestionStripView.setVisibility(View.VISIBLE);
-        } else {
-            mSuggestionStripView.setVisibility(isFullscreenMode() ? View.GONE : View.INVISIBLE);
-        }
-    }
-
     private int getAdjustedBackingViewHeight() {
         final int currentHeight = mKeyPreviewBackingView.getHeight();
         if (currentHeight > 0) {
@@ -1280,8 +1266,18 @@
             mSubtypeSwitcher.switchToShortcutIME(this);
             // Still call the *#onCodeInput methods for readability.
         }
-        mInputLogic.onCodeInput(codeToSend, keyX, keyY, mSettings.getCurrent(), mHandler,
-                mKeyboardSwitcher);
+        final InputTransaction completeInputTransaction =
+                mInputLogic.onCodeInput(mSettings.getCurrent(), codeToSend, keyX, keyY,
+                        mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
+        switch (completeInputTransaction.getRequiredShiftUpdate()) {
+            case InputTransaction.SHIFT_UPDATE_LATER:
+                mHandler.postUpdateShiftState();
+                break;
+            case InputTransaction.SHIFT_UPDATE_NOW:
+                mKeyboardSwitcher.updateShiftState();
+                break;
+            default: // SHIFT_NO_UPDATE
+        }
         mKeyboardSwitcher.onCodeInput(codePoint);
     }
 
@@ -1295,7 +1291,7 @@
 
     @Override
     public void onStartBatchInput() {
-        mInputLogic.onStartBatchInput(mSettings.getCurrent(),  mKeyboardSwitcher, mHandler);
+        mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler);
     }
 
     @Override
@@ -1305,7 +1301,7 @@
 
     @Override
     public void onEndBatchInput(final InputPointers batchPointers) {
-        mInputLogic.onEndBatchInput(mSettings.getCurrent(), batchPointers);
+        mInputLogic.onEndBatchInput(batchPointers);
     }
 
     @Override
@@ -1383,13 +1379,23 @@
     public void setSuggestedWords(final SuggestedWords suggestedWords,
             final boolean isSuggestionStripVisible) {
         mInputLogic.setSuggestedWords(suggestedWords);
+        // TODO: Modify this when we support suggestions with hard keyboard
         if (!hasSuggestionStripView()) {
             return;
         }
+        mKeyboardSwitcher.onAutoCorrectionStateChanged(suggestedWords.mWillAutoCorrect);
+        if (!onEvaluateInputViewShown()) {
+            return;
+        }
+        if (!isSuggestionStripVisible) {
+            mSuggestionStripView.setVisibility(isFullscreenMode() ? View.GONE : View.INVISIBLE);
+            return;
+        }
+        mSuggestionStripView.setVisibility(View.VISIBLE);
+
         final SettingsValues currentSettings = mSettings.getCurrent();
         final boolean showSuggestions;
-        if (SuggestedWords.EMPTY == suggestedWords
-                || suggestedWords.isPunctuationSuggestions()
+        if (SuggestedWords.EMPTY == suggestedWords || suggestedWords.isPunctuationSuggestions()
                 || !currentSettings.isSuggestionsRequested()) {
             showSuggestions = !mSuggestionStripView.maybeShowImportantNoticeTitle(
                     currentSettings.mInputAttributes);
@@ -1400,8 +1406,6 @@
             mSuggestionStripView.setSuggestions(suggestedWords,
                     SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype()));
         }
-        mKeyboardSwitcher.onAutoCorrectionStateChanged(suggestedWords.mWillAutoCorrect);
-        setSuggestionStripShownInternal(isSuggestionStripVisible);
     }
 
     // TODO[IL]: Move this out of LatinIME.
@@ -1445,32 +1449,6 @@
                 sequenceNumber, callback);
     }
 
-    // TODO[IL]: Move this to InputLogic
-    public SuggestedWords maybeRetrieveOlderSuggestions(final String typedWord,
-            final SuggestedWords suggestedWords, final SuggestedWords previousSuggestedWords) {
-        // TODO: consolidate this into getSuggestedWords
-        // We update the suggestion strip only when we have some suggestions to show, i.e. when
-        // the suggestion count is > 1; else, we leave the old suggestions, with the typed word
-        // replaced with the new one. However, when the length of the typed word is 1 or 0 (after
-        // a deletion typically), we do want to remove the old suggestions. Also, if we are showing
-        // the "add to dictionary" hint, we need to revert to suggestions - although it is unclear
-        // how we can come here if it's displayed.
-        if (suggestedWords.size() > 1 || typedWord.length() <= 1
-                || !hasSuggestionStripView() || isShowingAddToDictionaryHint()) {
-            return suggestedWords;
-        } else {
-            final SuggestedWords punctuationList =
-                    mSettings.getCurrent().mSpacingAndPunctuations.mSuggestPuncList;
-            final SuggestedWords oldSuggestedWords = previousSuggestedWords == punctuationList
-                    ? SuggestedWords.EMPTY : previousSuggestedWords;
-            final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
-                    SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords);
-            return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */,
-                    false /* typedWordValid */, false /* hasAutoCorrectionCandidate */,
-                    true /* isObsoleteSuggestions */, false /* isPrediction */);
-        }
-    }
-
     @Override
     public void showSuggestionStrip(final SuggestedWords sourceSuggestedWords) {
         final SuggestedWords suggestedWords =
@@ -1511,15 +1489,10 @@
         mSuggestionStripView.showAddToDictionaryHint(word);
     }
 
-    // TODO[IL]: Define a clean interface for this
     // This will show either an empty suggestion strip (if prediction is enabled) or
     // punctuation suggestions (if it's disabled).
     @Override
     public void setNeutralSuggestionStrip() {
-        setNeutralSuggestionStripInternal();
-    }
-
-    private void setNeutralSuggestionStripInternal() {
         final SettingsValues currentSettings = mSettings.getCurrent();
         final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled
                 ? SuggestedWords.EMPTY : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
@@ -1733,7 +1706,7 @@
         final DictionaryFacilitatorForSuggest dictionaryFacilitator =
                 new DictionaryFacilitatorForSuggest(this, locale, mSettings.getCurrent(),
                         this /* listener */, oldDictionaryFacilitator);
-        resetSuggest(new Suggest(locale, dictionaryFacilitator));
+        replaceSuggest(new Suggest(locale, dictionaryFacilitator));
     }
 
     // DO NOT USE THIS for any other purpose than testing.
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 6b6bbf3..630a036 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -50,7 +50,7 @@
     private static final RichInputMethodManager sInstance = new RichInputMethodManager();
 
     private InputMethodManagerCompatWrapper mImmWrapper;
-    private InputMethodInfo mInputMethodInfoOfThisIme;
+    private InputMethodInfoCache mInputMethodInfoCache;
     final HashMap<InputMethodInfo, List<InputMethodSubtype>>
             mSubtypeListCacheWithImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
     final HashMap<InputMethodInfo, List<InputMethodSubtype>>
@@ -83,7 +83,8 @@
             return;
         }
         mImmWrapper = new InputMethodManagerCompatWrapper(context);
-        mInputMethodInfoOfThisIme = getInputMethodInfoOfThisIme(context);
+        mInputMethodInfoCache = new InputMethodInfoCache(
+                mImmWrapper.mImm, context.getPackageName());
 
         // Initialize additional subtypes.
         SubtypeLocaleUtils.init(context);
@@ -99,20 +100,10 @@
         return mImmWrapper.mImm;
     }
 
-    private InputMethodInfo getInputMethodInfoOfThisIme(final Context context) {
-        final String packageName = context.getPackageName();
-        for (final InputMethodInfo imi : mImmWrapper.mImm.getInputMethodList()) {
-            if (imi.getPackageName().equals(packageName)) {
-                return imi;
-            }
-        }
-        throw new RuntimeException("Input method id for " + packageName + " not found.");
-    }
-
     public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
             boolean allowsImplicitlySelectedSubtypes) {
-        return getEnabledInputMethodSubtypeList(mInputMethodInfoOfThisIme,
-                allowsImplicitlySelectedSubtypes);
+        return getEnabledInputMethodSubtypeList(
+                getInputMethodInfoOfThisIme(), allowsImplicitlySelectedSubtypes);
     }
 
     public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
@@ -153,10 +144,10 @@
     private boolean switchToNextInputMethodAndSubtype(final IBinder token) {
         final InputMethodManager imm = mImmWrapper.mImm;
         final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
-        final int currentIndex = getImiIndexInList(mInputMethodInfoOfThisIme, enabledImis);
+        final int currentIndex = getImiIndexInList(getInputMethodInfoOfThisIme(), enabledImis);
         if (currentIndex == INDEX_NOT_FOUND) {
             Log.w(TAG, "Can't find current IME in enabled IMEs: IME package="
-                    + mInputMethodInfoOfThisIme.getPackageName());
+                    + getInputMethodInfoOfThisIme().getPackageName());
             return false;
         }
         final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis);
@@ -213,16 +204,45 @@
         return true;
     }
 
+    private static class InputMethodInfoCache {
+        private final InputMethodManager mImm;
+        private final String mImePackageName;
+
+        private InputMethodInfo mCachedValue;
+
+        public InputMethodInfoCache(final InputMethodManager imm, final String imePackageName) {
+            mImm = imm;
+            mImePackageName = imePackageName;
+        }
+
+        public synchronized InputMethodInfo get() {
+            if (mCachedValue != null) {
+                return mCachedValue;
+            }
+            for (final InputMethodInfo imi : mImm.getInputMethodList()) {
+                if (imi.getPackageName().equals(mImePackageName)) {
+                    mCachedValue = imi;
+                    return imi;
+                }
+            }
+            throw new RuntimeException("Input method id for " + mImePackageName + " not found.");
+        }
+
+        public synchronized void clear() {
+            mCachedValue = null;
+        }
+    }
+
     public InputMethodInfo getInputMethodInfoOfThisIme() {
-        return mInputMethodInfoOfThisIme;
+        return mInputMethodInfoCache.get();
     }
 
     public String getInputMethodIdOfThisIme() {
-        return mInputMethodInfoOfThisIme.getId();
+        return getInputMethodInfoOfThisIme().getId();
     }
 
     public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) {
-        return checkIfSubtypeBelongsToImeAndEnabled(mInputMethodInfoOfThisIme, subtype);
+        return checkIfSubtypeBelongsToImeAndEnabled(getInputMethodInfoOfThisIme(), subtype);
     }
 
     public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
@@ -258,7 +278,7 @@
     }
 
     public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) {
-        return getSubtypeIndexInIme(subtype, mInputMethodInfoOfThisIme) != INDEX_NOT_FOUND;
+        return getSubtypeIndexInIme(subtype, getInputMethodInfoOfThisIme()) != INDEX_NOT_FOUND;
     }
 
     private static int getSubtypeIndexInIme(final InputMethodSubtype subtype,
@@ -286,7 +306,8 @@
 
     public boolean hasMultipleEnabledSubtypesInThisIme(
             final boolean shouldIncludeAuxiliarySubtypes) {
-        final List<InputMethodInfo> imiList = Collections.singletonList(mInputMethodInfoOfThisIme);
+        final List<InputMethodInfo> imiList = Collections.singletonList(
+                getInputMethodInfoOfThisIme());
         return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
     }
 
@@ -340,7 +361,7 @@
 
     public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString,
             final String keyboardLayoutSetName) {
-        final InputMethodInfo myImi = mInputMethodInfoOfThisIme;
+        final InputMethodInfo myImi = getInputMethodInfoOfThisIme();
         final int count = myImi.getSubtypeCount();
         for (int i = 0; i < count; i++) {
             final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
@@ -355,13 +376,14 @@
 
     public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) {
         mImmWrapper.mImm.setInputMethodAndSubtype(
-                token, mInputMethodInfoOfThisIme.getId(), subtype);
+                token, getInputMethodIdOfThisIme(), subtype);
     }
 
     public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
         mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
-                mInputMethodInfoOfThisIme.getId(), subtypes);
-        // Clear the cache so that we go read the subtypes again next time.
+                getInputMethodIdOfThisIme(), subtypes);
+        // Clear the cache so that we go read the {@link InputMethodInfo} of this IME and list of
+        // subtypes again next time.
         clearSubtypeCaches();
     }
 
@@ -382,5 +404,6 @@
     public void clearSubtypeCaches() {
         mSubtypeListCacheWithImplicitlySelectedSubtypes.clear();
         mSubtypeListCacheWithoutImplicitlySelectedSubtypes.clear();
+        mInputMethodInfoCache.clear();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index df25b0c..ba64028 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -60,6 +60,7 @@
     // Locale used for upper- and title-casing words
     public final Locale mLocale;
 
+    // TODO: Move dictionaryFacilitator constructing logics from LatinIME to Suggest.
     public Suggest(final Locale locale,
             final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
         mLocale = locale;
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 06bc90c..dc2c9fd 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -167,15 +167,10 @@
             final CompletionInfo[] infos) {
         final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
         for (final CompletionInfo info : infos) {
-            if (info == null) continue;
-            final CharSequence text = info.getText();
-            if (null == text) continue;
-            final SuggestedWordInfo suggestedWordInfo = new SuggestedWordInfo(text.toString(),
-                    SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_APP_DEFINED,
-                    Dictionary.DICTIONARY_APPLICATION_DEFINED,
-                    SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
-                    SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
-            result.add(suggestedWordInfo);
+            if (null == info || null == info.getText()) {
+                continue;
+            }
+            result.add(new SuggestedWordInfo(info));
         }
         return result;
     }
@@ -234,6 +229,9 @@
         public static final int KIND_FLAG_EXACT_MATCH = 0x40000000;
 
         public final String mWord;
+        // The completion info from the application. Null for suggestions that don't come from
+        // the application (including keyboard-computed ones, so this is almost always null)
+        public final CompletionInfo mApplicationSpecifiedCompletionInfo;
         public final int mScore;
         public final int mKind; // one of the KIND_* constants above
         public final int mCodePointCount;
@@ -260,6 +258,7 @@
                 final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord,
                 final int autoCommitFirstWordConfidence) {
             mWord = word;
+            mApplicationSpecifiedCompletionInfo = null;
             mScore = score;
             mKind = kind;
             mSourceDict = sourceDict;
@@ -268,6 +267,22 @@
             mAutoCommitFirstWordConfidence = autoCommitFirstWordConfidence;
         }
 
+        /**
+         * Create a new suggested word info from an application-specified completion.
+         * If the passed argument or its contained text is null, this throws a NPE.
+         * @param applicationSpecifiedCompletion The application-specified completion info.
+         */
+        public SuggestedWordInfo(final CompletionInfo applicationSpecifiedCompletion) {
+            mWord = applicationSpecifiedCompletion.getText().toString();
+            mApplicationSpecifiedCompletionInfo = applicationSpecifiedCompletion;
+            mScore = SuggestedWordInfo.MAX_SCORE;
+            mKind = SuggestedWordInfo.KIND_APP_DEFINED;
+            mSourceDict = Dictionary.DICTIONARY_APPLICATION_DEFINED;
+            mCodePointCount = StringUtils.codePointCount(mWord);
+            mIndexOfTouchPointOfSecondWord = SuggestedWordInfo.NOT_AN_INDEX;
+            mAutoCommitFirstWordConfidence = SuggestedWordInfo.NOT_A_CONFIDENCE;
+        }
+
         public boolean isEligibleForAutoCommit() {
             return (KIND_CORRECTION == mKind && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord);
         }
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index f2f9f1e..dd9d6e8 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;
@@ -74,7 +74,7 @@
 
     // TODO : make all these fields private as soon as possible.
     // Current space state of the input method. This can be any of the above constants.
-    public int mSpaceState;
+    private int mSpaceState;
     // Never null
     public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
     // TODO: mSuggest should be touched by a single thread.
@@ -85,7 +85,7 @@
     public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
     public final WordComposer mWordComposer;
     public final RichInputConnection mConnection;
-    public final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
+    private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
 
     private int mDeleteCount;
     private long mLastKeyTime;
@@ -96,7 +96,7 @@
 
     // TODO: This boolean is persistent state and causes large side effects at unexpected times.
     // Find a way to remove it for readability.
-    public boolean mIsAutoCorrectionIndicatorOn;
+    private boolean mIsAutoCorrectionIndicatorOn;
 
     public InputLogic(final LatinIME latinIME,
             final SuggestionStripViewAccessor suggestionStripViewAccessor) {
@@ -205,9 +205,9 @@
             LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
             // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
             final int primaryCode = suggestion.charAt(0);
-            onCodeInput(primaryCode,
+            onCodeInput(settingsValues, primaryCode,
                     Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
-                    settingsValues, handler, keyboardSwitcher);
+                    keyboardSwitcher.getKeyboardShiftMode(), handler);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
                         false /* isBatchMode */, suggestedWords.mIsPrediction);
@@ -227,17 +227,16 @@
             }
         }
 
-        // TODO: stop relying on mApplicationSpecifiedCompletions. The SuggestionInfo object
-        // should contain a reference to the CompletionInfo instead.
-        if (settingsValues.isApplicationSpecifiedCompletionsOn()
-                && mLatinIME.mApplicationSpecifiedCompletions != null
-                && index >= 0 && index < mLatinIME.mApplicationSpecifiedCompletions.length) {
+        // TODO: We should not need the following branch. We should be able to take the same
+        // code path as for other kinds, use commitChosenWord, and do everything normally. We will
+        // however need to reset the suggestion strip right away, because we know we can't take
+        // the risk of calling commitCompletion twice because we don't know how the app will react.
+        if (SuggestedWordInfo.KIND_APP_DEFINED == suggestionInfo.mKind) {
             mSuggestedWords = SuggestedWords.EMPTY;
             mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
             keyboardSwitcher.updateShiftState();
             resetComposingState(true /* alsoResetLastComposedWord */);
-            final CompletionInfo completionInfo = mLatinIME.mApplicationSpecifiedCompletions[index];
-            mConnection.commitCompletion(completionInfo);
+            mConnection.commitCompletion(suggestionInfo.mApplicationSpecifiedCompletionInfo);
             mConnection.endBatchEdit();
             return;
         }
@@ -290,19 +289,14 @@
      * Consider an update to the cursor position. Evaluate whether this update has happened as
      * part of normal typing or whether it was an explicit cursor move by the user. In any case,
      * do the necessary adjustments.
-     * @param settingsValues the current settings
      * @param oldSelStart old selection start
      * @param oldSelEnd old selection end
      * @param newSelStart new selection start
      * @param newSelEnd new selection end
-     * @param composingSpanStart composing span start
-     * @param composingSpanEnd composing span end
      * @return whether the cursor has moved as a result of user interaction.
      */
-    public boolean onUpdateSelection(final SettingsValues settingsValues,
-            final int oldSelStart, final int oldSelEnd,
-            final int newSelStart, final int newSelEnd,
-            final int composingSpanStart, final int composingSpanEnd) {
+    public boolean onUpdateSelection(final int oldSelStart, final int oldSelEnd,
+            final int newSelStart, final int newSelEnd) {
         if (mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart, oldSelEnd, newSelEnd)) {
             return false;
         }
@@ -335,8 +329,7 @@
             // we'd have the suggestion strip noticeably janky. To avoid that, we don't clear
             // it here, which means we'll keep outdated suggestions for a split second but the
             // visual result is better.
-            resetEntireInputState(settingsValues, newSelStart, newSelEnd,
-                    false /* clearSuggestionStrip */);
+            resetEntireInputState(newSelStart, newSelEnd, false /* clearSuggestionStrip */);
         } else {
             // resetEntireInputState calls resetCachesUponCursorMove, but forcing the
             // composition to end. But in all cases where we don't reset the entire input
@@ -360,48 +353,49 @@
      * Typically, this is called whenever a key is pressed on the software keyboard. This is not
      * the entry point for gesture input; see the onBatchInput* family of functions for this.
      *
+     * @param settingsValues the current settings values.
      * @param code the code to handle. It may be a code point, or an internal key code.
      * @param x the x-coordinate where the user pressed the key, or NOT_A_COORDINATE.
      * @param y the y-coordinate where the user pressed the key, or NOT_A_COORDINATE.
+     * @param keyboardShiftMode the current shift mode of the keyboard, as returned by
+     *     {@link com.android.inputmethod.keyboard.KeyboardSwitcher#getKeyboardShiftMode()}
+     * @return the complete transaction object
      */
-    public void onCodeInput(final int code, final int x, final int y,
-            final SettingsValues settingsValues,
-            // TODO: remove these two arguments
-            final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) {
+    public InputTransaction onCodeInput(final SettingsValues settingsValues, final int code,
+            final int x, final int y, final int keyboardShiftMode,
+            // TODO: remove this argument
+            final LatinIME.UIHandler handler) {
+        final InputTransaction inputTransaction = new InputTransaction(settingsValues, code, x, y,
+                SystemClock.uptimeMillis(), mSpaceState,
+                getActualCapsMode(settingsValues, keyboardShiftMode));
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.latinIME_onCodeInput(code, x, y);
+            ResearchLogger.latinIME_onCodeInput(inputTransaction.mKeyCode,
+                    inputTransaction.mX, inputTransaction.mY);
         }
-        final long when = SystemClock.uptimeMillis();
-        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
@@ -455,32 +449,35 @@
             } else {
                 // No action label, and the action from imeOptions is NONE: this is a regular
                 // enter key that should input a carriage return.
-                didAutoCorrect = handleNonSpecialCharacter(settingsValues, Constants.CODE_ENTER,
-                        x, y, spaceState, 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();
+        return inputTransaction;
     }
 
     public void onStartBatchInput(final SettingsValues settingsValues,
@@ -504,7 +501,7 @@
             if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
                 // If we are in the middle of a recorrection, we need to commit the recorrection
                 // first so that we can insert the batch input at the current cursor position.
-                resetEntireInputState(settingsValues, mConnection.getExpectedSelectionStart(),
+                resetEntireInputState(mConnection.getExpectedSelectionStart(),
                         mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
             } else if (wordComposerSize <= 1) {
                 // We auto-correct the previous (typed, not gestured) string iff it's one character
@@ -585,8 +582,7 @@
         mInputLogicHandler.onUpdateBatchInput(batchPointers, mAutoCommitSequenceNumber);
     }
 
-    public void onEndBatchInput(final SettingsValues settingValues,
-            final InputPointers batchPointers) {
+    public void onEndBatchInput(final InputPointers batchPointers) {
         mInputLogicHandler.onEndBatchInput(batchPointers, mAutoCommitSequenceNumber);
         ++mAutoCommitSequenceNumber;
     }
@@ -625,31 +621,26 @@
      * manage keyboard-related stuff like shift, language switch, settings, layout switch, or
      * any key that results in multiple code points like the ".com" key.
      *
-     * @param settingsValues The current settings values.
-     * @param codePoint the code point associated with the key.
-     * @param x the x-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
-     * @param y the y-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
-     * @param spaceState the space state at start of the batch input.
+     * @param inputTransaction The transaction in progress.
      * @return whether this caused an auto-correction to happen.
      */
-    private boolean handleNonSpecialCharacter(final SettingsValues settingsValues,
-            final int codePoint, final int x, final int y, final int spaceState,
-            // 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);
@@ -658,14 +649,13 @@
                 if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
                     // If we are in the middle of a recorrection, we need to commit the recorrection
                     // first so that we can insert the character at the current cursor position.
-                    resetEntireInputState(settingsValues, mConnection.getExpectedSelectionStart(),
+                    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;
     }
@@ -673,15 +663,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.
@@ -689,7 +676,8 @@
 
         // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
         // See onStartBatchInput() to see how to do it.
-        if (SpaceState.PHANTOM == spaceState && !settingsValues.isWordConnector(codePoint)) {
+        if (SpaceState.PHANTOM == inputTransaction.mSpaceState
+                && !settingsValues.isWordConnector(inputTransaction.mKeyCode)) {
             if (isComposingWord) {
                 // Sanity check
                 throw new RuntimeException("Should not be composing here");
@@ -700,7 +688,7 @@
         if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
             // If we are in the middle of a recorrection, we need to commit the recorrection
             // first so that we can insert the character at the current cursor position.
-            resetEntireInputState(settingsValues, mConnection.getExpectedSelectionStart(),
+            resetEntireInputState(mConnection.getExpectedSelectionStart(),
                     mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
             isComposingWord = false;
         }
@@ -711,7 +699,7 @@
         if (!isComposingWord
         // We only start composing if this is a word code point. Essentially that means it's a
         // a letter or a word connector.
-                && settingsValues.isWordCodePoint(codePoint)
+                && settingsValues.isWordCodePoint(inputTransaction.mKeyCode)
         // We never go into composing state if suggestions are not requested.
                 && settingsValues.isSuggestionsRequested() &&
         // In languages with spaces, we only start composing a word when we are not already
@@ -722,8 +710,8 @@
             // the character is a single quote or a dash. The idea here is, single quote and dash
             // are not separators and they should be treated as normal characters, except in the
             // first position where they should not start composing a word.
-            isComposingWord = (Constants.CODE_SINGLE_QUOTE != codePoint
-                    && Constants.CODE_DASH != codePoint);
+            isComposingWord = (Constants.CODE_SINGLE_QUOTE != inputTransaction.mKeyCode
+                    && Constants.CODE_DASH != inputTransaction.mKeyCode);
             // Here we don't need to reset the last composed word. It will be reset
             // when we commit this one, if we ever do; if on the other hand we backspace
             // it entirely and resume suggestions on the previous word, we'd like to still
@@ -731,26 +719,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.
@@ -758,77 +745,80 @@
         }
         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
             // first so that we can insert the separator at the current cursor position.
-            resetEntireInputState(settingsValues, mConnection.getExpectedSelectionStart(),
+            resetEntireInputState(mConnection.getExpectedSelectionStart(),
                     mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
         }
         // 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;
@@ -839,11 +829,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
@@ -864,31 +855,29 @@
             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;
-        final int deleteCountAtStart = mDeleteCount;
         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
             // first so that we can remove the character at the current cursor position.
-            resetEntireInputState(settingsValues, mConnection.getExpectedSelectionStart(),
+            resetEntireInputState(mConnection.getExpectedSelectionStart(),
                     mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
             // When we exit this if-clause, mWordComposer.isComposingWord() will return false.
         }
@@ -909,14 +898,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)) {
@@ -933,14 +922,14 @@
                 // reverting any autocorrect at this point. So we can safely return.
                 return;
             }
-            if (SpaceState.DOUBLE == spaceState) {
+            if (SpaceState.DOUBLE == inputTransaction.mSpaceState) {
                 handler.cancelDoubleSpacePeriodTimer();
                 if (mConnection.revertDoubleSpacePeriod()) {
                     // No need to reset mSpaceState, it has already be done (that's why we
                     // receive it as a parameter)
                     return;
                 }
-            } else if (SpaceState.SWAP_PUNCTUATION == spaceState) {
+            } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
                 if (mConnection.revertSwapPunctuation()) {
                     // Likewise
                     return;
@@ -966,8 +955,8 @@
                     // This should never happen.
                     Log.e(TAG, "Backspace when we don't know the selection position");
                 }
-                if (settingsValues.isBeforeJellyBean() ||
-                        settingsValues.mInputAttributes.isTypeNull()) {
+                if (inputTransaction.mSettingsValues.isBeforeJellyBean() ||
+                        inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()) {
                     // There are two possible reasons to send a key event: either the field has
                     // type TYPE_NULL, in which case the keyboard should send events, or we are
                     // running in backward compatibility mode. Before Jelly bean, the keyboard
@@ -1013,15 +1002,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);
         }
     }
 
@@ -1037,9 +1027,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) {
@@ -1049,28 +1039,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;
@@ -1205,11 +1201,7 @@
                 timeStampInSeconds);
     }
 
-    public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
-            // TODO: Remove this argument
-            final LatinIME.UIHandler handler) {
-        handler.cancelUpdateSuggestionStrip();
-
+    public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) {
         // Check if we have a suggestion engine attached.
         if (mSuggest == null || !settingsValues.isSuggestionsRequested()) {
             if (mWordComposer.isComposingWord()) {
@@ -1229,11 +1221,15 @@
                 SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
                     @Override
                     public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
-                        final SuggestedWords suggestedWordsWithMaybeOlderSuggestions =
-                                mLatinIME.maybeRetrieveOlderSuggestions(
-                                        mWordComposer.getTypedWord(), suggestedWords,
-                                        mSuggestedWords);
-                        holder.set(suggestedWordsWithMaybeOlderSuggestions);
+                        final String typedWord = mWordComposer.getTypedWord();
+                        // Show new suggestions if we have at least one. Otherwise keep the old
+                        // suggestions with the new typed word. Exception: if the length of the
+                        // typed word is <= 1 (after a deletion typically) we clear old suggestions.
+                        if (suggestedWords.size() > 1 || typedWord.length() <= 1) {
+                            holder.set(suggestedWords);
+                        } else {
+                            holder.set(retrieveOlderSuggestions(typedWord, mSuggestedWords));
+                        }
                     }
                 }
         );
@@ -1304,7 +1300,10 @@
                     SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
                     SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
         }
-        if (!isResumableWord(settingsValues, typedWord)) return;
+        if (!isResumableWord(settingsValues, typedWord)) {
+            mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+            return;
+        }
         int i = 0;
         for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) {
             for (final String s : span.getSuggestions()) {
@@ -1485,7 +1484,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;
@@ -1623,15 +1624,13 @@
      * This will clear the composing word, reset the last composed word, clear the suggestion
      * strip and tell the input connection about it so that it can refresh its caches.
      *
-     * @param settingsValues the current values of the settings.
      * @param newSelStart the new selection start, in java characters.
      * @param newSelEnd the new selection end, in java characters.
      * @param clearSuggestionStrip whether this method should clear the suggestion strip.
      */
     // TODO: how is this different from startInput ?!
-    // TODO: remove all references to this in LatinIME and make this private
-    public void resetEntireInputState(final SettingsValues settingsValues,
-            final int newSelStart, final int newSelEnd, final boolean clearSuggestionStrip) {
+    private void resetEntireInputState(final int newSelStart, final int newSelEnd,
+            final boolean clearSuggestionStrip) {
         final boolean shouldFinishComposition = mWordComposer.isComposingWord();
         resetComposingState(true /* alsoResetLastComposedWord */);
         if (clearSuggestionStrip) {
@@ -1649,8 +1648,7 @@
      *
      * @param alsoResetLastComposedWord whether to also reset the last composed word.
      */
-    // TODO: remove all references to this in LatinIME and make this private.
-    public void resetComposingState(final boolean alsoResetLastComposedWord) {
+    private void resetComposingState(final boolean alsoResetLastComposedWord) {
         mWordComposer.reset();
         if (alsoResetLastComposedWord) {
             mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -1658,6 +1656,27 @@
     }
 
     /**
+     * Make a {@link com.android.inputmethod.latin.SuggestedWords} object containing a typed word
+     * and obsolete suggestions.
+     * See {@link com.android.inputmethod.latin.SuggestedWords#getTypedWordAndPreviousSuggestions(
+     *      String, com.android.inputmethod.latin.SuggestedWords)}.
+     * @param typedWord The typed word as a string.
+     * @param previousSuggestedWords The previously suggested words.
+     * @return Obsolete suggestions with the newly typed word.
+     */
+    private SuggestedWords retrieveOlderSuggestions(final String typedWord,
+            final SuggestedWords previousSuggestedWords) {
+        final SuggestedWords oldSuggestedWords =
+                previousSuggestedWords.isPunctuationSuggestions() ? SuggestedWords.EMPTY
+                        : previousSuggestedWords;
+        final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
+                SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords);
+        return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */,
+                false /* typedWordValid */, false /* hasAutoCorrectionCandidate */,
+                true /* isObsoleteSuggestions */, false /* isPrediction */);
+    }
+
+    /**
      * Gets a chunk of text with or the auto-correction indicator underline span as appropriate.
      *
      * This method looks at the old state of the auto-correction indicator to put or not put
@@ -1674,9 +1693,8 @@
      * @param text the text on which to maybe apply the span.
      * @return the same text, with the auto-correction underline span if that's appropriate.
      */
-    // TODO: remove all references to this in LatinIME and make this private. Also, shouldn't
-    // this go in some *Utils class instead?
-    public CharSequence getTextWithUnderline(final String text) {
+    // TODO: Shouldn't this go in some *Utils class instead?
+    private CharSequence getTextWithUnderline(final String text) {
         return mIsAutoCorrectionIndicatorOn
                 ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(mLatinIME, text)
                 : text;
@@ -1710,6 +1728,7 @@
      * @param settingsValues the current values of the settings.
      * @param codePoint the code point to send.
      */
+    // TODO: replace these two parameters with an InputTransaction
     private void sendKeyCodePoint(final SettingsValues settingsValues, final int codePoint) {
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_sendKeyCodePoint(codePoint);
@@ -1741,8 +1760,7 @@
      *
      * @param settingsValues the current values of the settings.
      */
-    // TODO: Make this private.
-    public void promotePhantomSpace(final SettingsValues settingsValues) {
+    private void promotePhantomSpace(final SettingsValues settingsValues) {
         if (settingsValues.shouldInsertSpacesAutomatically()
                 && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
                 && !mConnection.textBeforeCursorLooksLikeURL()) {
@@ -1848,7 +1866,8 @@
             final LatinIME.UIHandler handler) {
         // Complete any pending suggestions query first
         if (handler.hasPendingUpdateSuggestions()) {
-            performUpdateSuggestionStripSync(settingsValues, handler);
+            handler.cancelUpdateSuggestionStrip();
+            performUpdateSuggestionStripSync(settingsValues);
         }
         final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
         final String typedWord = mWordComposer.getTypedWord();
@@ -1892,8 +1911,7 @@
      * @param commitType the type of the commit, as one of LastComposedWord.COMMIT_TYPE_*
      * @param separatorString the separator that's causing the commit, or NOT_A_SEPARATOR if none.
      */
-    // TODO: Make this private
-    public void commitChosenWord(final SettingsValues settingsValues, final String chosenWord,
+    private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord,
             final int commitType, final String separatorString) {
         final SuggestedWords suggestedWords = mSuggestedWords;
         final CharSequence chosenWordWithSuggestions =
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index c87dd15..11d3692 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -42,8 +42,6 @@
     public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch";
     public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
     public static final String PREF_STATISTICS_LOGGING = "enable_logging";
-    public static final String PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
-            "use_only_personalization_dictionary_for_debug";
     public static final String PREF_KEY_PREVIEW_SHOW_UP_START_SCALE =
             "pref_key_preview_show_up_start_scale";
     public static final String PREF_KEY_PREVIEW_DISMISS_END_SCALE =
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 77968f7..50fbbb1 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -84,7 +84,6 @@
     public final float mAutoCorrectionThreshold;
     public final boolean mCorrectionEnabled;
     public final int mSuggestionVisibility;
-    public final boolean mUseOnlyPersonalizationDictionaryForDebug;
     public final int mDisplayOrientation;
     private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
 
@@ -168,8 +167,6 @@
                 prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_SCALE,
                 ResourceUtils.getFloatFromFraction(
                         res, R.fraction.config_key_preview_dismiss_end_scale));
-        mUseOnlyPersonalizationDictionaryForDebug = prefs.getBoolean(
-                DebugSettings.PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
         mDisplayOrientation = res.getConfiguration().orientation;
         mAppWorkarounds = new AsyncResultHolder<AppWorkaroundsUtils>();
         final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo(
@@ -390,8 +387,6 @@
         sb.append("" + mCorrectionEnabled);
         sb.append("\n   mSuggestionVisibility = ");
         sb.append("" + mSuggestionVisibility);
-        sb.append("\n   mUseOnlyPersonalizationDictionaryForDebug = ");
-        sb.append("" + mUseOnlyPersonalizationDictionaryForDebug);
         sb.append("\n   mDisplayOrientation = ");
         sb.append("" + mDisplayOrientation);
         sb.append("\n   mAppWorkarounds = ");
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 4ef562d..43cb11b 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -35,7 +35,6 @@
 import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
@@ -281,8 +280,8 @@
     private final MoreKeysPanel.Controller mMoreSuggestionsController =
             new MoreKeysPanel.Controller() {
         @Override
-        public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {
-            mMainKeyboardView.onDismissMoreKeysPanel(panel);
+        public void onDismissMoreKeysPanel() {
+            mMainKeyboardView.onDismissMoreKeysPanel();
         }
 
         @Override
@@ -291,7 +290,7 @@
         }
 
         @Override
-        public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {
+        public void onCancelMoreKeysPanel() {
             dismissMoreSuggestionsPanel();
         }
     };
@@ -312,7 +311,7 @@
     }
 
     boolean showMoreSuggestions() {
-        final Keyboard parentKeyboard = KeyboardSwitcher.getInstance().getKeyboard();
+        final Keyboard parentKeyboard = mMainKeyboardView.getKeyboard();
         if (parentKeyboard == null) {
             return false;
         }
@@ -320,6 +319,12 @@
         if (!layoutHelper.mMoreSuggestionsAvailable) {
             return false;
         }
+        // Dismiss another {@link MoreKeysPanel} that may be being showed, for example
+        // {@link MoreKeysKeyboardView}.
+        mMainKeyboardView.onDismissMoreKeysPanel();
+        // Dismiss all key previews and sliding key input preview that may be being showed.
+        mMainKeyboardView.dismissAllKeyPreviews();
+        mMainKeyboardView.dismissSlidingKeyInputPreview();
         final int stripWidth = getWidth();
         final View container = mMoreSuggestionsContainer;
         final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight();
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index ef1d0f4..2bb30a2 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
 import com.android.inputmethod.latin.R;
 
@@ -42,6 +43,7 @@
         // This utility class is not publicly instantiable.
     }
 
+    @UsedForTesting
     public static boolean isAdditionalSubtype(final InputMethodSubtype subtype) {
         return subtype.containsExtraValueKey(IS_ADDITIONAL_SUBTYPE);
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index e7932b5..b9d526b 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -507,4 +507,44 @@
         return codePointCount(casedText) == 1
                 ? casedText.codePointAt(0) : CODE_UNSPECIFIED;
     }
+
+    @UsedForTesting
+    public static class Stringizer<E> {
+        public String stringize(final E element) {
+            return element != null ? element.toString() : "null";
+        }
+
+        @UsedForTesting
+        public final String join(final E[] array) {
+            return joinStringArray(toStringArray(array), null /* delimiter */);
+        }
+
+        @UsedForTesting
+        public final String join(final E[] array, final String delimiter) {
+            return joinStringArray(toStringArray(array), delimiter);
+        }
+
+        protected String[] toStringArray(final E[] array) {
+            final String[] stringArray = new String[array.length];
+            for (int index = 0; index < array.length; index++) {
+                stringArray[index] = stringize(array[index]);
+            }
+            return stringArray;
+        }
+
+        protected String joinStringArray(final String[] stringArray, final String delimiter) {
+            if (stringArray == null) {
+                return "null";
+            }
+            if (delimiter == null) {
+                return Arrays.toString(stringArray);
+            }
+            final StringBuilder sb = new StringBuilder();
+            for (int index = 0; index < stringArray.length; index++) {
+                sb.append(index == 0 ? "[" : delimiter);
+                sb.append(stringArray[index]);
+            }
+            return sb + "]";
+        }
+    }
 }
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index 3b3da96..9657b9d 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -31,15 +31,12 @@
     -Wwrite-strings -Wfloat-equal -Wpointer-arith -Winit-self -Wredundant-decls \
     -Woverloaded-virtual -Wstrict-null-sentinel -Wsign-promo -Wno-system-headers
 
-ifeq ($(TARGET_ARCH), arm)
-ifeq ($(TARGET_GCC_VERSION), 4.6)
-LOCAL_CFLAGS += -Winline
-endif # TARGET_GCC_VERSION
-endif # TARGET_ARCH
-
 # To suppress compiler warnings for unused variables/functions used for debug features etc.
 LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function
 
+# For C++11
+LOCAL_CFLAGS += -std=c++11
+
 include $(LOCAL_PATH)/NativeFileList.mk
 
 LOCAL_SRC_FILES := \
@@ -64,7 +61,7 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_SDK_VERSION := 14
-LOCAL_NDK_STL_VARIANT := stlport_static
+LOCAL_NDK_STL_VARIANT := gnustl_static
 
 include $(BUILD_STATIC_LIBRARY)
 ######################################
@@ -87,7 +84,7 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_SDK_VERSION := 14
-LOCAL_NDK_STL_VARIANT := stlport_static
+LOCAL_NDK_STL_VARIANT := gnustl_static
 LOCAL_LDFLAGS += -ldl
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/native/jni/NativeFileList.mk b/native/jni/NativeFileList.mk
index 82237dc..1f58246 100644
--- a/native/jni/NativeFileList.mk
+++ b/native/jni/NativeFileList.mk
@@ -32,7 +32,6 @@
         digraph_utils.cpp \
         error_type_utils.cpp \
         multi_bigram_map.cpp \
-        suggestions_output_utils.cpp \
         word_property.cpp) \
     $(addprefix suggest/core/layout/, \
         additional_proximity_chars.cpp \
@@ -41,6 +40,7 @@
         proximity_info_state.cpp \
         proximity_info_state_utils.cpp) \
     suggest/core/policy/weighting.cpp \
+    suggest/core/result/suggestions_output_utils.cpp \
     suggest/core/session/dic_traverse_session.cpp \
     $(addprefix suggest/policyimpl/dictionary/, \
         header/header_policy.cpp \
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 7a7816d..5b97283 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -46,15 +46,16 @@
     char sourceDirChars[sourceDirUtf8Length + 1];
     env->GetStringUTFRegion(sourceDir, 0, env->GetStringLength(sourceDir), sourceDirChars);
     sourceDirChars[sourceDirUtf8Length] = '\0';
-    DictionaryStructureWithBufferPolicy::StructurePolicyPtr dictionaryStructureWithBufferPolicy =
+    DictionaryStructureWithBufferPolicy::StructurePolicyPtr dictionaryStructureWithBufferPolicy(
             DictionaryStructureWithBufferPolicyFactory::newDictionaryStructureWithBufferPolicy(
                     sourceDirChars, static_cast<int>(dictOffset), static_cast<int>(dictSize),
-                    isUpdatable == JNI_TRUE);
-    if (!dictionaryStructureWithBufferPolicy.get()) {
+                    isUpdatable == JNI_TRUE));
+    if (!dictionaryStructureWithBufferPolicy) {
         return 0;
     }
 
-    Dictionary *const dictionary = new Dictionary(env, dictionaryStructureWithBufferPolicy);
+    Dictionary *const dictionary =
+            new Dictionary(env, std::move(dictionaryStructureWithBufferPolicy));
     PROF_END(66);
     PROF_CLOSE;
     return reinterpret_cast<jlong>(dictionary);
@@ -161,7 +162,7 @@
     const jsize prevWordCodePointsLength =
             prevWordCodePointsForBigrams ? env->GetArrayLength(prevWordCodePointsForBigrams) : 0;
     int prevWordCodePointsInternal[prevWordCodePointsLength];
-    int *prevWordCodePoints = 0;
+    int *prevWordCodePoints = nullptr;
     env->GetIntArrayRegion(xCoordinatesArray, 0, inputSize, xCoordinates);
     env->GetIntArrayRegion(yCoordinatesArray, 0, inputSize, yCoordinates);
     env->GetIntArrayRegion(timesArray, 0, inputSize, times);
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 22cc4c0..4c57af0 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -344,21 +344,18 @@
 #define MAX_POINTER_COUNT 1
 #define MAX_POINTER_COUNT_G 2
 
-template<typename T> AK_FORCE_INLINE const T &min(const T &a, const T &b) { return a < b ? a : b; }
-template<typename T> AK_FORCE_INLINE const T &max(const T &a, const T &b) { return a > b ? a : b; }
-
 // DEBUG
 #define INPUTLENGTH_FOR_DEBUG (-1)
 #define MIN_OUTPUT_INDEX_FOR_DEBUG (-1)
 
 #define DISALLOW_DEFAULT_CONSTRUCTOR(TypeName) \
-  TypeName()
+  TypeName() = delete
 
 #define DISALLOW_COPY_CONSTRUCTOR(TypeName) \
-  TypeName(const TypeName&)
+  TypeName(const TypeName&) = delete
 
 #define DISALLOW_ASSIGNMENT_OPERATOR(TypeName) \
-  void operator=(const TypeName&)
+  void operator=(const TypeName&) = delete
 
 #define DISALLOW_COPY_AND_ASSIGN(TypeName) \
   DISALLOW_COPY_CONSTRUCTOR(TypeName);     \
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.cpp b/native/jni/src/suggest/core/dicnode/dic_node.cpp
index de088c7..7385597 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node.cpp
@@ -25,7 +25,7 @@
 #endif
           mDicNodeProperties(dicNode.mDicNodeProperties), mDicNodeState(dicNode.mDicNodeState),
           mIsCachedForNextSuggestion(dicNode.mIsCachedForNextSuggestion), mIsUsed(dicNode.mIsUsed),
-          mReleaseListener(0) {
+          mReleaseListener(nullptr) {
     /* empty */
 }
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index 558667e..b812f8f 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -93,7 +93,7 @@
               mProfiler(),
 #endif
               mDicNodeProperties(), mDicNodeState(), mIsCachedForNextSuggestion(false),
-              mIsUsed(false), mReleaseListener(0) {}
+              mIsUsed(false), mReleaseListener(nullptr) {}
 
     DicNode(const DicNode &dicNode);
     DicNode &operator=(const DicNode &dicNode);
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
index 7461f0c..1f02731 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DIC_NODE_PRIORITY_QUEUE_H
 #define LATINIME_DIC_NODE_PRIORITY_QUEUE_H
 
+#include <algorithm>
 #include <queue>
 #include <vector>
 
@@ -49,7 +50,7 @@
 
     AK_FORCE_INLINE void setMaxSize(const int maxSize) {
         ASSERT(maxSize <= mCapacity);
-        mMaxSize = min(maxSize, mCapacity);
+        mMaxSize = std::min(maxSize, mCapacity);
     }
 
     AK_FORCE_INLINE void clearAndResizeToCapacity() {
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index 71bcab6..a6ea68c 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -16,6 +16,7 @@
 
 #include "suggest/core/dicnode/dic_node_utils.h"
 
+#include <algorithm>
 #include <cstring>
 
 #include "suggest/core/dicnode/dic_node.h"
@@ -117,7 +118,7 @@
         }
         actualLength0 = i + 1;
     }
-    actualLength0 = min(actualLength0, MAX_WORD_LENGTH);
+    actualLength0 = std::min(actualLength0, MAX_WORD_LENGTH);
     memmove(dest, src0, actualLength0 * sizeof(dest[0]));
     if (!src1 || length1 == 0) {
         return actualLength0;
@@ -129,7 +130,7 @@
         }
         actualLength1 = i + 1;
     }
-    actualLength1 = min(actualLength1, MAX_WORD_LENGTH - actualLength0);
+    actualLength1 = std::min(actualLength1, MAX_WORD_LENGTH - actualLength0);
     memmove(&dest[actualLength0], src1, actualLength1 * sizeof(dest[0]));
     return actualLength0 + actualLength1;
 }
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
index 8493b6a..c31c056 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DIC_NODES_CACHE_H
 #define LATINIME_DIC_NODES_CACHE_H
 
+#include <algorithm>
 #include <stdint.h>
 
 #include "defines.h"
@@ -51,7 +52,7 @@
         // We want to use the max capacity for the current active dic node queue.
         mActiveDicNodes->clearAndResizeToCapacity();
         // nextActiveSize is used to limit the next iteration's active dic node size.
-        const int nextActiveSizeFittingToTheCapacity = min(nextActiveSize, getCacheCapacity());
+        const int nextActiveSizeFittingToTheCapacity = std::min(nextActiveSize, getCacheCapacity());
         mNextActiveDicNodes->clearAndResize(nextActiveSizeFittingToTheCapacity);
         mTerminalDicNodes->clearAndResize(terminalSize);
         // We want to use the max capacity for the cached dic nodes that will be used for the
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
index fc68510..abafc0e 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DIC_NODE_STATE_OUTPUT_H
 #define LATINIME_DIC_NODE_STATE_OUTPUT_H
 
+#include <algorithm>
 #include <cstring> // for memmove()
 #include <stdint.h>
 
@@ -49,7 +50,8 @@
     void addMergedNodeCodePoints(const uint16_t mergedNodeCodePointCount,
             const int *const mergedNodeCodePoints) {
         if (mergedNodeCodePoints) {
-            const int additionalCodePointCount = min(static_cast<int>(mergedNodeCodePointCount),
+            const int additionalCodePointCount = std::min(
+                    static_cast<int>(mergedNodeCodePointCount),
                     MAX_WORD_LENGTH - mOutputtedCodePointCount);
             memmove(&mCodePointsBuf[mOutputtedCodePointCount], mergedNodeCodePoints,
                     additionalCodePointCount * sizeof(mCodePointsBuf[0]));
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
index e7108d9..7868f78 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DIC_NODE_STATE_PREVWORD_H
 #define LATINIME_DIC_NODE_STATE_PREVWORD_H
 
+#include <algorithm>
 #include <cstring> // for memset() and memmove()
 #include <stdint.h>
 
@@ -69,7 +70,7 @@
             const int prevWordNodePos, const int *const src0, const int16_t length0,
             const int *const src1, const int16_t length1,
             const int prevWordSecondWordFirstInputIndex, const int lastInputIndex) {
-        mPrevWordCount = min(prevWordCount, static_cast<int16_t>(MAX_RESULTS));
+        mPrevWordCount = std::min(prevWordCount, static_cast<int16_t>(MAX_RESULTS));
         mPrevWordProbability = prevWordProbability;
         mPrevWordPtNodePos = prevWordNodePos;
         int twoWordsLen =
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
index 11c201e..18b7d73 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
@@ -17,6 +17,7 @@
 #ifndef LATINIME_DIC_NODE_STATE_SCORING_H
 #define LATINIME_DIC_NODE_STATE_SCORING_H
 
+#include <algorithm>
 #include <stdint.h>
 
 #include "defines.h"
@@ -199,7 +200,7 @@
             mNormalizedCompoundDistance = mSpatialDistance + mLanguageDistance;
         } else {
             mNormalizedCompoundDistance = (mSpatialDistance + mLanguageDistance)
-                    / static_cast<float>(max(1, totalInputIndex));
+                    / static_cast<float>(std::max(1, totalInputIndex));
         }
     }
 };
diff --git a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
index d0b96b0..0859df4 100644
--- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-#include <cstring>
-
 #define LOG_TAG "LatinIME: bigram_dictionary.cpp"
 
 #include "bigram_dictionary.h"
 
+#include <algorithm>
+#include <cstring>
+
 #include "defines.h"
 #include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
 #include "suggest/core/dictionary/dictionary.h"
@@ -142,7 +143,7 @@
                 outBigramCodePoints, outputTypes);
         ++bigramCount;
     }
-    return min(bigramCount, MAX_RESULTS);
+    return std::min(bigramCount, MAX_RESULTS);
 }
 
 // Returns a pointer to the start of the bigram list.
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index 035232f..59a8a55 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -34,9 +34,9 @@
 
 const int Dictionary::HEADER_ATTRIBUTE_BUFFER_SIZE = 32;
 
-Dictionary::Dictionary(JNIEnv *env, const DictionaryStructureWithBufferPolicy::StructurePolicyPtr
-        &dictionaryStructureWithBufferPolicy)
-        : mDictionaryStructureWithBufferPolicy(dictionaryStructureWithBufferPolicy),
+Dictionary::Dictionary(JNIEnv *env, DictionaryStructureWithBufferPolicy::StructurePolicyPtr
+        dictionaryStructureWithBufferPolicy)
+        : mDictionaryStructureWithBufferPolicy(std::move(dictionaryStructureWithBufferPolicy)),
           mBigramDictionary(new BigramDictionary(mDictionaryStructureWithBufferPolicy.get())),
           mGestureSuggest(new Suggest(GestureSuggestPolicyFactory::getGestureSuggestPolicy())),
           mTypingSuggest(new Suggest(TypingSuggestPolicyFactory::getTypingSuggestPolicy())) {
@@ -53,7 +53,7 @@
     if (suggestOptions->isGesture()) {
         DicTraverseSession::initSessionInstance(
                 traverseSession, this, prevWordCodePoints, prevWordLength, suggestOptions);
-        result = mGestureSuggest.get()->getSuggestions(proximityInfo, traverseSession, xcoordinates,
+        result = mGestureSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
                 ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint, outWords,
                 outputScores, spaceIndices, outputTypes, outputAutoCommitFirstWordConfidence);
         if (DEBUG_DICT) {
@@ -63,7 +63,7 @@
     } else {
         DicTraverseSession::initSessionInstance(
                 traverseSession, this, prevWordCodePoints, prevWordLength, suggestOptions);
-        result = mTypingSuggest.get()->getSuggestions(proximityInfo, traverseSession, xcoordinates,
+        result = mTypingSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
                 ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint,
                 outWords, outputScores, spaceIndices, outputTypes,
                 outputAutoCommitFirstWordConfidence);
@@ -78,7 +78,7 @@
         int *outputTypes) const {
     TimeKeeper::setCurrentTime();
     if (length <= 0) return 0;
-    return mBigramDictionary.get()->getPredictions(word, length, outWords, outputScores,
+    return mBigramDictionary->getPredictions(word, length, outWords, outputScores,
             outputTypes);
 }
 
@@ -95,7 +95,7 @@
 int Dictionary::getBigramProbability(const int *word0, int length0, const int *word1,
         int length1) const {
     TimeKeeper::setCurrentTime();
-    return mBigramDictionary.get()->getBigramProbability(word0, length0, word1, length1);
+    return mBigramDictionary->getBigramProbability(word0, length0, word1, length1);
 }
 
 void Dictionary::addUnigramWord(const int *const word, const int length, const int probability,
@@ -103,7 +103,7 @@
         const int shortcutProbability, const bool isNotAWord, const bool isBlacklisted,
         const int timestamp) {
     TimeKeeper::setCurrentTime();
-    mDictionaryStructureWithBufferPolicy.get()->addUnigramWord(word, length, probability,
+    mDictionaryStructureWithBufferPolicy->addUnigramWord(word, length, probability,
             shortcutTargetCodePoints, shortcutLength, shortcutProbability, isNotAWord,
             isBlacklisted, timestamp);
 }
@@ -111,48 +111,48 @@
 void Dictionary::addBigramWords(const int *const word0, const int length0, const int *const word1,
         const int length1, const int probability, const int timestamp) {
     TimeKeeper::setCurrentTime();
-    mDictionaryStructureWithBufferPolicy.get()->addBigramWords(word0, length0, word1, length1,
+    mDictionaryStructureWithBufferPolicy->addBigramWords(word0, length0, word1, length1,
             probability, timestamp);
 }
 
 void Dictionary::removeBigramWords(const int *const word0, const int length0,
         const int *const word1, const int length1) {
     TimeKeeper::setCurrentTime();
-    mDictionaryStructureWithBufferPolicy.get()->removeBigramWords(word0, length0, word1, length1);
+    mDictionaryStructureWithBufferPolicy->removeBigramWords(word0, length0, word1, length1);
 }
 
 void Dictionary::flush(const char *const filePath) {
     TimeKeeper::setCurrentTime();
-    mDictionaryStructureWithBufferPolicy.get()->flush(filePath);
+    mDictionaryStructureWithBufferPolicy->flush(filePath);
 }
 
 void Dictionary::flushWithGC(const char *const filePath) {
     TimeKeeper::setCurrentTime();
-    mDictionaryStructureWithBufferPolicy.get()->flushWithGC(filePath);
+    mDictionaryStructureWithBufferPolicy->flushWithGC(filePath);
 }
 
 bool Dictionary::needsToRunGC(const bool mindsBlockByGC) {
     TimeKeeper::setCurrentTime();
-    return mDictionaryStructureWithBufferPolicy.get()->needsToRunGC(mindsBlockByGC);
+    return mDictionaryStructureWithBufferPolicy->needsToRunGC(mindsBlockByGC);
 }
 
 void Dictionary::getProperty(const char *const query, const int queryLength, char *const outResult,
         const int maxResultLength) {
     TimeKeeper::setCurrentTime();
-    return mDictionaryStructureWithBufferPolicy.get()->getProperty(query, queryLength, outResult,
+    return mDictionaryStructureWithBufferPolicy->getProperty(query, queryLength, outResult,
             maxResultLength);
 }
 
 const WordProperty Dictionary::getWordProperty(const int *const codePoints,
         const int codePointCount) {
     TimeKeeper::setCurrentTime();
-    return mDictionaryStructureWithBufferPolicy.get()->getWordProperty(
+    return mDictionaryStructureWithBufferPolicy->getWordProperty(
             codePoints, codePointCount);
 }
 
 int Dictionary::getNextWordAndNextToken(const int token, int *const outCodePoints) {
     TimeKeeper::setCurrentTime();
-    return mDictionaryStructureWithBufferPolicy.get()->getNextWordAndNextToken(
+    return mDictionaryStructureWithBufferPolicy->getNextWordAndNextToken(
             token, outCodePoints);
 }
 
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
index c58be84..a7f19c9 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -18,6 +18,7 @@
 #define LATINIME_DICTIONARY_H
 
 #include <stdint.h>
+#include <memory>
 
 #include "defines.h"
 #include "jni.h"
@@ -26,7 +27,6 @@
 #include "suggest/core/policy/dictionary_header_structure_policy.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 #include "suggest/core/suggest_interface.h"
-#include "utils/exclusive_ownership_pointer.h"
 
 namespace latinime {
 
@@ -58,8 +58,8 @@
     static const int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
     static const int KIND_FLAG_EXACT_MATCH = 0x40000000;
 
-    Dictionary(JNIEnv *env, const DictionaryStructureWithBufferPolicy::StructurePolicyPtr
-            &dictionaryStructureWithBufferPolicy);
+    Dictionary(JNIEnv *env, DictionaryStructureWithBufferPolicy::StructurePolicyPtr
+            dictionaryStructureWithBufferPolicy);
 
     int getSuggestions(ProximityInfo *proximityInfo, DicTraverseSession *traverseSession,
             int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
@@ -108,8 +108,8 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary);
 
-    typedef ExclusiveOwnershipPointer<BigramDictionary> BigramDictionaryPtr;
-    typedef ExclusiveOwnershipPointer<SuggestInterface> SuggestInterfacePtr;
+    typedef std::unique_ptr<BigramDictionary> BigramDictionaryPtr;
+    typedef std::unique_ptr<SuggestInterface> SuggestInterfacePtr;
 
     static const int HEADER_ATTRIBUTE_BUFFER_SIZE;
 
diff --git a/native/jni/src/suggest/core/dictionary/digraph_utils.cpp b/native/jni/src/suggest/core/dictionary/digraph_utils.cpp
index 5f9b8f3..bb2ce50 100644
--- a/native/jni/src/suggest/core/dictionary/digraph_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/digraph_utils.cpp
@@ -84,7 +84,7 @@
 }
 
 /**
- * Returns the digraph for the input composite glyph codepoint, or 0 if none exists.
+ * Returns the digraph for the input composite glyph codepoint, or nullptr if none exists.
  * compositeGlyphCodePoint: the method returns the digraph corresponding to this codepoint.
  */
 /* static */ const DigraphUtils::digraph_t *DigraphUtils::getDigraphForCodePoint(
@@ -96,17 +96,17 @@
             return digraph;
         }
     }
-    return 0;
+    return nullptr;
 }
 
 /**
- * Returns the digraph for the input composite glyph codepoint, or 0 if none exists.
+ * Returns the digraph for the input composite glyph codepoint, or nullptr if none exists.
  * digraphType: the type of digraphs supported.
  * compositeGlyphCodePoint: the method returns the digraph corresponding to this codepoint.
  */
 /* static */ const DigraphUtils::digraph_t *DigraphUtils::getDigraphForDigraphTypeAndCodePoint(
         const DigraphUtils::DigraphType digraphType, const int compositeGlyphCodePoint) {
-    const DigraphUtils::digraph_t *digraphs = 0;
+    const DigraphUtils::digraph_t *digraphs = nullptr;
     const int compositeGlyphLowerCodePoint = CharUtils::toLowerCase(compositeGlyphCodePoint);
     const int digraphsSize =
             DigraphUtils::getAllDigraphsForDigraphTypeAndReturnSize(digraphType, &digraphs);
@@ -115,7 +115,7 @@
             return &digraphs[i];
         }
     }
-    return 0;
+    return nullptr;
 }
 
 } // namespace latinime
diff --git a/native/jni/src/suggest/core/layout/proximity_info.cpp b/native/jni/src/suggest/core/layout/proximity_info.cpp
index ee8e59e..8b3ae4d 100644
--- a/native/jni/src/suggest/core/layout/proximity_info.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info.cpp
@@ -18,6 +18,7 @@
 
 #include "suggest/core/layout/proximity_info.h"
 
+#include <algorithm>
 #include <cstring>
 #include <cmath>
 
@@ -63,7 +64,7 @@
                           static_cast<float>(mostCommonKeyWidth))),
           CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
           CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
-          KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
+          KEY_COUNT(std::min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
           KEYBOARD_WIDTH(keyboardWidth), KEYBOARD_HEIGHT(keyboardHeight),
           KEYBOARD_HYPOTENUSE(hypotf(KEYBOARD_WIDTH, KEYBOARD_HEIGHT)),
           HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.cpp b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
index 40c3448..2919904 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
@@ -18,6 +18,7 @@
 
 #include "suggest/core/layout/proximity_info_state.h"
 
+#include <algorithm>
 #include <cstring> // for memset() and memmove()
 #include <sstream> // for debug prints
 #include <vector>
@@ -171,7 +172,7 @@
     const int keyId = mProximityInfo->getKeyIndexOf(codePoint);
     if (keyId != NOT_AN_INDEX) {
         const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
-        return min(mSampledNormalizedSquaredLengthCache[index], mMaxPointToKeyLength);
+        return std::min(mSampledNormalizedSquaredLengthCache[index], mMaxPointToKeyLength);
     }
     if (CharUtils::isIntentionalOmissionCodePoint(codePoint)) {
         return 0.0f;
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.h b/native/jni/src/suggest/core/layout/proximity_info_state.h
index e941e43..9abd69a 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.h
@@ -43,7 +43,7 @@
     // Defined here                        //
     /////////////////////////////////////////
     AK_FORCE_INLINE ProximityInfoState()
-            : mProximityInfo(0), mMaxPointToKeyLength(0.0f), mAverageSpeed(0.0f),
+            : mProximityInfo(nullptr), mMaxPointToKeyLength(0.0f), mAverageSpeed(0.0f),
               mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0),
               mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
               mIsContinuousSuggestionPossible(false), mHasBeenUpdatedByGeometricInput(false),
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
index bc4ca8e..867f598 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
@@ -16,6 +16,7 @@
 
 #include "suggest/core/layout/proximity_info_state_utils.h"
 
+#include <algorithm>
 #include <cmath>
 #include <cstring> // for memset()
 #include <sstream> // for debug prints
@@ -240,7 +241,7 @@
         // Calculate velocity by using distances and durations of
         // ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION points for both forward and
         // backward.
-        const int forwardNumPoints = min(inputSize - 1,
+        const int forwardNumPoints = std::min(inputSize - 1,
                 index + ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION);
         for (int j = index; j < forwardNumPoints; ++j) {
             if (i < sampledInputSize - 1 && j >= (*sampledInputIndice)[i + 1]) {
@@ -250,7 +251,7 @@
                     xCoordinates[j + 1], yCoordinates[j + 1]);
             duration += times[j + 1] - times[j];
         }
-        const int backwardNumPoints = max(0,
+        const int backwardNumPoints = std::max(0,
                 index - ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION);
         for (int j = index - 1; j >= backwardNumPoints; --j) {
             if (i > 0 && j < (*sampledInputIndice)[i - 1]) {
@@ -272,7 +273,7 @@
 
     // Direction calculation.
     sampledDirections->resize(sampledInputSize - 1);
-    for (int i = max(0, lastSavedInputSize - 1); i < sampledInputSize - 1; ++i) {
+    for (int i = std::max(0, lastSavedInputSize - 1); i < sampledInputSize - 1; ++i) {
         (*sampledDirections)[i] = getDirection(sampledInputXs, sampledInputYs, i, i + 1);
     }
     return averageSpeed;
@@ -609,7 +610,7 @@
         const int inputIndex, const int keyId) {
     if (keyId != NOT_AN_INDEX) {
         const int index = inputIndex * keyCount + keyId;
-        return min((*sampledNormalizedSquaredLengthCache)[index], maxPointToKeyLength);
+        return std::min((*sampledNormalizedSquaredLengthCache)[index], maxPointToKeyLength);
     }
     // If the char is not a key on the keyboard then return the max length.
     return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
@@ -650,13 +651,13 @@
         }
 
         if (i == 0) {
-            skipProbability *= min(1.0f,
+            skipProbability *= std::min(1.0f,
                     nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT
                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS);
             // Promote the first point
             skipProbability *= ProximityInfoParams::SKIP_FIRST_POINT_PROBABILITY;
         } else if (i == sampledInputSize - 1) {
-            skipProbability *= min(1.0f,
+            skipProbability *= std::min(1.0f,
                     nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT_FOR_LAST
                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS_FOR_LAST);
             // Promote the last point
@@ -667,17 +668,17 @@
                     && speedRate
                             < (*sampledSpeedRates)[i + 1] - ProximityInfoParams::SPEED_MARGIN) {
                 if (currentAngle < ProximityInfoParams::CORNER_ANGLE_THRESHOLD) {
-                    skipProbability *= min(1.0f, speedRate
+                    skipProbability *= std::min(1.0f, speedRate
                             * ProximityInfoParams::SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY);
                 } else {
                     // If the angle is small enough, we promote this point more. (e.g. pit vs put)
-                    skipProbability *= min(1.0f,
+                    skipProbability *= std::min(1.0f,
                             speedRate * ProximityInfoParams::SPEED_WEIGHT_FOR_SKIP_PROBABILITY
                                     + ProximityInfoParams::MIN_SPEED_RATE_FOR_SKIP_PROBABILITY);
                 }
             }
 
-            skipProbability *= min(1.0f,
+            skipProbability *= std::min(1.0f,
                     speedRate * nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT
                             + ProximityInfoParams::NEAREST_DISTANCE_BIAS);
 
@@ -707,10 +708,10 @@
         // (1.0f - skipProbability).
         const float inputCharProbability = 1.0f - skipProbability;
 
-        const float speedxAngleRate = min(speedRate * currentAngle / M_PI_F
+        const float speedxAngleRate = std::min(speedRate * currentAngle / M_PI_F
                 * ProximityInfoParams::SPEEDxANGLE_WEIGHT_FOR_STANDARD_DEVIATION,
                         ProximityInfoParams::MAX_SPEEDxANGLE_RATE_FOR_STANDARD_DEVIATION);
-        const float speedxNearestKeyDistanceRate = min(speedRate * nearestKeyDistance
+        const float speedxNearestKeyDistanceRate = std::min(speedRate * nearestKeyDistance
                 * ProximityInfoParams::SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DEVIATION,
                         ProximityInfoParams::MAX_SPEEDxNEAREST_RATE_FOR_STANDARD_DEVIATION);
         const float sigma = speedxAngleRate + speedxNearestKeyDistanceRate
@@ -827,7 +828,7 @@
 
     // Decrease key probabilities of points which don't have the highest probability of that key
     // among nearby points. Probabilities of the first point and the last point are not suppressed.
-    for (int i = max(start, 1); i < sampledInputSize; ++i) {
+    for (int i = std::max(start, 1); i < sampledInputSize; ++i) {
         for (int j = i + 1; j < sampledInputSize; ++j) {
             if (!suppressCharProbabilities(
                     mostCommonKeyWidth, sampledInputSize, sampledLengthCache, i, j,
@@ -835,7 +836,7 @@
                 break;
             }
         }
-        for (int j = i - 1; j >= max(start, 0); --j) {
+        for (int j = i - 1; j >= std::max(start, 0); --j) {
             if (!suppressCharProbabilities(
                     mostCommonKeyWidth, sampledInputSize, sampledLengthCache, i, j,
                     charProbabilities)) {
@@ -878,7 +879,7 @@
         if (i >= lastSavedInputSize) {
             (*sampledSearchKeySets)[i].reset();
         }
-        for (int j = max(i, lastSavedInputSize); j < sampledInputSize; ++j) {
+        for (int j = std::max(i, lastSavedInputSize); j < sampledInputSize; ++j) {
             // TODO: Investigate if this is required. This may not fail.
             if ((*sampledLengthCache)[j] - (*sampledLengthCache)[i] >= readForwordLength) {
                 break;
@@ -929,7 +930,7 @@
             (*charProbabilities)[index0][NOT_AN_INDEX] += suppression;
 
             // Add the probability of the same key nearby index1
-            const float probabilityGain = min(suppression
+            const float probabilityGain = std::min(suppression
                     * ProximityInfoParams::SUPPRESSION_WEIGHT_FOR_PROBABILITY_GAIN,
                     (*charProbabilities)[index1][NOT_AN_INDEX]
                             * ProximityInfoParams::SKIP_PROBABALITY_WEIGHT_FOR_PROBABILITY_GAIN);
diff --git a/native/jni/src/suggest/core/layout/proximity_info_utils.h b/native/jni/src/suggest/core/layout/proximity_info_utils.h
index 0e28560..310bbdb 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_utils.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_utils.h
@@ -100,6 +100,10 @@
         const float dotProduct = ray1x * ray2x + ray1y * ray2y;
         const float lineLengthSqr = GeometryUtils::SQUARE_FLOAT(ray2x)
                 + GeometryUtils::SQUARE_FLOAT(ray2y);
+        if (lineLengthSqr <= 0.0f) {
+            // Return point to the point distance.
+            return getSquaredDistanceFloat(x, y, x1, y1);
+        }
         const float projectionLengthSqr = dotProduct / lineLengthSqr;
 
         float projectionX;
@@ -125,7 +129,7 @@
     struct NormalDistribution {
      public:
         NormalDistribution(const float u, const float sigma)
-                : mU(u), mSigma(sigma),
+                : mU(u),
                   mPreComputedNonExpPart(1.0f / sqrtf(2.0f * M_PI_F
                           * GeometryUtils::SQUARE_FLOAT(sigma))),
                   mPreComputedExponentPart(-1.0f / (2.0f * GeometryUtils::SQUARE_FLOAT(sigma))) {}
@@ -139,7 +143,6 @@
      private:
         DISALLOW_IMPLICIT_CONSTRUCTORS(NormalDistribution);
         const float mU; // mean value
-        const float mSigma; // standard deviation
         const float mPreComputedNonExpPart; // = 1 / sqrt(2 * PI * sigma^2)
         const float mPreComputedExponentPart; // = -1 / (2 * sigma^2)
     }; // struct NormalDistribution
@@ -167,6 +170,12 @@
         const int mostCommonKeyWidthSquare = mostCommonKeyWidth * mostCommonKeyWidth;
         int insertPos = 0;
         proximities[insertPos++] = primaryKey;
+        if (x == NOT_A_COORDINATE || y == NOT_A_COORDINATE) {
+            for (int i = insertPos; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+                proximities[i] = NOT_A_CODE_POINT;
+            }
+            return;
+        }
         const int startIndex = getStartIndexFromCoordinates(x, y, cellHeight, cellWidth, gridWidth);
         if (startIndex >= 0) {
             for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
diff --git a/native/jni/src/suggest/core/layout/touch_position_correction_utils.h b/native/jni/src/suggest/core/layout/touch_position_correction_utils.h
index 9130e87..14074c1 100644
--- a/native/jni/src/suggest/core/layout/touch_position_correction_utils.h
+++ b/native/jni/src/suggest/core/layout/touch_position_correction_utils.h
@@ -17,6 +17,8 @@
 #ifndef LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
 #define LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
 
+#include <algorithm>
+
 #include "defines.h"
 #include "suggest/core/layout/proximity_info_params.h"
 
@@ -34,7 +36,7 @@
         static const float R2 = 1.0f;
         const float x = normalizedSquaredDistance;
         if (!isTouchPositionCorrectionEnabled) {
-            return min(C, x);
+            return std::min(C, x);
         }
 
         // factor is a piecewise linear function like:
diff --git a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
index 38e8ff1..b6dc7d0 100644
--- a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
+++ b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
@@ -17,9 +17,10 @@
 #ifndef LATINIME_DICTIONARY_STRUCTURE_POLICY_H
 #define LATINIME_DICTIONARY_STRUCTURE_POLICY_H
 
+#include <memory>
+
 #include "defines.h"
 #include "suggest/core/dictionary/word_property.h"
-#include "utils/exclusive_ownership_pointer.h"
 
 namespace latinime {
 
@@ -35,7 +36,7 @@
  */
 class DictionaryStructureWithBufferPolicy {
  public:
-    typedef ExclusiveOwnershipPointer<DictionaryStructureWithBufferPolicy> StructurePolicyPtr;
+    typedef std::unique_ptr<DictionaryStructureWithBufferPolicy> StructurePolicyPtr;
 
     virtual ~DictionaryStructureWithBufferPolicy() {}
 
diff --git a/native/jni/src/suggest/core/dictionary/suggestions_output_utils.cpp b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
similarity index 97%
rename from native/jni/src/suggest/core/dictionary/suggestions_output_utils.cpp
rename to native/jni/src/suggest/core/result/suggestions_output_utils.cpp
index 07c2e6e..19912f2 100644
--- a/native/jni/src/suggest/core/dictionary/suggestions_output_utils.cpp
+++ b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-#include "suggest/core/dictionary/suggestions_output_utils.h"
+#include "suggest/core/result/suggestions_output_utils.h"
+
+#include <algorithm>
 
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
@@ -36,7 +38,7 @@
 #if DEBUG_EVALUATE_MOST_PROBABLE_STRING
     const int terminalSize = 0;
 #else
-    const int terminalSize = min(MAX_RESULTS,
+    const int terminalSize = std::min(MAX_RESULTS,
             static_cast<int>(traverseSession->getDicTraverseCache()->terminalSize()));
 #endif
     DicNode terminals[MAX_RESULTS]; // Avoiding non-POD variable length array
@@ -245,12 +247,12 @@
             // shortcut entry's score == its base entry's score - 1
             shortcutScore = finalScore;
             // Protection against int underflow
-            shortcutScore = max(S_INT_MIN + 1, shortcutScore) - 1;
+            shortcutScore = std::max(S_INT_MIN + 1, shortcutScore) - 1;
             kind = Dictionary::KIND_SHORTCUT;
         }
         outputTypes[outputWordIndex] = kind;
         outputScores[outputWordIndex] = shortcutScore;
-        outputScores[outputWordIndex] = max(S_INT_MIN + 1, shortcutScore) - 1;
+        outputScores[outputWordIndex] = std::max(S_INT_MIN + 1, shortcutScore) - 1;
         const int startIndex2 = outputWordIndex * MAX_WORD_LENGTH;
         DicNodeUtils::appendTwoWords(0, 0, shortcutTarget, shortcutTargetStringLength,
                 &outputCodePoints[startIndex2]);
diff --git a/native/jni/src/suggest/core/dictionary/suggestions_output_utils.h b/native/jni/src/suggest/core/result/suggestions_output_utils.h
similarity index 100%
rename from native/jni/src/suggest/core/dictionary/suggestions_output_utils.h
rename to native/jni/src/suggest/core/result/suggestions_output_utils.h
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.h b/native/jni/src/suggest/core/session/dic_traverse_session.h
index 6e4dda4..b718fb5 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.h
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -59,8 +59,8 @@
     }
 
     AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr, bool usesLargeCache)
-            : mPrevWordPtNodePos(NOT_A_DICT_POS), mProximityInfo(0),
-              mDictionary(0), mSuggestOptions(0), mDicNodesCache(usesLargeCache),
+            : mPrevWordPtNodePos(NOT_A_DICT_POS), mProximityInfo(nullptr),
+              mDictionary(nullptr), mSuggestOptions(nullptr), mDicNodesCache(usesLargeCache),
               mMultiBigramMap(), mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
               mMultiWordCostMultiplier(1.0f) {
         // NOTE: mProximityInfoStates is an array of instances.
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 56acc2d..c3b6703 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -21,11 +21,11 @@
 #include "suggest/core/dicnode/dic_node_vector.h"
 #include "suggest/core/dictionary/dictionary.h"
 #include "suggest/core/dictionary/digraph_utils.h"
-#include "suggest/core/dictionary/suggestions_output_utils.h"
 #include "suggest/core/layout/proximity_info.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 #include "suggest/core/policy/traversal.h"
 #include "suggest/core/policy/weighting.h"
+#include "suggest/core/result/suggestions_output_utils.h"
 #include "suggest/core/session/dic_traverse_session.h"
 
 namespace latinime {
diff --git a/native/jni/src/suggest/core/suggest.h b/native/jni/src/suggest/core/suggest.h
index b1d12ad..c42986a 100644
--- a/native/jni/src/suggest/core/suggest.h
+++ b/native/jni/src/suggest/core/suggest.h
@@ -42,9 +42,9 @@
 class Suggest : public SuggestInterface {
  public:
     AK_FORCE_INLINE Suggest(const SuggestPolicy *const suggestPolicy)
-            : TRAVERSAL(suggestPolicy ? suggestPolicy->getTraversal() : 0),
-              SCORING(suggestPolicy ? suggestPolicy->getScoring() : 0),
-              WEIGHTING(suggestPolicy ? suggestPolicy->getWeighting() : 0) {}
+            : TRAVERSAL(suggestPolicy ? suggestPolicy->getTraversal() : nullptr),
+              SCORING(suggestPolicy ? suggestPolicy->getScoring() : nullptr),
+              WEIGHTING(suggestPolicy ? suggestPolicy->getWeighting() : nullptr) {}
     AK_FORCE_INLINE virtual ~Suggest() {}
     int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
             int *times, int *pointerIds, int *inputCodePoints, int inputSize, int commitPoint,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
index 7c7b05c..ecc9fda 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
@@ -16,6 +16,8 @@
 
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
 
+#include <algorithm>
+
 namespace latinime {
 
 // Note that these are corresponding definitions in Java side in DictionaryHeader.
@@ -72,7 +74,7 @@
         outValue[1] = '\0';
         return;
     }
-    const int terminalIndex = min(static_cast<int>(it->second.size()), outValueSize - 1);
+    const int terminalIndex = std::min(static_cast<int>(it->second.size()), outValueSize - 1);
     for (int i = 0; i < terminalIndex; ++i) {
         outValue[i] = it->second[i];
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h
index ae863af..f2fa5b7 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h
@@ -31,8 +31,7 @@
  public:
     Ver4ShortcutListPolicy(ShortcutDictContent *const shortcutDictContent,
             const TerminalPositionLookupTable *const terminalPositionLookupTable)
-            : mShortcutDictContent(shortcutDictContent),
-              mTerminalPositionLookupTable(terminalPositionLookupTable) {}
+            : mShortcutDictContent(shortcutDictContent) {}
 
     ~Ver4ShortcutListPolicy() {}
 
@@ -104,7 +103,6 @@
     DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4ShortcutListPolicy);
 
     ShortcutDictContent *const mShortcutDictContent;
-    const TerminalPositionLookupTable *const mTerminalPositionLookupTable;
 };
 } // namespace latinime
 #endif // LATINIME_VER4_SHORTCUT_LIST_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
index 04f1198..79bcf6f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
@@ -41,7 +41,7 @@
         if (isUpdatable) {
             AKLOGE("One file dictionaries don't support updating. path: %s", path);
             ASSERT(false);
-            return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(0);
+            return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
         }
         return newPolicyforFileDict(path, bufOffset, size);
     }
@@ -55,13 +55,13 @@
     getHeaderFilePathInDictDir(path, headerFilePathBufSize, headerFilePath);
     // Allocated buffer in MmapedBuffer::openBuffer() will be freed in the destructor of
     // MmappedBufferPtr if the instance has the responsibility.
-    MmappedBuffer::MmappedBufferPtr mmappedBuffer = MmappedBuffer::openBuffer(headerFilePath,
-            isUpdatable);
-    if (!mmappedBuffer.get()) {
-        return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(0);
+    MmappedBuffer::MmappedBufferPtr mmappedBuffer(
+            MmappedBuffer::openBuffer(headerFilePath, isUpdatable));
+    if (!mmappedBuffer) {
+        return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
     }
-    switch (FormatUtils::detectFormatVersion(mmappedBuffer.get()->getBuffer(),
-            mmappedBuffer.get()->getBufferSize())) {
+    switch (FormatUtils::detectFormatVersion(mmappedBuffer->getBuffer(),
+            mmappedBuffer->getBufferSize())) {
         case FormatUtils::VERSION_2:
             AKLOGE("Given path is a directory but the format is version 2. path: %s", path);
             break;
@@ -72,25 +72,25 @@
                     Ver4DictConstants::HEADER_FILE_EXTENSION, dictDirPathBufSize, dictPath)) {
                 AKLOGE("Dictionary file name is not valid as a ver4 dictionary. path: %s", path);
                 ASSERT(false);
-                return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(0);
+                return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
             }
-            const Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers =
-                    Ver4DictBuffers::openVer4DictBuffers(dictPath, mmappedBuffer);
-            if (!dictBuffers.get() || !dictBuffers.get()->isValid()) {
+            Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers(
+                    Ver4DictBuffers::openVer4DictBuffers(dictPath, std::move(mmappedBuffer)));
+            if (!dictBuffers || !dictBuffers->isValid()) {
                 AKLOGE("DICT: The dictionary doesn't satisfy ver4 format requirements. path: %s",
                         path);
                 ASSERT(false);
-                return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(0);
+                return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
             }
             return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(
-                    new Ver4PatriciaTriePolicy(dictBuffers));
+                    new Ver4PatriciaTriePolicy(std::move(dictBuffers)));
         }
         default:
             AKLOGE("DICT: dictionary format is unknown, bad magic number. path: %s", path);
             break;
     }
     ASSERT(false);
-    return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(0);
+    return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
 }
 
 /* static */ DictionaryStructureWithBufferPolicy::StructurePolicyPtr
@@ -98,16 +98,16 @@
                 const char *const path, const int bufOffset, const int size) {
     // Allocated buffer in MmapedBuffer::openBuffer() will be freed in the destructor of
     // MmappedBufferPtr if the instance has the responsibility.
-    MmappedBuffer::MmappedBufferPtr mmappedBuffer = MmappedBuffer::openBuffer(path, bufOffset,
-            size, false /* isUpdatable */);
-    if (!mmappedBuffer.get()) {
-        return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(0);
+    MmappedBuffer::MmappedBufferPtr mmappedBuffer(
+            MmappedBuffer::openBuffer(path, bufOffset, size, false /* isUpdatable */));
+    if (!mmappedBuffer) {
+        return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
     }
-    switch (FormatUtils::detectFormatVersion(mmappedBuffer.get()->getBuffer(),
-            mmappedBuffer.get()->getBufferSize())) {
+    switch (FormatUtils::detectFormatVersion(mmappedBuffer->getBuffer(),
+            mmappedBuffer->getBufferSize())) {
         case FormatUtils::VERSION_2:
             return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(
-                    new PatriciaTriePolicy(mmappedBuffer));
+                    new PatriciaTriePolicy(std::move(mmappedBuffer)));
         case FormatUtils::VERSION_4:
             AKLOGE("Given path is a file but the format is version 4. path: %s", path);
             break;
@@ -116,7 +116,7 @@
             break;
     }
     ASSERT(false);
-    return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(0);
+    return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
 }
 
 /* static */ void DictionaryStructureWithBufferPolicyFactory::getHeaderFilePathInDictDir(
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h
index 45ab529..9454ddf 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h
@@ -21,7 +21,6 @@
 
 #include "defines.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
-#include "utils/exclusive_ownership_pointer.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
index 6a2345a..11a40de 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
@@ -37,12 +37,11 @@
 
 class PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy {
  public:
-    PatriciaTriePolicy(const MmappedBuffer::MmappedBufferPtr &mmappedBuffer)
-            : mMmappedBuffer(mmappedBuffer),
-              mHeaderPolicy(mMmappedBuffer.get()->getBuffer(), FormatUtils::VERSION_2),
-              mDictRoot(mMmappedBuffer.get()->getBuffer() + mHeaderPolicy.getSize()),
-              mDictBufferSize(mMmappedBuffer.get()->getBufferSize()
-                      - mHeaderPolicy.getSize()),
+    PatriciaTriePolicy(MmappedBuffer::MmappedBufferPtr mmappedBuffer)
+            : mMmappedBuffer(std::move(mmappedBuffer)),
+              mHeaderPolicy(mMmappedBuffer->getBuffer(), FormatUtils::VERSION_2),
+              mDictRoot(mMmappedBuffer->getBuffer() + mHeaderPolicy.getSize()),
+              mDictBufferSize(mMmappedBuffer->getBufferSize() - mHeaderPolicy.getSize()),
               mBigramListPolicy(mDictRoot), mShortcutListPolicy(mDictRoot),
               mPtNodeReader(mDictRoot, mDictBufferSize, &mBigramListPolicy, &mShortcutListPolicy),
               mPtNodeArrayReader(mDictRoot, mDictBufferSize),
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/single_dict_content.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/single_dict_content.h
index 9064b7e..2156422 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/single_dict_content.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/single_dict_content.h
@@ -31,14 +31,14 @@
     SingleDictContent(const char *const dictPath, const char *const contentFileName,
             const bool isUpdatable)
             : mMmappedBuffer(MmappedBuffer::openBuffer(dictPath, contentFileName, isUpdatable)),
-              mExpandableContentBuffer(mMmappedBuffer.get() ? mMmappedBuffer.get()->getBuffer() : 0,
-                      mMmappedBuffer.get() ? mMmappedBuffer.get()->getBufferSize() : 0,
+              mExpandableContentBuffer(mMmappedBuffer ? mMmappedBuffer->getBuffer() : nullptr,
+                      mMmappedBuffer ? mMmappedBuffer->getBufferSize() : 0,
                       BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
-              mIsValid(mMmappedBuffer.get() != 0) {}
+              mIsValid(mMmappedBuffer) {}
 
     SingleDictContent()
-            : mMmappedBuffer(0), mExpandableContentBuffer(Ver4DictConstants::MAX_DICTIONARY_SIZE),
-              mIsValid(true) {}
+            : mMmappedBuffer(nullptr),
+              mExpandableContentBuffer(Ver4DictConstants::MAX_DICTIONARY_SIZE), mIsValid(true) {}
 
     virtual ~SingleDictContent() {}
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/sparse_table_dict_content.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/sparse_table_dict_content.h
index a82e3f5..fb6c88e 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/sparse_table_dict_content.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/sparse_table_dict_content.h
@@ -38,25 +38,25 @@
                       MmappedBuffer::openBuffer(dictPath, lookupTableFileName, isUpdatable)),
               mAddressTableBuffer(
                       MmappedBuffer::openBuffer(dictPath, addressTableFileName, isUpdatable)),
-              mContentBuffer(MmappedBuffer::openBuffer(dictPath, contentFileName, isUpdatable)),
+              mContentBuffer(
+                      MmappedBuffer::openBuffer(dictPath, contentFileName, isUpdatable)),
               mExpandableLookupTableBuffer(
-                      mLookupTableBuffer.get() ? mLookupTableBuffer.get()->getBuffer() : 0,
-                      mLookupTableBuffer.get() ? mLookupTableBuffer.get()->getBufferSize() : 0,
+                      mLookupTableBuffer ? mLookupTableBuffer->getBuffer() : nullptr,
+                      mLookupTableBuffer ? mLookupTableBuffer->getBufferSize() : 0,
                       BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
               mExpandableAddressTableBuffer(
-                      mAddressTableBuffer.get() ? mAddressTableBuffer.get()->getBuffer() : 0,
-                      mAddressTableBuffer.get() ? mAddressTableBuffer.get()->getBufferSize() : 0,
+                      mAddressTableBuffer ? mAddressTableBuffer->getBuffer() : nullptr,
+                      mAddressTableBuffer ? mAddressTableBuffer->getBufferSize() : 0,
                       BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
-              mExpandableContentBuffer(mContentBuffer.get() ? mContentBuffer.get()->getBuffer() : 0,
-                      mContentBuffer.get() ? mContentBuffer.get()->getBufferSize() : 0,
+              mExpandableContentBuffer(mContentBuffer ? mContentBuffer->getBuffer() : nullptr,
+                      mContentBuffer ? mContentBuffer->getBufferSize() : 0,
                       BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
               mAddressLookupTable(&mExpandableLookupTableBuffer, &mExpandableAddressTableBuffer,
                       sparseTableBlockSize, sparseTableDataSize),
-              mIsValid(mLookupTableBuffer.get() != 0 && mAddressTableBuffer.get() != 0
-                      && mContentBuffer.get() != 0) {}
+              mIsValid(mLookupTableBuffer && mAddressTableBuffer && mContentBuffer) {}
 
     SparseTableDictContent(const int sparseTableBlockSize, const int sparseTableDataSize)
-            : mLookupTableBuffer(0), mAddressTableBuffer(0), mContentBuffer(0),
+            : mLookupTableBuffer(), mAddressTableBuffer(), mContentBuffer(),
               mExpandableLookupTableBuffer(Ver4DictConstants::MAX_DICTIONARY_SIZE),
               mExpandableAddressTableBuffer(Ver4DictConstants::MAX_DICTIONARY_SIZE),
               mExpandableContentBuffer(Ver4DictConstants::MAX_DICTIONARY_SIZE),
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
index 59dedee..9319ea9 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
@@ -27,15 +27,15 @@
 namespace latinime {
 
 /* static */ Ver4DictBuffers::Ver4DictBuffersPtr Ver4DictBuffers::openVer4DictBuffers(
-        const char *const dictPath, const MmappedBuffer::MmappedBufferPtr &headerBuffer) {
-    if (!headerBuffer.get()) {
+        const char *const dictPath, MmappedBuffer::MmappedBufferPtr headerBuffer) {
+    if (!headerBuffer) {
         ASSERT(false);
         AKLOGE("The header buffer must be valid to open ver4 dict buffers.");
-        return Ver4DictBuffersPtr(0);
+        return Ver4DictBuffersPtr(nullptr);
     }
     // TODO: take only dictDirPath, and open both header and trie files in the constructor below
-    return Ver4DictBuffersPtr(new Ver4DictBuffers(
-            dictPath, headerBuffer, headerBuffer.get()->isUpdatable()));
+    const bool isUpdatable = headerBuffer->isUpdatable();
+    return Ver4DictBuffersPtr(new Ver4DictBuffers(dictPath, std::move(headerBuffer), isUpdatable));
 }
 
 bool Ver4DictBuffers::flushHeaderAndDictBuffers(const char *const dictDirPath,
@@ -113,27 +113,25 @@
 }
 
 Ver4DictBuffers::Ver4DictBuffers(const char *const dictPath,
-        const MmappedBuffer::MmappedBufferPtr &headerBuffer, const bool isUpdatable)
-        : mHeaderBuffer(headerBuffer),
+        MmappedBuffer::MmappedBufferPtr headerBuffer, const bool isUpdatable)
+        : mHeaderBuffer(std::move(headerBuffer)),
           mDictBuffer(MmappedBuffer::openBuffer(dictPath,
                   Ver4DictConstants::TRIE_FILE_EXTENSION, isUpdatable)),
-          mHeaderPolicy(headerBuffer.get()->getBuffer(), FormatUtils::VERSION_4),
-          mExpandableHeaderBuffer(headerBuffer.get() ? headerBuffer.get()->getBuffer() : 0,
+          mHeaderPolicy(mHeaderBuffer->getBuffer(), FormatUtils::VERSION_4),
+          mExpandableHeaderBuffer(mHeaderBuffer ? mHeaderBuffer->getBuffer() : nullptr,
                   mHeaderPolicy.getSize(),
                   BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
-          mExpandableTrieBuffer(mDictBuffer.get() ? mDictBuffer.get()->getBuffer() : 0,
-                  mDictBuffer.get() ? mDictBuffer.get()->getBufferSize() : 0,
+          mExpandableTrieBuffer(mDictBuffer ? mDictBuffer->getBuffer() : nullptr,
+                  mDictBuffer ? mDictBuffer->getBufferSize() : 0,
                   BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
           mTerminalPositionLookupTable(dictPath, isUpdatable),
-          mProbabilityDictContent(dictPath, mHeaderPolicy.hasHistoricalInfoOfWords(),
-                  isUpdatable),
-          mBigramDictContent(dictPath, mHeaderPolicy.hasHistoricalInfoOfWords(),
-                  isUpdatable),
+          mProbabilityDictContent(dictPath, mHeaderPolicy.hasHistoricalInfoOfWords(), isUpdatable),
+          mBigramDictContent(dictPath, mHeaderPolicy.hasHistoricalInfoOfWords(), isUpdatable),
           mShortcutDictContent(dictPath, isUpdatable),
           mIsUpdatable(isUpdatable) {}
 
 Ver4DictBuffers::Ver4DictBuffers(const HeaderPolicy *const headerPolicy)
-        : mHeaderBuffer(0), mDictBuffer(0), mHeaderPolicy(),
+        : mHeaderBuffer(nullptr), mDictBuffer(nullptr), mHeaderPolicy(),
           mExpandableHeaderBuffer(Ver4DictConstants::MAX_DICTIONARY_SIZE),
           mExpandableTrieBuffer(Ver4DictConstants::MAX_DICTIONARY_SIZE),
           mTerminalPositionLookupTable(),
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
index 776bb98..ab756bb 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
@@ -17,6 +17,8 @@
 #ifndef LATINIME_VER4_DICT_BUFFER_H
 #define LATINIME_VER4_DICT_BUFFER_H
 
+#include <memory>
+
 #include "defines.h"
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
 #include "suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h"
@@ -31,10 +33,10 @@
 
 class Ver4DictBuffers {
  public:
-    typedef ExclusiveOwnershipPointer<Ver4DictBuffers> Ver4DictBuffersPtr;
+    typedef std::unique_ptr<Ver4DictBuffers> Ver4DictBuffersPtr;
 
     static Ver4DictBuffersPtr openVer4DictBuffers(const char *const dictDirPath,
-            const MmappedBuffer::MmappedBufferPtr &headerBuffer);
+            MmappedBuffer::MmappedBufferPtr headerBuffer);
 
     static AK_FORCE_INLINE Ver4DictBuffersPtr createVer4DictBuffers(
             const HeaderPolicy *const headerPolicy) {
@@ -42,7 +44,7 @@
     }
 
     AK_FORCE_INLINE bool isValid() const {
-        return mHeaderBuffer.get() && mDictBuffer.get() && mHeaderPolicy.isValid()
+        return mHeaderBuffer && mDictBuffer && mHeaderPolicy.isValid()
                 && mProbabilityDictContent.isValid() && mTerminalPositionLookupTable.isValid()
                 && mBigramDictContent.isValid() && mShortcutDictContent.isValid();
     }
@@ -118,7 +120,7 @@
     DISALLOW_COPY_AND_ASSIGN(Ver4DictBuffers);
 
     Ver4DictBuffers(const char *const dictDirPath,
-            const MmappedBuffer::MmappedBufferPtr &headerBuffer, const bool isUpdatable);
+            const MmappedBuffer::MmappedBufferPtr headerBuffer, const bool isUpdatable);
 
     Ver4DictBuffers(const HeaderPolicy *const headerPolicy);
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
index 4d1b0da..1a38a27 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
@@ -135,7 +135,7 @@
     if (ptNodeParams.isDeleted()) {
         return NOT_A_DICT_POS;
     }
-    return mBuffers.get()->getShortcutDictContent()->getShortcutListHeadPos(
+    return mBuffers->getShortcutDictContent()->getShortcutListHeadPos(
             ptNodeParams.getTerminalId());
 }
 
@@ -147,7 +147,7 @@
     if (ptNodeParams.isDeleted()) {
         return NOT_A_DICT_POS;
     }
-    return mBuffers.get()->getBigramDictContent()->getBigramListHeadPos(
+    return mBuffers->getBigramDictContent()->getBigramListHeadPos(
             ptNodeParams.getTerminalId());
 }
 
@@ -155,7 +155,7 @@
         const int probability, const int *const shortcutTargetCodePoints, const int shortcutLength,
         const int shortcutProbability, const bool isNotAWord, const bool isBlacklisted,
         const int timestamp) {
-    if (!mBuffers.get()->isUpdatable()) {
+    if (!mBuffers->isUpdatable()) {
         AKLOGI("Warning: addUnigramWord() is called for non-updatable dictionary.");
         return false;
     }
@@ -205,7 +205,7 @@
 bool Ver4PatriciaTriePolicy::addBigramWords(const int *const word0, const int length0,
         const int *const word1, const int length1, const int probability,
         const int timestamp) {
-    if (!mBuffers.get()->isUpdatable()) {
+    if (!mBuffers->isUpdatable()) {
         AKLOGI("Warning: addBigramWords() is called for non-updatable dictionary.");
         return false;
     }
@@ -243,7 +243,7 @@
 
 bool Ver4PatriciaTriePolicy::removeBigramWords(const int *const word0, const int length0,
         const int *const word1, const int length1) {
-    if (!mBuffers.get()->isUpdatable()) {
+    if (!mBuffers->isUpdatable()) {
         AKLOGI("Warning: addBigramWords() is called for non-updatable dictionary.");
         return false;
     }
@@ -276,7 +276,7 @@
 }
 
 void Ver4PatriciaTriePolicy::flush(const char *const filePath) {
-    if (!mBuffers.get()->isUpdatable()) {
+    if (!mBuffers->isUpdatable()) {
         AKLOGI("Warning: flush() is called for non-updatable dictionary. filePath: %s", filePath);
         return;
     }
@@ -287,7 +287,7 @@
 }
 
 void Ver4PatriciaTriePolicy::flushWithGC(const char *const filePath) {
-    if (!mBuffers.get()->isUpdatable()) {
+    if (!mBuffers->isUpdatable()) {
         AKLOGI("Warning: flushWithGC() is called for non-updatable dictionary.");
         return;
     }
@@ -298,11 +298,11 @@
 }
 
 bool Ver4PatriciaTriePolicy::needsToRunGC(const bool mindsBlockByGC) const {
-    if (!mBuffers.get()->isUpdatable()) {
+    if (!mBuffers->isUpdatable()) {
         AKLOGI("Warning: needsToRunGC() is called for non-updatable dictionary.");
         return false;
     }
-    if (mBuffers.get()->isNearSizeLimit()) {
+    if (mBuffers->isNearSizeLimit()) {
         // Additional buffer size is near the limit.
         return true;
     } else if (mHeaderPolicy->getExtendedRegionSize() + mDictBuffer->getUsedAdditionalBufferSize()
@@ -354,7 +354,7 @@
     std::vector<int> codePointVector(ptNodeParams.getCodePoints(),
             ptNodeParams.getCodePoints() + ptNodeParams.getCodePointCount());
     const ProbabilityEntry probabilityEntry =
-            mBuffers.get()->getProbabilityDictContent()->getProbabilityEntry(
+            mBuffers->getProbabilityDictContent()->getProbabilityEntry(
                     ptNodeParams.getTerminalId());
     const HistoricalInfo *const historicalInfo = probabilityEntry.getHistoricalInfo();
     // Fetch bigram information.
@@ -362,9 +362,9 @@
     const int bigramListPos = getBigramsPositionOfPtNode(ptNodePos);
     if (bigramListPos != NOT_A_DICT_POS) {
         int bigramWord1CodePoints[MAX_WORD_LENGTH];
-        const BigramDictContent *const bigramDictContent = mBuffers.get()->getBigramDictContent();
+        const BigramDictContent *const bigramDictContent = mBuffers->getBigramDictContent();
         const TerminalPositionLookupTable *const terminalPositionLookupTable =
-                mBuffers.get()->getTerminalPositionLookupTable();
+                mBuffers->getTerminalPositionLookupTable();
         bool hasNext = true;
         int readingPos = bigramListPos;
         while (hasNext) {
@@ -400,7 +400,7 @@
     if (shortcutPos != NOT_A_DICT_POS) {
         int shortcutTarget[MAX_WORD_LENGTH];
         const ShortcutDictContent *const shortcutDictContent =
-                mBuffers.get()->getShortcutDictContent();
+                mBuffers->getShortcutDictContent();
         bool hasNext = true;
         while (hasNext) {
             int shortcutTargetLength = 0;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
index 639c153..cffb1f6 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
@@ -37,17 +37,16 @@
 class DicNode;
 class DicNodeVector;
 
-// TODO: Implement.
 class Ver4PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy {
  public:
-    Ver4PatriciaTriePolicy(const Ver4DictBuffers::Ver4DictBuffersPtr &buffers)
-            : mBuffers(buffers), mHeaderPolicy(mBuffers.get()->getHeaderPolicy()),
-              mDictBuffer(mBuffers.get()->getWritableTrieBuffer()),
-              mBigramPolicy(mBuffers.get()->getMutableBigramDictContent(),
-                      mBuffers.get()->getTerminalPositionLookupTable(), mHeaderPolicy),
-              mShortcutPolicy(mBuffers.get()->getMutableShortcutDictContent(),
-                      mBuffers.get()->getTerminalPositionLookupTable()),
-              mNodeReader(mDictBuffer, mBuffers.get()->getProbabilityDictContent(), mHeaderPolicy),
+    Ver4PatriciaTriePolicy(Ver4DictBuffers::Ver4DictBuffersPtr buffers)
+            : mBuffers(std::move(buffers)), mHeaderPolicy(mBuffers->getHeaderPolicy()),
+              mDictBuffer(mBuffers->getWritableTrieBuffer()),
+              mBigramPolicy(mBuffers->getMutableBigramDictContent(),
+                      mBuffers->getTerminalPositionLookupTable(), mHeaderPolicy),
+              mShortcutPolicy(mBuffers->getMutableShortcutDictContent(),
+                      mBuffers->getTerminalPositionLookupTable()),
+              mNodeReader(mDictBuffer, mBuffers->getProbabilityDictContent(), mHeaderPolicy),
               mPtNodeArrayReader(mDictBuffer),
               mNodeWriter(mDictBuffer, mBuffers.get(), mHeaderPolicy, &mNodeReader,
                       &mPtNodeArrayReader, &mBigramPolicy, &mShortcutPolicy),
@@ -132,7 +131,7 @@
     static const int MARGIN_TO_REFUSE_DYNAMIC_OPERATIONS;
     static const int MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS;
 
-    Ver4DictBuffers::Ver4DictBuffersPtr mBuffers;
+    const Ver4DictBuffers::Ver4DictBuffersPtr mBuffers;
     const HeaderPolicy *const mHeaderPolicy;
     BufferWithExtendableBuffer *const mDictBuffer;
     Ver4BigramListPolicy mBigramPolicy;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
index 3907c84..2b1f60e 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
@@ -67,7 +67,7 @@
             unigramCount, bigramCount, 0 /* extendedRegionSize */, &headerBuffer)) {
         return false;
     }
-    return dictBuffers.get()->flushHeaderAndDictBuffers(dictDirPath, &headerBuffer);
+    return dictBuffers->flushHeaderAndDictBuffers(dictDirPath, &headerBuffer);
 }
 
 bool Ver4PatriciaTrieWritingHelper::runGC(const int rootPtNodeArrayPos,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
index faef720..4459e86 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
@@ -48,17 +48,17 @@
         const std::vector<int> localeAsCodePointVector,
         const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap) {
     HeaderPolicy headerPolicy(FormatUtils::VERSION_4, localeAsCodePointVector, attributeMap);
-    Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers =
-            Ver4DictBuffers::createVer4DictBuffers(&headerPolicy);
+    Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers(
+            Ver4DictBuffers::createVer4DictBuffers(&headerPolicy));
     headerPolicy.fillInAndWriteHeaderToBuffer(true /* updatesLastDecayedTime */,
             0 /* unigramCount */, 0 /* bigramCount */,
-            0 /* extendedRegionSize */, dictBuffers.get()->getWritableHeaderBuffer());
+            0 /* extendedRegionSize */, dictBuffers->getWritableHeaderBuffer());
     if (!DynamicPtWritingUtils::writeEmptyDictionary(
-            dictBuffers.get()->getWritableTrieBuffer(), 0 /* rootPos */)) {
+            dictBuffers->getWritableTrieBuffer(), 0 /* rootPos */)) {
         AKLOGE("Empty ver4 dictionary structure cannot be created on memory.");
         return false;
     }
-    return dictBuffers.get()->flush(dirPath);
+    return dictBuffers->flush(dirPath);
 }
 
 /* static */ bool DictFileWritingUtils::flushAllHeaderAndBodyToFile(const char *const filePath,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
index 35e05d7..bac4d4e 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
@@ -16,6 +16,7 @@
 
 #include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
 
+#include <algorithm>
 #include <cmath>
 #include <stdlib.h>
 
@@ -72,7 +73,7 @@
             headerPolicy->getForgettingCurveDurationToLevelDown());
     return sProbabilityTable.getProbability(
             headerPolicy->getForgettingCurveProbabilityValuesTableId(), historicalInfo->getLevel(),
-            min(max(elapsedTimeStepCount, 0), MAX_ELAPSED_TIME_STEP_COUNT));
+            std::min(std::max(elapsedTimeStepCount, 0), MAX_ELAPSED_TIME_STEP_COUNT));
 }
 
 /* static */ int ForgettingCurveUtils::getProbability(const int unigramProbability,
@@ -80,11 +81,11 @@
     if (unigramProbability == NOT_A_PROBABILITY) {
         return NOT_A_PROBABILITY;
     } else if (bigramProbability == NOT_A_PROBABILITY) {
-        return min(backoff(unigramProbability), MAX_PROBABILITY);
+        return std::min(backoff(unigramProbability), MAX_PROBABILITY);
     } else {
         // TODO: Investigate better way to handle bigram probability.
-        return min(max(unigramProbability, bigramProbability + MULTIPLIER_TWO_IN_PROBABILITY_SCALE),
-                MAX_PROBABILITY);
+        return std::min(std::max(unigramProbability,
+                bigramProbability + MULTIPLIER_TWO_IN_PROBABILITY_SCALE), MAX_PROBABILITY);
     }
 }
 
@@ -183,7 +184,7 @@
                                 -1.0f * static_cast<float>(timeStepCount)
                                         / static_cast<float>(MAX_ELAPSED_TIME_STEP_COUNT + 1));
                 mTables[tableId][level][timeStepCount] =
-                        min(max(static_cast<int>(probability), 1), MAX_PROBABILITY);
+                        std::min(std::max(static_cast<int>(probability), 1), MAX_PROBABILITY);
             }
         }
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp
index e88d6e0..d3e0c23 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp
@@ -33,7 +33,7 @@
     const int mmapFd = open(path, O_RDONLY);
     if (mmapFd < 0) {
         AKLOGE("DICT: Can't open the source. path=%s errno=%d", path, errno);
-        return MmappedBufferPtr(0);
+        return MmappedBufferPtr(nullptr);
     }
     const int pagesize = sysconf(_SC_PAGESIZE);
     const int offset = bufferOffset % pagesize;
@@ -45,13 +45,13 @@
     if (mmappedBuffer == MAP_FAILED) {
         AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
         close(mmapFd);
-        return MmappedBufferPtr(0);
+        return MmappedBufferPtr(nullptr);
     }
     uint8_t *const buffer = static_cast<uint8_t *>(mmappedBuffer) + offset;
     if (!buffer) {
         AKLOGE("DICT: buffer is null");
         close(mmapFd);
-        return MmappedBufferPtr(0);
+        return MmappedBufferPtr(nullptr);
     }
     return MmappedBufferPtr(new MmappedBuffer(buffer, bufferSize, mmappedBuffer, alignedSize,
             mmapFd, isUpdatable));
@@ -61,7 +61,7 @@
         const char *const path, const bool isUpdatable) {
     const int fileSize = FileUtils::getFileSize(path);
     if (fileSize == -1) {
-        return MmappedBufferPtr(0);
+        return MmappedBufferPtr(nullptr);
     } else if (fileSize == 0) {
         return MmappedBufferPtr(new MmappedBuffer(isUpdatable));
     } else {
@@ -76,7 +76,7 @@
     const int filePathLength = snprintf(filePath, filePathBufferSize, "%s%s", dirPath,
             fileName);
     if (filePathLength >= filePathBufferSize) {
-        return 0;
+        return MmappedBufferPtr(nullptr);
     }
     return openBuffer(filePath, isUpdatable);
 }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h
index 73a733b..f73716c 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h
@@ -17,16 +17,16 @@
 #ifndef LATINIME_MMAPPED_BUFFER_H
 #define LATINIME_MMAPPED_BUFFER_H
 
+#include <memory>
 #include <stdint.h>
 
 #include "defines.h"
-#include "utils/exclusive_ownership_pointer.h"
 
 namespace latinime {
 
 class MmappedBuffer {
  public:
-    typedef ExclusiveOwnershipPointer<MmappedBuffer> MmappedBufferPtr;
+    typedef std::unique_ptr<const MmappedBuffer> MmappedBufferPtr;
 
     static MmappedBufferPtr openBuffer(const char *const path,
             const int bufferOffset, const int bufferSize, const bool isUpdatable);
@@ -60,8 +60,8 @@
 
     // Empty file. We have to handle an empty file as a valid part of a dictionary.
     AK_FORCE_INLINE MmappedBuffer(const bool isUpdatable)
-            : mBuffer(0), mBufferSize(0), mMmappedBuffer(0), mAlignedSize(0), mMmapFd(0),
-              mIsUpdatable(isUpdatable) {}
+            : mBuffer(nullptr), mBufferSize(0), mMmappedBuffer(nullptr), mAlignedSize(0),
+              mMmapFd(0), mIsUpdatable(isUpdatable) {}
 
     DISALLOW_IMPLICIT_CONSTRUCTORS(MmappedBuffer);
 
diff --git a/native/jni/src/suggest/policyimpl/utils/edit_distance.h b/native/jni/src/suggest/policyimpl/utils/edit_distance.h
index 0871c37..4cfd0b3 100644
--- a/native/jni/src/suggest/policyimpl/utils/edit_distance.h
+++ b/native/jni/src/suggest/policyimpl/utils/edit_distance.h
@@ -17,6 +17,8 @@
 #ifndef LATINIME_EDIT_DISTANCE_H
 #define LATINIME_EDIT_DISTANCE_H
 
+#include <algorithm>
+
 #include "defines.h"
 #include "suggest/policyimpl/utils/edit_distance_policy.h"
 
@@ -38,13 +40,13 @@
 
         for (int i = 0; i < beforeLength; ++i) {
             for (int j = 0; j < afterLength; ++j) {
-                dp[(afterLength + 1) * (i + 1) + (j + 1)] = min(
+                dp[(afterLength + 1) * (i + 1) + (j + 1)] = std::min(
                         dp[(afterLength + 1) * i + (j + 1)] + policy->getInsertionCost(i, j),
-                        min(dp[(afterLength + 1) * (i + 1) + j] + policy->getDeletionCost(i, j),
-                                dp[(afterLength + 1) * i + j]
-                                        + policy->getSubstitutionCost(i, j)));
+                        std::min(
+                                dp[(afterLength + 1) * (i + 1) + j] + policy->getDeletionCost(i, j),
+                                dp[(afterLength + 1) * i + j] + policy->getSubstitutionCost(i, j)));
                 if (policy->allowTransposition(i, j)) {
-                    dp[(afterLength + 1) * (i + 1) + (j + 1)] = min(
+                    dp[(afterLength + 1) * (i + 1) + (j + 1)] = std::min(
                             dp[(afterLength + 1) * (i + 1) + (j + 1)],
                             dp[(afterLength + 1) * (i - 1) + (j - 1)]
                                     + policy->getTranspositionCost(i, j));
diff --git a/native/jni/src/utils/autocorrection_threshold_utils.cpp b/native/jni/src/utils/autocorrection_threshold_utils.cpp
index 1f8ee08..349786a 100644
--- a/native/jni/src/utils/autocorrection_threshold_utils.cpp
+++ b/native/jni/src/utils/autocorrection_threshold_utils.cpp
@@ -16,6 +16,7 @@
 
 #include "utils/autocorrection_threshold_utils.h"
 
+#include <algorithm>
 #include <cmath>
 
 #include "defines.h"
@@ -99,7 +100,7 @@
     const float maxScore = score >= S_INT_MAX ? static_cast<float>(S_INT_MAX)
             : static_cast<float>(MAX_INITIAL_SCORE)
                     * powf(static_cast<float>(TYPED_LETTER_MULTIPLIER),
-                            static_cast<float>(min(beforeLength, afterLength - spaceCount)))
+                            static_cast<float>(std::min(beforeLength, afterLength - spaceCount)))
                     * static_cast<float>(FULL_WORD_MULTIPLIER);
 
     return (static_cast<float>(score) / maxScore) * weight;
diff --git a/native/jni/src/utils/exclusive_ownership_pointer.h b/native/jni/src/utils/exclusive_ownership_pointer.h
deleted file mode 100644
index 081802e..0000000
--- a/native/jni/src/utils/exclusive_ownership_pointer.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2013, 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 LATINIME_EXCLUSIVE_OWNERSHIP_POINTER_H
-#define LATINIME_EXCLUSIVE_OWNERSHIP_POINTER_H
-
-#include "defines.h"
-
-namespace latinime {
-
-template<class T>
-class ExclusiveOwnershipPointer {
- public:
-    // This instance become an owner of the raw pointer.
-    AK_FORCE_INLINE ExclusiveOwnershipPointer(T *const rawPointer)
-            : mPointer(rawPointer),
-              mSharedOwnerPtr(new (ExclusiveOwnershipPointer<T> *)(this)) {}
-
-    // Move the ownership.
-    AK_FORCE_INLINE ExclusiveOwnershipPointer(const ExclusiveOwnershipPointer<T> &pointer)
-            : mPointer(pointer.mPointer), mSharedOwnerPtr(pointer.mSharedOwnerPtr) {
-        transferOwnership(&pointer);
-    }
-
-    AK_FORCE_INLINE ~ExclusiveOwnershipPointer() {
-        deletePointersIfHavingOwnership();
-    }
-
-    AK_FORCE_INLINE T *get() const {
-        return mPointer;
-    }
-
- private:
-    // This class allows to copy and ensures only one instance has the ownership of the
-    // managed pointer.
-    DISALLOW_DEFAULT_CONSTRUCTOR(ExclusiveOwnershipPointer);
-    DISALLOW_ASSIGNMENT_OPERATOR(ExclusiveOwnershipPointer);
-
-    void transferOwnership(const ExclusiveOwnershipPointer<T> *const src) {
-        if (*mSharedOwnerPtr != src) {
-           AKLOGE("Failed to transfer the ownership because src is not the current owner."
-                   "src: %p, owner: %p", src, *mSharedOwnerPtr);
-           ASSERT(false);
-           return;
-        }
-        // Transfer the ownership from src to this instance.
-        *mSharedOwnerPtr = this;
-    }
-
-    void deletePointersIfHavingOwnership() {
-        if (mSharedOwnerPtr && *mSharedOwnerPtr == this) {
-            if (mPointer) {
-                if (DEBUG_DICT) {
-                    AKLOGI("Releasing pointer: %p", mPointer);
-                }
-                delete mPointer;
-            }
-            delete mSharedOwnerPtr;
-        }
-    }
-
-    T *mPointer;
-    // mSharedOwnerPtr points a shared memory space where the instance which has the ownership is
-    // stored.
-    ExclusiveOwnershipPointer<T> **mSharedOwnerPtr;
-};
-} // namespace latinime
-#endif /* LATINIME_EXCLUSIVE_OWNERSHIP_POINTER_H */
diff --git a/native/jni/src/utils/hash_map_compat.h b/native/jni/src/utils/hash_map_compat.h
index a1e982b..7bf35a6 100644
--- a/native/jni/src/utils/hash_map_compat.h
+++ b/native/jni/src/utils/hash_map_compat.h
@@ -17,18 +17,12 @@
 #ifndef LATINIME_HASH_MAP_COMPAT_H
 #define LATINIME_HASH_MAP_COMPAT_H
 
-// TODO: Use std::unordered_map that has been standardized in C++11
+#include <unordered_map>
 
-#ifdef __APPLE__
-#include <ext/hash_map>
-#else // __APPLE__
-#include <hash_map>
-#endif // __APPLE__
+#define hash_map_compat std::unordered_map
 
-#ifdef __SGI_STL_PORT
-#define hash_map_compat stlport::hash_map
-#else // __SGI_STL_PORT
-#define hash_map_compat __gnu_cxx::hash_map
-#endif // __SGI_STL_PORT
+#if 0 // TODO: Use this instead of the above macro.
+template <typename TKey, typename TValue> using hash_map_compat = std::unordered_map<TKey, TValue>;
+#endif
 
 #endif // LATINIME_HASH_MAP_COMPAT_H
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java
new file mode 100644
index 0000000..06139b8
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.content.res.Resources;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.text.InputType;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.RunInLocale;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+@MediumTest
+public final class KeyboardLayoutSetActionLabelTests extends KeyboardLayoutSetTestsBase {
+    private static void doTestActionKey(final String tag, final KeyboardLayoutSet layoutSet,
+            final int elementId, final String label, final int iconId) {
+        final Keyboard keyboard = layoutSet.getKeyboard(elementId);
+        final Key enterKey = keyboard.getKey(Constants.CODE_ENTER);
+        assertNotNull(tag + " enter key on " + keyboard.mId, enterKey);
+        assertEquals(tag + " enter label " + enterKey, label, enterKey.getLabel());
+        assertEquals(tag + " enter icon " + enterKey, iconId, enterKey.getIconId());
+    }
+
+    private void doTestActionLabel(final String tag, final InputMethodSubtype subtype,
+            final int actionId, final int labelResId) {
+        final EditorInfo editorInfo = new EditorInfo();
+        editorInfo.imeOptions = actionId;
+        final RunInLocale<String> job = new RunInLocale<String>() {
+            @Override
+            protected String job(final Resources res) {
+                return res.getString(labelResId);
+            }
+        };
+        final Resources res = getContext().getResources();
+        final String label;
+        if (subtype.getLocale().equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
+            // Using system locale.
+            label = res.getString(labelResId);
+        } else {
+            label = job.runInLocale(res, SubtypeLocaleUtils.getSubtypeLocale(subtype));
+        }
+        // Test text layouts.
+        editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+        final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_ALPHABET,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS_SHIFTED,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        // Test phone number layouts.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE_SYMBOLS,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        // Test normal number layout.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_NUMBER,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+        // Test number password layouts.
+        editorInfo.inputType =
+                InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD;
+        final KeyboardLayoutSet passwordSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, passwordSet, KeyboardId.ELEMENT_NUMBER,
+                label, KeyboardIconsSet.ICON_UNDEFINED);
+    }
+
+    private void doTestActionKeyIcon(final String tag, final InputMethodSubtype subtype,
+            final int actionId, final String iconName) {
+        final int iconId = KeyboardIconsSet.getIconId(iconName);
+        final EditorInfo editorInfo = new EditorInfo();
+        editorInfo.imeOptions = actionId;
+        // Test text layouts.
+        editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+        final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_ALPHABET, null /* label */, iconId);
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS, null /* label */, iconId);
+        doTestActionKey(
+                tag, layoutSet, KeyboardId.ELEMENT_SYMBOLS_SHIFTED, null /* label */, iconId);
+        // Test phone number layouts.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_PHONE, null /* label */, iconId);
+        doTestActionKey(
+                tag, layoutSet, KeyboardId.ELEMENT_PHONE_SYMBOLS, null /* label */, iconId);
+        // Test normal number layout.
+        doTestActionKey(tag, layoutSet, KeyboardId.ELEMENT_NUMBER, null /* label */, iconId);
+        // Test number password layout.
+        editorInfo.inputType =
+                InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD;
+        final KeyboardLayoutSet passwordSet = createKeyboardLayoutSet(subtype, editorInfo);
+        doTestActionKey(tag, passwordSet, KeyboardId.ELEMENT_NUMBER, null /* label */, iconId);
+    }
+
+    public void testActionUnspecified() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "unspecifiled "
+                    + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_UNSPECIFIED, "enter_key");
+        }
+    }
+
+    public void testActionNone() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "none " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_NONE, "enter_key");
+        }
+    }
+
+    public void testActionGo() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "go " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_GO, R.string.label_go_key);
+        }
+    }
+
+    public void testActionSearch() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "search " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_SEARCH, "search_key");
+        }
+    }
+
+    public void testActionSend() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "send " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_SEND, R.string.label_send_key);
+        }
+    }
+
+    public void testActionNext() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "next " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_NEXT, R.string.label_next_key);
+        }
+    }
+
+    public void testActionDone() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "done " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionLabel(tag, subtype, EditorInfo.IME_ACTION_DONE, R.string.label_done_key);
+        }
+    }
+
+    public void testActionPrevious() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "previous " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionLabel(
+                    tag, subtype, EditorInfo.IME_ACTION_PREVIOUS, R.string.label_previous_key);
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
new file mode 100644
index 0000000..e691639
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+import java.util.ArrayList;
+
+@SmallTest
+public class KeyboardLayoutSetSubtypesCountTests extends KeyboardLayoutSetTestsBase {
+    private static final int NUMBER_OF_SUBTYPES = 63;
+    private static final int NUMBER_OF_ASCII_CAPABLE_SUBTYPES = 40;
+    private static final int NUMBER_OF_PREDEFINED_ADDITIONAL_SUBTYPES = 2;
+
+    private static String toString(final ArrayList<InputMethodSubtype> subtypeList) {
+        final StringBuilder sb = new StringBuilder();
+        for (int index = 0; index < subtypeList.size(); index++) {
+            final InputMethodSubtype subtype = subtypeList.get(index);
+            sb.append(index + ": ");
+            sb.append(SubtypeLocaleUtils.getSubtypeNameForLogging(subtype));
+            sb.append("\n");
+        }
+        return sb.toString();
+    }
+
+    public final void testAllSubtypesCount() {
+        final ArrayList<InputMethodSubtype> allSubtypesList = getAllSubtypesList();
+        assertEquals(toString(allSubtypesList), NUMBER_OF_SUBTYPES, allSubtypesList.size());
+    }
+
+    public final void testAsciiCapableSubtypesCount() {
+        final ArrayList<InputMethodSubtype> asciiCapableSubtypesList =
+                getAsciiCapableSubtypesList();
+        assertEquals(toString(asciiCapableSubtypesList),
+                NUMBER_OF_ASCII_CAPABLE_SUBTYPES, asciiCapableSubtypesList.size());
+    }
+
+    public final void testAdditionalSubtypesCount() {
+        final ArrayList<InputMethodSubtype> additionalSubtypesList = getAdditionalSubtypesList();
+        assertEquals(toString(additionalSubtypesList),
+                NUMBER_OF_PREDEFINED_ADDITIONAL_SUBTYPES, additionalSubtypesList.size());
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
index 9939a43..0993c4b 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
@@ -41,9 +41,6 @@
 
 @SmallTest
 public class KeyboardLayoutSetTestsBase extends AndroidTestCase {
-    private static final int NUMBER_OF_SUBTYPES = 63;
-    private static final int NUMBER_OF_ASCII_CAPABLE_SUBTYPES = 40;
-
     private static final KeyboardTheme DEFAULT_KEYBOARD_THEME =
             KeyboardSwitcher.KEYBOARD_THEMES[KeyboardSwitcher.THEME_INDEX_DEFAULT];
 
@@ -51,6 +48,8 @@
     private final ArrayList<InputMethodSubtype> mAllSubtypesList = CollectionUtils.newArrayList();
     private final ArrayList<InputMethodSubtype> mAsciiCapableSubtypesList =
             CollectionUtils.newArrayList();
+    private final ArrayList<InputMethodSubtype> mAdditionalSubtypesList =
+            CollectionUtils.newArrayList();
 
     private Context mThemeContext;
     private int mScreenMetrics;
@@ -68,6 +67,10 @@
         final int subtypeCount = imi.getSubtypeCount();
         for (int index = 0; index < subtypeCount; index++) {
             final InputMethodSubtype subtype = imi.getSubtypeAt(index);
+            if (AdditionalSubtypeUtils.isAdditionalSubtype(subtype)) {
+                mAdditionalSubtypesList.add(subtype);
+                continue;
+            }
             mAllSubtypesList.add(subtype);
             if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) {
                 mAsciiCapableSubtypesList.add(subtype);
@@ -75,32 +78,23 @@
         }
     }
 
+    protected final ArrayList<InputMethodSubtype> getAllSubtypesList() {
+        return mAllSubtypesList;
+    }
+
+    protected final ArrayList<InputMethodSubtype> getAsciiCapableSubtypesList() {
+        return mAsciiCapableSubtypesList;
+    }
+
+    protected final ArrayList<InputMethodSubtype> getAdditionalSubtypesList() {
+        return mAdditionalSubtypesList;
+    }
+
     protected final boolean isPhone() {
         return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_PHONE
                 || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_PHONE;
     }
 
-    private static String toString(final ArrayList<InputMethodSubtype> subtypeList) {
-        final StringBuilder sb = new StringBuilder();
-        for (int index = 0; index < subtypeList.size(); index++) {
-            final InputMethodSubtype subtype = subtypeList.get(index);
-            sb.append((index + 1) + ": ");
-            sb.append(SubtypeLocaleUtils.getSubtypeNameForLogging(subtype));
-            sb.append("\n");
-        }
-        return sb.toString();
-    }
-
-    public final void testAllSubtypesCount() {
-        assertEquals(toString(mAllSubtypesList),
-                NUMBER_OF_SUBTYPES, mAllSubtypesList.size());
-    }
-
-    public final void testAsciiCapableSubtypesCount() {
-        assertEquals(toString(mAsciiCapableSubtypesList),
-                NUMBER_OF_ASCII_CAPABLE_SUBTYPES, mAsciiCapableSubtypesList.size());
-    }
-
     protected final InputMethodSubtype getSubtype(final Locale locale,
             final String keyboardLayout) {
         for (final InputMethodSubtype subtype : mAllSubtypesList) {
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java b/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java
new file mode 100644
index 0000000..be3ed12
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/AlphabetShifted.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout;
+
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
+import com.android.inputmethod.keyboard.layout.expected.LayoutBase;
+import com.android.inputmethod.latin.Constants;
+
+import java.util.Locale;
+
+/**
+ * The generic upper case alphabet keyboard layout.
+ */
+public final class AlphabetShifted extends LayoutBase {
+    public static ExpectedKey[][] getAlphabet(final ExpectedKey[][] lowerCaseKeyboard,
+            final Locale locale) {
+        final ExpectedKey[][] upperCaseKeyboard = ExpectedKeyboardBuilder.toUpperCase(
+                lowerCaseKeyboard, locale);
+        return new ExpectedKeyboardBuilder(upperCaseKeyboard)
+                .replaceKeyOfAll(SHIFT_KEY, SHIFTED_SHIFT_KEY)
+                .build();
+    }
+
+    // Icon id.
+    private static final int ICON_SHIFTED_SHIFT = KeyboardIconsSet.getIconId("shift_key_shifted");
+
+    // Functional key.
+    private static final ExpectedKey SHIFTED_SHIFT_KEY = key(
+            ICON_SHIFTED_SHIFT, Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY);
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java b/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java
new file mode 100644
index 0000000..f7179b7
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Qwerty.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout;
+
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
+import com.android.inputmethod.keyboard.layout.expected.LayoutBase;
+
+/**
+ * The QWERTY alphabet keyboard.
+ */
+public final class Qwerty extends LayoutBase {
+    public static ExpectedKey[][] getAlphabet(final boolean isPhone) {
+        return toCommonAlphabet(ALPHABET_COMMON, isPhone);
+    }
+
+    private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder(10, 9, 7, 3)
+            .setLabelsOfRow(1, "q", "w", "e", "r", "t", "y", "u", "i", "o", "p")
+            .setMoreKeysOf("q", "1")
+            .setMoreKeysOf("w", "2")
+            .setMoreKeysOf("e", "3")
+            .setMoreKeysOf("r", "4")
+            .setMoreKeysOf("t", "5")
+            .setMoreKeysOf("y", "6")
+            .setMoreKeysOf("u", "7")
+            .setMoreKeysOf("i", "8")
+            .setMoreKeysOf("o", "9")
+            .setMoreKeysOf("p", "0")
+            .setLabelsOfRow(2, "a", "s", "d", "f", "g", "h", "j", "k", "l")
+            .setLabelsOfRow(3, "z", "x", "c", "v", "b", "n", "m")
+            .build();
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
new file mode 100644
index 0000000..03d7f07
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout;
+
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
+import com.android.inputmethod.keyboard.layout.expected.LayoutBase;
+import com.android.inputmethod.latin.Constants;
+
+/**
+ * The symbols keyboard layout.
+ */
+public final class Symbols extends LayoutBase {
+    public static ExpectedKey[][] getSymbols(final boolean isPhone) {
+        return isPhone ? toPhoneSymbol(SYMBOLS_COMMON) : toTabletSymbols(SYMBOLS_COMMON);
+    }
+
+    // Functional keys.
+    public static final ExpectedKey ALPHABET_KEY = key("ABC", Constants.CODE_SWITCH_ALPHA_SYMBOL);
+    public static final ExpectedKey SYMBOLS_SHIFT_KEY = key("= \\ <", Constants.CODE_SHIFT);
+    public static final ExpectedKey TABLET_SYMBOLS_SHIFT_KEY = key("~ [ <", Constants.CODE_SHIFT);
+
+    // Common symbols keyboard layout.
+    public static final ExpectedKey[][] SYMBOLS_COMMON = new ExpectedKeyboardBuilder(10, 9, 7, 5)
+            .setLabelsOfRow(1, "1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
+            // U+00B9: "¹" SUPERSCRIPT ONE
+            // U+00BD: "½" VULGAR FRACTION ONE HALF
+            // U+2153: "⅓" VULGAR FRACTION ONE THIRD
+            // U+00BC: "¼" VULGAR FRACTION ONE QUARTER
+            // U+215B: "⅛" VULGAR FRACTION ONE EIGHTH
+            .setMoreKeysOf("1", "\u00B9", "\u00BD", "\u2153", "\u00BC", "\u215B")
+            // U+00B2: "²" SUPERSCRIPT TWO
+            // U+2154: "⅔" VULGAR FRACTION TWO THIRDS
+            .setMoreKeysOf("2", "\u00B2", "\u2154")
+            // U+00B3: "³" SUPERSCRIPT THREE
+            // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS
+            // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS
+            .setMoreKeysOf("3", "\u00B3", "\u00BE", "\u215C")
+            // U+2074: "⁴" SUPERSCRIPT FOUR
+            .setMoreKeysOf("4", "\u2074")
+            // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS
+            .setMoreKeysOf("5", "\u215D")
+            // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
+            .setMoreKeysOf("7", "\u215E")
+            // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
+            // U+2205: "∅" EMPTY SET
+            .setMoreKeysOf("0", "\u207F", "\u2205")
+            .setLabelsOfRow(2, "@", "#", "$", "%", "&", "-", "+", "(", ")")
+            // U+00A2: "¢" CENT SIGN
+            // U+00A3: "£" POUND SIGN
+            // U+20AC: "€" EURO SIGN
+            // U+00A5: "¥" YEN SIGN
+            // U+20B1: "₱" PESO SIGN
+            .setMoreKeysOf("$", "\u00A2", "\u00A3", "\u20AC", "\u00A5", "\u20B1")
+            // U+2030: "‰" PER MILLE SIGN
+            .setMoreKeysOf("%", "\u2030")
+            // U+2013: "–" EN DASH
+            // U+2014: "—" EM DASH
+            // U+00B7: "·" MIDDLE DOT
+            .setMoreKeysOf("-", "_", "\u2013", "\u2014", "\u00B7")
+            // U+00B1: "±" PLUS-MINUS SIGN
+            .setMoreKeysOf("+", "\u00B1")
+            .setMoreKeysOf("(", "<", "{", "[")
+            .setMoreKeysOf(")", ">", "}", "]")
+            .setLabelsOfRow(3, "*", "\"", "'", ":", ";", "!", "?")
+            // U+2020: "†" DAGGER
+            // U+2021: "‡" DOUBLE DAGGER
+            // U+2605: "★" BLACK STAR
+            .setMoreKeysOf("*", "\u2020", "\u2021", "\u2605")
+            // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+            // U+201C: "“" LEFT DOUBLE QUOTATION MARK
+            // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+            // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+            // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+            .setMoreKeysOf("\"", "\u201E", "\u201C", "\u201D", "\u00AB", "\u00BB")
+            // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+            // U+2018: "‘" LEFT SINGLE QUOTATION MARK
+            // U+2019: "’" RIGHT SINGLE QUOTATION MARK
+            // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+            // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+            .setMoreKeysOf("'", "\u201A", "\u2018", "\u2019", "\u2039", "\u203A")
+            // U+00A1: "¡" INVERTED EXCLAMATION MARK
+            .setMoreKeysOf("!", "\u00A1")
+            // U+00BF: "¿" INVERTED QUESTION MARK
+            .setMoreKeysOf("?", "\u00BF")
+            .setLabelsOfRow(4, "_", "/", " ", ",", ".")
+            // U+2026: "…" HORIZONTAL ELLIPSIS
+            .setMoreKeysOf(".", "\u2026")
+            .build();
+
+    private static ExpectedKey[][] toPhoneSymbol(final ExpectedKey[][] common) {
+        return new ExpectedKeyboardBuilder(common)
+                .addKeysOnTheLeftOfRow(3, Symbols.SYMBOLS_SHIFT_KEY)
+                .addKeysOnTheRightOfRow(3, DELETE_KEY)
+                .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY)
+                .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY))
+                .build();
+    }
+
+    private static ExpectedKey[][] toTabletSymbols(final ExpectedKey[][] common) {
+        return new ExpectedKeyboardBuilder(common)
+                .addKeysOnTheLeftOfRow(3,
+                        key("\\"), key("="))
+                .addKeysOnTheRightOfRow(1, DELETE_KEY)
+                .addKeysOnTheRightOfRow(2, ENTER_KEY)
+                .addKeysOnTheLeftOfRow(3, Symbols.TABLET_SYMBOLS_SHIFT_KEY)
+                .addKeysOnTheRightOfRow(3, Symbols.TABLET_SYMBOLS_SHIFT_KEY)
+                .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY)
+                .addKeysOnTheRightOfRow(4, EMOJI_KEY)
+                .build();
+    }
+
+    // Helper method to add currency symbols for Euro.
+    public static ExpectedKeyboardBuilder euro(final ExpectedKeyboardBuilder builder) {
+        return builder
+                // U+20AC: "€" EURO SIGN
+                // U+00A2: "¢" CENT SIGN
+                // U+00A3: "£" POUND SIGN
+                // U+00A5: "¥" YEN SIGN
+                // U+20B1: "₱" PESO SIGN
+                .replaceKeyOfLabel("$", key("\u20AC",
+                        moreKey("\u00A2"), moreKey("\u00A3"), moreKey("$"),
+                        moreKey("\u00A5"), moreKey("\u20B1")));
+    }
+
+    // Helper method to add single quotes "more keys".
+    // "9LLR" means "9-low/Left quotation marks, Left/Right-pointing angle quotation marks".
+    public static ExpectedKeyboardBuilder singleQuotes9LLR(final ExpectedKeyboardBuilder builder) {
+        return builder
+                // U+2019: "’" RIGHT SINGLE QUOTATION MARK
+                // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+                // U+2018: "‘" LEFT SINGLE QUOTATION MARK
+                // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+                // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+                .setMoreKeysOf("'", "\u2019", "\u201A", "\u2018", "\u2039", "\u203A");
+    }
+
+    // Helper method to add single quotes "more keys".
+    // "9LLR" means "9-low/Left quotation marks, Right/Left-pointing angle quotation marks".
+    public static ExpectedKeyboardBuilder singleQuotes9LRL(final ExpectedKeyboardBuilder builder) {
+        return builder
+                // U+2019: "’" RIGHT SINGLE QUOTATION MARK
+                // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+                // U+2018: "‘" LEFT SINGLE QUOTATION MARK
+                // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+                // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+                .setMoreKeysOf("'", "\u2019", "\u201A", "\u2018", "\u203A", "\u2039");
+    }
+
+    // Helper method to add double quotes "more keys".
+    // "9LLR" means "9-low/Left quotation marks, Left/Right-pointing angle quotation marks".
+    public static ExpectedKeyboardBuilder doubleQuotes9LLR(final ExpectedKeyboardBuilder builder) {
+        return builder
+                // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+                // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+                // U+201C: "“" LEFT DOUBLE QUOTATION MARK
+                // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+                // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+                .setMoreKeysOf("\"", "\u201D", "\u201E", "\u201C", "\u00AB", "\u00BB");
+    }
+
+    // Helper method to add double quotes "more keys".
+    // "9LLR" means "9-low/Left quotation marks, Right/Left-pointing angle quotation marks".
+    public static ExpectedKeyboardBuilder doubleQuotes9LRL(final ExpectedKeyboardBuilder builder) {
+        return builder
+                // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+                // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+                // U+201C: "“" LEFT DOUBLE QUOTATION MARK
+                // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+                // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+                .setMoreKeysOf("\"", "\u201D", "\u201E", "\u201C", "\u00BB", "\u00AB");
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java b/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java
new file mode 100644
index 0000000..368f9db
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout;
+
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
+import com.android.inputmethod.keyboard.layout.expected.LayoutBase;
+import com.android.inputmethod.latin.Constants;
+
+/**
+ * The symbols shifted keyboard layout.
+ */
+public final class SymbolsShifted extends LayoutBase {
+    public static ExpectedKey[][] getSymbolsShifted(final boolean isPhone) {
+        return isPhone ? toPhoneSymbolsShifted(SYMBOLS_SHIFTED_COMMON)
+                : toTabletSymbolsShifted(SYMBOLS_SHIFTED_COMMON);
+    }
+
+    // Functional key.
+    public static final ExpectedKey BACK_TO_SYMBOLS_KEY = key("?123", Constants.CODE_SHIFT);
+
+    // Common symbols shifted keyboard layout.
+    public static final ExpectedKey[][] SYMBOLS_SHIFTED_COMMON =
+            new ExpectedKeyboardBuilder(10, 9, 7, 5)
+            // U+0060: "`" GRAVE ACCENT
+            // U+2022: "•" BULLET
+            // U+221A: "√" SQUARE ROOT
+            // U+03C0: "π" GREEK SMALL LETTER PI
+            // U+00F7: "÷" DIVISION SIGN
+            // U+00D7: "×" MULTIPLICATION SIGN
+            // U+00B6: "¶" PILCROW SIGN
+            // U+2206: "∆" INCREMENT
+            .setLabelsOfRow(1,
+                    "~", "\u0060", "|", "\u2022", "\u221A",
+                    "\u03C0", "\u00F7", "\u00D7", "\u00B6", "\u2206")
+            // U+2022: "•" BULLET
+            // U+266A: "♪" EIGHTH NOTE
+            // U+2665: "♥" BLACK HEART SUIT
+            // U+2660: "♠" BLACK SPADE SUIT
+            // U+2666: "♦" BLACK DIAMOND SUIT
+            // U+2663: "♣" BLACK CLUB SUIT
+            .setMoreKeysOf("\u2022", "\u266A", "\u2665", "\u2660", "\u2666", "\u2663")
+            // U+03C0: "π" GREEK SMALL LETTER PI
+            // U+03A0: "Π" GREEK CAPITAL LETTER PI
+            .setMoreKeysOf("\u03C0", "\u03A0")
+            // U+00B6: "¶" PILCROW SIGN
+            // U+00A7: "§" SECTION SIGN
+            .setMoreKeysOf("\u00B6", "\u00A7")
+            // U+00A3: "£" POUND SIGN
+            // U+00A2: "¢" CENT SIGN
+            // U+20AC: "€" EURO SIGN
+            // U+00A5: "¥" YEN SIGN
+            // U+00B0: "°" DEGREE SIGN
+            .setLabelsOfRow(2,
+                    "\u00A3", "\u00A2", "\u20AC", "\u00A5", "^",
+                    "\u00B0", "=", "{", "}")
+            // U+2191: "↑" UPWARDS ARROW
+            // U+2193: "↓" DOWNWARDS ARROW
+            // U+2190: "←" LEFTWARDS ARROW
+            // U+2192: "→" RIGHTWARDS ARROW
+            .setMoreKeysOf("^", "\u2191", "\u2193", "\u2190", "\u2192")
+            // U+00B0: "°" DEGREE SIGN
+            // U+2032: "′" PRIME
+            // U+2033: "″" DOUBLE PRIME
+            .setMoreKeysOf("\u00B0", "\u2032", "\u2033")
+            // U+2260: "≠" NOT EQUAL TO
+            // U+2248: "≈" ALMOST EQUAL TO
+            // U+221E: "∞" INFINITY
+            .setMoreKeysOf("=", "\u2260", "\u2248", "\u221E")
+            // U+00A9: "©" COPYRIGHT SIGN
+            // U+00AE: "®" REGISTERED SIGN
+            // U+2122: "™" TRADE MARK SIGN
+            // U+2105: "℅" CARE OF
+            .setLabelsOfRow(3,
+                    "\\", "\u00A9", "\u00AE", "\u2122", "\u2105",
+                    "[", "]")
+            .setLabelsOfRow(4,
+                    "<", ">", " ", ",", ".")
+            // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+            // U+2264: "≤" LESS-THAN OR EQUAL TO
+            // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+            .setMoreKeysOf("<", "\u2039", "\u2264", "\u00AB")
+            // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+            // U+2265: "≥" GREATER-THAN EQUAL TO
+            // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+            .setMoreKeysOf(">", "\u203A", "\u2265", "\u00BB")
+            // U+2026: "…" HORIZONTAL ELLIPSIS
+            .setMoreKeysOf(".", "\u2026")
+            .build();
+
+    private static ExpectedKey[][] toPhoneSymbolsShifted(final ExpectedKey[][] common) {
+        return new ExpectedKeyboardBuilder(common)
+                .addKeysOnTheLeftOfRow(3, BACK_TO_SYMBOLS_KEY)
+                .addKeysOnTheRightOfRow(3, DELETE_KEY)
+                .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY)
+                .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY))
+                .build();
+    }
+
+    private static ExpectedKey[][] toTabletSymbolsShifted(final ExpectedKey[][] common) {
+        return new ExpectedKeyboardBuilder(common)
+                // U+00BF: "¿" INVERTED QUESTION MARK
+                // U+00A1: "¡" INVERTED EXCLAMATION MARK
+                .addKeysOnTheRightOfRow(3,
+                        key("\u00A1"), key("\u00BF"))
+                .addKeysOnTheRightOfRow(1, DELETE_KEY)
+                .addKeysOnTheRightOfRow(2, ENTER_KEY)
+                .addKeysOnTheLeftOfRow(3, BACK_TO_SYMBOLS_KEY)
+                .addKeysOnTheRightOfRow(3, BACK_TO_SYMBOLS_KEY)
+                .addKeysOnTheLeftOfRow(4, Symbols.ALPHABET_KEY)
+                .addKeysOnTheRightOfRow(4, EMOJI_KEY)
+                .build();
+    }
+
+    // Helper method to add currency symbols for Euro.
+    public static ExpectedKeyboardBuilder euro(final ExpectedKeyboardBuilder builder) {
+        return builder
+                // U+00A5: "¥" YEN SIGN
+                // U+00A2: "¢" CENT SIGN
+                .replaceKeyOfLabel("\u00A5", key("\u00A2"))
+                // U+20AC: "€" EURO SIGN
+                // U+00A2: "¢" CENT SIGN
+                .replaceKeyOfLabel("\u20AC", key("$", moreKey("\u00A2")))
+                // U+00A2: "¢" CENT SIGN
+                // U+00A5: "¥" YEN SIGN
+                .replaceKeyOfLabel("\u00A2", key("\u00A5"));
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
new file mode 100644
index 0000000..45449b7
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.expected;
+
+import java.util.Arrays;
+
+/**
+ * This class builds a keyboard that is a two dimensional array of elements <code>E</code>.
+ *
+ * A keyboard consists of array of rows, and a row consists of array of elements. Each row may have
+ * different number of elements. A element of a keyboard can be specified by a row number and a
+ * column number, both numbers starts from 1.
+ *
+ * @param <E> the type of a keyboard element. A keyboard element must be an immutable object.
+ */
+abstract class AbstractKeyboardBuilder<E> {
+    // A building array of rows.
+    private final E[][] mRows;
+
+    // Returns an instance of default element.
+    abstract E defaultElement();
+    // Returns an <code>E</code> array instance of the <code>size</code>.
+    abstract E[] newArray(final int size);
+    // Returns an <code>E[]</code> array instance of the <code>size</code>.
+    abstract E[][] newArrayOfArray(final int size);
+
+    /**
+     * Construct a builder filled with the default element.
+     * @param dimensions the integer array of each row's size.
+     */
+    AbstractKeyboardBuilder(final int ... dimensions) {
+        mRows = newArrayOfArray(dimensions.length);
+        for (int rowIndex = 0; rowIndex < dimensions.length; rowIndex++) {
+            mRows[rowIndex] = newArray(dimensions[rowIndex]);
+            Arrays.fill(mRows[rowIndex], defaultElement());
+        }
+    }
+
+    /**
+     * Construct a builder from template keyboard. This builder has the same dimensions and
+     * elements of <code>rows</rows>.
+     * @param rows the template keyboard rows. The elements of the <code>rows</code> will be
+     *        shared with this builder. Therefore a element must be an immutable object.
+     */
+    AbstractKeyboardBuilder(final E[][] rows) {
+        mRows = newArrayOfArray(rows.length);
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            final E[] row = rows[rowIndex];
+            mRows[rowIndex] = Arrays.copyOf(row, row.length);
+        }
+    }
+
+    /**
+     * Return current constructing keyboard.
+     * @return the array of the array of the element being constructed.
+     */
+    E[][] build() {
+        return mRows;
+    }
+
+    /**
+     * Get the current contents of the specified row.
+     * @param row the row number to get the contents.
+     * @return the array of elements at row number <code>row</code>.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    E[] getRowAt(final int row) {
+        final int rowIndex = row - 1;
+        if (rowIndex < 0 || rowIndex >= mRows.length) {
+            throw new RuntimeException("Illegal row number: " + row);
+        }
+        return mRows[rowIndex];
+    }
+
+    /**
+     * Set an array of elements to the specified row.
+     * @param row the row number to set <code>elements</code>.
+     * @param elements the array of elements to set at row number <code>row</code>.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    void setRowAt(final int row, final E[] elements) {
+        final int rowIndex = row - 1;
+        if (rowIndex < 0 || rowIndex >= mRows.length) {
+            throw new RuntimeException("Illegal row number: " + row);
+        }
+        mRows[rowIndex] = elements;
+    }
+
+    /**
+     * Set or insert an element at specified position.
+     * @param row the row number to set or insert the <code>element</code>.
+     * @param column the column number to set or insert the <code>element</code>.
+     * @param element the element to set or insert at <code>row,column</code>.
+     * @param insert if true, the <code>element</code> is inserted at <code>row,column</code>.
+     *        Otherwise the <code>element</code> replace the element at <code>row,column</code>.
+     * @throws {@link RuntimeException} if <code>row</code> or <code>column</code> is illegal.
+     */
+    void setElementAt(final int row, final int column, final E element, final boolean insert) {
+        final E[] elements = getRowAt(row);
+        final int columnIndex = column - 1;
+        if (insert) {
+            if (columnIndex < 0 || columnIndex >= elements.length + 1) {
+                throw new RuntimeException("Illegal column number: " + column);
+            }
+            final E[] newElements = Arrays.copyOf(elements, elements.length + 1);
+            // Shift the remaining elements.
+            System.arraycopy(newElements, columnIndex, newElements, columnIndex + 1,
+                    elements.length - columnIndex);
+            // Insert the element at <code>row,column</code>.
+            newElements[columnIndex] = element;
+            // Replace the current row with one.
+            setRowAt(row, newElements);
+            return;
+        }
+        if (columnIndex < 0 || columnIndex >= elements.length) {
+            throw new RuntimeException("Illegal column number: " + column);
+        }
+        elements[columnIndex] = element;
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
new file mode 100644
index 0000000..b918d47
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.expected;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * This class builds an actual keyboard for unit test.
+ */
+public final class ActualKeyboardBuilder extends AbstractKeyboardBuilder<Key> {
+    // Comparator to sort {@link Key}s from top-left to bottom-right order.
+    private static final Comparator<Key> ROW_COLUMN_COMPARATOR = new Comparator<Key>() {
+        @Override
+        public int compare(final Key lhs, final Key rhs) {
+            if (lhs.getY() < rhs.getY()) return -1;
+            if (lhs.getY() > rhs.getY()) return 1;
+            if (lhs.getX() < rhs.getX()) return -1;
+            if (lhs.getX() > rhs.getX()) return 1;
+            return 0;
+        }
+    };
+
+    /**
+     * Create the keyboard that consists of the array of rows of the actual keyboard's keys.
+     * @param keys the array of keys of the actual keyboard.
+     * @return the actual keyboard grouped with rows.
+     */
+    public static Key[][] buildKeyboard(final Key[] keys) {
+        // Sort keys from top-left to bottom-right order to prepare to create rows.
+        final ArrayList<Key> sortedKeys = CollectionUtils.newArrayList(Arrays.asList(keys));
+        Collections.sort(sortedKeys, ROW_COLUMN_COMPARATOR);
+
+        // Grouping keys into rows.
+        final ArrayList<ArrayList<Key>> rows = CollectionUtils.newArrayList();
+        ArrayList<Key> elements = CollectionUtils.newArrayList();
+        int lastY = sortedKeys.get(0).getY();
+        for (final Key key : sortedKeys) {
+            if (lastY != key.getY()) {
+                // A new row is starting.
+                lastY = key.getY();
+                rows.add(elements);
+                elements = CollectionUtils.newArrayList();
+            }
+            elements.add(key);
+        }
+        rows.add(elements); // Add the last row.
+
+        // Calculate each dimension of rows and create a builder.
+        final int[] dimensions = new int[rows.size()];
+        for (int rowIndex = 0; rowIndex < dimensions.length; rowIndex++) {
+            dimensions[rowIndex] = rows.get(rowIndex).size();
+        }
+        final ActualKeyboardBuilder builder = new ActualKeyboardBuilder(dimensions);
+
+        for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
+            final int row = rowIndex + 1;
+            final ArrayList<Key> rowKeys = rows.get(rowIndex);
+            builder.setRowAt(row, rowKeys.toArray(new Key[rowKeys.size()]));
+        }
+        return builder.build();
+    }
+
+    private ActualKeyboardBuilder(final int ... dimensions) {
+        super(dimensions);
+    }
+
+    @Override
+    Key defaultElement() { return null; }
+
+    @Override
+    Key[] newArray(final int size) { return new Key[size]; }
+
+    @Override
+    Key[][] newArrayOfArray(final int size) { return new Key[size][]; }
+
+    // Helper class to create concise representation from the key specification.
+    static class MoreKeySpecStringizer extends StringUtils.Stringizer<MoreKeySpec> {
+        static final MoreKeySpecStringizer STRINGIZER = new MoreKeySpecStringizer();
+
+        @Override
+        public String stringize(final MoreKeySpec spec) {
+            return toString(spec.mLabel, spec.mIconId, spec.mOutputText, spec.mCode);
+        }
+
+        static String toString(final String label, final int iconId, final String outputText,
+                final int code) {
+            final String visual = (iconId != KeyboardIconsSet.ICON_UNDEFINED)
+                    ? KeyboardIconsSet.getIconName(iconId) : label;
+            final String output;
+            if (code == Constants.CODE_OUTPUT_TEXT) {
+                output = outputText;
+            } else if (code < Constants.CODE_SPACE) {
+                output = Constants.printableCode(code);
+            } else {
+                output = StringUtils.newSingleCodePointString(code);
+            }
+            if (visual.equals(output)) {
+                return visual;
+            }
+            return visual + "|" + output;
+        }
+    }
+
+    // Helper class to create concise representation from the key.
+    static class KeyStringizer extends StringUtils.Stringizer<Key> {
+        static final KeyStringizer STRINGIZER = new KeyStringizer();
+
+        @Override
+        public String stringize(final Key key) {
+            if (key == null) {
+                return "NULL";
+            }
+            if (key.isSpacer()) {
+                return "SPACER";
+            }
+            final StringBuilder sb = new StringBuilder();
+            sb.append(MoreKeySpecStringizer.toString(
+                    key.getLabel(), key.getIconId(), key.getOutputText(), key.getCode()));
+            final MoreKeySpec[] moreKeys = key.getMoreKeys();
+            if (moreKeys == null) {
+                return sb.toString();
+            }
+            sb.append("^");
+            sb.append(MoreKeySpecStringizer.STRINGIZER.join(moreKeys));
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Convert the key to human readable string.
+     * @param key the key to be converted to string.
+     * @return the human readable representation of <code>key</code>.
+     */
+    public static String toString(final Key key) {
+        return KeyStringizer.STRINGIZER.stringize(key);
+    }
+
+    /**
+     * Convert the keyboard row to human readable string.
+     * @param keys the keyboard row to be converted to string.
+     * @return the human readable representation of <code>keys</code>.
+     */
+    public static String toString(final Key[] keys) {
+        return KeyStringizer.STRINGIZER.join(keys);
+    }
+
+    // Helper class to create concise representation from the array of the key.
+    static class KeyArrayStringizer extends StringUtils.Stringizer<Key[]> {
+        static final KeyArrayStringizer STRINGIZER = new KeyArrayStringizer();
+
+        @Override
+        public String stringize(final Key[] keyArray) {
+            return KeyStringizer.STRINGIZER.join(keyArray);
+        }
+    }
+
+    /**
+     * Convert the keyboard to human readable string.
+     * @param rows the keyboard to be converted to string.
+     * @return the human readable representation of <code>rows</code>.
+     */
+    public static String toString(final Key[][] rows) {
+        return KeyArrayStringizer.STRINGIZER.join(rows, "\n" /* delimiter */);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java
new file mode 100644
index 0000000..e22d75c
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.expected;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * This class represents an expected key.
+ */
+public class ExpectedKey {
+    static ExpectedKey EMPTY_KEY = newInstance("");
+
+    // A key that has a string label and may have "more keys".
+    static ExpectedKey newInstance(final String label, final ExpectedKey ... moreKeys) {
+        return newInstance(label, label, moreKeys);
+    }
+
+    // A key that has a string label and a different output text and may have "more keys".
+    static ExpectedKey newInstance(final String label, final String outputText,
+            final ExpectedKey ... moreKeys) {
+        return newInstance(ExpectedKeyVisual.newInstance(label),
+                ExpectedKeyOutput.newInstance(outputText), moreKeys);
+    }
+
+    // A key that has a string label and a code point output and may have "more keys".
+    static ExpectedKey newInstance(final String label, final int code,
+            final ExpectedKey ... moreKeys) {
+        return newInstance(ExpectedKeyVisual.newInstance(label),
+                ExpectedKeyOutput.newInstance(code), moreKeys);
+    }
+
+    // A key that has an icon and a code point output and may have "more keys".
+    static ExpectedKey newInstance(final int iconId, final int code,
+            final ExpectedKey ... moreKeys) {
+        return newInstance(ExpectedKeyVisual.newInstance(iconId),
+                ExpectedKeyOutput.newInstance(code), moreKeys);
+    }
+
+    static ExpectedKey newInstance(final ExpectedKeyVisual visual, final ExpectedKeyOutput output,
+            final ExpectedKey ... moreKeys) {
+        if (moreKeys.length == 0) {
+            return new ExpectedKey(visual, output);
+        }
+        return new ExpectedKeyWithMoreKeys(visual, output, moreKeys);
+    }
+
+    private static final ExpectedKey[] EMPTY_KEYS = new ExpectedKey[0];
+
+    // The expected visual outlook of this key.
+    private final ExpectedKeyVisual mVisual;
+    // The expected output of this key.
+    private final ExpectedKeyOutput mOutput;
+
+    public final ExpectedKeyVisual getVisual() {
+        return mVisual;
+    }
+
+    public final ExpectedKeyOutput getOutput() {
+        return mOutput;
+    }
+
+    public ExpectedKey[] getMoreKeys() {
+        // This key has no "more keys".
+        return EMPTY_KEYS;
+    }
+
+    protected ExpectedKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) {
+        mVisual = visual;
+        mOutput = output;
+    }
+
+    public ExpectedKey toUpperCase(Locale locale) {
+        return newInstance(mVisual.toUpperCase(locale), mOutput.toUpperCase(locale));
+    }
+
+    public boolean equalsTo(final Key key) {
+        // This key has no "more keys".
+        return mVisual.equalsTo(key) && mOutput.equalsTo(key) && key.getMoreKeys() == null;
+    }
+
+    public boolean equalsTo(final MoreKeySpec moreKeySpec) {
+        return mVisual.equalsTo(moreKeySpec) && mOutput.equalsTo(moreKeySpec);
+    }
+
+    @Override
+    public boolean equals(final Object object) {
+        if (object instanceof ExpectedKey) {
+            final ExpectedKey key = (ExpectedKey)object;
+            return mVisual.equalsTo(key.mVisual) && mOutput.equalsTo(key.mOutput)
+                    && Arrays.equals(getMoreKeys(), key.getMoreKeys());
+        }
+        return false;
+    }
+
+    private static int hashCode(final Object ... objects) {
+        return Arrays.hashCode(objects);
+    }
+
+    @Override
+    public int hashCode() {
+        return hashCode(mVisual, mOutput, getMoreKeys());
+    }
+
+    @Override
+    public String toString() {
+        if (mVisual.equalsTo(mOutput)) {
+            return mVisual.toString();
+        }
+        return mVisual + "|" + mOutput;
+    }
+
+    /**
+     * This class represents an expected key that has "more keys".
+     */
+    private static final class ExpectedKeyWithMoreKeys extends ExpectedKey {
+        private final ExpectedKey[] mMoreKeys;
+
+        ExpectedKeyWithMoreKeys(final ExpectedKeyVisual visual,
+                final ExpectedKeyOutput output, final ExpectedKey ... moreKeys) {
+            super(visual, output);
+            mMoreKeys = moreKeys;
+        }
+
+        @Override
+        public ExpectedKey toUpperCase(final Locale locale) {
+            final ExpectedKey[] upperCaseMoreKeys = new ExpectedKey[mMoreKeys.length];
+            for (int i = 0; i < mMoreKeys.length; i++) {
+                upperCaseMoreKeys[i] = mMoreKeys[i].toUpperCase(locale);
+            }
+            return newInstance(getVisual().toUpperCase(locale), getOutput().toUpperCase(locale),
+                    upperCaseMoreKeys);
+        }
+
+        @Override
+        public ExpectedKey[] getMoreKeys() {
+            return mMoreKeys;
+        }
+
+        @Override
+        public boolean equalsTo(final Key key) {
+            if (getVisual().equalsTo(key) && getOutput().equalsTo(key)) {
+                final MoreKeySpec[] moreKeys = key.getMoreKeys();
+                // This key should have at least one "more key".
+                if (moreKeys == null || moreKeys.length != mMoreKeys.length) {
+                    return false;
+                }
+                for (int index = 0; index < moreKeys.length; index++) {
+                    if (!mMoreKeys[index].equalsTo(moreKeys[index])) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean equalsTo(final MoreKeySpec moreKeySpec) {
+            // MoreKeySpec has no "more keys".
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + "^" + Arrays.toString(mMoreKeys);
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
new file mode 100644
index 0000000..1be51e6
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.expected;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.StringUtils;
+
+import java.util.Locale;
+
+/**
+ * This class represents an expected output of a key.
+ *
+ * There are two types of expected output, an integer code point and a string output text.
+ */
+abstract class ExpectedKeyOutput {
+    static ExpectedKeyOutput newInstance(final int code) {
+        return new Code(code);
+    }
+
+    static ExpectedKeyOutput newInstance(final String outputText) {
+        // If the <code>outputText</code> is one code point string, use {@link CodePoint} object.
+        if (StringUtils.codePointCount(outputText) == 1) {
+            return new Code(outputText.codePointAt(0));
+        }
+        return new Text(outputText);
+    }
+
+    abstract ExpectedKeyOutput toUpperCase(final Locale locale);
+    abstract boolean equalsTo(final String text);
+    abstract boolean equalsTo(final Key key);
+    abstract boolean equalsTo(final MoreKeySpec moreKeySpec);
+    abstract boolean equalsTo(final ExpectedKeyOutput output);
+
+    /**
+     * This class represents an integer code point.
+     */
+    private static class Code extends ExpectedKeyOutput {
+        // UNICODE code point or a special negative value defined in {@link Constants}.
+        private final int mCode;
+
+        Code(final int code) { mCode = code; }
+
+        @Override
+        ExpectedKeyOutput toUpperCase(final Locale locale) {
+            if (Constants.isLetterCode(mCode)) {
+                final String codeString = StringUtils.newSingleCodePointString(mCode);
+                // A letter may have an upper case counterpart that consists of multiple code
+                // points, for instance the upper case of "ß" is "SS".
+                return newInstance(codeString.toUpperCase(locale));
+            }
+            // A special negative value has no upper case.
+            return this;
+        }
+
+        @Override
+        boolean equalsTo(final String text) {
+            return StringUtils.codePointCount(text) == 1 && text.codePointAt(0) == mCode;
+        }
+
+        @Override
+        boolean equalsTo(final Key key) {
+            return mCode == key.getCode();
+        }
+
+        @Override
+        boolean equalsTo(final MoreKeySpec moreKeySpec) {
+            return mCode == moreKeySpec.mCode;
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyOutput output) {
+            return (output instanceof Code) && mCode == ((Code)output).mCode;
+        }
+
+        @Override
+        public String toString() {
+            return Constants.isLetterCode(mCode) ? StringUtils.newSingleCodePointString(mCode)
+                    : Constants.printableCode(mCode);
+        }
+    }
+
+    /**
+     * This class represents a string output text.
+     */
+    private static class Text extends ExpectedKeyOutput {
+        private final String mText;
+
+        Text(final String text) { mText = text; }
+
+        @Override
+        ExpectedKeyOutput toUpperCase(final Locale locale) {
+            return newInstance(mText.toUpperCase(locale));
+        }
+
+        @Override
+        boolean equalsTo(final String text) {
+            return text.equals(text);
+        }
+
+        @Override
+        boolean equalsTo(final Key key) {
+            return key.getCode() == Constants.CODE_OUTPUT_TEXT
+                    && mText.equals(key.getOutputText());
+        }
+
+        @Override
+        boolean equalsTo(final MoreKeySpec moreKeySpec) {
+            return moreKeySpec.mCode == Constants.CODE_OUTPUT_TEXT
+                    && mText.equals(moreKeySpec.mOutputText);
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyOutput output) {
+            return (output instanceof Text) && mText == ((Text)output).mText;
+        }
+
+        @Override
+        public String toString() {
+            return mText;
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java
new file mode 100644
index 0000000..0a0da32
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.expected;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
+
+import java.util.Locale;
+
+/**
+ * This class represents an expected visual outlook of a key.
+ *
+ * There are two types of expected visual, an integer icon id and a string label.
+ */
+abstract class ExpectedKeyVisual {
+    static ExpectedKeyVisual newInstance(final String label) {
+        return new Label(label);
+    }
+
+    static ExpectedKeyVisual newInstance(final int iconId) {
+        return new Icon(iconId);
+    }
+
+    abstract ExpectedKeyVisual toUpperCase(final Locale locale);
+    abstract boolean equalsTo(final String text);
+    abstract boolean equalsTo(final Key key);
+    abstract boolean equalsTo(final MoreKeySpec moreKeySpec);
+    abstract boolean equalsTo(final ExpectedKeyOutput output);
+    abstract boolean equalsTo(final ExpectedKeyVisual visual);
+
+    /**
+     * This class represents an integer icon id.
+     */
+    private static class Icon extends ExpectedKeyVisual {
+        private final int mIconId;
+
+        Icon(final int iconId) {
+            mIconId = iconId;
+        }
+
+        @Override
+        ExpectedKeyVisual toUpperCase(final Locale locale) {
+            return this;
+        }
+
+        @Override
+        boolean equalsTo(final String text) {
+            return false;
+        }
+
+        @Override
+        boolean equalsTo(final Key key) {
+            return mIconId == key.getIconId();
+        }
+
+        @Override
+        boolean equalsTo(final MoreKeySpec moreKeySpec) {
+            return mIconId == moreKeySpec.mIconId;
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyOutput output) {
+            return false;
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyVisual visual) {
+            return (visual instanceof Icon) && mIconId == ((Icon)visual).mIconId;
+        }
+
+        @Override
+        public String toString() {
+            return KeyboardIconsSet.getIconName(mIconId);
+        }
+    }
+
+    /**
+     * This class represents a string label.
+     */
+    private static class Label extends ExpectedKeyVisual {
+        private final String mLabel;
+
+        Label(final String label) { mLabel = label; }
+
+        @Override
+        ExpectedKeyVisual toUpperCase(final Locale locale) {
+            return new Label(mLabel.toUpperCase(locale));
+        }
+
+        @Override
+        boolean equalsTo(final String text) {
+            return mLabel.equals(text);
+        }
+
+        @Override
+        boolean equalsTo(final Key key) {
+            return mLabel.equals(key.getLabel());
+        }
+
+        @Override
+        boolean equalsTo(final MoreKeySpec moreKeySpec) {
+            return mLabel.equals(moreKeySpec.mLabel);
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyOutput output) {
+            return output.equalsTo(mLabel);
+        }
+
+        @Override
+        boolean equalsTo(final ExpectedKeyVisual visual) {
+            return (visual instanceof Label) && mLabel.equals(((Label)visual).mLabel);
+        }
+
+        @Override
+        public String toString() {
+            return mLabel;
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
new file mode 100644
index 0000000..61288f0
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.expected;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * This class builds an expected keyboard for unit test.
+ */
+public final class ExpectedKeyboardBuilder extends AbstractKeyboardBuilder<ExpectedKey> {
+    public ExpectedKeyboardBuilder(final int ... dimensions) {
+        super(dimensions);
+    }
+
+    public ExpectedKeyboardBuilder(final ExpectedKey[][] rows) {
+        super(rows);
+    }
+
+    @Override
+    protected ExpectedKey defaultElement() {
+        return ExpectedKey.EMPTY_KEY;
+    }
+
+    @Override
+    ExpectedKey[] newArray(final int size) {
+        return new ExpectedKey[size];
+    }
+
+    @Override
+    ExpectedKey[][] newArrayOfArray(final int size) {
+        return new ExpectedKey[size][];
+    }
+
+    @Override
+    public ExpectedKey[][] build() {
+        return super.build();
+    }
+
+    // A replacement job to be performed.
+    interface ReplaceJob {
+        // Returns a {@link ExpectedKey} object to replace.
+        ExpectedKey replace(final ExpectedKey oldKey);
+        // Return true if replacing should be stopped at first occurrence.
+        boolean stopAtFirstOccurrence();
+    }
+
+    // Replace key(s) that has the specified visual.
+    private void replaceKeyOf(final ExpectedKeyVisual visual, final ReplaceJob job) {
+        int replacedCount = 0;
+        final ExpectedKey[][] rows = build();
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            final ExpectedKey[] keys = rows[rowIndex];
+            for (int columnIndex = 0; columnIndex < keys.length; columnIndex++) {
+                if (keys[columnIndex].getVisual().equalsTo(visual)) {
+                    keys[columnIndex] = job.replace(keys[columnIndex]);
+                    replacedCount++;
+                    if (job.stopAtFirstOccurrence()) {
+                        return;
+                    }
+                }
+            }
+        }
+        if (replacedCount == 0) {
+            throw new RuntimeException(
+                    "Can't find key that has visual: " + visual + " in\n" + toString(rows));
+        }
+    }
+
+    /**
+     * Set the row with specified keys that have specified labels.
+     * @param row the row number to set keys.
+     * @param labels the label texts of the keys.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setLabelsOfRow(final int row, final String ... labels) {
+        final ExpectedKey[] keys = new ExpectedKey[labels.length];
+        for (int columnIndex = 0; columnIndex < labels.length; columnIndex++) {
+            keys[columnIndex] = ExpectedKey.newInstance(labels[columnIndex]);
+        }
+        setRowAt(row, keys);
+        return this;
+    }
+
+    /**
+     * Set the "more keys" of the key that has the specified label.
+     * @param label the label of the key to set the "more keys".
+     * @param moreKeys the array of labels of the "more keys" to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setMoreKeysOf(final String label, final String ... moreKeys) {
+        final ExpectedKey[] expectedMoreKeys = new ExpectedKey[moreKeys.length];
+        for (int index = 0; index < moreKeys.length; index++) {
+            expectedMoreKeys[index] = ExpectedKey.newInstance(moreKeys[index]);
+        }
+        setMoreKeysOf(label, expectedMoreKeys);
+        return this;
+    }
+
+    /**
+     * Set the "more keys" of the key that has the specified label.
+     * @param label the label of the key to set the "more keys".
+     * @param moreKeys the array of "more key" to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setMoreKeysOf(final String label,
+            final ExpectedKey ... moreKeys) {
+        setMoreKeysOf(ExpectedKeyVisual.newInstance(label), moreKeys);
+        return this;
+    }
+
+    /**
+     * Set the "more keys" of the key that has the specified icon.
+     * @param iconId the icon id of the key to set the "more keys".
+     * @param moreKeys the array of "more key" to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setMoreKeysOf(final int iconId, final ExpectedKey ... moreKeys) {
+        setMoreKeysOf(ExpectedKeyVisual.newInstance(iconId), moreKeys);
+        return this;
+    }
+
+    private void setMoreKeysOf(final ExpectedKeyVisual visual, final ExpectedKey[] moreKeys) {
+        replaceKeyOf(visual, new ReplaceJob() {
+            @Override
+            public ExpectedKey replace(final ExpectedKey oldKey) {
+                return ExpectedKey.newInstance(oldKey.getVisual(), oldKey.getOutput(), moreKeys);
+            }
+            @Override
+            public boolean stopAtFirstOccurrence() {
+                return true;
+            }
+        });
+    }
+
+    /**
+     * Insert the keys at specified position.
+     * @param row the row number to insert the <code>keys</code>.
+     * @param column the column number to insert the <code>keys</code>.
+     * @param keys the array of keys to insert at <code>row,column</code>.
+     * @return this builder.
+     * @throws {@link RuntimeException} if <code>row</code> or <code>column</code> is illegal.
+     */
+    public ExpectedKeyboardBuilder insertKeysAtRow(final int row, final int column,
+            final ExpectedKey ... keys) {
+        for (int index = 0; index < keys.length; index++) {
+            setElementAt(row, column + index, keys[index], true /* insert */);
+        }
+        return this;
+    }
+
+    /**
+     * Add the keys on the left most of the row.
+     * @param row the row number to add the <code>keys</code>.
+     * @param keys the array of keys to add on the left most of the row.
+     * @return this builder.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    public ExpectedKeyboardBuilder addKeysOnTheLeftOfRow(final int row,
+            final ExpectedKey ... keys) {
+        // Keys should be inserted from the last to preserve the order.
+        for (int index = keys.length - 1; index >= 0; index--) {
+            setElementAt(row, 1, keys[index], true /* insert */);
+        }
+        return this;
+    }
+
+    /**
+     * Add the keys on the right most of the row.
+     * @param row the row number to add the <code>keys</code>.
+     * @param keys the array of keys to add on the right most of the row.
+     * @return this builder.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    public ExpectedKeyboardBuilder addKeysOnTheRightOfRow(final int row,
+            final ExpectedKey ... keys) {
+        final int rightEnd = getRowAt(row).length + 1;
+        insertKeysAtRow(row, rightEnd, keys);
+        return this;
+    }
+
+    /**
+     * Replace the most top-left key that has the specified label with the new key.
+     * @param label the label of the key to set <code>newKey</code>.
+     * @param newKey the key to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder replaceKeyOfLabel(final String label, final ExpectedKey newKey) {
+        final ExpectedKeyVisual visual = ExpectedKeyVisual.newInstance(label);
+        replaceKeyOf(visual, new ReplaceJob() {
+            @Override
+            public ExpectedKey replace(final ExpectedKey oldKey) {
+                return newKey;
+            }
+            @Override
+            public boolean stopAtFirstOccurrence() {
+                return true;
+            }
+        });
+        return this;
+    }
+
+    /**
+     * Replace the all specified keys  with the new key.
+     * @param key the key to be replaced by <code>newKey</code>.
+     * @param newKey the key to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder replaceKeyOfAll(final ExpectedKey key,
+            final ExpectedKey newKey) {
+        replaceKeyOf(key.getVisual(), new ReplaceJob() {
+            @Override
+            public ExpectedKey replace(final ExpectedKey oldKey) {
+                return newKey;
+            }
+            @Override
+            public boolean stopAtFirstOccurrence() {
+                return false;
+            }
+        });
+        return this;
+    }
+
+    /**
+     * Returns new keyboard instance that has upper case keys of the specified keyboard.
+     * @param rows the lower case keyboard.
+     * @param locale the locale used to convert cases.
+     * @return the upper case keyboard.
+     */
+    public static ExpectedKey[][] toUpperCase(final ExpectedKey[][] rows, final Locale locale) {
+        final ExpectedKey[][] upperCaseRows = new ExpectedKey[rows.length][];
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            final ExpectedKey[] lowerCaseKeys = rows[rowIndex];
+            final ExpectedKey[] upperCaseKeys = new ExpectedKey[lowerCaseKeys.length];
+            for (int columnIndex = 0; columnIndex < lowerCaseKeys.length; columnIndex++) {
+                upperCaseKeys[columnIndex] = lowerCaseKeys[columnIndex].toUpperCase(locale);
+            }
+            upperCaseRows[rowIndex] = upperCaseKeys;
+        }
+        return upperCaseRows;
+    }
+
+    /**
+     * Convert the keyboard to human readable string.
+     * @param rows the keyboard to be converted to string.
+     * @return the human readable representation of <code>rows</code>.
+     */
+    public static String toString(final ExpectedKey[][] rows) {
+        final StringBuilder sb = new StringBuilder();
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            if (rowIndex > 0) {
+                sb.append("\n");
+            }
+            sb.append(Arrays.toString(rows[rowIndex]));
+        }
+        return sb.toString();
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java
new file mode 100644
index 0000000..1aeb8c0
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/LayoutBase.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.expected;
+
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.latin.Constants;
+
+/**
+ * Base class to create an expected keyboard for unit test.
+ */
+public class LayoutBase {
+    // Those helper methods have a lower case name to be readable when defining expected keyboard
+    // layouts.
+
+    // Helper method to create {@link ExpectedKey} object that has the label.
+    public static ExpectedKey key(final String label, final ExpectedKey ... moreKeys) {
+        return ExpectedKey.newInstance(label, moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object that has the label and the output text.
+    public static ExpectedKey key(final String label, final String outputText,
+            final ExpectedKey ... moreKeys) {
+        return ExpectedKey.newInstance(label, outputText, moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object that has the label and the output code.
+    public static ExpectedKey key(final String label, final int code,
+            final ExpectedKey ... moreKeys) {
+        return ExpectedKey.newInstance(label, code, moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object that has the icon and the output code.
+    public static ExpectedKey key(final int iconId, final int code,
+            final ExpectedKey ... moreKeys) {
+        return ExpectedKey.newInstance(iconId, code, moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object that has new "more keys".
+    public static ExpectedKey key(final ExpectedKey key, final ExpectedKey ... moreKeys) {
+        return ExpectedKey.newInstance(key.getVisual(), key.getOutput(), moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object for "more key" that has the label.
+    public static ExpectedKey moreKey(final String label) {
+        return ExpectedKey.newInstance(label);
+    }
+
+    // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the
+    // output text.
+    public static ExpectedKey moreKey(final String label, final String outputText) {
+        return ExpectedKey.newInstance(label, outputText);
+    }
+
+    // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the
+    // output code.
+    public static ExpectedKey moreKey(final String label, final int code) {
+        return ExpectedKey.newInstance(label, code);
+    }
+
+    // Icon ids.
+    private static final int ICON_SHIFT = KeyboardIconsSet.getIconId("shift_key");
+    private static final int ICON_DELETE = KeyboardIconsSet.getIconId("delete_key");
+    private static final int ICON_SETTINGS = KeyboardIconsSet.getIconId("settings_key");
+    private static final int ICON_ENTER = KeyboardIconsSet.getIconId("enter_key");
+    private static final int ICON_EMOJI = KeyboardIconsSet.getIconId("emoji_key");
+
+    // Functional keys.
+    public static final ExpectedKey CAPSLOCK_MORE_KEY = key(" ", Constants.CODE_CAPSLOCK);
+    public static final ExpectedKey SHIFT_KEY = key(ICON_SHIFT, Constants.CODE_SHIFT);
+    public static final ExpectedKey DELETE_KEY = key(ICON_DELETE, Constants.CODE_DELETE);
+    public static final ExpectedKey SYMBOLS_KEY = key("?123", Constants.CODE_SWITCH_ALPHA_SYMBOL);
+    public static final ExpectedKey SETTINGS_KEY = key(ICON_SETTINGS, Constants.CODE_SETTINGS);
+    public static final ExpectedKey ENTER_KEY = key(ICON_ENTER, Constants.CODE_ENTER);
+    public static final ExpectedKey EMOJI_KEY = key(ICON_EMOJI, Constants.CODE_EMOJI);
+
+    // Punctuation more keys for phone form factor.
+    public static final String[] PHONE_PUNCTUATION_MORE_KEYS = {
+            ";", "/", "(", ")", "#", "!", ",", "?",
+            "&", "%", "+", "\"", "-", ":", "'", "@"
+    };
+
+    // Punctuation more keys for tablet form factor.
+    public static final String[] TABLET_PUNCTUATION_MORE_KEYS = {
+            ";", "/", "(", ")", "#", "'", ",",
+            "&", "%", "+", "\"", "-", ":", "@"
+    };
+
+    private static ExpectedKeyboardBuilder toPhoneAlphabet(final ExpectedKeyboardBuilder builder) {
+        return builder
+                .addKeysOnTheLeftOfRow(3, key(SHIFT_KEY, CAPSLOCK_MORE_KEY))
+                .addKeysOnTheRightOfRow(3, DELETE_KEY)
+                .setLabelsOfRow(4, ",", " ", ".")
+                .setMoreKeysOf(",", SETTINGS_KEY)
+                .setMoreKeysOf(".", PHONE_PUNCTUATION_MORE_KEYS)
+                .addKeysOnTheLeftOfRow(4, SYMBOLS_KEY)
+                .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_KEY));
+    }
+
+    // Helper method to create alphabet layout for tablet by adding special function keys except
+    // shift key.
+    public static ExpectedKeyboardBuilder toTabletAlphabetWithoutShiftKeys(
+            final ExpectedKeyboardBuilder builder) {
+        return builder
+                // U+00BF: "¿" INVERTED QUESTION MARK
+                // U+00A1: "¡" INVERTED EXCLAMATION MARK
+                .addKeysOnTheRightOfRow(3,
+                        key("!", moreKey("\u00A1")), key("?", moreKey("\u00BF")))
+                .addKeysOnTheRightOfRow(1, DELETE_KEY)
+                .addKeysOnTheRightOfRow(2, ENTER_KEY)
+                .setLabelsOfRow(4, "/", " ", ",", ".")
+                .setMoreKeysOf(".", TABLET_PUNCTUATION_MORE_KEYS)
+                .addKeysOnTheLeftOfRow(4, SYMBOLS_KEY, SETTINGS_KEY)
+                .addKeysOnTheRightOfRow(4, EMOJI_KEY);
+    }
+
+    // Helper method to create alphabet layout by adding special function keys.
+    public static ExpectedKey[][] toCommonAlphabet(final ExpectedKey[][] common,
+            final boolean isPhone) {
+        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(common);
+        if (isPhone) {
+            toPhoneAlphabet(builder);
+        } else {
+            toTabletAlphabetWithoutShiftKeys(builder);
+            builder.addKeysOnTheLeftOfRow(3, key(SHIFT_KEY, CAPSLOCK_MORE_KEY))
+                    .addKeysOnTheRightOfRow(3, key(SHIFT_KEY, CAPSLOCK_MORE_KEY));
+        }
+        return builder.build();
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
new file mode 100644
index 0000000..427e7de
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.tests;
+
+import android.util.Log;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
+import com.android.inputmethod.keyboard.KeyboardLayoutSetTestsBase;
+import com.android.inputmethod.keyboard.layout.AlphabetShifted;
+import com.android.inputmethod.keyboard.layout.Symbols;
+import com.android.inputmethod.keyboard.layout.SymbolsShifted;
+import com.android.inputmethod.keyboard.layout.expected.ActualKeyboardBuilder;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
+import com.android.inputmethod.keyboard.layout.expected.LayoutBase;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Base class for keyboard layout unit test.
+ */
+abstract class LayoutTestsBase extends KeyboardLayoutSetTestsBase {
+    private InputMethodSubtype mSubtype;
+    private String mLogTag;
+    private KeyboardLayoutSet mKeyboardLayoutSet;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mSubtype = getSubtype(getTestLocale(), getTestKeyboardLayout());
+        mLogTag = SubtypeLocaleUtils.getSubtypeNameForLogging(mSubtype) + "/"
+                + (isPhone() ? "phone" : "tablet");
+        mKeyboardLayoutSet = createKeyboardLayoutSet(mSubtype, null /* editorInfo */);
+    }
+
+    // Those helper methods have a lower case name to be readable when defining expected keyboard
+    // layouts.
+
+    // Helper method to create {@link ExpectedKey} object that has the label.
+    static ExpectedKey key(final String label, final ExpectedKey ... moreKeys) {
+        return LayoutBase.key(label, moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object that has the label and the output text.
+    static ExpectedKey key(final String label, final String outputText,
+            final ExpectedKey ... moreKeys) {
+        return LayoutBase.key(label, outputText, moreKeys);
+    }
+
+    // Helper method to create {@link ExpectedKey} object for "more key" that has the label.
+    static ExpectedKey moreKey(final String label) {
+        return LayoutBase.moreKey(label);
+    }
+
+    // Helper method to create {@link ExpectedKey} object for "more key" that has the label and the
+    // output text.
+    static ExpectedKey moreKey(final String label, final String outputText) {
+        return LayoutBase.moreKey(label, outputText);
+    }
+
+    // Locale for testing subtype.
+    abstract Locale getTestLocale();
+
+    // Keyboard layout name for testing subtype.
+    abstract String getTestKeyboardLayout();
+
+    // Alphabet keyboard for testing subtype.
+    abstract ExpectedKey[][] getAlphabet(final boolean isPhone);
+
+    // Alphabet automatic shifted keyboard for testing subtype.
+    ExpectedKey[][] getAlphabetAutomaticShifted(final boolean isPhone) {
+        return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale());
+    }
+
+    // Alphabet manual shifted  keyboard for testing subtype.
+    ExpectedKey[][] getAlphabetManualShifted(final boolean isPhone) {
+        return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale());
+    }
+
+    // Alphabet shift locked keyboard for testing subtype.
+    ExpectedKey[][] getAlphabetShiftLocked(final boolean isPhone) {
+        return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale());
+    }
+
+    // Alphabet shift lock shifted keyboard for testing subtype.
+    ExpectedKey[][] getAlphabetShiftLockShifted(final boolean isPhone) {
+        return AlphabetShifted.getAlphabet(getAlphabet(isPhone), getTestLocale());
+    }
+
+    // Symbols keyboard for testing subtype.
+    ExpectedKey[][] getSymbols(final boolean isPhone) {
+        return Symbols.getSymbols(isPhone);
+    }
+
+    // Symbols shifted keyboard for testing subtype.
+    ExpectedKey[][] getSymbolsShifted(final boolean isPhone) {
+        return SymbolsShifted.getSymbolsShifted(isPhone);
+    }
+
+    // TODO: Add phone, phone symbols, number, number password layout tests.
+
+    public final void testAlphabet() {
+        final int elementId = KeyboardId.ELEMENT_ALPHABET;
+        doKeyboardTests(elementId, getAlphabet(isPhone()));
+    }
+
+    public final void testAlphabetAutomaticShifted() {
+        final int elementId = KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED;
+        doKeyboardTests(elementId, getAlphabetAutomaticShifted(isPhone()));
+    }
+
+    public final void testAlphabetManualShifted() {
+        final int elementId = KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED;
+        doKeyboardTests(elementId, getAlphabetManualShifted(isPhone()));
+    }
+
+    public final void testAlphabetShiftLocked() {
+        final int elementId = KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED;
+        doKeyboardTests(elementId, getAlphabetShiftLocked(isPhone()));
+    }
+
+    public final void testAlphabetShiftLockShifted() {
+        final int elementId = KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED;
+        doKeyboardTests(elementId, getAlphabetShiftLockShifted(isPhone()));
+    }
+
+    public final void testSymbols() {
+        final int elementId = KeyboardId.ELEMENT_SYMBOLS;
+        doKeyboardTests(elementId, getSymbols(isPhone()));
+    }
+
+    public final void testSymbolsShifted() {
+        final int elementId = KeyboardId.ELEMENT_SYMBOLS_SHIFTED;
+        doKeyboardTests(elementId, getSymbolsShifted(isPhone()));
+    }
+
+    // Comparing expected keyboard and actual keyboard.
+    private void doKeyboardTests(final int elementId, final ExpectedKey[][] expectedKeyboard) {
+        // Skip test if no keyboard is defined.
+        if (expectedKeyboard == null) {
+            return;
+        }
+        final String tag = mLogTag + "/" + KeyboardId.elementIdToName(elementId);
+        // Create actual keyboard object.
+        final Keyboard keyboard = mKeyboardLayoutSet.getKeyboard(elementId);
+        // Create actual keyboard to be compared with the expected keyboard.
+        final Key[][] actualKeyboard = ActualKeyboardBuilder.buildKeyboard(keyboard.getKeys());
+
+        // Dump human readable definition of expected/actual keyboards.
+        Log.d(tag, "expected=\n" + ExpectedKeyboardBuilder.toString(expectedKeyboard));
+        Log.d(tag, "actual  =\n" + ActualKeyboardBuilder.toString(actualKeyboard));
+        // Test both keyboards have the same number of rows.
+        assertEquals(tag + " labels"
+                + "\nexpected=" + Arrays.deepToString(expectedKeyboard)
+                + "\nactual  =" + ActualKeyboardBuilder.toString(actualKeyboard),
+                expectedKeyboard.length, actualKeyboard.length);
+        for (int r = 0; r < actualKeyboard.length; r++) {
+            final int row = r + 1;
+            // Test both keyboards' rows have the same number of columns.
+            assertEquals(tag + " labels row=" + row
+                    + "\nexpected=" + Arrays.toString(expectedKeyboard[r])
+                    + "\nactual  =" + ActualKeyboardBuilder.toString(actualKeyboard[r]),
+                    expectedKeyboard[r].length, actualKeyboard[r].length);
+            for (int c = 0; c < actualKeyboard[r].length; c++) {
+                final int column = c + 1;
+                final Key actualKey = actualKeyboard[r][c];
+                final ExpectedKey expectedKey = expectedKeyboard[r][c];
+                // Test both keyboards' keys have the same visual outlook and key output.
+                assertTrue(tag + " labels row,column=" + row + "," + column
+                        + "\nexpected=" + expectedKey
+                        + "\nactual  =" + ActualKeyboardBuilder.toString(actualKey),
+                        expectedKey.equalsTo(actualKey));
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java
new file mode 100644
index 0000000..0792a57
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishUS.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.tests;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.inputmethod.keyboard.layout.Qwerty;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
+
+import java.util.Locale;
+
+/**
+ * en_US: English (United States)/qwerty
+ */
+@SmallTest
+public final class TestsEnglishUS extends LayoutTestsBase {
+    @Override
+    Locale getTestLocale() {
+        return new Locale("en", "US");
+    }
+
+    @Override
+    String getTestKeyboardLayout() {
+        return "qwerty";
+    }
+
+    @Override
+    ExpectedKey[][] getAlphabet(final boolean isPhone) {
+        final ExpectedKey[][] keyboard = Qwerty.getAlphabet(isPhone);
+        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(keyboard);
+        setAccentedLetters(builder);
+        return builder.build();
+    }
+
+    static ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) {
+        return builder
+                // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+                // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+                // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+                // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+                // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+                .setMoreKeysOf("e", "3", "\u00E8", "\u00E9", "\u00EA", "\u00EB", "\u0113")
+                // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+                // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+                // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+                // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+                // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+                .setMoreKeysOf("u", "7", "\u00FB", "\u00FC", "\u00F9", "\u00FA", "\u016B")
+                // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+                // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+                // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+                // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+                // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+                .setMoreKeysOf("i", "8", "\u00EE", "\u00EF", "\u00ED", "\u012B", "\u00EC")
+                // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+                // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+                // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+                // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+                // U+0153: "œ" LATIN SMALL LIGATURE OE
+                // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+                // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+                // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+                .setMoreKeysOf("o",
+                        "9", "\u00F4", "\u00F6", "\u00F2", "\u00F3", "\u0153", "\u00F8", "\u014D",
+                        "\u00F5")
+                // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+                // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+                // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+                // U+00E6: "æ" LATIN SMALL LETTER AE
+                // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+                // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+                // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+                .setMoreKeysOf("a",
+                        "\u00E0", "\u00E1", "\u00E2", "\u00E4", "\u00E6", "\u00E3", "\u00E5",
+                        "\u0101")
+                // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+                .setMoreKeysOf("s", "\u00DF")
+                // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+                .setMoreKeysOf("c", "\u00E7")
+                // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+                .setMoreKeysOf("n", "\u00F1");
+    }
+}
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index b1dd7f6..c0a5562 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -13,6 +13,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# HACK: Temporarily disable host tool build on Mac until the build system is ready for C++11.
+LATINIME_HOST_OSNAME := $(shell uname -s)
+ifneq ($(LATINIME_HOST_OSNAME), Darwin) # TODO: Remove this
+
 LATINIME_DICTTOOL_AOSP_LOCAL_PATH := $(call my-dir)
 LOCAL_PATH := $(LATINIME_DICTTOOL_AOSP_LOCAL_PATH)
 LATINIME_HOST_NATIVE_LIBNAME := liblatinime-aosp-dicttool-host
@@ -74,10 +78,14 @@
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LATINIME_HOST_NATIVE_LIBNAME)
 LOCAL_JAR_MANIFEST := etc/manifest.txt
 LOCAL_MODULE := dicttool_aosp
+LOCAL_IS_HOST_MODULE := true
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 include $(LOCAL_PATH)/etc/Android.mk
 
+endif # Darwin - TODO: Remove this
+
 # Clear our private variables
 LATINIME_DICTTOOL_AOSP_LOCAL_PATH :=
 LATINIME_LOCAL_DIR :=
+LATINIME_HOST_OSNAME :=
diff --git a/tools/dicttool/NativeLib.mk b/tools/dicttool/NativeLib.mk
index 05e5841..95f767d 100644
--- a/tools/dicttool/NativeLib.mk
+++ b/tools/dicttool/NativeLib.mk
@@ -13,6 +13,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# HACK: Temporarily disable host tool build on Mac until the build system is ready for C++11.
+LATINIME_HOST_OSNAME := $(shell uname -s)
+ifneq ($(LATINIME_HOST_OSNAME), Darwin) # TODO: Remove this
+
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
@@ -33,6 +37,10 @@
 LOCAL_CFLAGS += -DHOST_TOOL -fPIC -Wno-deprecated
 LOCAL_NO_DEFAULT_COMPILER_FLAGS := true
 
+# For C++11
+# TODO: Change this to -std=c++11
+LOCAL_CFLAGS += -std=gnu++0x
+
 LATINIME_NATIVE_JNI_DIR := $(LATINIME_DIR_RELATIVE_TO_DICTTOOL)/native/jni
 LATINIME_NATIVE_SRC_DIR := $(LATINIME_DIR_RELATIVE_TO_DICTTOOL)/native/jni/src
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(LATINIME_NATIVE_SRC_DIR)
@@ -45,9 +53,13 @@
     $(addprefix $(LATINIME_NATIVE_SRC_DIR)/, $(LATIN_IME_CORE_SRC_FILES))
 
 LOCAL_MODULE := $(LATINIME_HOST_NATIVE_LIBNAME)
+LOCAL_IS_HOST_MODULE := true
 
 include $(BUILD_HOST_SHARED_LIBRARY)
 
+endif # Darwin - TODO: Remove this
+
 # Clear our private variables
 include $(LOCAL_PATH)/$(LATINIME_NATIVE_JNI_DIR)/CleanupNativeFileList.mk
 LATINIME_DIR_RELATIVE_TO_DICTTOOL := ../..
+LATINIME_HOST_OSNAME :=
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>