Merge "Add SparseTableTest."
diff --git a/java/res/values/config-common.xml b/java/res/values/config-common.xml
index 063fbfb..36fd30a 100644
--- a/java/res/values/config-common.xml
+++ b/java/res/values/config-common.xml
@@ -24,7 +24,7 @@
          at input history to suggest a hopefully helpful suggestions for the next word? -->
     <bool name="config_default_next_word_prediction">true</bool>
 
-    <integer name="config_delay_update_shift_state">100</integer>
+    <integer name="config_delay_in_milliseconds_to_update_shift_state">100</integer>
     <integer name="config_double_space_period_timeout">1100</integer>
 
     <integer name="config_key_repeat_start_timeout">400</integer>
@@ -107,8 +107,8 @@
     <!-- Common suggestion strip configuration. -->
     <integer name="config_suggestions_count_in_strip">3</integer>
     <fraction name="config_center_suggestion_percentile">36%</fraction>
-    <integer name="config_delay_update_suggestions">100</integer>
-    <integer name="config_delay_update_old_suggestions">300</integer>
+    <integer name="config_delay_in_milliseconds_to_update_suggestions">100</integer>
+    <integer name="config_delay_in_milliseconds_to_update_old_suggestions">300</integer>
 
     <!-- Common more suggestions configuraion. -->
     <dimen name="config_more_suggestions_key_horizontal_padding">12dp</dimen>
diff --git a/java/res/xml-sw600dp/key_comma.xml b/java/res/xml-sw600dp/key_comma.xml
index 67199e2..e616a8d 100644
--- a/java/res/xml-sw600dp/key_comma.xml
+++ b/java/res/xml-sw600dp/key_comma.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2014, The Android Open Source Project
+** 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.
@@ -21,27 +21,36 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- The table comma key which may have settings as popup key. -->
-    <!-- Kept as a separate file for cleaner overriding by an overlay.  -->
-    <key-style
-        latin:styleName="baseTabletCommaKeyStyle"
-        latin:keySpec="!text/keyspec_tablet_comma"
-        latin:keyHintLabel="!text/keyhintlabel_tablet_comma"
-        latin:keyLabelFlags="hasPopupHint"
-        latin:parentStyle="hasShiftedLetterHintStyle" />
     <switch>
         <case
-            latin:clobberSettingsKey="true"
+            latin:mode="url"
         >
             <Key
-                latin:moreKeys="!text/morekeys_tablet_comma"
-                latin:keyStyle="baseTabletCommaKeyStyle" />
+                latin:keySpec="/"
+                latin:keyStyle="settingsMoreKeysStyle" />
         </case>
-        <!-- clobberSettingsKey="false" -->
+        <case
+            latin:mode="email"
+        >
+            <Key
+                latin:keySpec="\@"
+                latin:keyStyle="settingsMoreKeysStyle" />
+        </case>
+        <case
+            latin:keyboardLayoutSet="dvorak"
+        >
+            <Key
+                latin:keySpec="!"
+                latin:moreKeys="!text/morekeys_exclamation,%"
+                latin:keyStyle="settingsMoreKeysStyle" />
+        </case>
         <default>
             <Key
-                latin:moreKeys="!text/morekeys_tablet_comma,!text/keyspec_settings"
-                latin:keyStyle="baseTabletCommaKeyStyle" />
+                latin:keySpec="!text/keyspec_tablet_comma"
+                latin:moreKeys="!text/morekeys_tablet_comma,%"
+                latin:keyHintLabel="!text/keyhintlabel_tablet_comma"
+                latin:keyLabelFlags="hasPopupHint"
+                latin:keyStyle="settingsMoreKeysStyle" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/key_f1.xml b/java/res/xml-sw600dp/key_f1.xml
deleted file mode 100644
index ba78a64..0000000
--- a/java/res/xml-sw600dp/key_f1.xml
+++ /dev/null
@@ -1,36 +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:mode="email"
-        >
-            <Key
-                latin:keySpec="\@" />
-        </case>
-        <default>
-            <Key
-                latin:keySpec="/" />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml-sw600dp/key_period.xml b/java/res/xml-sw600dp/key_period.xml
index d2909d8..df1daf7 100644
--- a/java/res/xml-sw600dp/key_period.xml
+++ b/java/res/xml-sw600dp/key_period.xml
@@ -35,6 +35,13 @@
                 latin:moreKeys="!autoColumnOrder!8,\\,,.,',#,),(,/,;,@,:,-,&quot;,+,\\%,&amp;"
                 latin:backgroundType="functional" />
         </case>
+        <case
+            latin:keyboardLayoutSet="dvorak"
+        >
+            <Key
+                latin:keySpec="\?"
+                latin:moreKeys="!text/morekeys_tablet_period,!text/morekeys_question" />
+        </case>
         <default>
             <Key
                 latin:keySpec="!text/keyspec_tablet_period"
diff --git a/java/res/xml-sw600dp/key_question_exclamation.xml b/java/res/xml-sw600dp/key_question_exclamation.xml
deleted file mode 100644
index edee5c5..0000000
--- a/java/res/xml-sw600dp/key_question_exclamation.xml
+++ /dev/null
@@ -1,39 +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:mode="email|url"
-        >
-            <Key
-                latin:keySpec="-" />
-        </case>
-        <default>
-            <Key
-                latin:keySpec="\?"
-                latin:keyHintLabel="!"
-                latin:moreKeys="!"
-                latin:keyStyle="hasShiftedLetterHintStyle" />
-        </default>
-    </switch>
-</merge>
diff --git a/java/res/xml-sw600dp/key_space_5kw.xml b/java/res/xml-sw600dp/key_space_7kw.xml
similarity index 93%
rename from java/res/xml-sw600dp/key_space_5kw.xml
rename to java/res/xml-sw600dp/key_space_7kw.xml
index 8302184..3311f81 100644
--- a/java/res/xml-sw600dp/key_space_5kw.xml
+++ b/java/res/xml-sw600dp/key_space_7kw.xml
@@ -34,7 +34,7 @@
                 latin:keyStyle="languageSwitchKeyStyle" />
             <Key
                 latin:keyStyle="spaceKeyStyle"
-                latin:keyWidth="27.0%p" />
+                latin:keyWidth="45.0%p" />
             <Key
                 latin:keyStyle="zwnjKeyStyle" />
         </case>
@@ -44,7 +44,7 @@
         >
             <Key
                 latin:keyStyle="spaceKeyStyle"
-                latin:keyWidth="36.0%p" />
+                latin:keyWidth="54.0%p" />
             <Key
                 latin:keyStyle="zwnjKeyStyle" />
         </case>
@@ -55,13 +55,13 @@
                 latin:keyStyle="languageSwitchKeyStyle" />
             <Key
                 latin:keyStyle="spaceKeyStyle"
-                latin:keyWidth="36.0%p" />
+                latin:keyWidth="54.0%p" />
         </case>
         <!-- languageSwitchKeyEnabled="false" -->
         <default>
             <Key
                 latin:keyStyle="spaceKeyStyle"
-                latin:keyWidth="45.0%p" />
+                latin:keyWidth="63.0%p" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/key_styles_common.xml b/java/res/xml-sw600dp/key_styles_common.xml
index 1b52b1e..c750a93 100644
--- a/java/res/xml-sw600dp/key_styles_common.xml
+++ b/java/res/xml-sw600dp/key_styles_common.xml
@@ -36,6 +36,9 @@
         </default>
     </switch>
     <!-- Base key style for the key which may have settings key as more keys. -->
+    <key-style
+        latin:styleName="baseSettingsMoreKeysStyle"
+        latin:parentStyle="hasShiftedLetterHintStyle" />
     <include
         latin:keyboardLayout="@xml/key_styles_settings" />
     <!-- Functional key styles -->
diff --git a/java/res/xml-sw600dp/row_dvorak4.xml b/java/res/xml-sw600dp/row_dvorak4.xml
deleted file mode 100644
index ab2b560..0000000
--- a/java/res/xml-sw600dp/row_dvorak4.xml
+++ /dev/null
@@ -1,49 +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"
->
-    <Row
-        latin:keyWidth="9.0%p"
-        latin:backgroundType="functional"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyWidth="10.0%p" />
-        <include
-            latin:keyboardLayout="@xml/key_settings" />
-        <Key
-            latin:keySpec="_"
-            latin:keyHintLabel="-"
-            latin:moreKeys="-"
-            latin:keyStyle="hasShiftedLetterHintStyle" />
-        <include
-            latin:keyXPos="28.0%p"
-            latin:keyboardLayout="@xml/key_space_5kw"
-            latin:backgroundType="normal" />
-        <include
-            latin:keyboardLayout="@xml/key_f1" />
-        <include
-            latin:keyboardLayout="@xml/key_question_exclamation" />
-        <include
-            latin:keyboardLayout="@xml/key_f2" />
-    </Row>
-</merge>
diff --git a/java/res/xml-sw600dp/row_pcqwerty5.xml b/java/res/xml-sw600dp/row_pcqwerty5.xml
index ac07f11..4dcae14 100644
--- a/java/res/xml-sw600dp/row_pcqwerty5.xml
+++ b/java/res/xml-sw600dp/row_pcqwerty5.xml
@@ -50,6 +50,6 @@
             latin:keyXPos="-9.0%p"
             latin:keyWidth="9.0%p"
             latin:backgroundType="functional"
-            latin:keyboardLayout="@xml/key_f2" />
+            latin:keyboardLayout="@xml/key_emoji" />
     </Row>
 </merge>
diff --git a/java/res/xml-sw600dp/row_qwerty4.xml b/java/res/xml-sw600dp/row_qwerty4.xml
index 0eb86f2..ed7150d 100644
--- a/java/res/xml-sw600dp/row_qwerty4.xml
+++ b/java/res/xml-sw600dp/row_qwerty4.xml
@@ -30,18 +30,14 @@
             latin:keyWidth="10.0%p" />
         <include
             latin:keyboardLayout="@xml/key_comma" />
-        <Key
-            latin:keySpec="_" />
         <!-- Space key. -->
         <include
-            latin:keyXPos="28.0%p"
-            latin:keyboardLayout="@xml/key_space_5kw"
+            latin:keyXPos="19.0%p"
+            latin:keyboardLayout="@xml/key_space_7kw"
             latin:backgroundType="normal" />
         <include
-            latin:keyboardLayout="@xml/key_f1" />
-        <include
             latin:keyboardLayout="@xml/key_period" />
         <include
-            latin:keyboardLayout="@xml/key_f2" />
+            latin:keyboardLayout="@xml/key_emoji" />
     </Row>
 </merge>
diff --git a/java/res/xml-sw600dp/rows_dvorak.xml b/java/res/xml-sw600dp/rows_dvorak.xml
index 8859267..c8f5e3a 100644
--- a/java/res/xml-sw600dp/rows_dvorak.xml
+++ b/java/res/xml-sw600dp/rows_dvorak.xml
@@ -53,6 +53,8 @@
             latin:keyStyle="shiftKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
+    <!-- Dvorak layout shares almost the same row with Qwerty layout.
+         The difference is defined in xml-sw600dp/row_qwerty4.xml. -->
     <include
-        latin:keyboardLayout="@xml/row_dvorak4" />
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml-sw600dp/rows_symbols.xml b/java/res/xml-sw600dp/rows_symbols.xml
index a915c33..05e7c68 100644
--- a/java/res/xml-sw600dp/rows_symbols.xml
+++ b/java/res/xml-sw600dp/rows_symbols.xml
@@ -70,6 +70,6 @@
         <include
             latin:keyboardLayout="@xml/row_symbols4" />
         <include
-            latin:keyboardLayout="@xml/key_f2" />
+            latin:keyboardLayout="@xml/key_emoji" />
     </Row>
 </merge>
diff --git a/java/res/xml-sw600dp/rows_symbols_shift.xml b/java/res/xml-sw600dp/rows_symbols_shift.xml
index 7ead4d5..70ac42e 100644
--- a/java/res/xml-sw600dp/rows_symbols_shift.xml
+++ b/java/res/xml-sw600dp/rows_symbols_shift.xml
@@ -72,6 +72,6 @@
         <include
             latin:keyboardLayout="@xml/row_symbols_shift4" />
         <include
-            latin:keyboardLayout="@xml/key_f2" />
+            latin:keyboardLayout="@xml/key_emoji" />
     </Row>
 </merge>
diff --git a/java/res/xml/key_f1.xml b/java/res/xml/key_comma.xml
similarity index 82%
rename from java/res/xml/key_f1.xml
rename to java/res/xml/key_comma.xml
index 7bd7385..cf919a8 100644
--- a/java/res/xml/key_f1.xml
+++ b/java/res/xml/key_comma.xml
@@ -23,6 +23,15 @@
 >
     <switch>
         <case
+            latin:keyboardLayoutSet="dvorak"
+        >
+            <Key
+                latin:keySpec="q"
+                latin:moreKeys="!text/morekeys_q,%"
+                latin:backgroundType="normal"
+                latin:keyStyle="settingsMoreKeysStyle" />
+        </case>
+        <case
             latin:mode="url"
         >
             <Key
diff --git a/java/res/xml/key_f2.xml b/java/res/xml/key_emoji.xml
similarity index 100%
rename from java/res/xml/key_f2.xml
rename to java/res/xml/key_emoji.xml
diff --git a/java/res/xml/key_period.xml b/java/res/xml/key_period.xml
index e1d4bbd..fc27c02 100644
--- a/java/res/xml/key_period.xml
+++ b/java/res/xml/key_period.xml
@@ -48,6 +48,14 @@
                 latin:moreKeys="!text/morekeys_punctuation"
                 latin:backgroundType="functional" />
         </case>
+        <case
+            latin:keyboardLayoutSet="dvorak"
+        >
+            <Key
+                latin:keySpec="z"
+                latin:keyLabelFlags="hasPopupHint"
+                latin:moreKeys="!text/morekeys_punctuation,!text/morekeys_z" />
+        </case>
         <default>
             <Key
                 latin:keySpec="!text/keyspec_period"
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index 2d006c9..167e6f8 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -36,6 +36,8 @@
         </default>
     </switch>
     <!-- Base key style for the key which may have settings key as more keys. -->
+    <key-style
+        latin:styleName="baseSettingsMoreKeysStyle" />
     <include
         latin:keyboardLayout="@xml/key_styles_settings" />
     <!-- Functional key styles -->
diff --git a/java/res/xml/key_styles_settings.xml b/java/res/xml/key_styles_settings.xml
index 956b402..a504bed 100644
--- a/java/res/xml/key_styles_settings.xml
+++ b/java/res/xml/key_styles_settings.xml
@@ -29,15 +29,17 @@
         >
             <key-style
                 latin:styleName="settingsMoreKeysStyle"
-                latin:backgroundType="functional" />
+                latin:backgroundType="functional"
+                latin:parentStyle="baseSettingsMoreKeysStyle" />
         </case>
         <!-- clobberSettingsKey="false" -->
         <default>
             <key-style
                 latin:styleName="settingsMoreKeysStyle"
                 latin:keyLabelFlags="hasPopupHint"
-                latin:moreKeys="!text/keyspec_settings"
-                latin:backgroundType="functional" />
+                latin:additionalMoreKeys="!text/keyspec_settings"
+                latin:backgroundType="functional"
+                latin:parentStyle="baseSettingsMoreKeysStyle" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml/row_dvorak4.xml b/java/res/xml/row_dvorak4.xml
deleted file mode 100644
index e7a3ee7..0000000
--- a/java/res/xml/row_dvorak4.xml
+++ /dev/null
@@ -1,45 +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"
->
-    <Row
-        latin:keyWidth="10%p"
-    >
-        <Key
-            latin:keyStyle="toSymbolKeyStyle"
-            latin:keyWidth="15%p" />
-        <Key
-            latin:keySpec="q"
-            latin:backgroundType="normal"
-            latin:keyStyle="settingsMoreKeysStyle" />
-        <include
-            latin:keyXPos="25%p"
-            latin:keyboardLayout="@xml/key_space_5kw" />
-        <Key
-            latin:keySpec="z"
-            latin:keyLabelFlags="hasPopupHint"
-            latin:moreKeys="!text/morekeys_punctuation,!text/morekeys_z" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
-    </Row>
-</merge>
diff --git a/java/res/xml/row_qwerty4.xml b/java/res/xml/row_qwerty4.xml
index 509092d..5bc104f 100644
--- a/java/res/xml/row_qwerty4.xml
+++ b/java/res/xml/row_qwerty4.xml
@@ -28,7 +28,7 @@
             latin:keyStyle="toSymbolKeyStyle"
             latin:keyWidth="15%p" />
         <include
-            latin:keyboardLayout="@xml/key_f1" />
+            latin:keyboardLayout="@xml/key_comma" />
         <include
             latin:keyXPos="25%p"
             latin:keyboardLayout="@xml/key_space_5kw" />
diff --git a/java/res/xml/rows_dvorak.xml b/java/res/xml/rows_dvorak.xml
index 13d7021..f656613 100644
--- a/java/res/xml/rows_dvorak.xml
+++ b/java/res/xml/rows_dvorak.xml
@@ -49,6 +49,8 @@
             latin:keyWidth="fillRight"
             latin:visualInsetsLeft="1%p" />
     </Row>
+    <!-- Dvorak layout shares almost the same row with Qwerty layout.
+         The difference is defined in xml/row_qwerty4.xml. -->
     <include
-        latin:keyboardLayout="@xml/row_dvorak4" />
+        latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
index 3abfa3f..b9a5367 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
@@ -20,6 +20,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.RichInputMethodSubtype;
 
 import java.lang.reflect.Constructor;
@@ -69,7 +70,8 @@
     }
 
     public static boolean isAsciiCapable(final InputMethodSubtype subtype) {
-        return InputMethodSubtypeCompatUtils.isAsciiCapableWithAPI(subtype);
+        return isAsciiCapableWithAPI(subtype)
+                || subtype.containsExtraValueKey(Constants.Subtype.ExtraValue.ASCII_CAPABLE);
     }
 
     @UsedForTesting
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 140e768..77cdf49 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -261,6 +261,9 @@
     }
 
     public void onToggleEmojiKeyboard() {
+        if (mKeyboardLayoutSet == null) {
+            return;
+        }
         if (isShowingEmojiPalettes()) {
             setAlphabetKeyboard();
         } else {
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 36a0266..60d6bc3 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.annotations.UsedForTesting;
@@ -656,4 +657,15 @@
         }
         dictToDump.dumpAllWordsForDebug();
     }
+
+    public ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts() {
+        final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>();
+        final Dictionaries dictionaries = mDictionaries;
+        for (final String dictType : SUB_DICT_TYPES) {
+            final ExpandableBinaryDictionary dictionary = dictionaries.getSubDict(dictType);
+            if (dictionary == null) continue;
+            statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats()));
+        }
+        return statsOfEnabledSubDicts;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 660b2da..87b34a9 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -51,6 +51,7 @@
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
@@ -192,8 +193,8 @@
         private static final int ARG1_FALSE = 0;
         private static final int ARG1_TRUE = 1;
 
-        private int mDelayUpdateSuggestions;
-        private int mDelayUpdateShiftState;
+        private int mDelayInMillisecondsToUpdateSuggestions;
+        private int mDelayInMillisecondsToUpdateShiftState;
 
         public UIHandler(final LatinIME ownerInstance) {
             super(ownerInstance);
@@ -205,8 +206,10 @@
                 return;
             }
             final Resources res = latinIme.getResources();
-            mDelayUpdateSuggestions = res.getInteger(R.integer.config_delay_update_suggestions);
-            mDelayUpdateShiftState = res.getInteger(R.integer.config_delay_update_shift_state);
+            mDelayInMillisecondsToUpdateSuggestions =
+                    res.getInteger(R.integer.config_delay_in_milliseconds_to_update_suggestions);
+            mDelayInMillisecondsToUpdateShiftState =
+                    res.getInteger(R.integer.config_delay_in_milliseconds_to_update_shift_state);
         }
 
         @Override
@@ -272,7 +275,7 @@
 
         public void postUpdateSuggestionStrip(final int inputStyle) {
             sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP, inputStyle,
-                    0 /* ignored */), mDelayUpdateSuggestions);
+                    0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions);
         }
 
         public void postReopenDictionaries() {
@@ -285,16 +288,14 @@
             if (latinIme == null) {
                 return;
             }
-            if (!latinIme.mSettings.getCurrent()
-                    .isSuggestionsEnabledPerUserSettings()) {
+            if (!latinIme.mSettings.getCurrent().isSuggestionsEnabledPerUserSettings()) {
                 return;
             }
             removeMessages(MSG_RESUME_SUGGESTIONS);
             if (shouldDelay) {
                 sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS,
-                                shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
-                                0 /* ignored */),
-                        mDelayUpdateSuggestions);
+                        shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
+                        0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions);
             } else {
                 sendMessage(obtainMessage(MSG_RESUME_SUGGESTIONS,
                         shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
@@ -335,7 +336,8 @@
 
         public void postUpdateShiftState() {
             removeMessages(MSG_UPDATE_SHIFT_STATE);
-            sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mDelayUpdateShiftState);
+            sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE),
+                    mDelayInMillisecondsToUpdateShiftState);
         }
 
         @UsedForTesting
@@ -1139,6 +1141,17 @@
     }
 
     @Override
+    public boolean onShowInputRequested(final int flags, final boolean configChange) {
+        if ((flags & InputMethod.SHOW_EXPLICIT) == 0 && mKeyboardSwitcher.hasHardwareKeyboard()) {
+            // Even when IME is implicitly shown and physical keyboard is connected, we should
+            // show {@link InputView}.
+            // See {@link InputMethodService#onShowInputRequested(int,boolean)}.
+            return true;
+        }
+        return super.onShowInputRequested(flags, configChange);
+    }
+
+    @Override
     public boolean onEvaluateFullscreenMode() {
         if (mKeyboardSwitcher.hasHardwareKeyboard()) {
             // If there is a hardware keyboard, disable full screen mode.
@@ -1259,10 +1272,26 @@
         mSubtypeState.switchSubtype(token, mRichImm);
     }
 
+    // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
+    // alphabetic shift and shift while in symbol layout and get rid of this method.
+    private int getCodePointForKeyboard(final int codePoint) {
+        if (Constants.CODE_SHIFT == codePoint) {
+            final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
+            if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
+                return codePoint;
+            } else {
+                return Constants.CODE_SYMBOL_SHIFT;
+            }
+        } else {
+            return codePoint;
+        }
+    }
+
     // Implementation of {@link KeyboardActionListener}.
     @Override
     public void onCodeInput(final int codePoint, final int x, final int y,
             final boolean isKeyRepeat) {
+        // TODO: this processing does not belong inside LatinIME, the caller should be doing this.
         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         // x and y include some padding, but everything down the line (especially native
         // code) needs the coordinates in the keyboard frame.
@@ -1271,36 +1300,30 @@
         // this transformation, it should be done already before calling onCodeInput.
         final int keyX = mainKeyboardView.getKeyX(x);
         final int keyY = mainKeyboardView.getKeyY(y);
-        final int codeToSend;
-        if (Constants.CODE_SHIFT == codePoint) {
-            // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
-            // alphabetic shift and shift while in symbol layout.
-            final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
-            if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
-                codeToSend = codePoint;
-            } else {
-                codeToSend = Constants.CODE_SYMBOL_SHIFT;
-            }
-        } else {
-            codeToSend = codePoint;
-        }
-        if (Constants.CODE_SHORTCUT == codePoint) {
+        final Event event = createSoftwareKeypressEvent(getCodePointForKeyboard(codePoint),
+                keyX, keyY, isKeyRepeat);
+        onEvent(event);
+    }
+
+    // This method is public for testability of LatinIME, but also in the future it should
+    // completely replace #onCodeInput.
+    public void onEvent(final Event event) {
+        if (Constants.CODE_SHORTCUT == event.mCodePoint) {
             mSubtypeSwitcher.switchToShortcutIME(this);
-            // Still call the *#onCodeInput methods for readability.
         }
-        final Event event = createSoftwareKeypressEvent(codeToSend, keyX, keyY, isKeyRepeat);
         final InputTransaction completeInputTransaction =
                 mInputLogic.onCodeInput(mSettings.getCurrent(), event,
                         mKeyboardSwitcher.getKeyboardShiftMode(),
                         mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler);
         updateStateAfterInputTransaction(completeInputTransaction);
-        mKeyboardSwitcher.onCodeInput(codePoint, getCurrentAutoCapsState(),
+        mKeyboardSwitcher.onCodeInput(event.mCodePoint, getCurrentAutoCapsState(),
                 getCurrentRecapitalizeState());
     }
 
     // A helper method to split the code point and the key code. Ultimately, they should not be
     // squashed into the same variable, and this method should be removed.
-    private static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
+    // public for testing, as we don't want to copy the same logic into test code
+    public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
              final int keyY, final boolean isKeyRepeat) {
         final int keyCode;
         final int codePoint;
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index ea63cef..0355576 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -16,9 +16,13 @@
 
 package com.android.inputmethod.latin;
 
+import android.graphics.Color;
 import android.inputmethodservice.InputMethodService;
 import android.os.Build;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
 import android.text.TextUtils;
+import android.text.style.BackgroundColorSpan;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
@@ -81,6 +85,18 @@
      */
     private final StringBuilder mComposingText = new StringBuilder();
 
+    /**
+     * This variable is a temporary object used in
+     * {@link #commitTextWithBackgroundColor(CharSequence, int, int)} to avoid object creation.
+     */
+    private SpannableStringBuilder mTempObjectForCommitText = new SpannableStringBuilder();
+    /**
+     * This variable is used to track whether the last committed text had the background color or
+     * not.
+     * TODO: Omit this flag if possible.
+     */
+    private boolean mLastCommittedTextHasBackgroundColor = false;
+
     private final InputMethodService mParent;
     InputConnection mIC;
     int mNestLevel;
@@ -219,12 +235,37 @@
         // it works, but it's wrong and should be fixed.
         mCommittedTextBeforeComposingText.append(mComposingText);
         mComposingText.setLength(0);
+        // TODO: Clear this flag in setComposingRegion() and setComposingText() as well if needed.
+        mLastCommittedTextHasBackgroundColor = false;
         if (null != mIC) {
             mIC.finishComposingText();
         }
     }
 
-    public void commitText(final CharSequence text, final int i) {
+    /**
+     * Synonym of {@code commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT}.
+     * @param text The text to commit. This may include styles.
+     * See {@link InputConnection#commitText(CharSequence, int)}.
+     * @param newCursorPosition The new cursor position around the text.
+     * See {@link InputConnection#commitText(CharSequence, int)}.
+     */
+    public void commitText(final CharSequence text, final int newCursorPosition) {
+        commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT);
+    }
+
+    /**
+     * Calls {@link InputConnection#commitText(CharSequence, int)} with the given background color.
+     * @param text The text to commit. This may include styles.
+     * See {@link InputConnection#commitText(CharSequence, int)}.
+     * @param newCursorPosition The new cursor position around the text.
+     * See {@link InputConnection#commitText(CharSequence, int)}.
+     * @param color The background color to be attached. Set {@link Color#TRANSPARENT} to disable
+     * the background color. Note that this method specifies {@link BackgroundColorSpan} with
+     * {@link Spanned#SPAN_COMPOSING} flag, meaning that the background color persists until
+     * {@link #finishComposingText()} is called.
+     */
+    public void commitTextWithBackgroundColor(final CharSequence text, final int newCursorPosition,
+            final int color) {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         mCommittedTextBeforeComposingText.append(text);
@@ -234,11 +275,43 @@
         mExpectedSelStart += text.length() - mComposingText.length();
         mExpectedSelEnd = mExpectedSelStart;
         mComposingText.setLength(0);
+        mLastCommittedTextHasBackgroundColor = false;
         if (null != mIC) {
-            mIC.commitText(text, i);
+            if (color == Color.TRANSPARENT) {
+                mIC.commitText(text, newCursorPosition);
+            } else {
+                mTempObjectForCommitText.clear();
+                mTempObjectForCommitText.append(text);
+                final BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(color);
+                mTempObjectForCommitText.setSpan(backgroundColorSpan, 0, text.length(),
+                        Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                mIC.commitText(mTempObjectForCommitText, newCursorPosition);
+                mLastCommittedTextHasBackgroundColor = true;
+            }
         }
     }
 
+    /**
+     * Removes the background color from the highlighted text if necessary. Should be called while
+     * there is no on-going composing text.
+     *
+     * <p>CAVEAT: This method internally calls {@link InputConnection#finishComposingText()}.
+     * Be careful of any unexpected side effects.</p>
+     */
+    public void removeBackgroundColorFromHighlightedTextIfNecessary() {
+        // TODO: We haven't yet full tested if we really need to check this flag or not. Omit this
+        // flag if everything works fine without this condition.
+        if (!mLastCommittedTextHasBackgroundColor) {
+            return;
+        }
+        if (mComposingText.length() > 0) {
+            Log.e(TAG, "clearSpansWithComposingFlags should be called when composing text is " +
+                    "empty. mComposingText=" + mComposingText);
+            return;
+        }
+        finishComposingText();
+    }
+
     public CharSequence getSelectedText(final int flags) {
         return (null == mIC) ? null : mIC.getSelectedText(flags);
     }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index c39c254..45d67ff 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -315,9 +315,12 @@
 
     public RichInputMethodSubtype getEmojiSubtype() {
         if (mEmojiSubtype == null) {
-            mEmojiSubtype = new RichInputMethodSubtype(
+            final InputMethodSubtype rawEmojiSubtype =
                     mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                            SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI));
+                        SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
+            if (null != rawEmojiSubtype) {
+                mEmojiSubtype = new RichInputMethodSubtype(rawEmojiSubtype);
+            }
         }
         if (mEmojiSubtype != null) {
             return mEmojiSubtype;
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index d7693af..38fcb68 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -19,6 +19,7 @@
 import android.text.TextUtils;
 import android.view.inputmethod.CompletionInfo;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.utils.StringUtils;
 
@@ -420,4 +421,18 @@
                 mWillAutoCorrect, mIsObsoleteSuggestions, mIsPrediction,
                 INPUT_STYLE_TAIL_BATCH);
     }
+
+    /**
+     * @return the {@link SuggestedWordInfo} which corresponds to the word that is originally
+     * typed by the user. Otherwise returns {@code null}. Note that gesture input is not
+     * considered to be a typed word.
+     */
+    @UsedForTesting
+    public SuggestedWordInfo getTypedWordInfoOrNull() {
+        if (this == EMPTY) {
+            return null;
+        }
+        final SuggestedWordInfo info = getInfo(SuggestedWords.INDEX_OF_TYPED_WORD);
+        return (info.getKind() == SuggestedWordInfo.KIND_TYPED) ? info : null;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 348bae6..616828e 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.latin.inputlogic;
 
+import android.graphics.Color;
 import android.os.SystemClock;
 import android.text.SpannableString;
 import android.text.TextUtils;
@@ -232,6 +233,20 @@
     }
 
     /**
+     * Determines whether "Touch again to save" should be shown or not.
+     * @param suggestionInfo the suggested word chosen by the user.
+     * @return {@code true} if we should show the "Touch again to save" hint.
+     */
+    private boolean shouldShowAddToDictionaryHint(final SuggestedWordInfo suggestionInfo) {
+        // We should show the "Touch again to save" hint if the user pressed the first entry
+        // AND it's in none of our current dictionaries (main, user or otherwise).
+        return (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_TYPED)
+                || suggestionInfo.isKindOf(SuggestedWordInfo.KIND_OOV_CORRECTION))
+                && !mDictionaryFacilitator.isValidWord(suggestionInfo.mWord, true /* ignoreCase */)
+                && mDictionaryFacilitator.isUserDictionaryEnabled();
+    }
+
+    /**
      * A suggestion was picked from the suggestion strip.
      * @param settingsValues the current values of the settings.
      * @param suggestionInfo the suggestion info.
@@ -297,14 +312,7 @@
         mSpaceState = SpaceState.PHANTOM;
         inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
 
-        // We should show the "Touch again to save" hint if the user pressed the first entry
-        // AND it's in none of our current dictionaries (main, user or otherwise).
-        final boolean showingAddToDictionaryHint =
-                (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_TYPED)
-                        || suggestionInfo.isKindOf(SuggestedWordInfo.KIND_OOV_CORRECTION))
-                        && !mDictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */);
-
-        if (showingAddToDictionaryHint && mDictionaryFacilitator.isUserDictionaryEnabled()) {
+        if (shouldShowAddToDictionaryHint(suggestionInfo)) {
             mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
         } else {
             // If we're not showing the "Touch again to save", then update the suggestion strip.
@@ -743,6 +751,13 @@
             final InputTransaction inputTransaction,
             // TODO: remove this argument
             final LatinIME.UIHandler handler) {
+        // In case the "add to dictionary" hint was still displayed.
+        // TODO: Do we really need to check if we have composing text here?
+        if (!mWordComposer.isComposingWord() &&
+                mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) {
+            mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
+        }
+
         final int codePoint = event.mCodePoint;
         mSpaceState = SpaceState.NONE;
         if (inputTransaction.mSettingsValues.isWordSeparator(codePoint)
@@ -838,8 +853,6 @@
             } else {
                 sendKeyCodePoint(settingsValues, codePoint);
             }
-            // In case the "add to dictionary" hint was still displayed.
-            mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
         }
         inputTransaction.setRequiresUpdateSuggestions();
     }
@@ -1994,7 +2007,9 @@
     }
 
     /**
-     * Commits the chosen word to the text field and saves it for later retrieval.
+     * Commits the chosen word to the text field and saves it for later retrieval. This is a
+     * synonym of {@code commitChosenWordWithBackgroundColor(settingsValues, chosenWord,
+     * commitType, separatorString, Color.TRANSPARENT}.
      *
      * @param settingsValues the current values of the settings.
      * @param chosenWord the word we want to commit.
@@ -2003,6 +2018,23 @@
      */
     private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord,
             final int commitType, final String separatorString) {
+        commitChosenWordWithBackgroundColor(settingsValues, chosenWord, commitType, separatorString,
+                Color.TRANSPARENT);
+    }
+
+    /**
+     * Commits the chosen word to the text field and saves it for later retrieval.
+     *
+     * @param settingsValues the current values of the settings.
+     * @param chosenWord the word we want to commit.
+     * @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.
+     * @param backgroundColor the background color to be specified with the committed text. Pass
+     * {@link Color#TRANSPARENT} to not specify the background color.
+     */
+    private void commitChosenWordWithBackgroundColor(final SettingsValues settingsValues,
+            final String chosenWord, final int commitType, final String separatorString,
+            final int backgroundColor) {
         final SuggestedWords suggestedWords = mSuggestedWords;
         final CharSequence chosenWordWithSuggestions =
                 SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
@@ -2012,7 +2044,7 @@
         // information from the 1st previous word.
         final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord(
                 settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1);
-        mConnection.commitText(chosenWordWithSuggestions, 1);
+        mConnection.commitTextWithBackgroundColor(chosenWordWithSuggestions, 1, backgroundColor);
         // Add the word to the user history dictionary
         performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo);
         // TODO: figure out here if this is an auto-correct or if the best word is actually
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index c12474b..a9789d8 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -51,7 +51,7 @@
 
     // From resources:
     public final SpacingAndPunctuations mSpacingAndPunctuations;
-    public final int mDelayUpdateOldSuggestions;
+    public final int mDelayInMillisecondsToUpdateOldSuggestions;
     public final long mDoubleSpacePeriodTimeout;
 
     // From preferences, in the same order as xml/prefs.xml:
@@ -107,7 +107,8 @@
             final InputAttributes inputAttributes) {
         mLocale = res.getConfiguration().locale;
         // Get the resources
-        mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions);
+        mDelayInMillisecondsToUpdateOldSuggestions =
+                res.getInteger(R.integer.config_delay_in_milliseconds_to_update_old_suggestions);
         mSpacingAndPunctuations = new SpacingAndPunctuations(res);
 
         // Store the input attributes
@@ -337,8 +338,8 @@
         final StringBuilder sb = new StringBuilder("Current settings :");
         sb.append("\n   mSpacingAndPunctuations = ");
         sb.append("" + mSpacingAndPunctuations.dump());
-        sb.append("\n   mDelayUpdateOldSuggestions = ");
-        sb.append("" + mDelayUpdateOldSuggestions);
+        sb.append("\n   mDelayInMillisecondsToUpdateOldSuggestions = ");
+        sb.append("" + mDelayInMillisecondsToUpdateOldSuggestions);
         sb.append("\n   mAutoCap = ");
         sb.append("" + mAutoCap);
         sb.append("\n   mVibrateOn = ");
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
index 22460ef..0a76a9d 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
@@ -113,13 +113,13 @@
                 "Unknown subtype: locale=" + locale + " keyboardLayout=" + keyboardLayout);
     }
 
-    protected final KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
+    protected KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
             final EditorInfo editorInfo) {
         return createKeyboardLayoutSet(subtype, editorInfo, false /* voiceInputKeyEnabled */,
                 false /* languageSwitchKeyEnabled */);
     }
 
-    protected final KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
+    protected KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
             final EditorInfo editorInfo, final boolean voiceInputKeyEnabled,
             final boolean languageSwitchKeyEnabled) {
         final Context context = mThemeContext;
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Arabic.java b/tests/src/com/android/inputmethod/keyboard/layout/Arabic.java
index fa81865..3f85e4b 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Arabic.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Arabic.java
@@ -78,8 +78,7 @@
             // U+061F: "؟" ARABIC QUESTION MARK
             // U+061B: "؛" ARABIC SEMICOLON
             return joinKeys(key("\u060C", joinMoreKeys(
-                    ":", "!", "\u061F", "\u061B", "-", "\"", "'", SETTINGS_KEY)),
-                    "_");
+                    ":", "!", "\u061F", "\u061B", "-", "\"", "'", SETTINGS_KEY)));
         }
 
         @Override
@@ -90,7 +89,7 @@
             // U+060C: "،" ARABIC COMMA
             // U+061F: "؟" ARABIC QUESTION MARK
             // U+061B: "؛" ARABIC SEMICOLON
-            return joinKeys("/", key(".", getPunctuationMoreKeys(isPhone)));
+            return joinKeys(key(".", getPunctuationMoreKeys(isPhone)));
         }
 
         @Override
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/ArmenianPhonetic.java b/tests/src/com/android/inputmethod/keyboard/layout/ArmenianPhonetic.java
index dba91b4..2cecedc 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/ArmenianPhonetic.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/ArmenianPhonetic.java
@@ -62,14 +62,14 @@
         public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
             // U+055D: "՝" ARMENIAN COMMA
             return isPhone ? joinKeys(key("\u055D", SETTINGS_KEY))
-                    : joinKeys(key("\u055D", SETTINGS_KEY), "_");
+                    : joinKeys(key("\u055D", SETTINGS_KEY));
         }
 
         @Override
         public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) {
             // U+0589: "։" ARMENIAN FULL STOP
             final ExpectedKey fullStopKey = key("\u0589", getPunctuationMoreKeys(isPhone));
-            return isPhone ? joinKeys(fullStopKey) : joinKeys("/", fullStopKey);
+            return joinKeys(fullStopKey);
         }
 
         @Override
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java b/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java
index e75cfd0..ba94c8d 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Dvorak.java
@@ -20,13 +20,14 @@
 import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
 import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
 import com.android.inputmethod.keyboard.layout.expected.ExpectedKey.ExpectedAdditionalMoreKey;
+import com.android.inputmethod.latin.settings.CustomInputStyleSettingsFragment;
 
 import java.util.Locale;
 
 /**
- * The QWERTY alphabet keyboard.
+ * The Dvorak alphabet keyboard.
  */
-public final class Dvorak extends LayoutBase {
+public class Dvorak extends LayoutBase {
     private static final String LAYOUT_NAME = "dvorak";
 
     public Dvorak(final LayoutCustomizer customizer) {
@@ -51,17 +52,19 @@
 
         @Override
         public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
-            return isPhone ? joinKeys(key("q", SETTINGS_KEY)) :
-                joinKeys(SETTINGS_KEY, key("_", moreKey("-")));
+            // U+00A1: "¡" INVERTED EXCLAMATION MARK
+            return isPhone ? joinKeys(key("q", SETTINGS_KEY))
+                    : joinKeys(key("!", joinMoreKeys("\u00A1", SETTINGS_KEY)));
         }
 
         @Override
         public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) {
             final ExpectedAdditionalMoreKey[] punctuationMoreKeys =
                     convertToAdditionalMoreKeys(getPunctuationMoreKeys(isPhone));
+            // U+00BF: "¿" INVERTED QUESTION MARK
             return isPhone
                     ? joinKeys(key("z", punctuationMoreKeys))
-                    : joinKeys("/", key("?", moreKey("!")));
+                    : joinKeys(key("?", joinMoreKeys(punctuationMoreKeys, "\u00BF")));
         }
 
         private static ExpectedAdditionalMoreKey[] convertToAdditionalMoreKeys(
@@ -76,7 +79,33 @@
     }
 
     @Override
-    ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) { return ALPHABET_COMMON; }
+    public ExpectedKey[][] getCommonAlphabetLayout(final boolean isPhone) {
+        return ALPHABET_COMMON;
+    }
+
+    protected ExpectedKey getRow1_1Key(final boolean isPhone, final int elementId) {
+        if (elementId == KeyboardId.ELEMENT_ALPHABET
+                || elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
+            return key("'", joinMoreKeys(additionalMoreKey("1"), "!", "\""));
+        }
+        return key("\"", additionalMoreKey("1"));
+    }
+
+    protected ExpectedKey getRow1_2Key(final boolean isPhone, final int elementId) {
+        if (elementId == KeyboardId.ELEMENT_ALPHABET
+                || elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
+            return key(",", joinMoreKeys(additionalMoreKey("2"), "?", "<"));
+        }
+        return key("<", additionalMoreKey("2"));
+    }
+
+    protected ExpectedKey getRow1_3Key(final boolean isPhone, final int elementId) {
+        if (elementId == KeyboardId.ELEMENT_ALPHABET
+                || elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
+            return key(".", joinMoreKeys(additionalMoreKey("3"), ">"));
+        }
+        return key(">", additionalMoreKey("3"));
+    }
 
     @Override
     public ExpectedKey[][] getLayout(final boolean isPhone, final int elementId) {
@@ -86,18 +115,9 @@
         }
         final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(
                 getCommonAlphabetLayout(isPhone));
-        if (elementId == KeyboardId.ELEMENT_ALPHABET
-                || elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
-            builder.addKeysOnTheLeftOfRow(1,
-                    key("'", joinMoreKeys(additionalMoreKey("1"), "!", "\"")),
-                    key(",", joinMoreKeys(additionalMoreKey("2"), "?", "<")),
-                    key(".", joinMoreKeys(additionalMoreKey("3"), ">")));
-        } else {
-            builder.addKeysOnTheLeftOfRow(1,
-                    key("\"", additionalMoreKey("1")),
-                    key("<", additionalMoreKey("2")),
-                    key(">", additionalMoreKey("3")));
-        }
+        builder.replaceKeyOfLabel(ROW1_1, getRow1_1Key(isPhone, elementId))
+                .replaceKeyOfLabel(ROW1_2, getRow1_2Key(isPhone, elementId))
+                .replaceKeyOfLabel(ROW1_3, getRow1_3Key(isPhone, elementId));
         convertCommonLayoutToKeyboard(builder, isPhone);
         getCustomizer().setAccentedLetters(builder);
         if (elementId != KeyboardId.ELEMENT_ALPHABET) {
@@ -107,8 +127,13 @@
         return builder.build();
     }
 
+    public static final String ROW1_1 = "ROW1_1";
+    public static final String ROW1_2 = "ROW1_2";
+    public static final String ROW1_3 = "ROW1_3";
+
     private static final ExpectedKey[][] ALPHABET_COMMON = new ExpectedKeyboardBuilder()
             .setKeysOfRow(1,
+                    ROW1_1, ROW1_2, ROW1_3,
                     key("p", additionalMoreKey("4")),
                     key("y", additionalMoreKey("5")),
                     key("f", additionalMoreKey("6")),
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java b/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java
index a513740..7390457 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Farsi.java
@@ -77,8 +77,7 @@
             // U+061B: "؛" ARABIC SEMICOLON
             return joinKeys(key("\u060C", joinMoreKeys(
                     ":", "!", "\u061F", "\u061B", "-", RtlSymbols.DOUBLE_ANGLE_QUOTES_LR_RTL,
-                    SETTINGS_KEY)),
-                    "_");
+                    SETTINGS_KEY)));
         }
 
         @Override
@@ -86,7 +85,7 @@
             if (isPhone) {
                 return super.getKeysRightToSpacebar(isPhone);
             }
-            return joinKeys("/", key(".", getPunctuationMoreKeys(isPhone)));
+            return joinKeys(key(".", getPunctuationMoreKeys(isPhone)));
         }
 
         @Override
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java b/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java
index 2b625c3..c2a15f3 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/HindiCompact.java
@@ -50,7 +50,7 @@
         public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) {
             // U+0964: "।" DEVANAGARI DANDA
             final ExpectedKey periodKey = key("\u0964", getPunctuationMoreKeys(isPhone));
-            return isPhone ? joinKeys(periodKey) : joinKeys("/", periodKey);
+            return joinKeys(periodKey);
         }
 
         @Override
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java
index 0548a01..b05789b 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/LayoutBase.java
@@ -149,6 +149,24 @@
         }
 
         /**
+         * Get the enter key.
+         * @param isPhone true if requesting phone's key.
+         * @return the array of {@link ExpectedKey} that should be placed as an enter key.
+         */
+        public ExpectedKey getEnterKey(final boolean isPhone) {
+            return isPhone ? key(ENTER_KEY, EMOJI_ACTION_KEY) : ENTER_KEY;
+        }
+
+        /**
+         * Get the emoji key.
+         * @param isPhone true if requesting phone's key.
+         * @return the array of {@link ExpectedKey} that should be placed as an emoji key.
+         */
+        public ExpectedKey getEmojiKey(final boolean isPhone) {
+            return EMOJI_NORMAL_KEY;
+        }
+
+        /**
          * Get the space keys.
          * @param isPhone true if requesting phone's keys.
          * @return the array of {@link ExpectedKey} that should be placed at the center of the
@@ -165,8 +183,7 @@
          */
         public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
             // U+002C: "," COMMA
-            return isPhone ? joinKeys(key("\u002C", SETTINGS_KEY))
-                    : joinKeys(key("\u002C", SETTINGS_KEY), "_");
+            return joinKeys(key("\u002C", SETTINGS_KEY));
         }
 
         /**
@@ -176,7 +193,7 @@
          */
         public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) {
             final ExpectedKey periodKey = key(".", getPunctuationMoreKeys(isPhone));
-            return isPhone ? joinKeys(periodKey) : joinKeys("/", periodKey);
+            return joinKeys(periodKey);
         }
 
         /**
@@ -241,7 +258,25 @@
      */
     public final LayoutCustomizer getCustomizer() { return mCustomizer; }
 
-    // Icon id.
+    // Icon ids.
+    private static final int ICON_DELETE = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_DELETE_KEY);
+    private static final int ICON_SPACE = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_SPACE_KEY);
+    private static final int ICON_TAB = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_TAB_KEY);
+    private static final int ICON_SHORTCUT = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_SHORTCUT_KEY);
+    private static final int ICON_SETTINGS = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_SETTINGS_KEY);
+    private static final int ICON_LANGUAGE_SWITCH = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_LANGUAGE_SWITCH_KEY);
+    private static final int ICON_ENTER = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_ENTER_KEY);
+    private static final int ICON_EMOJI_ACTION = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_EMOJI_ACTION_KEY);
+    private static final int ICON_EMOJI_NORMAL = KeyboardIconsSet.getIconId(
+            KeyboardIconsSet.NAME_EMOJI_NORMAL_KEY);
     private static final int ICON_SHIFT = KeyboardIconsSet.getIconId(
             KeyboardIconsSet.NAME_SHIFT_KEY);
     private static final int ICON_SHIFTED_SHIFT = KeyboardIconsSet.getIconId(
@@ -251,11 +286,21 @@
     private static final int ICON_ZWJ = KeyboardIconsSet.getIconId(
             KeyboardIconsSet.NAME_ZWJ_KEY);
 
-    // Functional key.
+    // Functional keys.
+    public static final ExpectedKey DELETE_KEY = key(ICON_DELETE, Constants.CODE_DELETE);
+    public static final ExpectedKey TAB_KEY = key(ICON_TAB, Constants.CODE_TAB);
+    public static final ExpectedKey SHORTCUT_KEY = key(ICON_SHORTCUT, Constants.CODE_SHORTCUT);
+    public static final ExpectedKey SETTINGS_KEY = key(ICON_SETTINGS, Constants.CODE_SETTINGS);
+    public static final ExpectedKey LANGUAGE_SWITCH_KEY = key(
+            ICON_LANGUAGE_SWITCH, Constants.CODE_LANGUAGE_SWITCH);
+    public static final ExpectedKey ENTER_KEY = key(ICON_ENTER, Constants.CODE_ENTER);
+    public static final ExpectedKey EMOJI_ACTION_KEY = key(ICON_EMOJI_ACTION, Constants.CODE_EMOJI);
+    public static final ExpectedKey EMOJI_NORMAL_KEY = key(ICON_EMOJI_NORMAL, Constants.CODE_EMOJI);
+    public static final ExpectedKey SPACE_KEY = key(ICON_SPACE, Constants.CODE_SPACE);
     static final ExpectedKey CAPSLOCK_MORE_KEY = key(" ", Constants.CODE_CAPSLOCK);
-    static final ExpectedKey SHIFT_KEY = key(ICON_SHIFT,
+    public static final ExpectedKey SHIFT_KEY = key(ICON_SHIFT,
             Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY);
-    static final ExpectedKey SHIFTED_SHIFT_KEY = key(ICON_SHIFTED_SHIFT,
+    public static final ExpectedKey SHIFTED_SHIFT_KEY = key(ICON_SHIFTED_SHIFT,
             Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY);
     static final ExpectedKey ALPHABET_KEY = key("ABC", Constants.CODE_SWITCH_ALPHA_SYMBOL);
     static final ExpectedKey SYMBOLS_KEY = key("?123", Constants.CODE_SWITCH_ALPHA_SYMBOL);
@@ -271,6 +316,9 @@
     // U+200D: ZERO WIDTH JOINER
     static final ExpectedKey ZWNJ_KEY = key(ICON_ZWNJ, "\u200C");
     static final ExpectedKey ZWJ_KEY = key(ICON_ZWJ, "\u200D");
+    // Domain key
+    public static final ExpectedKey DOMAIN_KEY =
+            key(".com", joinMoreKeys(".net", ".org", ".gov", ".edu")).preserveCase();
 
     // Punctuation more keys for phone form factor.
     public static final ExpectedKey[] PHONE_PUNCTUATION_MORE_KEYS = joinKeys(
@@ -300,12 +348,12 @@
         if (isPhone) {
             builder.addKeysOnTheRightOfRow(numberOfRows - 1, DELETE_KEY)
                     .addKeysOnTheLeftOfRow(numberOfRows, customizer.getSymbolsKey())
-                    .addKeysOnTheRightOfRow(numberOfRows, key(ENTER_KEY, EMOJI_ACTION_KEY));
+                    .addKeysOnTheRightOfRow(numberOfRows, customizer.getEnterKey(isPhone));
         } else {
             builder.addKeysOnTheRightOfRow(1, DELETE_KEY)
-                    .addKeysOnTheRightOfRow(numberOfRows - 2, ENTER_KEY)
+                    .addKeysOnTheRightOfRow(numberOfRows - 2, customizer.getEnterKey(isPhone))
                     .addKeysOnTheLeftOfRow(numberOfRows, customizer.getSymbolsKey())
-                    .addKeysOnTheRightOfRow(numberOfRows, EMOJI_NORMAL_KEY);
+                    .addKeysOnTheRightOfRow(numberOfRows, customizer.getEmojiKey(isPhone));
         }
         builder.addKeysOnTheLeftOfRow(
                 numberOfRows - 1, (Object[])customizer.getLeftShiftKeys(isPhone));
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Myanmar.java b/tests/src/com/android/inputmethod/keyboard/layout/Myanmar.java
index 15c74ed..3c70d32 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Myanmar.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Myanmar.java
@@ -55,14 +55,14 @@
             // U+002C: "," COMMA
             // U+104A: "၊" MYANMAR SIGN LITTLE SECTION
             return isPhone ? joinKeys(key("\u002C", SETTINGS_KEY))
-                    : joinKeys(key("\u104A", moreKey(","), SETTINGS_KEY), "_");
+                    : joinKeys(key("\u104A", moreKey(","), SETTINGS_KEY));
         }
 
         @Override
         public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) {
             // U+104B: "။" MYANMAR SIGN SECTION
             final ExpectedKey periodKey = key("\u104B", getPunctuationMoreKeys(isPhone));
-            return isPhone ? joinKeys(periodKey) : joinKeys("/", periodKey);
+            return joinKeys(periodKey);
         }
 
         @Override
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
index 2cee2d9..8030897 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/Symbols.java
@@ -41,18 +41,18 @@
                 customizer.getSingleQuoteMoreKeys(), customizer.getSingleAngleQuoteKeys())));
         if (isPhone) {
             builder.addKeysOnTheLeftOfRow(3, customizer.getSymbolsShiftKey(isPhone))
-                    .addKeysOnTheRightOfRow(3, DELETE_KEY)
+                    .addKeysOnTheRightOfRow(3, LayoutBase.DELETE_KEY)
                     .addKeysOnTheLeftOfRow(4, customizer.getAlphabetKey())
-                    .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_ACTION_KEY));
+                    .addKeysOnTheRightOfRow(4, customizer.getEnterKey(isPhone));
         } else {
             // Tablet symbols keyboard has extra two keys at the left edge of the 3rd row.
             builder.addKeysOnTheLeftOfRow(3, (Object[])joinKeys("\\", "="));
-            builder.addKeysOnTheRightOfRow(1, DELETE_KEY)
-                    .addKeysOnTheRightOfRow(2, ENTER_KEY)
+            builder.addKeysOnTheRightOfRow(1, LayoutBase.DELETE_KEY)
+                    .addKeysOnTheRightOfRow(2, customizer.getEnterKey(isPhone))
                     .addKeysOnTheLeftOfRow(3, customizer.getSymbolsShiftKey(isPhone))
                     .addKeysOnTheRightOfRow(3, customizer.getSymbolsShiftKey(isPhone))
                     .addKeysOnTheLeftOfRow(4, customizer.getAlphabetKey())
-                    .addKeysOnTheRightOfRow(4, EMOJI_NORMAL_KEY);
+                    .addKeysOnTheRightOfRow(4, customizer.getEmojiKey(isPhone));
         }
         return builder.build();
     }
@@ -167,7 +167,7 @@
                     // U+00BF: "¿" INVERTED QUESTION MARK
                     key("?", moreKey("\u00BF")))
             .setKeysOfRow(4,
-                    key(","), key("_"), SPACE_KEY, key("/"),
+                    key(","), key("_"), LayoutBase.SPACE_KEY, key("/"),
                     // U+2026: "…" HORIZONTAL ELLIPSIS
                     key(".", moreKey("\u2026")))
             .build();
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java b/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java
index 7ed103b..19cb607 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/SymbolsShifted.java
@@ -37,20 +37,20 @@
         builder.replaceKeyOfLabel(OTHER_CURRENCIES, (Object[])customizer.getOtherCurrencyKeys());
         if (isPhone) {
             builder.addKeysOnTheLeftOfRow(3, customizer.getBackToSymbolsKey())
-                    .addKeysOnTheRightOfRow(3, DELETE_KEY)
+                    .addKeysOnTheRightOfRow(3, LayoutBase.DELETE_KEY)
                     .addKeysOnTheLeftOfRow(4, customizer.getAlphabetKey())
-                    .addKeysOnTheRightOfRow(4, key(ENTER_KEY, EMOJI_ACTION_KEY));
+                    .addKeysOnTheRightOfRow(4, customizer.getEnterKey(isPhone));
         } else {
             // Tablet symbols shifted keyboard has extra two keys at the right edge of the 3rd row.
             // U+00BF: "¿" INVERTED QUESTION MARK
             // U+00A1: "¡" INVERTED EXCLAMATION MARK
             builder.addKeysOnTheRightOfRow(3, (Object[])joinKeys("\u00A1", "\u00BF"));
-            builder.addKeysOnTheRightOfRow(1, DELETE_KEY)
-                    .addKeysOnTheRightOfRow(2, ENTER_KEY)
+            builder.addKeysOnTheRightOfRow(1, LayoutBase.DELETE_KEY)
+                    .addKeysOnTheRightOfRow(2, customizer.getEnterKey(isPhone))
                     .addKeysOnTheLeftOfRow(3, customizer.getBackToSymbolsKey())
                     .addKeysOnTheRightOfRow(3, customizer.getBackToSymbolsKey())
                     .addKeysOnTheLeftOfRow(4, customizer.getAlphabetKey())
-                    .addKeysOnTheRightOfRow(4, EMOJI_NORMAL_KEY);
+                    .addKeysOnTheRightOfRow(4, customizer.getEmojiKey(isPhone));
         }
         return builder.build();
     }
@@ -122,7 +122,7 @@
                     // U+2264: "≤" LESS-THAN OR EQUAL TO
                     // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
                     key("<", joinMoreKeys("\u2039", "\u2264", "\u00AB")),
-                    SPACE_KEY,
+                    LayoutBase.SPACE_KEY,
                     // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
                     // U+2265: "≥" GREATER-THAN EQUAL TO
                     // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java
index 0f7bef2..3556cb4 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractLayoutBase.java
@@ -16,9 +16,7 @@
 
 package com.android.inputmethod.keyboard.layout.expected;
 
-import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.layout.expected.ExpectedKey.ExpectedAdditionalMoreKey;
-import com.android.inputmethod.latin.Constants;
 
 /**
  * Base class to create an expected keyboard for unit test.
@@ -104,36 +102,4 @@
     public static ExpectedKey[] joinKeys(final Object ... keys) {
         return ExpectedKeyboardBuilder.joinKeys(keys);
     }
-
-    // Icon ids.
-    private static final int ICON_DELETE = KeyboardIconsSet.getIconId(
-            KeyboardIconsSet.NAME_DELETE_KEY);
-    private static final int ICON_SPACE = KeyboardIconsSet.getIconId(
-            KeyboardIconsSet.NAME_SPACE_KEY);
-    private static final int ICON_TAB = KeyboardIconsSet.getIconId(
-            KeyboardIconsSet.NAME_TAB_KEY);
-    private static final int ICON_SHORTCUT = KeyboardIconsSet.getIconId(
-            KeyboardIconsSet.NAME_SHORTCUT_KEY);
-    private static final int ICON_SETTINGS = KeyboardIconsSet.getIconId(
-            KeyboardIconsSet.NAME_SETTINGS_KEY);
-    private static final int ICON_LANGUAGE_SWITCH = KeyboardIconsSet.getIconId(
-            KeyboardIconsSet.NAME_LANGUAGE_SWITCH_KEY);
-    private static final int ICON_ENTER = KeyboardIconsSet.getIconId(
-            KeyboardIconsSet.NAME_ENTER_KEY);
-    private static final int ICON_EMOJI_ACTION = KeyboardIconsSet.getIconId(
-            KeyboardIconsSet.NAME_EMOJI_ACTION_KEY);
-    private static final int ICON_EMOJI_NORMAL = KeyboardIconsSet.getIconId(
-            KeyboardIconsSet.NAME_EMOJI_NORMAL_KEY);
-
-    // Functional keys.
-    public static final ExpectedKey DELETE_KEY = key(ICON_DELETE, Constants.CODE_DELETE);
-    public static final ExpectedKey TAB_KEY = key(ICON_TAB, Constants.CODE_TAB);
-    public static final ExpectedKey SHORTCUT_KEY = key(ICON_SHORTCUT, Constants.CODE_SHORTCUT);
-    public static final ExpectedKey SETTINGS_KEY = key(ICON_SETTINGS, Constants.CODE_SETTINGS);
-    public static final ExpectedKey LANGUAGE_SWITCH_KEY = key(
-            ICON_LANGUAGE_SWITCH, Constants.CODE_LANGUAGE_SWITCH);
-    public static final ExpectedKey ENTER_KEY = key(ICON_ENTER, Constants.CODE_ENTER);
-    public static final ExpectedKey EMOJI_ACTION_KEY = key(ICON_EMOJI_ACTION, Constants.CODE_EMOJI);
-    public static final ExpectedKey EMOJI_NORMAL_KEY = key(ICON_EMOJI_NORMAL, Constants.CODE_EMOJI);
-    public static final ExpectedKey SPACE_KEY = key(ICON_SPACE, Constants.CODE_SPACE);
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java
index 0e1c71c..2674a6a 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKey.java
@@ -148,6 +148,18 @@
         return newInstance(mVisual.toUpperCase(locale), mOutput.toUpperCase(locale));
     }
 
+    public ExpectedKey preserveCase() {
+        final ExpectedKey[] moreKeys = getMoreKeys();
+        final ExpectedKey[] casePreservedMoreKeys = new ExpectedKey[moreKeys.length];
+        for (int index = 0; index < moreKeys.length; index++) {
+            final ExpectedKey moreKey = moreKeys[index];
+            casePreservedMoreKeys[index] = newInstance(
+                    moreKey.getVisual().preserveCase(), moreKey.getOutput().preserveCase());
+        }
+        return newInstance(
+                getVisual().preserveCase(), getOutput().preserveCase(), casePreservedMoreKeys);
+    }
+
     public boolean equalsTo(final Key key) {
         // This key has no "more keys".
         return mVisual.equalsTo(key) && mOutput.equalsTo(key) && key.getMoreKeys() == null;
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
index 1be51e6..737d169 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
@@ -42,6 +42,7 @@
     }
 
     abstract ExpectedKeyOutput toUpperCase(final Locale locale);
+    abstract ExpectedKeyOutput preserveCase();
     abstract boolean equalsTo(final String text);
     abstract boolean equalsTo(final Key key);
     abstract boolean equalsTo(final MoreKeySpec moreKeySpec);
@@ -69,6 +70,11 @@
         }
 
         @Override
+        ExpectedKeyOutput preserveCase() {
+            return new CasePreservedCode(mCode);
+        }
+
+        @Override
         boolean equalsTo(final String text) {
             return StringUtils.codePointCount(text) == 1 && text.codePointAt(0) == mCode;
         }
@@ -93,6 +99,16 @@
             return Constants.isLetterCode(mCode) ? StringUtils.newSingleCodePointString(mCode)
                     : Constants.printableCode(mCode);
         }
+
+        private static class CasePreservedCode extends Code {
+            CasePreservedCode(final int code) { super(code); }
+
+            @Override
+            ExpectedKeyOutput toUpperCase(final Locale locale) { return this; }
+
+            @Override
+            ExpectedKeyOutput preserveCase() { return this; }
+        }
     }
 
     /**
@@ -109,6 +125,11 @@
         }
 
         @Override
+        ExpectedKeyOutput preserveCase() {
+            return new CasePreservedText(mText);
+        }
+
+        @Override
         boolean equalsTo(final String text) {
             return text.equals(text);
         }
@@ -134,5 +155,15 @@
         public String toString() {
             return mText;
         }
+
+        private static class CasePreservedText extends Text {
+            CasePreservedText(final String text) { super(text); }
+
+            @Override
+            ExpectedKeyOutput toUpperCase(final Locale locale) { return this; }
+
+            @Override
+            ExpectedKeyOutput preserveCase() { return this; }
+        }
     }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java
index 0a0da32..facdf70 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyVisual.java
@@ -37,6 +37,7 @@
     }
 
     abstract ExpectedKeyVisual toUpperCase(final Locale locale);
+    abstract ExpectedKeyVisual preserveCase();
     abstract boolean equalsTo(final String text);
     abstract boolean equalsTo(final Key key);
     abstract boolean equalsTo(final MoreKeySpec moreKeySpec);
@@ -59,6 +60,11 @@
         }
 
         @Override
+        ExpectedKeyVisual preserveCase() {
+            return this;
+        }
+
+        @Override
         boolean equalsTo(final String text) {
             return false;
         }
@@ -103,6 +109,11 @@
         }
 
         @Override
+        ExpectedKeyVisual preserveCase() {
+            return new CasePreservedLabel(mLabel);
+        }
+
+        @Override
         boolean equalsTo(final String text) {
             return mLabel.equals(text);
         }
@@ -131,5 +142,15 @@
         public String toString() {
             return mLabel;
         }
+
+        private static class CasePreservedLabel extends Label {
+            CasePreservedLabel(final String label) { super(label); }
+
+            @Override
+            ExpectedKeyVisual toUpperCase(final Locale locale) { return this; }
+
+            @Override
+            ExpectedKeyVisual preserveCase() { return this; }
+        }
     }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDvorakEmail.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDvorakEmail.java
new file mode 100644
index 0000000..37ca092
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDvorakEmail.java
@@ -0,0 +1,91 @@
+/*
+ * 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 android.text.InputType;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
+import com.android.inputmethod.keyboard.layout.Dvorak;
+import com.android.inputmethod.keyboard.layout.LayoutBase;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+import com.android.inputmethod.keyboard.layout.tests.TestsEnglishDvorak.EnglishDvorakCustomizer;
+
+import java.util.Locale;
+
+/**
+ * en_US: English (United States)/dvorak, email input field.
+ */
+@SmallTest
+public class TestsDvorakEmail extends LayoutTestsBase {
+    private static final Locale LOCALE = new Locale("en", "US");
+    private static final LayoutBase LAYOUT = new DvorakEmail(new DvorakEmailCustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+
+    @Override
+    protected KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
+            final EditorInfo editorInfo, final boolean voiceInputKeyEnabled,
+            final boolean languageSwitchKeyEnabled) {
+        final EditorInfo emailField = new EditorInfo();
+        emailField.inputType =
+                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+        return super.createKeyboardLayoutSet(
+                subtype, emailField, voiceInputKeyEnabled, languageSwitchKeyEnabled);
+    }
+
+    private static class DvorakEmailCustomizer extends EnglishDvorakCustomizer {
+        DvorakEmailCustomizer(final Locale locale) {
+            super(locale);
+        }
+
+        @Override
+        public ExpectedKey getEnterKey(final boolean isPhone) {
+            return isPhone ? LayoutBase.ENTER_KEY : super.getEnterKey(isPhone);
+        }
+
+        @Override
+        public ExpectedKey getEmojiKey(final boolean isPhone) {
+            return LayoutBase.DOMAIN_KEY;
+        }
+
+        @Override
+        public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
+            return isPhone ? super.getKeysLeftToSpacebar(isPhone)
+                    : joinKeys(key("@", LayoutBase.SETTINGS_KEY));
+        }
+    }
+
+    private static class DvorakEmail extends Dvorak {
+        public DvorakEmail(final LayoutCustomizer customizer) {
+            super(customizer);
+        }
+
+        @Override
+        protected ExpectedKey getRow1_1Key(final boolean isPhone, final int elementId) {
+            if (isPhone && (elementId == KeyboardId.ELEMENT_ALPHABET
+                    || elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)) {
+                return key("@", joinMoreKeys(additionalMoreKey("1")));
+            }
+            return super.getRow1_1Key(isPhone, elementId);
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDvorakUrl.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDvorakUrl.java
new file mode 100644
index 0000000..3bcae0c
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsDvorakUrl.java
@@ -0,0 +1,91 @@
+/*
+ * 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 android.text.InputType;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
+import com.android.inputmethod.keyboard.layout.Dvorak;
+import com.android.inputmethod.keyboard.layout.LayoutBase;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+import com.android.inputmethod.keyboard.layout.tests.TestsEnglishDvorak.EnglishDvorakCustomizer;
+
+import java.util.Locale;
+
+/**
+ * en_US: English (United States)/dvorak, URL input field.
+ */
+@SmallTest
+public class TestsDvorakUrl extends LayoutTestsBase {
+    private static final Locale LOCALE = new Locale("en", "US");
+    private static final LayoutBase LAYOUT = new DvorakEmail(new DvorakUrlCustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+
+    @Override
+    protected KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
+            final EditorInfo editorInfo, final boolean voiceInputKeyEnabled,
+            final boolean languageSwitchKeyEnabled) {
+        final EditorInfo emailField = new EditorInfo();
+        emailField.inputType =
+                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI;
+        return super.createKeyboardLayoutSet(
+                subtype, emailField, voiceInputKeyEnabled, languageSwitchKeyEnabled);
+    }
+
+    private static class DvorakUrlCustomizer extends EnglishDvorakCustomizer {
+        DvorakUrlCustomizer(final Locale locale) {
+            super(locale);
+        }
+
+        @Override
+        public ExpectedKey getEnterKey(final boolean isPhone) {
+            return isPhone ? LayoutBase.ENTER_KEY : super.getEnterKey(isPhone);
+        }
+
+        @Override
+        public ExpectedKey getEmojiKey(final boolean isPhone) {
+            return LayoutBase.DOMAIN_KEY;
+        }
+
+        @Override
+        public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
+            return isPhone ? super.getKeysLeftToSpacebar(isPhone)
+                    : joinKeys(key("/", LayoutBase.SETTINGS_KEY));
+        }
+    }
+
+    private static class DvorakEmail extends Dvorak {
+        public DvorakEmail(final LayoutCustomizer customizer) {
+            super(customizer);
+        }
+
+        @Override
+        protected ExpectedKey getRow1_1Key(final boolean isPhone, final int elementId) {
+            if (isPhone && (elementId == KeyboardId.ELEMENT_ALPHABET
+                    || elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)) {
+                return key("/", joinMoreKeys(additionalMoreKey("1")));
+            }
+            return super.getRow1_1Key(isPhone, elementId);
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishDvorak.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishDvorak.java
index a052693..e647f8a 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishDvorak.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsEnglishDvorak.java
@@ -36,7 +36,7 @@
     @Override
     LayoutBase getLayout() { return LAYOUT; }
 
-    private static class EnglishDvorakCustomizer extends DvorakCustomizer {
+    public static class EnglishDvorakCustomizer extends DvorakCustomizer {
         private final EnglishCustomizer mEnglishCustomizer;
 
         EnglishDvorakCustomizer(final Locale locale) {
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsQwertyEmail.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsQwertyEmail.java
new file mode 100644
index 0000000..8563d69
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsQwertyEmail.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.text.InputType;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
+import com.android.inputmethod.keyboard.layout.LayoutBase;
+import com.android.inputmethod.keyboard.layout.Qwerty;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+
+import java.util.Locale;
+
+/**
+ * en_US: English (United States)/qwerty, email input field.
+ */
+@SmallTest
+public class TestsQwertyEmail extends LayoutTestsBase {
+    private static final Locale LOCALE = new Locale("en", "US");
+    private static final LayoutBase LAYOUT = new Qwerty(new EnglishEmailCustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+
+    @Override
+    protected KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
+            final EditorInfo editorInfo, final boolean voiceInputKeyEnabled,
+            final boolean languageSwitchKeyEnabled) {
+        final EditorInfo emailField = new EditorInfo();
+        emailField.inputType =
+                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+        return super.createKeyboardLayoutSet(
+                subtype, emailField, voiceInputKeyEnabled, languageSwitchKeyEnabled);
+    }
+
+    private static class EnglishEmailCustomizer extends EnglishCustomizer {
+        EnglishEmailCustomizer(final Locale locale) {
+            super(locale);
+        }
+
+        @Override
+        public ExpectedKey getEnterKey(final boolean isPhone) {
+            return isPhone ? LayoutBase.ENTER_KEY : super.getEnterKey(isPhone);
+        }
+
+        @Override
+        public ExpectedKey getEmojiKey(final boolean isPhone) {
+            return LayoutBase.DOMAIN_KEY;
+        }
+
+        @Override
+        public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
+            return joinKeys(key("@", LayoutBase.SETTINGS_KEY));
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsQwertyUrl.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsQwertyUrl.java
new file mode 100644
index 0000000..1c1a2bb
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsQwertyUrl.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.text.InputType;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
+import com.android.inputmethod.keyboard.layout.LayoutBase;
+import com.android.inputmethod.keyboard.layout.Qwerty;
+import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
+
+import java.util.Locale;
+
+/**
+ * en_US: English (United States)/qwerty, URL input field.
+ */
+@SmallTest
+public class TestsQwertyUrl extends LayoutTestsBase {
+    private static final Locale LOCALE = new Locale("en", "US");
+    private static final LayoutBase LAYOUT = new Qwerty(new EnglishUrlCustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+
+    @Override
+    protected KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
+            final EditorInfo editorInfo, final boolean voiceInputKeyEnabled,
+            final boolean languageSwitchKeyEnabled) {
+        final EditorInfo emailField = new EditorInfo();
+        emailField.inputType =
+                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI;
+        return super.createKeyboardLayoutSet(
+                subtype, emailField, voiceInputKeyEnabled, languageSwitchKeyEnabled);
+    }
+
+    private static class EnglishUrlCustomizer extends EnglishCustomizer {
+        EnglishUrlCustomizer(final Locale locale) {
+            super(locale);
+        }
+
+        @Override
+        public ExpectedKey getEnterKey(final boolean isPhone) {
+            return isPhone ? LayoutBase.ENTER_KEY : super.getEnterKey(isPhone);
+        }
+
+        @Override
+        public ExpectedKey getEmojiKey(final boolean isPhone) {
+            return LayoutBase.DOMAIN_KEY;
+        }
+
+        @Override
+        public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
+            return joinKeys(key("/", LayoutBase.SETTINGS_KEY));
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 986fb10..d7a649a 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -36,6 +36,7 @@
 import android.widget.FrameLayout;
 
 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
+import com.android.inputmethod.event.Event;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -263,14 +264,16 @@
         // but keep them in mind if something breaks. Commenting them out as is should work.
         //mLatinIME.onPressKey(codePoint, 0 /* repeatCount */, true /* isSinglePointer */);
         final Key key = mKeyboard.getKey(codePoint);
+        final Event event;
         if (key == null) {
-            mLatinIME.onCodeInput(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
-                    isKeyRepeat);
+            event = Event.createSoftwareKeypressEvent(codePoint, Event.NOT_A_KEY_CODE,
+                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, isKeyRepeat);
         } else {
             final int x = key.getX() + key.getWidth() / 2;
             final int y = key.getY() + key.getHeight() / 2;
-            mLatinIME.onCodeInput(codePoint, x, y, isKeyRepeat);
+            event = mLatinIME.createSoftwareKeypressEvent(codePoint, x, y, isKeyRepeat);
         }
+        mLatinIME.onEvent(event);
         // Also see the comment at the top of this function about onReleaseKey
         //mLatinIME.onReleaseKey(codePoint, false /* withSliding */);
     }
diff --git a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
index a5f20b5..2785dec 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
@@ -23,24 +23,49 @@
 
 import java.util.ArrayList;
 import java.util.Locale;
-import java.util.Random;
 
 @SmallTest
 public class SuggestedWordsTests extends AndroidTestCase {
+
+    /**
+     * Helper method to create a dummy {@link SuggestedWordInfo} with specifying
+     * {@link SuggestedWordInfo#KIND_TYPED}.
+     *
+     * @param word the word to be used to create {@link SuggestedWordInfo}.
+     * @return a new instance of {@link SuggestedWordInfo}.
+     */
+    private static SuggestedWordInfo createTypedWordInfo(final String word) {
+        // Use 100 as the frequency because the numerical value does not matter as
+        // long as it's > 1 and < INT_MAX.
+        return new SuggestedWordInfo(word, 100 /* score */,
+                SuggestedWordInfo.KIND_TYPED,
+                null /* sourceDict */,
+                SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                1 /* autoCommitFirstWordConfidence */);
+    }
+
+    /**
+     * Helper method to create a dummy {@link SuggestedWordInfo} with specifying
+     * {@link SuggestedWordInfo#KIND_CORRECTION}.
+     *
+     * @param word the word to be used to create {@link SuggestedWordInfo}.
+     * @return a new instance of {@link SuggestedWordInfo}.
+     */
+    private static SuggestedWordInfo createCorrectionWordInfo(final String word) {
+        return new SuggestedWordInfo(word, 1 /* score */,
+                SuggestedWordInfo.KIND_CORRECTION,
+                null /* sourceDict */,
+                SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
+    }
+
     public void testGetSuggestedWordsExcludingTypedWord() {
         final String TYPED_WORD = "typed";
-        final int TYPED_WORD_FREQ = 5;
         final int NUMBER_OF_ADDED_SUGGESTIONS = 5;
         final ArrayList<SuggestedWordInfo> list = new ArrayList<>();
-        list.add(new SuggestedWordInfo(TYPED_WORD, TYPED_WORD_FREQ,
-                SuggestedWordInfo.KIND_TYPED, null /* sourceDict */,
-                SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
-                SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
+        list.add(createTypedWordInfo(TYPED_WORD));
         for (int i = 0; i < NUMBER_OF_ADDED_SUGGESTIONS; ++i) {
-            list.add(new SuggestedWordInfo("" + i, 1, SuggestedWordInfo.KIND_CORRECTION,
-                    null /* sourceDict */,
-                    SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
-                    SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
+            list.add(createCorrectionWordInfo(Integer.toString(i)));
         }
 
         final SuggestedWords words = new SuggestedWords(
@@ -66,19 +91,9 @@
     }
 
     // Helper for testGetTransformedWordInfo
-    private SuggestedWordInfo createWordInfo(final String s) {
-        // Use 100 as the frequency because the numerical value does not matter as
-        // long as it's > 1 and < INT_MAX.
-        return new SuggestedWordInfo(s, 100,
-                SuggestedWordInfo.KIND_TYPED, null /* sourceDict */,
-                SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
-                new Random().nextInt(1000000) /* autoCommitFirstWordConfidence */);
-    }
-
-    // Helper for testGetTransformedWordInfo
     private SuggestedWordInfo transformWordInfo(final String info,
             final int trailingSingleQuotesCount) {
-        final SuggestedWordInfo suggestedWordInfo = createWordInfo(info);
+        final SuggestedWordInfo suggestedWordInfo = createTypedWordInfo(info);
         final SuggestedWordInfo returnedWordInfo =
                 Suggest.getTransformedSuggestedWordInfo(suggestedWordInfo,
                 Locale.ENGLISH, false /* isAllUpperCase */, false /* isFirstCharCapitalized */,
@@ -102,4 +117,35 @@
         result = transformWordInfo("didn't", 3);
         assertEquals(result.mWord, "didn't''");
     }
+
+    public void testGetTypedWordInfoOrNull() {
+        final String TYPED_WORD = "typed";
+        final int NUMBER_OF_ADDED_SUGGESTIONS = 5;
+        final ArrayList<SuggestedWordInfo> list = new ArrayList<>();
+        list.add(createTypedWordInfo(TYPED_WORD));
+        for (int i = 0; i < NUMBER_OF_ADDED_SUGGESTIONS; ++i) {
+            list.add(createCorrectionWordInfo(Integer.toString(i)));
+        }
+
+        // Make sure getTypedWordInfoOrNull() returns non-null object.
+        final SuggestedWords wordsWithTypedWord = new SuggestedWords(
+                list, null /* rawSuggestions */,
+                false /* typedWordValid */,
+                false /* willAutoCorrect */,
+                false /* isObsoleteSuggestions */,
+                false /* isPrediction*/,
+                SuggestedWords.INPUT_STYLE_NONE);
+        final SuggestedWordInfo typedWord = wordsWithTypedWord.getTypedWordInfoOrNull();
+        assertNotNull(typedWord);
+        assertEquals(TYPED_WORD, typedWord.mWord);
+
+        // Make sure getTypedWordInfoOrNull() returns null.
+        final SuggestedWords wordsWithoutTypedWord =
+                wordsWithTypedWord.getSuggestedWordsExcludingTypedWord(
+                        SuggestedWords.INPUT_STYLE_NONE);
+        assertNull(wordsWithoutTypedWord.getTypedWordInfoOrNull());
+
+        // Make sure getTypedWordInfoOrNull() returns null.
+        assertNull(SuggestedWords.EMPTY.getTypedWordInfoOrNull());
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtilsTests.java
index 91c9c37..66a12b9 100644
--- a/tests/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtilsTests.java
@@ -151,25 +151,25 @@
     }
 
     public void testRestorable() {
-        final InputMethodSubtype EN_UK_DVORAK =
+        final InputMethodSubtype EN_US_DVORAK =
                 AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
                         Locale.US.toString(), "dvorak");
         final InputMethodSubtype ZZ_AZERTY =
                 AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
                         SubtypeLocaleUtils.NO_LANGUAGE, "azerty");
-        assertEnUsDvorak(EN_UK_DVORAK);
+        assertEnUsDvorak(EN_US_DVORAK);
         assertAzerty(ZZ_AZERTY);
 
         // Make sure the subtype can be stored and restored in a deterministic manner.
-        final InputMethodSubtype[] subtypes = { EN_UK_DVORAK, ZZ_AZERTY };
+        final InputMethodSubtype[] subtypes = { EN_US_DVORAK, ZZ_AZERTY };
         final String prefSubtype = AdditionalSubtypeUtils.createPrefSubtypes(subtypes);
         final InputMethodSubtype[] restoredSubtypes =
                 AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype);
         assertEquals(2, restoredSubtypes.length);
-        final InputMethodSubtype restored_EN_UK_DVORAK = restoredSubtypes[0];
+        final InputMethodSubtype restored_EN_US_DVORAK = restoredSubtypes[0];
         final InputMethodSubtype restored_ZZ_AZERTY = restoredSubtypes[1];
 
-        assertEnUsDvorak(restored_EN_UK_DVORAK);
+        assertEnUsDvorak(restored_EN_US_DVORAK);
         assertAzerty(restored_ZZ_AZERTY);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
index 40e298c..5afbbbe 100644
--- a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
@@ -310,9 +310,11 @@
         assertNotNull("Hebrew", HEBREW);
 
         for (final RichInputMethodSubtype subtype : mSubtypesList) {
+            final InputMethodSubtype rawSubtype = subtype.getRawSubtype();
             final String subtypeName = SubtypeLocaleUtils
-                    .getSubtypeDisplayNameInSystemLocale(subtype.getRawSubtype());
-            if (subtype.equals(ARABIC) || subtype.equals(FARSI) || subtype.equals(HEBREW)) {
+                    .getSubtypeDisplayNameInSystemLocale(rawSubtype);
+            if (rawSubtype.equals(ARABIC) || rawSubtype.equals(FARSI)
+                    || rawSubtype.equals(HEBREW)) {
                 assertTrue(subtypeName, SubtypeLocaleUtils.isRtlLanguage(subtype));
             } else {
                 assertFalse(subtypeName, SubtypeLocaleUtils.isRtlLanguage(subtype));