Merge "Add a class to have global counters for LanguageModelDictContent."
diff --git a/common/Android.mk b/common/Android.mk
index 99aed4c..085543f 100644
--- a/common/Android.mk
+++ b/common/Android.mk
@@ -14,8 +14,9 @@
 
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_MODULE := latinime-common
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := jsr305
 LOCAL_SDK_VERSION := 21
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
@@ -23,4 +24,5 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := latinime-common-host
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := jsr305lib
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/src/com/android/inputmethod/latin/common/Constants.java b/common/src/com/android/inputmethod/latin/common/Constants.java
index 9a244d4..8f4a1e5 100644
--- a/common/src/com/android/inputmethod/latin/common/Constants.java
+++ b/common/src/com/android/inputmethod/latin/common/Constants.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin.common;
 
+import com.android.inputmethod.annotations.UsedForTesting;
+
 public final class Constants {
     public static final class Color {
         /**
@@ -305,6 +307,18 @@
     public static final int SCREEN_METRICS_LARGE_TABLET = 2;
     public static final int SCREEN_METRICS_SMALL_TABLET = 3;
 
+    @UsedForTesting
+    public static boolean isPhone(final int screenMetrics) {
+        return screenMetrics == SCREEN_METRICS_SMALL_PHONE
+                || screenMetrics == SCREEN_METRICS_LARGE_PHONE;
+    }
+
+    @UsedForTesting
+    public static boolean isTablet(final int screenMetrics) {
+        return screenMetrics == SCREEN_METRICS_SMALL_TABLET
+                || screenMetrics == SCREEN_METRICS_LARGE_TABLET;
+    }
+
     /**
      * Default capacity of gesture points container.
      * This constant is used by {@link com.android.inputmethod.keyboard.internal.BatchInputArbiter}
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/common/src/com/android/inputmethod/latin/common/InputPointers.java
similarity index 79%
rename from java/src/com/android/inputmethod/latin/InputPointers.java
rename to common/src/com/android/inputmethod/latin/common/InputPointers.java
index d57a881..40131ac 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/common/src/com/android/inputmethod/latin/common/InputPointers.java
@@ -14,18 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
-
-import android.util.Log;
-import android.util.SparseIntArray;
+package com.android.inputmethod.latin.common;
 
 import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.utils.ResizableIntArray;
 
 // TODO: This class is not thread-safe.
 public final class InputPointers {
-    private static final String TAG = InputPointers.class.getSimpleName();
     private static final boolean DEBUG_TIME = false;
 
     private final int mDefaultCapacity;
@@ -61,14 +55,14 @@
         mXCoordinates.addAt(index, x);
         mYCoordinates.addAt(index, y);
         mPointerIds.addAt(index, pointerId);
-        if (DebugFlags.DEBUG_ENABLED || DEBUG_TIME) {
+        if (DEBUG_TIME) {
             fillWithLastTimeUntil(index);
         }
         mTimes.addAt(index, time);
     }
 
     @UsedForTesting
-    void addPointer(int x, int y, int pointerId, int time) {
+    public void addPointer(int x, int y, int pointerId, int time) {
         mXCoordinates.add(x);
         mYCoordinates.add(y);
         mPointerIds.add(pointerId);
@@ -152,11 +146,6 @@
      * the sequence.
      */
     public int[] getTimes() {
-        if (DebugFlags.DEBUG_ENABLED || DEBUG_TIME) {
-            if (!isValidTimeStamps()) {
-                throw new RuntimeException("Time stamps are invalid.");
-            }
-        }
         return mTimes.getPrimitiveArray();
     }
 
@@ -165,25 +154,4 @@
         return "size=" + getPointerSize() + " id=" + mPointerIds + " time=" + mTimes
                 + " x=" + mXCoordinates + " y=" + mYCoordinates;
     }
-
-    private boolean isValidTimeStamps() {
-        final int[] times = mTimes.getPrimitiveArray();
-        final int[] pointerIds = mPointerIds.getPrimitiveArray();
-        final SparseIntArray lastTimeOfPointers = new SparseIntArray();
-        final int size = getPointerSize();
-        for (int i = 0; i < size; ++i) {
-            final int pointerId = pointerIds[i];
-            final int time = times[i];
-            final int lastTime = lastTimeOfPointers.get(pointerId, time);
-            if (time < lastTime) {
-                // dump
-                for (int j = 0; j < size; ++j) {
-                    Log.d(TAG, "--- (" + j + ") " + times[j]);
-                }
-                return false;
-            }
-            lastTimeOfPointers.put(pointerId, time);
-        }
-        return true;
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java b/common/src/com/android/inputmethod/latin/common/ResizableIntArray.java
similarity index 98%
rename from java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
rename to common/src/com/android/inputmethod/latin/common/ResizableIntArray.java
index 64c9e2c..ea23d8a 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
+++ b/common/src/com/android/inputmethod/latin/common/ResizableIntArray.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin.utils;
+package com.android.inputmethod.latin.common;
 
 import java.util.Arrays;
 
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/common/src/com/android/inputmethod/latin/common/StringUtils.java
similarity index 74%
rename from java/src/com/android/inputmethod/latin/utils/StringUtils.java
rename to common/src/com/android/inputmethod/latin/common/StringUtils.java
index f96ed04..be72603 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/common/src/com/android/inputmethod/latin/common/StringUtils.java
@@ -14,25 +14,23 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin.utils;
-
-import android.text.Spanned;
-import android.text.TextUtils;
+package com.android.inputmethod.latin.common;
 
 import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.common.Constants;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 public final class StringUtils {
     public static final int CAPITALIZE_NONE = 0;  // No caps, or mixed case
     public static final int CAPITALIZE_FIRST = 1; // First only
     public static final int CAPITALIZE_ALL = 2;   // All caps
 
+    @Nonnull
     private static final String EMPTY_STRING = "";
 
     private static final char CHAR_LINE_FEED = 0X000A;
@@ -47,12 +45,77 @@
         // This utility class is not publicly instantiable.
     }
 
-    public static int codePointCount(final CharSequence text) {
-        if (TextUtils.isEmpty(text)) return 0;
+    // Taken from android.text.TextUtils. We are extensively using this method in many places,
+    // some of which don't have the android libraries available.
+    /**
+     * Returns true if the string is null or 0-length.
+     * @param str the string to be examined
+     * @return true if str is null or zero length
+     */
+    public static boolean isEmpty(@Nullable final CharSequence str) {
+        return (str == null || str.length() == 0);
+    }
+
+    // Taken from android.text.TextUtils to cut the dependency to the Android framework.
+    /**
+     * Returns a string containing the tokens joined by delimiters.
+     * @param delimiter the delimiter
+     * @param tokens an array objects to be joined. Strings will be formed from
+     *     the objects by calling object.toString().
+     */
+    @Nonnull
+    public static String join(@Nonnull final CharSequence delimiter,
+            @Nonnull final Iterable<?> tokens) {
+        final StringBuilder sb = new StringBuilder();
+        boolean firstTime = true;
+        for (final Object token: tokens) {
+            if (firstTime) {
+                firstTime = false;
+            } else {
+                sb.append(delimiter);
+            }
+            sb.append(token);
+        }
+        return sb.toString();
+    }
+
+    // Taken from android.text.TextUtils to cut the dependency to the Android framework.
+    /**
+     * Returns true if a and b are equal, including if they are both null.
+     * <p><i>Note: In platform versions 1.1 and earlier, this method only worked well if
+     * both the arguments were instances of String.</i></p>
+     * @param a first CharSequence to check
+     * @param b second CharSequence to check
+     * @return true if a and b are equal
+     */
+    public static boolean equals(@Nullable final CharSequence a, @Nullable final CharSequence b) {
+        if (a == b) {
+            return true;
+        }
+        final int length;
+        if (a != null && b != null && (length = a.length()) == b.length()) {
+            if (a instanceof String && b instanceof String) {
+                return a.equals(b);
+            }
+            for (int i = 0; i < length; i++) {
+                if (a.charAt(i) != b.charAt(i)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public static int codePointCount(@Nullable final CharSequence text) {
+        if (isEmpty(text)) {
+            return 0;
+        }
         return Character.codePointCount(text, 0, text.length());
     }
 
-    public static String newSingleCodePointString(int codePoint) {
+    @Nonnull
+    public static String newSingleCodePointString(final int codePoint) {
         if (Character.charCount(codePoint) == 1) {
             // Optimization: avoid creating a temporary array for characters that are
             // represented by a single char value
@@ -62,9 +125,12 @@
         return new String(Character.toChars(codePoint));
     }
 
-    public static boolean containsInArray(final String text, final String[] array) {
+    public static boolean containsInArray(@Nonnull final String text,
+            @Nonnull final String[] array) {
         for (final String element : array) {
-            if (text.equals(element)) return true;
+            if (text.equals(element)) {
+                return true;
+            }
         }
         return false;
     }
@@ -74,19 +140,21 @@
      * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain
      * a comma character in it.
      */
+    @Nonnull
     private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ",";
 
-    public static boolean containsInCommaSplittableText(final String text,
-            final String extraValues) {
-        if (TextUtils.isEmpty(extraValues)) {
+    public static boolean containsInCommaSplittableText(@Nonnull final String text,
+            @Nullable final String extraValues) {
+        if (isEmpty(extraValues)) {
             return false;
         }
         return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
     }
 
-    public static String removeFromCommaSplittableTextIfExists(final String text,
-            final String extraValues) {
-        if (TextUtils.isEmpty(extraValues)) {
+    @Nonnull
+    public static String removeFromCommaSplittableTextIfExists(@Nonnull final String text,
+            @Nullable final String extraValues) {
+        if (isEmpty(extraValues)) {
             return EMPTY_STRING;
         }
         final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
@@ -99,7 +167,7 @@
                 result.add(element);
             }
         }
-        return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
+        return join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
     }
 
     /**
@@ -108,8 +176,10 @@
      * This method will always keep the first occurrence of all strings at their position
      * in the array, removing the subsequent ones.
      */
-    public static void removeDupes(final ArrayList<String> suggestions) {
-        if (suggestions.size() < 2) return;
+    public static void removeDupes(@Nonnull final ArrayList<String> suggestions) {
+        if (suggestions.size() < 2) {
+            return;
+        }
         int i = 1;
         // Don't cache suggestions.size(), since we may be removing items
         while (i < suggestions.size()) {
@@ -117,7 +187,7 @@
             // Compare each suggestion with each previous suggestion
             for (int j = 0; j < i; j++) {
                 final String previous = suggestions.get(j);
-                if (TextUtils.equals(cur, previous)) {
+                if (equals(cur, previous)) {
                     suggestions.remove(i);
                     i--;
                     break;
@@ -127,7 +197,9 @@
         }
     }
 
-    public static String capitalizeFirstCodePoint(final String s, final Locale locale) {
+    @Nonnull
+    public static String capitalizeFirstCodePoint(@Nonnull final String s,
+            @Nonnull final Locale locale) {
         if (s.length() <= 1) {
             return s.toUpperCase(locale);
         }
@@ -137,7 +209,9 @@
         return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff);
     }
 
-    public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) {
+    @Nonnull
+    public static String capitalizeFirstAndDowncaseRest(@Nonnull final String s,
+            @Nonnull final Locale locale) {
         if (s.length() <= 1) {
             return s.toUpperCase(locale);
         }
@@ -153,12 +227,14 @@
         return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff).toLowerCase(locale);
     }
 
-    private static final int[] EMPTY_CODEPOINTS = {};
-
-    public static int[] toCodePointArray(final CharSequence charSequence) {
+    @Nonnull
+    public static int[] toCodePointArray(@Nonnull final CharSequence charSequence) {
         return toCodePointArray(charSequence, 0, charSequence.length());
     }
 
+    @Nonnull
+    private static final int[] EMPTY_CODEPOINTS = {};
+
     /**
      * Converts a range of a string to an array of code points.
      * @param charSequence the source string.
@@ -166,7 +242,8 @@
      * @param endIndex the end index inside the string in java chars, exclusive.
      * @return a new array of code points. At most endIndex - startIndex, but possibly less.
      */
-    public static int[] toCodePointArray(final CharSequence charSequence,
+    @Nonnull
+    public static int[] toCodePointArray(@Nonnull final CharSequence charSequence,
             final int startIndex, final int endIndex) {
         final int length = charSequence.length();
         if (length <= 0) {
@@ -197,8 +274,8 @@
      * @param downCase if this is true, code points will be downcased before being copied.
      * @return the number of copied code points.
      */
-    public static int copyCodePointsAndReturnCodePointCount(final int[] destination,
-            final CharSequence charSequence, final int startIndex, final int endIndex,
+    public static int copyCodePointsAndReturnCodePointCount(@Nonnull final int[] destination,
+            @Nonnull final CharSequence charSequence, final int startIndex, final int endIndex,
             final boolean downCase) {
         int destIndex = 0;
         for (int index = startIndex; index < endIndex;
@@ -212,7 +289,8 @@
         return destIndex;
     }
 
-    public static int[] toSortedCodePointArray(final String string) {
+    @Nonnull
+    public static int[] toSortedCodePointArray(@Nonnull final String string) {
         final int[] codePoints = toCodePointArray(string);
         Arrays.sort(codePoints);
         return codePoints;
@@ -225,7 +303,9 @@
      * shorter than the array length.
      * @return a string constructed from the code point array.
      */
-    public static String getStringFromNullTerminatedCodePointArray(final int[] codePoints) {
+    @Nonnull
+    public static String getStringFromNullTerminatedCodePointArray(
+            @Nonnull final int[] codePoints) {
         int stringLength = codePoints.length;
         for (int i = 0; i < codePoints.length; i++) {
             if (codePoints[i] == 0) {
@@ -237,7 +317,7 @@
     }
 
     // This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE.
-    public static int getCapitalizationType(final String text) {
+    public static int getCapitalizationType(@Nonnull final String text) {
         // If the first char is not uppercase, then the word is either all lower case or
         // camel case, and in either case we return CAPITALIZE_NONE.
         final int len = text.length();
@@ -273,7 +353,7 @@
         return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
     }
 
-    public static boolean isIdenticalAfterUpcase(final String text) {
+    public static boolean isIdenticalAfterUpcase(@Nonnull final String text) {
         final int length = text.length();
         int i = 0;
         while (i < length) {
@@ -286,7 +366,7 @@
         return true;
     }
 
-    public static boolean isIdenticalAfterDowncase(final String text) {
+    public static boolean isIdenticalAfterDowncase(@Nonnull final String text) {
         final int length = text.length();
         int i = 0;
         while (i < length) {
@@ -299,8 +379,8 @@
         return true;
     }
 
-    public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
-            final int[] sortedSeparators) {
+    public static boolean isIdenticalAfterCapitalizeEachWord(@Nonnull final String text,
+            @Nonnull final int[] sortedSeparators) {
         boolean needsCapsNext = true;
         final int len = text.length();
         for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
@@ -319,8 +399,9 @@
 
     // TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph
     // which should be capitalized together in *some* cases.
-    public static String capitalizeEachWord(final String text, final int[] sortedSeparators,
-            final Locale locale) {
+    @Nonnull
+    public static String capitalizeEachWord(@Nonnull final String text,
+            @Nonnull final int[] sortedSeparators, @Nonnull final Locale locale) {
         final StringBuilder builder = new StringBuilder();
         boolean needsCapsNext = true;
         final int len = text.length();
@@ -354,9 +435,11 @@
      * TODO: This will return that "abc./def" and ".abc/def" look like URLs to keep down the
      * code complexity, but ideally it should not. It's acceptable for now.
      */
-    public static boolean lastPartLooksLikeURL(final CharSequence text) {
+    public static boolean lastPartLooksLikeURL(@Nonnull final CharSequence text) {
         int i = text.length();
-        if (0 == i) return false;
+        if (0 == i) {
+            return false;
+        }
         int wCount = 0;
         int slashCount = 0;
         boolean hasSlash = false;
@@ -393,11 +476,17 @@
         }
         // End of the text run.
         // If it starts with www and includes a period, then it looks like a URL.
-        if (wCount >= 3 && hasPeriod) return true;
+        if (wCount >= 3 && hasPeriod) {
+            return true;
+        }
         // If it starts with a slash, and the code point before is whitespace, it looks like an URL.
-        if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) return true;
+        if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) {
+            return true;
+        }
         // If it has both a period and a slash, it looks like an URL.
-        if (hasPeriod && hasSlash) return true;
+        if (hasPeriod && hasSlash) {
+            return true;
+        }
         // Otherwise, it doesn't look like an URL.
         return false;
     }
@@ -418,18 +507,24 @@
      * @param text the text to examine.
      * @return whether we're inside a double quote.
      */
-    public static boolean isInsideDoubleQuoteOrAfterDigit(final CharSequence text) {
+    public static boolean isInsideDoubleQuoteOrAfterDigit(@Nonnull final CharSequence text) {
         int i = text.length();
-        if (0 == i) return false;
+        if (0 == i) {
+            return false;
+        }
         int codePoint = Character.codePointBefore(text, i);
-        if (Character.isDigit(codePoint)) return true;
+        if (Character.isDigit(codePoint)) {
+            return true;
+        }
         int prevCodePoint = 0;
         while (i > 0) {
             codePoint = Character.codePointBefore(text, i);
             if (Constants.CODE_DOUBLE_QUOTE == codePoint) {
                 // If we see a double quote followed by whitespace, then that
                 // was a closing quote.
-                if (Character.isWhitespace(prevCodePoint)) return false;
+                if (Character.isWhitespace(prevCodePoint)) {
+                    return false;
+                }
             }
             if (Character.isWhitespace(codePoint) && Constants.CODE_DOUBLE_QUOTE == prevCodePoint) {
                 // If we see a double quote preceded by whitespace, then that
@@ -444,7 +539,7 @@
         return Constants.CODE_DOUBLE_QUOTE == codePoint;
     }
 
-    public static boolean isEmptyStringOrWhiteSpaces(final String s) {
+    public static boolean isEmptyStringOrWhiteSpaces(@Nonnull final String s) {
         final int N = codePointCount(s);
         for (int i = 0; i < N; ++i) {
             if (!Character.isWhitespace(s.codePointAt(i))) {
@@ -455,12 +550,13 @@
     }
 
     @UsedForTesting
-    public static String byteArrayToHexString(final byte[] bytes) {
+    @Nonnull
+    public static String byteArrayToHexString(@Nullable final byte[] bytes) {
         if (bytes == null || bytes.length == 0) {
             return EMPTY_STRING;
         }
         final StringBuilder sb = new StringBuilder();
-        for (byte b : bytes) {
+        for (final byte b : bytes) {
             sb.append(String.format("%02x", b & 0xff));
         }
         return sb.toString();
@@ -470,8 +566,9 @@
      * Convert hex string to byte array. The string length must be an even number.
      */
     @UsedForTesting
-    public static byte[] hexStringToByteArray(final String hexString) {
-        if (TextUtils.isEmpty(hexString)) {
+    @Nullable
+    public static byte[] hexStringToByteArray(@Nullable final String hexString) {
+        if (isEmpty(hexString)) {
             return null;
         }
         final int N = hexString.length();
@@ -487,15 +584,20 @@
         return bytes;
     }
 
-    public static String toUpperCaseOfStringForLocale(final String text,
-            final boolean needsToUpperCase, final Locale locale) {
-        if (text == null || !needsToUpperCase) return text;
+    @Nullable
+    public static String toUpperCaseOfStringForLocale(@Nullable final String text,
+            final boolean needsToUpperCase, @Nonnull final Locale locale) {
+        if (text == null || !needsToUpperCase) {
+            return text;
+        }
         return text.toUpperCase(locale);
     }
 
     public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase,
-            final Locale locale) {
-        if (!Constants.isLetterCode(code) || !needsToUpperCase) return code;
+            @Nonnull final Locale locale) {
+        if (!Constants.isLetterCode(code) || !needsToUpperCase) {
+            return code;
+        }
         final String text = newSingleCodePointString(code);
         final String casedText = toUpperCaseOfStringForLocale(
                 text, needsToUpperCase, locale);
@@ -503,7 +605,7 @@
                 ? casedText.codePointAt(0) : Constants.CODE_UNSPECIFIED;
     }
 
-    public static int getTrailingSingleQuotesCount(final CharSequence charSequence) {
+    public static int getTrailingSingleQuotesCount(@Nonnull final CharSequence charSequence) {
         final int lastIndex = charSequence.length() - 1;
         int i = lastIndex;
         while (i >= 0 && charSequence.charAt(i) == Constants.CODE_SINGLE_QUOTE) {
@@ -512,72 +614,36 @@
         return lastIndex - i;
     }
 
-    /**
-     * Splits the given {@code charSequence} with at occurrences of the given {@code regex}.
-     * <p>
-     * This is equivalent to
-     * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)}
-     * except that the spans are preserved in the result array.
-     * </p>
-     * @param charSequence the character sequence to be split.
-     * @param regex the regex pattern to be used as the separator.
-     * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty
-     * segments. Otherwise, trailing empty segments will be removed before being returned.
-     * @return the array which contains the result. All the spans in the <code>charSequence</code>
-     * is preserved.
-     */
-    @UsedForTesting
-    public static CharSequence[] split(final CharSequence charSequence, final String regex,
-            final boolean preserveTrailingEmptySegments) {
-        // A short-cut for non-spanned strings.
-        if (!(charSequence instanceof Spanned)) {
-            // -1 means that trailing empty segments will be preserved.
-            return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0);
-        }
-
-        // Hereafter, emulate String.split for CharSequence.
-        final ArrayList<CharSequence> sequences = new ArrayList<>();
-        final Matcher matcher = Pattern.compile(regex).matcher(charSequence);
-        int nextStart = 0;
-        boolean matched = false;
-        while (matcher.find()) {
-            sequences.add(charSequence.subSequence(nextStart, matcher.start()));
-            nextStart = matcher.end();
-            matched = true;
-        }
-        if (!matched) {
-            // never matched. preserveTrailingEmptySegments is ignored in this case.
-            return new CharSequence[] { charSequence };
-        }
-        sequences.add(charSequence.subSequence(nextStart, charSequence.length()));
-        if (!preserveTrailingEmptySegments) {
-            for (int i = sequences.size() - 1; i >= 0; --i) {
-                if (!TextUtils.isEmpty(sequences.get(i))) {
-                    break;
-                }
-                sequences.remove(i);
-            }
-        }
-        return sequences.toArray(new CharSequence[sequences.size()]);
-    }
-
     @UsedForTesting
     public static class Stringizer<E> {
-        public String stringize(final E element) {
-            return element != null ? element.toString() : "null";
+        @Nonnull
+        private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+        @UsedForTesting
+        @Nonnull
+        public String stringize(@Nullable final E element) {
+            if (element == null) {
+                return "null";
+            }
+            return element.toString();
         }
 
         @UsedForTesting
-        public final String join(final E[] array) {
+        @Nonnull
+        public final String join(@Nullable final E[] array) {
             return joinStringArray(toStringArray(array), null /* delimiter */);
         }
 
         @UsedForTesting
-        public final String join(final E[] array, final String delimiter) {
+        public final String join(@Nullable final E[] array, @Nullable final String delimiter) {
             return joinStringArray(toStringArray(array), delimiter);
         }
 
-        protected String[] toStringArray(final E[] array) {
+        @Nonnull
+        protected String[] toStringArray(@Nullable final E[] array) {
+            if (array == null) {
+                return EMPTY_STRING_ARRAY;
+            }
             final String[] stringArray = new String[array.length];
             for (int index = 0; index < array.length; index++) {
                 stringArray[index] = stringize(array[index]);
@@ -585,10 +651,9 @@
             return stringArray;
         }
 
-        protected String joinStringArray(final String[] stringArray, final String delimiter) {
-            if (stringArray == null) {
-                return "null";
-            }
+        @Nonnull
+        protected String joinStringArray(@Nonnull final String[] stringArray,
+                @Nullable final String delimiter) {
             if (delimiter == null) {
                 return Arrays.toString(stringArray);
             }
@@ -606,9 +671,8 @@
      * @param text the text to be examined.
      * @return {@code true} if the last composed word contains line-breaking separator.
      */
-    @UsedForTesting
-    public static boolean hasLineBreakCharacter(final String text) {
-        if (TextUtils.isEmpty(text)) {
+    public static boolean hasLineBreakCharacter(@Nullable final String text) {
+        if (isEmpty(text)) {
             return false;
         }
         for (int i = text.length() - 1; i >= 0; --i) {
diff --git a/java-overridable/src/com/android/inputmethod/latin/SpecialKeyDetector.java b/java-overridable/src/com/android/inputmethod/latin/SpecialKeyDetector.java
deleted file mode 100644
index 27b2f50..0000000
--- a/java-overridable/src/com/android/inputmethod/latin/SpecialKeyDetector.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2014, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-import android.view.KeyEvent;
-
-final class SpecialKeyDetector {
-    /**
-     * Special physical key detector
-     * @param context a context of this detector.
-     */
-    public SpecialKeyDetector(final Context context) {
-    }
-
-    /**
-     * Record a down key event.
-     * @param keyEvent a down key event.
-     */
-    public void onKeyDown(final KeyEvent keyEvent) {
-    }
-
-    /**
-     * Record an up key event.
-     * @param keyEvent an up key event.
-     */
-    public void onKeyUp(final KeyEvent keyEvent) {
-    }
-}
diff --git a/java-overridable/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.java b/java-overridable/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.java
index 1d60e0c..4a44aaa 100644
--- a/java-overridable/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.java
+++ b/java-overridable/src/com/android/inputmethod/latin/touchinputconsumer/GestureConsumer.java
@@ -19,8 +19,8 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.common.InputPointers;
 import com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer;
 
 import java.util.List;
diff --git a/java/res/values/donottranslate-debug-settings.xml b/java/res/values/donottranslate-debug-settings.xml
index cc8c1a0..c612010 100644
--- a/java/res/values/donottranslate-debug-settings.xml
+++ b/java/res/values/donottranslate-debug-settings.xml
@@ -22,7 +22,6 @@
     <string name="english_ime_debug_settings">Android Keyboard Debug settings</string>
     <string name="prefs_debug_mode">Debug Mode</string>
     <string name="prefs_force_non_distinct_multitouch">Force non-distinct multitouch</string>
-    <string name="prefs_force_physical_keyboard_special_key">Force physical keyboard special key</string>
     <string name="prefs_should_show_lxx_suggestion_ui">Show LXX suggestion UI</string>
     <!-- Option to enable sliding key input indicator. The user can see a rubber band-like effect during sliding key input. [CHAR LIMIT=30]-->
     <string name="sliding_key_input_preview">Show slide indicator</string>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 817bb51..08d8bb2 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -39,6 +39,8 @@
         <item>hi_ZZ</item>
         <item>sr_ZZ</item>
     </string-array>
+    <string name="subtype_in_root_locale_hi_ZZ">Hinglish</string>
+    <string name="subtype_in_root_locale_sr_ZZ">Srpski</string>
 
     <!-- Generic subtype label -->
     <string name="subtype_generic">%s</string>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 54bfc51..e1a72c4 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -214,8 +214,7 @@
     <!-- Description for Hinglish (https://en.wikipedia.org/wiki/Hinglish) keyboard subtype [CHAR LIMIT=25] -->
     <string name="subtype_hi_ZZ">Hinglish</string>
     <!-- Description for Serbian (Latin) keyboard subtype [CHAR LIMIT=25]
-         (Latin) can be an abbreviation to fit in the CHAR LIMIT.
-         Note for Serbian translator: this should be translated with Latin script and (Latin) should be omitted. -->
+         (Latin) can be an abbreviation to fit in the CHAR LIMIT. -->
     <string name="subtype_sr_ZZ">Serbian (Latin)</string>
     <!-- Description for English (UK) keyboard subtype with explicit keyboard layout [CHAR LIMIT=25]
          (UK) should be an abbreviation of United Kingdom to fit in the CHAR LIMIT.
@@ -233,16 +232,14 @@
          This should be identical to subtype_hi_ZZ aside from the trailing (%s). -->
     <string name="subtype_with_layout_hi_ZZ">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
     <!-- Description for Serbian (Latin) keyboard subtype with explicit keyboard layout [CHAR LIMIT=25]
-         This should be identical to subtype_sr_ZZ aside from the trailing (%s).
-         Note for Serbian translator: this should be translated with Latin script. -->
+         This should be identical to subtype_sr_ZZ aside from the trailing (%s). -->
     <string name="subtype_with_layout_sr_ZZ">Serbian (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
     <!-- Description for "LANGUAGE_NAME" (Traditional) keyboard subtype [CHAR LIMIT=25]
          (Traditional) can be an abbreviation to fit in the CHAR LIMIT. -->
     <string name="subtype_generic_traditional"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Traditional)</string>
     <!-- Description for "LANGUAGE_NAME" (Compact) keyboard subtype [CHAR LIMIT=25]
-         (Compact) can be an abbreviation to fit in the CHAR LIMIT.
-         TODO: Remove translatable=false once we are settled down with the naming. -->
-    <string name="subtype_generic_compact" translatable="false"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Compact)</string>
+         (Compact) can be an abbreviation to fit in the CHAR LIMIT. -->
+    <string name="subtype_generic_compact"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Compact)</string>
     <!-- This string is displayed in a language list that allows to choose a language for
 suggestions in a software keyboard. This setting won't give suggestions in any particular
 language, hence "No language".
@@ -363,6 +360,12 @@
     <string name="prefs_keypress_sound_volume_settings">Keypress sound volume</string>
     <!-- Title of the settings for key long press delay [CHAR LIMIT=35] -->
     <string name="prefs_key_longpress_timeout_settings">Key long press delay</string>
+    <!-- TODO: Let's finalize title of the settings and remove translatable="false" -->
+    <!-- Title of the settings for enabling Emoji palette triggered by the Alt key on physical keyboards [CHAR LIMIT=35] -->
+    <string name="prefs_enable_emoji_alt_physical_key" translatable="false">Emoji for physical keyboard</string>
+    <!-- TODO: Let's finalize title of the settings and remove translatable="false" -->
+    <!-- Description of the settings for enabling Emoji palette triggered by the Alt key on physical keyboards [CHAR LIMIT=64] -->
+    <string name="prefs_enable_emoji_alt_physical_key_summary" translatable="false">Physical Alt key shows the emoji palette</string>
 
     <!-- Title of the button to revert to the default value of the device in the settings dialog [CHAR LIMIT=15] -->
     <string name="button_default">Default</string>
diff --git a/java/res/xml/prefs_screen_advanced.xml b/java/res/xml/prefs_screen_advanced.xml
index 3298220..1fa6fd0 100644
--- a/java/res/xml/prefs_screen_advanced.xml
+++ b/java/res/xml/prefs_screen_advanced.xml
@@ -37,6 +37,12 @@
         latin:minValue="@integer/config_min_longpress_timeout"
         latin:maxValue="@integer/config_max_longpress_timeout"
         latin:stepValue="@integer/config_longpress_timeout_step" />
+    <CheckBoxPreference
+        android:key="pref_enable_emoji_alt_physical_key"
+        android:title="@string/prefs_enable_emoji_alt_physical_key"
+        android:summary="@string/prefs_enable_emoji_alt_physical_key_summary"
+        android:defaultValue="true"
+        android:persistent="true" />
     <!-- The settings for showing setup wizard application icon shouldn't be persistent and
          the default value is added programmatically. -->
     <CheckBoxPreference
diff --git a/java/res/xml/prefs_screen_debug.xml b/java/res/xml/prefs_screen_debug.xml
index 486d236..13edf3e 100644
--- a/java/res/xml/prefs_screen_debug.xml
+++ b/java/res/xml/prefs_screen_debug.xml
@@ -31,11 +31,6 @@
         android:defaultValue="false"
         android:persistent="true" />
     <CheckBoxPreference
-        android:key="force_physical_keyboard_special_key"
-        android:title="@string/prefs_force_physical_keyboard_special_key"
-        android:defaultValue="false"
-        android:persistent="true" />
-    <CheckBoxPreference
         android:key="pref_should_show_lxx_suggestion_ui"
         android:title="@string/prefs_should_show_lxx_suggestion_ui"
         android:defaultValue="true"
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 9f7caa4..bbda9f8 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -28,7 +28,7 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Locale;
 
diff --git a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
index 1fb597b..b78c357 100644
--- a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
@@ -16,42 +16,33 @@
 
 package com.android.inputmethod.compat;
 
+import android.annotation.TargetApi;
 import android.content.Context;
-import android.provider.UserDictionary.Words;
+import android.os.Build;
+import android.provider.UserDictionary;
 
-import java.lang.reflect.Method;
 import java.util.Locale;
 
 public final class UserDictionaryCompatUtils {
-    // UserDictionary.Words#addWord(Context, String, int, String, Locale) was introduced
-    // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
-    private static final Method METHOD_addWord = CompatUtils.getMethod(Words.class, "addWord",
-            Context.class, String.class, int.class, String.class, Locale.class);
-
     @SuppressWarnings("deprecation")
     public static void addWord(final Context context, final String word,
             final int freq, final String shortcut, final Locale locale) {
-        if (hasNewerAddWord()) {
-            CompatUtils.invoke(Words.class, null, METHOD_addWord, context, word, freq, shortcut,
-                    locale);
-        } else {
-            // Fall back to the pre-JellyBean method.
-            final int localeType;
-            if (null == locale) {
-                localeType = Words.LOCALE_TYPE_ALL;
-            } else {
-                final Locale currentLocale = context.getResources().getConfiguration().locale;
-                if (locale.equals(currentLocale)) {
-                    localeType = Words.LOCALE_TYPE_CURRENT;
-                } else {
-                    localeType = Words.LOCALE_TYPE_ALL;
-                }
-            }
-            Words.addWord(context, word, freq, localeType);
+        if (BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            addWordWithShortcut(context, word, freq, shortcut, locale);
+            return;
         }
+        // Fall back to the pre-JellyBean method.
+        final Locale currentLocale = context.getResources().getConfiguration().locale;
+        final int localeType = currentLocale.equals(locale)
+                ? UserDictionary.Words.LOCALE_TYPE_CURRENT : UserDictionary.Words.LOCALE_TYPE_ALL;
+        UserDictionary.Words.addWord(context, word, freq, localeType);
     }
 
-    public static final boolean hasNewerAddWord() {
-        return null != METHOD_addWord;
+    // {@link UserDictionary.Words#addWord(Context,String,int,String,Locale)} was introduced
+    // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    private static void addWordWithShortcut(final Context context, final String word,
+            final int freq, final String shortcut, final Locale locale) {
+        UserDictionary.Words.addWord(context, word, freq, shortcut, locale);
     }
 }
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index 92bcda4..a1226dc 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -19,7 +19,7 @@
 import com.android.inputmethod.annotations.ExternallyReferenced;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import javax.annotation.Nonnull;
 
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 04a0f1e..6b2094b 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -38,7 +38,7 @@
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Arrays;
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index ac66f7c..cdd632b 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -16,8 +16,8 @@
 
 package com.android.inputmethod.keyboard;
 
-import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
 
 public interface KeyboardActionListener {
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index e66523b..6dbd947 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -56,9 +56,9 @@
 import com.android.inputmethod.latin.RichInputMethodSubtype;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.DebugSettings;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 import java.util.Locale;
@@ -451,13 +451,6 @@
         windowContentView.addView(mDrawingPreviewPlacerView);
     }
 
-    // Implements {@link DrawingHandler.Callbacks} method.
-    @Override
-    public void dismissAllKeyPreviews() {
-        mKeyPreviewChoreographer.dismissAllKeyPreviews();
-        PointerTracker.setReleasedKeyGraphicsToAllKeys();
-    }
-
     @Override
     public void showKeyPreview(final Key key) {
         // If the key is invalid or has no key preview, we must not show key preview.
@@ -673,6 +666,12 @@
     @Override
     public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
         locatePreviewPlacerView();
+        // Dismiss another {@link MoreKeysPanel} that may be being showed.
+        onDismissMoreKeysPanel();
+        // Dismiss all key previews that may be being showed.
+        PointerTracker.setReleasedKeyGraphicsToAllKeys();
+        // Dismiss  sliding key input preview that may be being showed.
+        dismissSlidingKeyInputPreview();
         panel.showInParent(mDrawingPreviewPlacerView);
         mMoreKeysPanel = panel;
     }
@@ -740,7 +739,7 @@
     public void cancelAllOngoingEvents() {
         mKeyTimerHandler.cancelAllMessages();
         mDrawingHandler.cancelAllMessages();
-        dismissAllKeyPreviews();
+        PointerTracker.setReleasedKeyGraphicsToAllKeys();
         dismissGestureFloatingPreviewText();
         dismissSlidingKeyInputPreview();
         PointerTracker.dismissAllMoreKeysPanels();
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index abcfff8..f0de86f 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -24,7 +24,7 @@
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 public final class MoreKeysKeyboard extends Keyboard {
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index fe6270f..5a540ff 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -31,9 +31,9 @@
 import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionParams;
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
 import com.android.inputmethod.keyboard.internal.TypingTimeRecorder;
-import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
index c8e832b..a9711ae 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
@@ -30,7 +30,6 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.KeyboardLayoutSet;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.common.Constants;
 import com.android.inputmethod.latin.settings.Settings;
 
 import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java b/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java
index efb365e..77d0e7a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/BatchInputArbiter.java
@@ -16,8 +16,8 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
 
 /**
  * This class arbitrates batch input.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
index 5797b7e..2e2ed52 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
@@ -17,7 +17,7 @@
 package com.android.inputmethod.keyboard.internal;
 
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import android.text.TextUtils;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
index 1a55359..6513485 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
@@ -29,7 +29,6 @@
 public class DrawingHandler extends LeakGuardHandlerWrapper<Callbacks> {
     public interface Callbacks {
         public void dismissKeyPreviewWithoutDelay(Key key);
-        public void dismissAllKeyPreviews();
         public void showGestureFloatingPreviewText(SuggestedWords suggestedWords);
     }
 
@@ -60,20 +59,11 @@
         sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, key), delay);
     }
 
-    private void cancelAllDismissKeyPreviews() {
-        removeMessages(MSG_DISMISS_KEY_PREVIEW);
-        final Callbacks callbacks = getOwnerInstance();
-        if (callbacks == null) {
-            return;
-        }
-        callbacks.dismissAllKeyPreviews();
-    }
-
     public void dismissGestureFloatingPreviewText(final long delay) {
         sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
     }
 
     public void cancelAllMessages() {
-        cancelAllDismissKeyPreviews();
+        removeMessages(MSG_DISMISS_KEY_PREVIEW);
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java
index 7d09e9d..07ef009 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java
@@ -16,7 +16,7 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import com.android.inputmethod.latin.utils.ResizableIntArray;
+import com.android.inputmethod.latin.common.ResizableIntArray;
 
 /**
  * This class holds drawing points to represent a gesture stroke on the screen.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
index 99ec18f..3e90111 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java
@@ -18,9 +18,9 @@
 
 import android.util.Log;
 
-import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.ResizableIntArray;
+import com.android.inputmethod.latin.common.InputPointers;
+import com.android.inputmethod.latin.common.ResizableIntArray;
 
 /**
  * This class holds event points to recognize a gesture stroke.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java
index 67683e2..4d998e4 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java
@@ -24,7 +24,7 @@
 import android.os.SystemClock;
 
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.ResizableIntArray;
+import com.android.inputmethod.latin.common.ResizableIntArray;
 
 /**
  * This class holds drawing points to represent a gesture trail. The gesture trail may contain
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
index ddb193e..d376487 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
@@ -28,7 +28,6 @@
 
 import java.util.ArrayDeque;
 import java.util.HashMap;
-import java.util.HashSet;
 
 /**
  * This class controls pop up key previews. This class decides:
@@ -69,12 +68,6 @@
         return mShowingKeyPreviewViews.containsKey(key);
     }
 
-    public void dismissAllKeyPreviews() {
-        for (final Key key : new HashSet<>(mShowingKeyPreviewViews.keySet())) {
-            dismissKeyPreview(key, false /* withAnimation */);
-        }
-    }
-
     public void dismissKeyPreview(final Key key, final boolean withAnimation) {
         if (key == null) {
             return;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index d33e53a..63aab96 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -20,7 +20,7 @@
 import static com.android.inputmethod.latin.common.Constants.CODE_UNSPECIFIED;
 
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 /**
  * The string parser of the key specification.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 6c75fb6..c739bf3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -34,8 +34,8 @@
 import com.android.inputmethod.keyboard.KeyboardTheme;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 import com.android.inputmethod.latin.utils.XmlParseUtils;
 import com.android.inputmethod.latin.utils.XmlParseUtils.ParseException;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index 4392dbc..a0bb406 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -22,9 +22,9 @@
 import com.android.inputmethod.compat.CharacterCompat;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 76459f8..8e9b5c6 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -24,6 +24,8 @@
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.makedict.DictionaryHeader;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
@@ -33,7 +35,6 @@
 import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.FileUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.WordInputEventForPersonalization;
 
 import java.io.File;
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 08e1983..59763c0 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -30,9 +30,9 @@
 
 import com.android.inputmethod.annotations.ExternallyReferenced;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.personalization.AccountUtils;
 import com.android.inputmethod.latin.utils.ExecutorUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.io.File;
 import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java b/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java
new file mode 100644
index 0000000..8116a49
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.view.KeyEvent;
+
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.latin.settings.Settings;
+
+/**
+ * A class for detecting Emoji-Alt physical key.
+ */
+final class EmojiAltPhysicalKeyDetector {
+    // True if the Alt key has been used as a modifier. In this case the Alt key up isn't
+    // recognized as an emoji key.
+    private boolean mAltHasBeenUsedAsAModifier;
+
+    /**
+     * Record a down key event.
+     * @param keyEvent a down key event.
+     */
+    public void onKeyDown(final KeyEvent keyEvent) {
+        if (isAltKey(keyEvent)) {
+            mAltHasBeenUsedAsAModifier = false;
+        }
+        if (containsAltModifier(keyEvent)) {
+            mAltHasBeenUsedAsAModifier = true;
+        }
+    }
+
+    /**
+     * Determine whether an up key event is a special key up or not.
+     * @param keyEvent an up key event.
+     */
+    public void onKeyUp(final KeyEvent keyEvent) {
+        if (keyEvent.isCanceled()) {
+            // This key up event was a part of key combinations and should be ignored.
+            return;
+        }
+        if (!isAltKey(keyEvent)) {
+            mAltHasBeenUsedAsAModifier |= containsAltModifier(keyEvent);
+            return;
+        }
+        if (containsAltModifier(keyEvent)) {
+            mAltHasBeenUsedAsAModifier = true;
+            return;
+        }
+        if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) {
+            return;
+        }
+        if (!mAltHasBeenUsedAsAModifier) {
+            onEmojiAltKeyDetected();
+        }
+    }
+
+    private static void onEmojiAltKeyDetected() {
+        KeyboardSwitcher.getInstance().onToggleEmojiKeyboard();
+    }
+
+    private static boolean isAltKey(final KeyEvent keyEvent) {
+        final int keyCode = keyEvent.getKeyCode();
+        return keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT;
+    }
+
+    private static boolean containsAltModifier(final KeyEvent keyEvent) {
+        final int metaState = keyEvent.getMetaState();
+        // TODO: Support multiple keyboards. Take device id into account.
+        switch (keyEvent.getKeyCode()) {
+        case KeyEvent.KEYCODE_ALT_LEFT:
+            // Return true if Left-Alt is pressed with Right-Alt pressed.
+            return (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
+        case KeyEvent.KEYCODE_ALT_RIGHT:
+            // Return true if Right-Alt is pressed with Left-Alt pressed.
+            return (metaState & KeyEvent.META_ALT_LEFT_ON) != 0;
+        default:
+            return (metaState & (KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON)) != 0;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 0022220..37effee 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -24,8 +24,8 @@
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
 
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.InputTypeUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index c4c3894..9fcdb22 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -20,6 +20,7 @@
 
 import com.android.inputmethod.event.Event;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
 
 import java.util.ArrayList;
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 11cbec3..5fa28d5 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -77,6 +77,7 @@
 import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.define.ProductionFlags;
 import com.android.inputmethod.latin.inputlogic.InputLogic;
@@ -167,7 +168,8 @@
     @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
     final SubtypeSwitcher mSubtypeSwitcher;
     private final SubtypeState mSubtypeState = new SubtypeState();
-    private final SpecialKeyDetector mSpecialKeyDetector;
+    private final EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector =
+            new EmojiAltPhysicalKeyDetector();
     private StatsUtilsManager mStatsUtilsManager;
     // Working variable for {@link #startShowingInputView()} and
     // {@link #onEvaluateInputViewShown()}.
@@ -545,7 +547,6 @@
         mSettings = Settings.getInstance();
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
-        mSpecialKeyDetector = new SpecialKeyDetector(this);
         mStatsUtilsManager = StatsUtilsManager.getInstance();
         mIsHardwareAcceleratedDrawingEnabled =
                 InputMethodServiceCompatUtils.enableHardwareAcceleration(this);
@@ -1765,7 +1766,8 @@
     // Hooks for hardware keyboard
     @Override
     public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
-        mSpecialKeyDetector.onKeyDown(keyEvent);
+        // TODO: This should be processed in {@link InputLogic}.
+        mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent);
         if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
             return super.onKeyDown(keyCode, keyEvent);
         }
@@ -1786,7 +1788,8 @@
 
     @Override
     public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) {
-        mSpecialKeyDetector.onKeyUp(keyEvent);
+        // TODO: This should be processed in {@link InputLogic}.
+        mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent);
         if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
             return super.onKeyUp(keyCode, keyEvent);
         }
diff --git a/java/src/com/android/inputmethod/latin/NgramContext.java b/java/src/com/android/inputmethod/latin/NgramContext.java
index 0ccea47..82a1327 100644
--- a/java/src/com/android/inputmethod/latin/NgramContext.java
+++ b/java/src/com/android/inputmethod/latin/NgramContext.java
@@ -20,7 +20,7 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Arrays;
 
diff --git a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
index 93598d2..a65304c 100644
--- a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
@@ -18,7 +18,7 @@
 
 import com.android.inputmethod.keyboard.internal.KeySpecParser;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 0763ef8..834f747 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -35,6 +35,7 @@
 
 import com.android.inputmethod.compat.InputConnectionCompatUtils;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
@@ -42,7 +43,6 @@
 import com.android.inputmethod.latin.utils.NgramContextUtils;
 import com.android.inputmethod.latin.utils.ScriptUtils;
 import com.android.inputmethod.latin.utils.SpannableStringUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.TextRange;
 
 import javax.annotation.Nonnull;
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 17df1ea..bb7d243 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -21,11 +21,11 @@
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
 import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SuggestionResults;
 
 import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index a642889..b84de69 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -21,8 +21,8 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 8830521..0b77f2c 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -20,9 +20,10 @@
 import com.android.inputmethod.event.Event;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index a67f461..bafea17 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -41,7 +41,6 @@
 import com.android.inputmethod.keyboard.TextDecoratorUiOperator;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.LastComposedWord;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.NgramContext;
@@ -52,6 +51,8 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
@@ -61,7 +62,6 @@
 import com.android.inputmethod.latin.utils.InputTypeUtils;
 import com.android.inputmethod.latin.utils.RecapitalizeStatus;
 import com.android.inputmethod.latin.utils.StatsUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.TextRange;
 
 import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
index 5f391dd..ddc4ad9 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -21,10 +21,10 @@
 import android.os.Message;
 
 import com.android.inputmethod.compat.LooperCompatUtils;
-import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
+import com.android.inputmethod.latin.common.InputPointers;
 
 /**
  * A helper to manage deferred tasks for the input logic.
diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
index e7808e4..388d578 100644
--- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
+++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
@@ -21,8 +21,8 @@
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.NgramContext;
 import com.android.inputmethod.latin.NgramContext.WordInfo;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.CombinedFormatUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
index a9884ba..554edc8 100644
--- a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java
@@ -19,9 +19,9 @@
 import android.os.Bundle;
 
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.Constants;
 import com.android.inputmethod.latin.define.ProductionFlags;
 
-
 /**
  * "Appearance" settings sub screen.
  */
@@ -30,8 +30,8 @@
     public void onCreate(final Bundle icicle) {
         super.onCreate(icicle);
         addPreferencesFromResource(R.xml.prefs_screen_appearance);
-        if (!ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED
-                || !Settings.getInstance().getCurrent().isTablet()) {
+        if (!ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED ||
+                Constants.isPhone(Settings.readScreenMetrics(getResources()))) {
             removePreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD);
         }
     }
@@ -43,4 +43,4 @@
                 findPreference(Settings.PREF_CUSTOM_INPUT_STYLES));
         ThemeSettingsFragment.updateKeyboardThemeSummary(findPreference(Settings.SCREEN_THEME));
     }
-}
\ No newline at end of file
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index 768cba9..4985c2f 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -27,8 +27,6 @@
 public final class DebugSettings {
     public static final String PREF_DEBUG_MODE = "debug_mode";
     public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch";
-    public static final String PREF_FORCE_PHYSICAL_KEYBOARD_SPECIAL_KEY =
-            "force_physical_keyboard_special_key";
     public static final String PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS =
             "pref_has_custom_key_preview_animation_params";
     public static final String PREF_KEY_PREVIEW_DISMISS_DURATION =
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
index 475f1de..2e5c3c4 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
@@ -142,8 +142,7 @@
             mServiceNeedsRestart = true;
             return;
         }
-        if (key.equals(DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH)
-                || key.equals(DebugSettings.PREF_FORCE_PHYSICAL_KEYBOARD_SPECIAL_KEY)) {
+        if (key.equals(DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH)) {
             mServiceNeedsRestart = true;
             return;
         }
diff --git a/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java b/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java
index c9e9dc8..0fd94b0 100644
--- a/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java
+++ b/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java
@@ -46,7 +46,6 @@
         // correctly set for it to work on a new device.
         DebugSettings.PREF_DEBUG_MODE,
         DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH,
-        DebugSettings.PREF_FORCE_PHYSICAL_KEYBOARD_SPECIAL_KEY,
         DebugSettings.PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS,
         DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION,
         DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_X_SCALE,
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 5e23693..19db606 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -29,11 +29,11 @@
 import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 import com.android.inputmethod.latin.utils.RunInLocale;
 import com.android.inputmethod.latin.utils.StatsUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.Collections;
 import java.util.Locale;
@@ -97,6 +97,8 @@
             "pref_vibration_duration_settings";
     public static final String PREF_KEYPRESS_SOUND_VOLUME = "pref_keypress_sound_volume";
     public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout";
+    public static final String PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY =
+            "pref_enable_emoji_alt_physical_key";
     public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail";
     public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT =
             "pref_gesture_floating_preview_text";
@@ -211,6 +213,10 @@
         return mSettingsValues.mBlockPotentiallyOffensive;
     }
 
+    public static int readScreenMetrics(final Resources res) {
+        return res.getInteger(R.integer.config_screen_metrics);
+    }
+
     // Accessed from the settings interface, hence public
     public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
             final Resources res) {
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 660be06..bdb4e64 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -29,7 +29,6 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodManager;
 import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.common.Constants;
 import com.android.inputmethod.latin.utils.AsyncResultHolder;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
@@ -80,6 +79,7 @@
     public final boolean mSlidingKeyInputPreviewEnabled;
     public final boolean mPhraseGestureEnabled;
     public final int mKeyLongpressTimeout;
+    public final boolean mEnableEmojiAltPhysicalKey;
     public final boolean mEnableMetricsLogging;
     public final boolean mShouldShowLxxSuggestionUi;
     // Use split layout for keyboard.
@@ -157,7 +157,7 @@
         mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration());
         mEnableMetricsLogging = prefs.getBoolean(Settings.PREF_ENABLE_METRICS_LOGGING, true);
         mIsSplitKeyboardEnabled = prefs.getBoolean(Settings.PREF_ENABLE_SPLIT_KEYBOARD, false);
-        mScreenMetrics = res.getInteger(R.integer.config_screen_metrics);
+        mScreenMetrics = Settings.readScreenMetrics(res);
 
         mShouldShowLxxSuggestionUi = Settings.SHOULD_SHOW_LXX_SUGGESTION_UI
                 && prefs.getBoolean(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, true);
@@ -166,6 +166,8 @@
         mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res);
         mKeypressSoundVolume = Settings.readKeypressSoundVolume(prefs, res);
         mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res);
+        mEnableEmojiAltPhysicalKey = prefs.getBoolean(
+                Settings.PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY, true);
         mAutoCorrectionThreshold = readAutoCorrectionThreshold(res,
                 autoCorrectionThresholdRawValue);
         mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
@@ -221,11 +223,6 @@
         return mEnableMetricsLogging;
     }
 
-    public boolean isTablet() {
-        return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET
-                || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET;
-    }
-
     public boolean isApplicationSpecifiedCompletionsOn() {
         return mInputAttributes.mApplicationSpecifiedCompletionOn;
     }
diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
index 46aef89..70d97a5 100644
--- a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
+++ b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
@@ -23,7 +23,7 @@
 import com.android.inputmethod.latin.PunctuationSuggestions;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Arrays;
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index ac395bf..2c690ae 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -28,7 +28,7 @@
 
 import com.android.inputmethod.compat.TextInfoCompatUtils;
 import com.android.inputmethod.latin.NgramContext;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.SpannableStringUtils;
 
 import java.util.ArrayList;
 import java.util.Locale;
@@ -71,9 +71,10 @@
             if (!subText.toString().contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
                 continue;
             }
-            final CharSequence[] splitTexts = StringUtils.split(subText,
+            // Split preserving spans.
+            final CharSequence[] splitTexts = SpannableStringUtils.split(subText,
                     AndroidSpellCheckerService.SINGLE_QUOTE,
-                    true /* preserveTrailingEmptySegments */ );
+                    true /* preserveTrailingEmptySegments */);
             if (splitTexts == null || splitTexts.length <= 1) {
                 continue;
             }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 514bfca..3ad8fb9 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -34,11 +34,11 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
 import com.android.inputmethod.latin.utils.ScriptUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SuggestionResults;
 
 import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index b18fcc7..b71bd1f 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -344,12 +344,6 @@
         if (mSuggestedWords.size() <= mStartIndexOfMoreSuggestions) {
             return false;
         }
-        // Dismiss another {@link MoreKeysPanel} that may be being showed, for example
-        // {@link MoreKeysKeyboardView}.
-        mMainKeyboardView.onDismissMoreKeysPanel();
-        // Dismiss all key previews and sliding key input preview that may be being showed.
-        mMainKeyboardView.dismissAllKeyPreviews();
-        mMainKeyboardView.dismissSlidingKeyInputPreview();
         final int stripWidth = getWidth();
         final View container = mMoreSuggestionsContainer;
         final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight();
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index 02ace6a..2aac7c5 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -31,6 +31,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
index ce25fe6..23ffde2 100644
--- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
@@ -18,6 +18,7 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.makedict.DictionaryHeader;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.personalization.PersonalizationHelper;
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index b087e9e..0db63fd 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -21,6 +21,7 @@
 
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
 
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
index d0df724..f9839eb 100644
--- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
@@ -19,15 +19,17 @@
 import java.util.ArrayList;
 import java.util.Collection;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 public final class CollectionUtils {
     private CollectionUtils() {
         // This utility class is not publicly instantiable.
     }
 
-    public static <E> ArrayList<E> arrayAsList(final E[] array, final int start, final int end) {
-        if (array == null) {
-            throw new NullPointerException();
-        }
+    @Nonnull
+    public static <E> ArrayList<E> arrayAsList(@Nonnull final E[] array, final int start,
+            final int end) {
         if (start < 0 || start > end || end > array.length) {
             throw new IllegalArgumentException();
         }
@@ -44,7 +46,7 @@
      * @param c Collection to test.
      * @return Whether c contains no elements.
      */
-    public static boolean isNullOrEmpty(final Collection<?> c) {
+    public static boolean isNullOrEmpty(@Nullable final Collection<?> c) {
         return c == null || c.isEmpty();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
index 87df013..3a97059 100644
--- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
@@ -16,7 +16,7 @@
 
 package com.android.inputmethod.latin.utils;
 
-import java.util.Arrays;
+import javax.annotation.Nonnull;
 
 public final class CoordinateUtils {
     private static final int INDEX_X = 0;
@@ -27,32 +27,35 @@
         // This utility class is not publicly instantiable.
     }
 
+    @Nonnull
     public static int[] newInstance() {
         return new int[ELEMENT_SIZE];
     }
 
-    public static int x(final int[] coords) {
+    public static int x(@Nonnull final int[] coords) {
         return coords[INDEX_X];
     }
 
-    public static int y(final int[] coords) {
+    public static int y(@Nonnull final int[] coords) {
         return coords[INDEX_Y];
     }
 
-    public static void set(final int[] coords, final int x, final int y) {
+    public static void set(@Nonnull final int[] coords, final int x, final int y) {
         coords[INDEX_X] = x;
         coords[INDEX_Y] = y;
     }
 
-    public static void copy(final int[] destination, final int[] source) {
+    public static void copy(@Nonnull final int[] destination, @Nonnull final int[] source) {
         destination[INDEX_X] = source[INDEX_X];
         destination[INDEX_Y] = source[INDEX_Y];
     }
 
+    @Nonnull
     public static int[] newCoordinateArray(final int arraySize) {
         return new int[ELEMENT_SIZE * arraySize];
     }
 
+    @Nonnull
     public static int[] newCoordinateArray(final int arraySize,
             final int defaultX, final int defaultY) {
         final int[] result = new int[ELEMENT_SIZE * arraySize];
@@ -62,30 +65,30 @@
         return result;
     }
 
-    public static int xFromArray(final int[] coordsArray, final int index) {
+    public static int xFromArray(@Nonnull final int[] coordsArray, final int index) {
         return coordsArray[ELEMENT_SIZE * index + INDEX_X];
     }
 
-    public static int yFromArray(final int[] coordsArray, final int index) {
+    public static int yFromArray(@Nonnull final int[] coordsArray, final int index) {
         return coordsArray[ELEMENT_SIZE * index + INDEX_Y];
     }
 
-    public static int[] coordinateFromArray(final int[] coordsArray, final int index) {
-        final int baseIndex = ELEMENT_SIZE * index;
-        return Arrays.copyOfRange(coordsArray, baseIndex, baseIndex + ELEMENT_SIZE);
+    @Nonnull
+    public static int[] coordinateFromArray(@Nonnull final int[] coordsArray, final int index) {
+        final int[] coords = newInstance();
+        set(coords, xFromArray(coordsArray, index), yFromArray(coordsArray, index));
+        return coords;
     }
 
-    public static void setXYInArray(final int[] coordsArray, final int index,
+    public static void setXYInArray(@Nonnull final int[] coordsArray, final int index,
             final int x, final int y) {
         final int baseIndex = ELEMENT_SIZE * index;
         coordsArray[baseIndex + INDEX_X] = x;
         coordsArray[baseIndex + INDEX_Y] = y;
     }
 
-    public static void setCoordinateInArray(final int[] coordsArray, final int index,
-            final int[] coords) {
-        final int baseIndex = ELEMENT_SIZE * index;
-        coordsArray[baseIndex + INDEX_X] = coords[INDEX_X];
-        coordsArray[baseIndex + INDEX_Y] = coords[INDEX_Y];
+    public static void setCoordinateInArray(@Nonnull final int[] coordsArray, final int index,
+            @Nonnull final int[] coords) {
+        setXYInArray(coordsArray, index, x(coords), y(coords));
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
index 8f0f9bb..56c8249 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
@@ -40,6 +40,7 @@
 import com.android.inputmethod.latin.RichInputMethodSubtype;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
 
 /**
diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index e3cac97..21daddc 100644
--- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin.utils;
 
+import com.android.inputmethod.latin.common.StringUtils;
+
 import java.util.Locale;
 
 /**
diff --git a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
index cea1d1d..c41817f 100644
--- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
@@ -24,6 +24,12 @@
 import android.text.style.SuggestionSpan;
 import android.text.style.URLSpan;
 
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public final class SpannableStringUtils {
     /**
      * Copies the spans from the region <code>start...end</code> in
@@ -125,4 +131,53 @@
         final URLSpan[] spans = spanned.getSpans(startIndex - 1, endIndex + 1, URLSpan.class);
         return null != spans && spans.length > 0;
     }
+
+    /**
+     * Splits the given {@code charSequence} with at occurrences of the given {@code regex}.
+     * <p>
+     * This is equivalent to
+     * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)}
+     * except that the spans are preserved in the result array.
+     * </p>
+     * @param charSequence the character sequence to be split.
+     * @param regex the regex pattern to be used as the separator.
+     * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty
+     * segments. Otherwise, trailing empty segments will be removed before being returned.
+     * @return the array which contains the result. All the spans in the <code>charSequence</code>
+     * is preserved.
+     */
+    @UsedForTesting
+    public static CharSequence[] split(final CharSequence charSequence, final String regex,
+            final boolean preserveTrailingEmptySegments) {
+        // A short-cut for non-spanned strings.
+        if (!(charSequence instanceof Spanned)) {
+            // -1 means that trailing empty segments will be preserved.
+            return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0);
+        }
+
+        // Hereafter, emulate String.split for CharSequence.
+        final ArrayList<CharSequence> sequences = new ArrayList<>();
+        final Matcher matcher = Pattern.compile(regex).matcher(charSequence);
+        int nextStart = 0;
+        boolean matched = false;
+        while (matcher.find()) {
+            sequences.add(charSequence.subSequence(nextStart, matcher.start()));
+            nextStart = matcher.end();
+            matched = true;
+        }
+        if (!matched) {
+            // never matched. preserveTrailingEmptySegments is ignored in this case.
+            return new CharSequence[] { charSequence };
+        }
+        sequences.add(charSequence.subSequence(nextStart, charSequence.length()));
+        if (!preserveTrailingEmptySegments) {
+            for (int i = sequences.size() - 1; i >= 0; --i) {
+                if (!TextUtils.isEmpty(sequences.get(i))) {
+                    break;
+                }
+                sequences.remove(i);
+            }
+        }
+        return sequences.toArray(new CharSequence[sequences.size()]);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index eb85c1b..55c1dc9 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -28,10 +28,10 @@
 
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodSubtype;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Locale;
 
 /**
@@ -59,7 +59,8 @@
     // Keyboard layout to subtype name resource id map.
     private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>();
     // Exceptional locale whose name should be displayed in Locale.ROOT.
-    static final HashSet<String> sExceptionalLocaleDisplayedInRootLocale = new HashSet<>();
+    private static final HashMap<String, Integer> sExceptionalLocaleDisplayedInRootLocale =
+            new HashMap<>();
     // Exceptional locale to subtype name resource id map.
     private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>();
     // Exceptional locale to subtype name with layout resource id map.
@@ -73,6 +74,8 @@
             "string/subtype_with_layout_";
     private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX =
             "string/subtype_no_language_";
+    private static final String SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX =
+            "string/subtype_in_root_locale_";
     // Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
     // This is for compatibility to keep the same subtype ids as pre-JellyBean.
     private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
@@ -117,7 +120,10 @@
         final String[] exceptionalLocaleInRootLocale = res.getStringArray(
                 R.array.subtype_locale_displayed_in_root_locale);
         for (int i = 0; i < exceptionalLocaleInRootLocale.length; i++) {
-            sExceptionalLocaleDisplayedInRootLocale.add(exceptionalLocaleInRootLocale[i]);
+            final String localeString = exceptionalLocaleInRootLocale[i];
+            final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + localeString;
+            final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME);
+            sExceptionalLocaleDisplayedInRootLocale.put(localeString, resId);
         }
 
         final String[] exceptionalLocales = res.getStringArray(
@@ -171,7 +177,7 @@
         if (NO_LANGUAGE.equals(localeString)) {
             return sResources.getConfiguration().locale;
         }
-        if (sExceptionalLocaleDisplayedInRootLocale.contains(localeString)) {
+        if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
             return Locale.ROOT;
         }
         return LocaleUtils.constructLocaleFromString(localeString);
@@ -190,7 +196,7 @@
     public static String getSubtypeLanguageDisplayName(final String localeString) {
         final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
         final String languageString;
-        if (sExceptionalLocaleDisplayedInRootLocale.contains(localeString)) {
+        if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
             languageString = localeString;
         } else {
             final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
@@ -205,7 +211,16 @@
             // No language subtype should be displayed in system locale.
             return sResources.getString(R.string.subtype_no_language);
         }
-        final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
+        final Integer exceptionalNameResId;
+        if (displayLocale.equals(Locale.ROOT)
+                && sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
+            exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(localeString);
+        } else if (sExceptionalLocaleToNameIdsMap.containsKey(localeString)) {
+            exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
+        } else {
+            exceptionalNameResId = null;
+        }
+
         final String displayName;
         if (exceptionalNameResId != null) {
             final RunInLocale<String> getExceptionalName = new RunInLocale<String>() {
diff --git a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
index d3fa0c7..86a5b19 100644
--- a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
+++ b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
@@ -21,6 +21,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.NgramContext;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
 import com.android.inputmethod.latin.utils.DistracterFilter.HandlingType;
 
diff --git a/native/jni/src/suggest/core/policy/weighting.cpp b/native/jni/src/suggest/core/policy/weighting.cpp
index a06e7d0..450203d 100644
--- a/native/jni/src/suggest/core/policy/weighting.cpp
+++ b/native/jni/src/suggest/core/policy/weighting.cpp
@@ -119,7 +119,7 @@
         return weighting->getSubstitutionCost()
                 + weighting->getMatchedCost(traverseSession, dicNode, inputStateG);
     case CT_NEW_WORD_SPACE_OMISSION:
-        return weighting->getNewWordSpatialCost(traverseSession, dicNode, inputStateG);
+        return weighting->getSpaceOmissionCost(traverseSession, dicNode, inputStateG);
     case CT_MATCH:
         return weighting->getMatchedCost(traverseSession, dicNode, inputStateG);
     case CT_COMPLETION:
diff --git a/native/jni/src/suggest/core/policy/weighting.h b/native/jni/src/suggest/core/policy/weighting.h
index bd6b3cf..863c4ea 100644
--- a/native/jni/src/suggest/core/policy/weighting.h
+++ b/native/jni/src/suggest/core/policy/weighting.h
@@ -57,7 +57,7 @@
             const DicTraverseSession *const traverseSession,
             const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
 
-    virtual float getNewWordSpatialCost(const DicTraverseSession *const traverseSession,
+    virtual float getSpaceOmissionCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode, DicNode_InputStateG *const inputStateG) const = 0;
 
     virtual float getNewWordBigramLanguageCost(
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index c715262..68a3645 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -160,8 +160,7 @@
             // TODO: Remove. Do not prune node here.
             const bool allowsErrorCorrections = TRAVERSAL->allowsErrorCorrections(&dicNode);
             // Process for handling space substitution (e.g., hevis => he is)
-            if (allowsErrorCorrections
-                    && TRAVERSAL->isSpaceSubstitutionTerminal(traverseSession, &dicNode)) {
+            if (TRAVERSAL->isSpaceSubstitutionTerminal(traverseSession, &dicNode)) {
                 createNextWordDicNode(traverseSession, &dicNode, true /* spaceSubstitution */);
             }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
index 249d822..d3de322 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
@@ -97,6 +97,9 @@
         return NOT_A_WORD_ID;
     }
     const PtNodeParams ptNodeParams = mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos);
+    if (ptNodeParams.isDeleted()) {
+        return NOT_A_WORD_ID;
+    }
     return ptNodeParams.getTerminalId();
 }
 
diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
index 6a2db68..a6f9a8b 100644
--- a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
@@ -48,17 +48,17 @@
 const float ScoringParams::INSERTION_COST_PROXIMITY_CHAR = 0.674f;
 const float ScoringParams::INSERTION_COST_FIRST_CHAR = 0.639f;
 const float ScoringParams::TRANSPOSITION_COST = 0.5608f;
-const float ScoringParams::SPACE_SUBSTITUTION_COST = 0.334f;
+const float ScoringParams::SPACE_SUBSTITUTION_COST = 0.33f;
+const float ScoringParams::SPACE_OMISSION_COST = 0.1f;
 const float ScoringParams::ADDITIONAL_PROXIMITY_COST = 0.37972f;
 const float ScoringParams::SUBSTITUTION_COST = 0.3806f;
-const float ScoringParams::COST_NEW_WORD = 0.0314f;
 const float ScoringParams::COST_SECOND_OR_LATER_WORD_FIRST_CHAR_UPPERCASE = 0.3224f;
 const float ScoringParams::DISTANCE_WEIGHT_LANGUAGE = 1.1214f;
 const float ScoringParams::COST_FIRST_COMPLETION = 0.4836f;
 const float ScoringParams::COST_COMPLETION = 0.00624f;
 const float ScoringParams::HAS_PROXIMITY_TERMINAL_COST = 0.0683f;
 const float ScoringParams::HAS_EDIT_CORRECTION_TERMINAL_COST = 0.0362f;
-const float ScoringParams::HAS_MULTI_WORD_TERMINAL_COST = 0.4182f;
+const float ScoringParams::HAS_MULTI_WORD_TERMINAL_COST = 0.3482f;
 const float ScoringParams::TYPING_BASE_OUTPUT_SCORE = 1.0f;
 const float ScoringParams::TYPING_MAX_OUTPUT_SCORE_PER_INPUT = 0.1f;
 const float ScoringParams::NORMALIZED_SPATIAL_DISTANCE_THRESHOLD_FOR_EDIT = 0.095f;
diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.h b/native/jni/src/suggest/policyimpl/typing/scoring_params.h
index 731424f..b8f8895 100644
--- a/native/jni/src/suggest/policyimpl/typing/scoring_params.h
+++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.h
@@ -56,9 +56,9 @@
     static const float INSERTION_COST_FIRST_CHAR;
     static const float TRANSPOSITION_COST;
     static const float SPACE_SUBSTITUTION_COST;
+    static const float SPACE_OMISSION_COST;
     static const float ADDITIONAL_PROXIMITY_COST;
     static const float SUBSTITUTION_COST;
-    static const float COST_NEW_WORD;
     static const float COST_SECOND_OR_LATER_WORD_FIRST_CHAR_UPPERCASE;
     static const float DISTANCE_WEIGHT_LANGUAGE;
     static const float COST_FIRST_COMPLETION;
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index 8407717..1338ac8 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -150,9 +150,10 @@
         return cost + weightedDistance;
     }
 
-    float getNewWordSpatialCost(const DicTraverseSession *const traverseSession,
+    float getSpaceOmissionCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode, DicNode_InputStateG *inputStateG) const {
-        return ScoringParams::COST_NEW_WORD * traverseSession->getMultiWordCostMultiplier();
+        const float cost = ScoringParams::SPACE_OMISSION_COST;
+        return cost * traverseSession->getMultiWordCostMultiplier();
     }
 
     float getNewWordBigramLanguageCost(const DicTraverseSession *const traverseSession,
@@ -202,7 +203,10 @@
 
     AK_FORCE_INLINE float getSpaceSubstitutionCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode) const {
-        const float cost = ScoringParams::SPACE_SUBSTITUTION_COST + ScoringParams::COST_NEW_WORD;
+        const int inputIndex = dicNode->getInputIndex(0);
+        const float distanceToSpaceKey = traverseSession->getProximityInfoState(0)
+                ->getPointToKeyLength(inputIndex, KEYCODE_SPACE);
+        const float cost = ScoringParams::SPACE_SUBSTITUTION_COST * distanceToSpaceKey;
         return cost * traverseSession->getMultiWordCostMultiplier();
     }
 
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
index ec836cc..0246c49 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
@@ -17,9 +17,7 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.res.Resources;
-import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
 import android.view.ContextThemeWrapper;
 import android.view.inputmethod.EditorInfo;
@@ -62,8 +60,8 @@
         }
     };
 
-    private SharedPreferences mSharedPreferences;
-    private String mSavedAdditionalSubtypes;
+    private RichInputMethodManager mRichImm;
+    private InputMethodSubtype[] mSavedAdditionalSubtypes;
     private int mScreenMetrics;
 
     protected abstract int getKeyboardThemeForTests();
@@ -72,26 +70,26 @@
     protected void setUp() throws Exception {
         super.setUp();
         final Context context = getContext();
-        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
         final Resources res = context.getResources();
+        RichInputMethodManager.init(context);
+        mRichImm = RichInputMethodManager.getInstance();
 
-        // Save additional subtypes preference.
-        mSavedAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(mSharedPreferences, res);
-        final String predefinedSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(
-                res.getStringArray(R.array.predefined_subtypes));
-        // Reset additional subtypes to predefined ones.
-        Settings.writePrefAdditionalSubtypes(mSharedPreferences, predefinedSubtypes);
+        // Save and reset additional subtypes preference.
+        mSavedAdditionalSubtypes = mRichImm.getAdditionalSubtypes(context);
+        final InputMethodSubtype[] predefinedAdditionalSubtypes =
+                AdditionalSubtypeUtils.createAdditionalSubtypesArray(
+                        AdditionalSubtypeUtils.createPrefSubtypes(
+                                res.getStringArray(R.array.predefined_subtypes)));
+        mRichImm.setAdditionalInputMethodSubtypes(predefinedAdditionalSubtypes);
 
         final KeyboardTheme keyboardTheme = KeyboardTheme.searchKeyboardThemeById(
                 getKeyboardThemeForTests(), KeyboardTheme.KEYBOARD_THEMES);
         setContext(new ContextThemeWrapper(getContext(), keyboardTheme.mStyleId));
         KeyboardLayoutSet.onKeyboardThemeChanged();
 
-        mScreenMetrics = res.getInteger(R.integer.config_screen_metrics);
-        RichInputMethodManager.init(context);
-        final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
+        mScreenMetrics = Settings.readScreenMetrics(res);
 
-        final InputMethodInfo imi = richImm.getInputMethodInfoOfThisIme();
+        final InputMethodInfo imi = mRichImm.getInputMethodInfoOfThisIme();
         final int subtypeCount = imi.getSubtypeCount();
         for (int index = 0; index < subtypeCount; index++) {
             mAllSubtypesList.add(imi.getSubtypeAt(index));
@@ -101,7 +99,7 @@
     @Override
     protected void tearDown() throws Exception {
         // Restore additional subtypes preference.
-        Settings.writePrefAdditionalSubtypes(mSharedPreferences, mSavedAdditionalSubtypes);
+        mRichImm.setAdditionalInputMethodSubtypes(mSavedAdditionalSubtypes);
         super.tearDown();
     }
 
@@ -121,8 +119,7 @@
     }
 
     protected final boolean isPhone() {
-        return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_PHONE
-                || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_PHONE;
+        return Constants.isPhone(mScreenMetrics);
     }
 
     protected final InputMethodSubtype getSubtype(final Locale locale,
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
index a6aac35..2a040f5 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
@@ -20,11 +20,14 @@
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 /**
  * This class builds an actual keyboard for unit test.
  *
@@ -98,9 +101,13 @@
 
         @Override
         public String stringize(final MoreKeySpec spec) {
+            if (spec == null) {
+                return "null";
+            }
             return toString(spec.mLabel, spec.mIconId, spec.mOutputText, spec.mCode);
         }
 
+        @Nonnull
         static String toString(final String label, final int iconId, final String outputText,
                 final int code) {
             final String visual = (iconId != KeyboardIconsSet.ICON_UNDEFINED)
@@ -125,7 +132,7 @@
         static final KeyStringizer STRINGIZER = new KeyStringizer();
 
         @Override
-        public String stringize(final Key key) {
+        public String stringize(@Nullable final Key key) {
             if (key == null) {
                 return "NULL";
             }
@@ -150,7 +157,8 @@
      * @param key the key to be converted to string.
      * @return the human readable representation of <code>key</code>.
      */
-    public static String toString(final Key key) {
+    @Nonnull
+    public static String toString(@Nullable final Key key) {
         return KeyStringizer.STRINGIZER.stringize(key);
     }
 
@@ -159,7 +167,8 @@
      * @param keys the keyboard row to be converted to string.
      * @return the human readable representation of <code>keys</code>.
      */
-    public static String toString(final Key[] keys) {
+    @Nonnull
+    public static String toString(@Nullable final Key[] keys) {
         return KeyStringizer.STRINGIZER.join(keys);
     }
 
@@ -168,7 +177,7 @@
         static final KeyArrayStringizer STRINGIZER = new KeyArrayStringizer();
 
         @Override
-        public String stringize(final Key[] keyArray) {
+        public String stringize(@Nullable final Key[] keyArray) {
             return KeyStringizer.STRINGIZER.join(keyArray);
         }
     }
@@ -178,7 +187,8 @@
      * @param rows the keyboard to be converted to string.
      * @return the human readable representation of <code>rows</code>.
      */
-    public static String toString(final Key[][] rows) {
+    @Nonnull
+    public static String toString(@Nullable final Key[][] rows) {
         return KeyArrayStringizer.STRINGIZER.join(rows, "\n" /* delimiter */);
     }
 }
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 0547ea3..9bb5f18 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
@@ -19,7 +19,7 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Locale;
 
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index 47badc1..f90b266 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -746,7 +746,12 @@
         assertTrue(binaryDictionary.isValidWord("aaa"));
         assertTrue(binaryDictionary.removeUnigramEntry("aaa"));
         assertFalse(binaryDictionary.isValidWord("aaa"));
-
+        onInputWord(binaryDictionary, "aaa", false /* isValidWord */);
+        assertFalse(binaryDictionary.isValidWord("aaa"));
+        onInputWord(binaryDictionary, "aaa", false /* isValidWord */);
+        assertTrue(binaryDictionary.isValidWord("aaa"));
+        assertTrue(binaryDictionary.removeUnigramEntry("aaa"));
+        assertFalse(binaryDictionary.isValidWord("aaa"));
         binaryDictionary.close();
     }
 
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 7d356a4..c76f6f4 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -674,10 +674,15 @@
                 mEditText.getText().toString());
     }
 
-    private void typeWordAndPutCursorInside(final String word, final int startPos) {
+    private void typeOrGestureWordAndPutCursorInside(final boolean gesture, final String word,
+            final int startPos) {
         final int END_OF_WORD = startPos + word.length();
         final int NEW_CURSOR_POSITION = startPos + word.length() / 2;
-        type(word);
+        if (gesture) {
+            gesture(word);
+        } else {
+            type(word);
+        }
         sendUpdateForCursorMoveTo(END_OF_WORD);
         runMessages();
         sendUpdateForCursorMoveTo(NEW_CURSOR_POSITION);
@@ -687,6 +692,14 @@
                 startPos, END_OF_WORD);
     }
 
+    private void typeWordAndPutCursorInside(final String word, final int startPos) {
+        typeOrGestureWordAndPutCursorInside(false /* gesture */, word, startPos);
+    }
+
+    private void gestureWordAndPutCursorInside(final String word, final int startPos) {
+        typeOrGestureWordAndPutCursorInside(true /* gesture */, word, startPos);
+    }
+
     private void ensureComposingSpanPos(final String message, final int from, final int to) {
         assertEquals(message, from, BaseInputConnection.getComposingSpanStart(mEditText.getText()));
         assertEquals(message, to, BaseInputConnection.getComposingSpanEnd(mEditText.getText()));
@@ -703,6 +716,23 @@
         int cursorPos = sendUpdateForCursorMoveToEndOfLine();
         runMessages();
         type(" ");
+        assertEquals("mbo", "some thing ", mEditText.getText().toString());
+        typeWordAndPutCursorInside(WORD_TO_TYPE, cursorPos + 1 /* startPos */);
+        type(Constants.CODE_DELETE);
+        ensureComposingSpanPos("space while in the middle of a word cancels composition", -1, -1);
+    }
+
+    public void testTypeWithinGestureComposing() {
+        final String WORD_TO_TYPE = "something";
+        final String EXPECTED_RESULT = "some thing";
+        gestureWordAndPutCursorInside(WORD_TO_TYPE, 0 /* startPos */);
+        type(" ");
+        ensureComposingSpanPos("space while in the middle of a word cancels composition", -1, -1);
+        assertEquals("space in the middle of a composing word", EXPECTED_RESULT,
+                mEditText.getText().toString());
+        int cursorPos = sendUpdateForCursorMoveToEndOfLine();
+        runMessages();
+        type(" ");
         typeWordAndPutCursorInside(WORD_TO_TYPE, cursorPos + 1 /* startPos */);
         type(Constants.CODE_DELETE);
         ensureComposingSpanPos("space while in the middle of a word cancels composition", -1, -1);
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 78b4b18..926a2d3 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -43,10 +43,11 @@
 import com.android.inputmethod.latin.Dictionary.PhonyDictionary;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.InputPointers;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.DebugSettings;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.LocaleUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.Locale;
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
index 34c2600..bcf016a 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
@@ -31,11 +31,11 @@
 import android.view.inputmethod.InputConnectionWrapper;
 
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
 import com.android.inputmethod.latin.utils.NgramContextUtils;
 import com.android.inputmethod.latin.utils.RunInLocale;
 import com.android.inputmethod.latin.utils.ScriptUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.TextRange;
 
 import java.util.Locale;
diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
index 8f5e8f7..20256e6 100644
--- a/tests/src/com/android/inputmethod/latin/WordComposerTests.java
+++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
@@ -20,8 +20,8 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 /**
  * Unit tests for WordComposer.
diff --git a/tests/src/com/android/inputmethod/latin/InputPointersTests.java b/tests/src/com/android/inputmethod/latin/common/InputPointersTests.java
similarity index 99%
rename from tests/src/com/android/inputmethod/latin/InputPointersTests.java
rename to tests/src/com/android/inputmethod/latin/common/InputPointersTests.java
index 1a47cdd..6b3490d 100644
--- a/tests/src/com/android/inputmethod/latin/InputPointersTests.java
+++ b/tests/src/com/android/inputmethod/latin/common/InputPointersTests.java
@@ -14,13 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.common;
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.inputmethod.latin.utils.ResizableIntArray;
-
 import java.util.Arrays;
 
 @SmallTest
diff --git a/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java b/tests/src/com/android/inputmethod/latin/common/ResizableIntArrayTests.java
similarity index 99%
rename from tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java
rename to tests/src/com/android/inputmethod/latin/common/ResizableIntArrayTests.java
index 7519bec..bd1629f 100644
--- a/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java
+++ b/tests/src/com/android/inputmethod/latin/common/ResizableIntArrayTests.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin.utils;
+package com.android.inputmethod.latin.common;
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
diff --git a/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java b/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java
index f44673e..ed632db 100644
--- a/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java
+++ b/tests/src/com/android/inputmethod/latin/settings/SpacingAndPunctuationsTests.java
@@ -20,7 +20,6 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.common.Constants;
 import com.android.inputmethod.latin.utils.RunInLocale;
@@ -37,13 +36,11 @@
     private int mScreenMetrics;
 
     private boolean isPhone() {
-        return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_PHONE
-                || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_PHONE;
+        return Constants.isPhone(mScreenMetrics);
     }
 
     private boolean isTablet() {
-        return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET
-                || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET;
+        return Constants.isTablet(mScreenMetrics);
     }
 
     private SpacingAndPunctuations ENGLISH;
@@ -70,7 +67,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        mScreenMetrics = mContext.getResources().getInteger(R.integer.config_screen_metrics);
+        mScreenMetrics = Settings.readScreenMetrics(getContext().getResources());
 
         // Language only
         ENGLISH = getSpacingAndPunctuations(Locale.ENGLISH);
diff --git a/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java
index 3feb60e..a5979c3 100644
--- a/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java
@@ -51,5 +51,4 @@
         assertTrue(CollectionUtils.isNullOrEmpty(Collections.EMPTY_SET));
         assertFalse(CollectionUtils.isNullOrEmpty(Collections.singleton("Not empty")));
     }
-
 }
diff --git a/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtilsTests.java
index e6131cf..83afd78 100644
--- a/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtilsTests.java
@@ -23,6 +23,7 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodManager;
 import com.android.inputmethod.latin.RichInputMethodSubtype;
 
@@ -36,6 +37,7 @@
 
     private RichInputMethodManager mRichImm;
     private Resources mRes;
+    private InputMethodSubtype mSavedAddtionalSubtypes[];
 
     RichInputMethodSubtype EN_US;
     RichInputMethodSubtype EN_GB;
@@ -45,6 +47,8 @@
     RichInputMethodSubtype FR_CH;
     RichInputMethodSubtype DE;
     RichInputMethodSubtype DE_CH;
+    RichInputMethodSubtype HI;
+    RichInputMethodSubtype SR;
     RichInputMethodSubtype ZZ;
     RichInputMethodSubtype DE_QWERTY;
     RichInputMethodSubtype FR_QWERTZ;
@@ -54,17 +58,27 @@
     RichInputMethodSubtype ZZ_AZERTY;
     RichInputMethodSubtype ZZ_PC;
 
-    // This is a preliminary subtype and may not exist.
-    RichInputMethodSubtype HI_LATN;
+    // These are preliminary subtypes and may not exist.
+    RichInputMethodSubtype HI_LATN; // Hinglish
+    RichInputMethodSubtype SR_LATN; // Serbian Latin
+    RichInputMethodSubtype HI_LATN_DVORAK;
+    RichInputMethodSubtype SR_LATN_QWERTY;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         final Context context = getContext();
+        mRes = context.getResources();
         RichInputMethodManager.init(context);
         mRichImm = RichInputMethodManager.getInstance();
-        mRes = context.getResources();
-        SubtypeLocaleUtils.init(context);
+
+        // Save and reset additional subtypes
+        mSavedAddtionalSubtypes = mRichImm.getAdditionalSubtypes(context);
+        final InputMethodSubtype[] predefinedAddtionalSubtypes =
+                AdditionalSubtypeUtils.createAdditionalSubtypesArray(
+                        AdditionalSubtypeUtils.createPrefSubtypes(
+                                mRes.getStringArray(R.array.predefined_subtypes)));
+        mRichImm.setAdditionalInputMethodSubtypes(predefinedAddtionalSubtypes);
 
         final InputMethodInfo imi = mRichImm.getInputMethodInfoOfThisIme();
         final int subtypeCount = imi.getSubtypeCount();
@@ -89,6 +103,10 @@
                 Locale.GERMAN.toString(), "qwertz"));
         DE_CH = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 "de_CH", "swiss"));
+        HI = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "hi", "hindi"));
+        SR = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "sr", "south_slavic"));
         ZZ = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 SubtypeLocaleUtils.NO_LANGUAGE, "qwerty"));
         DE_QWERTY = new RichInputMethodSubtype(
@@ -117,7 +135,25 @@
                 "hi_ZZ", "qwerty");
         if (hiLatn != null) {
             HI_LATN = new RichInputMethodSubtype(hiLatn);
+            HI_LATN_DVORAK = new RichInputMethodSubtype(
+                    AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
+                            "hi_ZZ", "dvorak"));
         }
+        final InputMethodSubtype srLatn = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "sr_ZZ", "serbian_qwertz");
+        if (srLatn != null) {
+            SR_LATN = new RichInputMethodSubtype(srLatn);
+            SR_LATN_QWERTY = new RichInputMethodSubtype(
+                    AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
+                            "sr_ZZ", "qwerty"));
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Restore additional subtypes.
+        mRichImm.setAdditionalInputMethodSubtypes(mSavedAddtionalSubtypes);
+        super.tearDown();
     }
 
     public void testAllFullDisplayNameForSpacebar() {
@@ -150,10 +186,11 @@
                 continue;
             }
             final Locale locale = locales[0];
-            if (SubtypeLocaleUtils.sExceptionalLocaleDisplayedInRootLocale.contains(
-                    locale.toString())) {
+            final Locale displayLocale = SubtypeLocaleUtils.getDisplayLocaleOfSubtypeLocale(
+                    locale.toString());
+            if (Locale.ROOT.equals(displayLocale)) {
                 // Skip test because the language part of this locale string doesn't represent
-                // the locale to be displayed on the spacebar (for example hi_ZZ and Hinglish).
+                // the locale to be displayed on the spacebar (for example Hinglish).
                 continue;
             }
             final String spacebarText = subtype.getMiddleDisplayName();
@@ -162,30 +199,36 @@
                         subtype.getRawSubtype()), spacebarText);
             } else {
                 assertEquals(subtypeName,
-                        SubtypeLocaleUtils.getSubtypeLocaleDisplayName(locale.getLanguage()),
+                        SubtypeLocaleUtils.getSubtypeLanguageDisplayName(locale.toString()),
                         spacebarText);
             }
         }
     }
 
     // InputMethodSubtype's display name for spacebar text in its locale.
-    //        isAdditionalSubtype (T=true, F=false)
-    // locale layout  |  Middle     Full
-    // ------ ------- - --------- ----------------------
-    //  en_US qwerty  F  English   English (US)           exception
-    //  en_GB qwerty  F  English   English (UK)           exception
-    //  es_US spanish F  Español   Español (EE.UU.)       exception
-    //  fr    azerty  F  Français  Français
-    //  fr_CA qwerty  F  Français  Français (Canada)
-    //  fr_CH swiss   F  Français  Français (Suisse)
-    //  de    qwertz  F  Deutsch   Deutsch
-    //  de_CH swiss   F  Deutsch   Deutsch (Schweiz)
-    //  hi_ZZ qwerty  F  Hinglish  Hinglish
-    //  zz    qwerty  F  QWERTY    QWERTY
-    //  fr    qwertz  T  Français  Français
-    //  de    qwerty  T  Deutsch   Deutsch
-    //  en_US azerty  T  English   English (US)
-    //  zz    azerty  T  AZERTY    AZERTY
+    //               isAdditionalSubtype (T=true, F=false)
+    // locale layout         |  Middle     Full
+    // ------ -------------- - --------- ----------------------
+    //  en_US qwerty         F  English   English (US)           exception
+    //  en_GB qwerty         F  English   English (UK)           exception
+    //  es_US spanish        F  Español   Español (EE.UU.)       exception
+    //  fr    azerty         F  Français  Français
+    //  fr_CA qwerty         F  Français  Français (Canada)
+    //  fr_CH swiss          F  Français  Français (Suisse)
+    //  de    qwertz         F  Deutsch   Deutsch
+    //  de_CH swiss          F  Deutsch   Deutsch (Schweiz)
+    //  hi    hindi          F  हिन्दी       हिन्दी
+    //  hi_ZZ qwerty         F  Hinglish  Hinglish               exception
+    //  sr    south_slavic   F  Српски    Српски
+    //  sr_ZZ serbian_qwertz F  Srpski    Srpski                 exception
+    //  zz    qwerty         F  QWERTY    QWERTY
+    //  fr    qwertz         T  Français  Français
+    //  de    qwerty         T  Deutsch   Deutsch
+    //  en_US azerty         T  English   English (US)
+    //  en_GB dvorak         T  English   English (UK)
+    //  hi_ZZ dvorak         T  Hinglish  Hinglish               exception
+    //  sr_ZZ qwerty         T  Srpski    Srpski                 exception
+    //  zz    azerty         T  AZERTY    AZERTY
 
     private final RunInLocale<Void> testsPredefinedSubtypesForSpacebar = new RunInLocale<Void>() {
         @Override
@@ -198,11 +241,9 @@
             assertEquals("fr_CH", "Français (Suisse)", FR_CH.getFullDisplayName());
             assertEquals("de", "Deutsch", DE.getFullDisplayName());
             assertEquals("de_CH", "Deutsch (Schweiz)", DE_CH.getFullDisplayName());
+            assertEquals("hi", "हिन्दी", HI.getFullDisplayName());
+            assertEquals("sr", "Српски", SR.getFullDisplayName());
             assertEquals("zz", "QWERTY", ZZ.getFullDisplayName());
-            // This is a preliminary subtype and may not exist.
-            if (HI_LATN != null) {
-                assertEquals("hi_ZZ", "Hinglish", HI_LATN.getFullDisplayName());
-            }
 
             assertEquals("en_US", "English", EN_US.getMiddleDisplayName());
             assertEquals("en_GB", "English", EN_GB.getMiddleDisplayName());
@@ -213,10 +254,16 @@
             assertEquals("de", "Deutsch", DE.getMiddleDisplayName());
             assertEquals("de_CH", "Deutsch", DE_CH.getMiddleDisplayName());
             assertEquals("zz", "QWERTY", ZZ.getMiddleDisplayName());
-            // This is a preliminary subtype and may not exist.
+
+            // These are preliminary subtypes and may not exist.
             if (HI_LATN != null) {
+                assertEquals("hi_ZZ", "Hinglish", HI_LATN.getFullDisplayName());
                 assertEquals("hi_ZZ", "Hinglish", HI_LATN.getMiddleDisplayName());
             }
+            if (SR_LATN != null) {
+                assertEquals("sr_ZZ", "Srpski", SR_LATN.getFullDisplayName());
+                assertEquals("sr_ZZ", "Srpski", SR_LATN.getMiddleDisplayName());
+            }
             return null;
         }
     };
@@ -239,6 +286,16 @@
             assertEquals("es_US colemak", "Español", ES_US_COLEMAK.getMiddleDisplayName());
             assertEquals("zz azerty", "AZERTY", ZZ_AZERTY.getMiddleDisplayName());
             assertEquals("zz pc", "PC", ZZ_PC.getMiddleDisplayName());
+
+            // These are preliminary subtypes and may not exist.
+            if (HI_LATN_DVORAK != null) {
+                assertEquals("hi_ZZ dvorak", "Hinglish", HI_LATN_DVORAK.getFullDisplayName());
+                assertEquals("hi_ZZ dvorak", "Hinglish", HI_LATN_DVORAK.getMiddleDisplayName());
+            }
+            if (SR_LATN_QWERTY != null) {
+                assertEquals("sr_ZZ qwerty", "Srpski", SR_LATN_QWERTY.getFullDisplayName());
+                assertEquals("sr_ZZ qwerty", "Srpski", SR_LATN_QWERTY.getMiddleDisplayName());
+            }
             return null;
         }
     };
diff --git a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
index 7d9d399..0389fef 100644
--- a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
@@ -23,6 +23,8 @@
 import android.text.SpannedString;
 
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
+import com.android.inputmethod.latin.utils.SpannableStringUtils;
 
 import java.util.Arrays;
 import java.util.List;
@@ -375,9 +377,9 @@
         spannableString.setSpan(span1, 0, 7, SPAN1_FLAGS);
         spannableString.setSpan(span2, 0, 5, SPAN2_FLAGS);
         spannableString.setSpan(span3, 12, 13, SPAN3_FLAGS);
-        final CharSequence[] charSequencesFromSpanned = StringUtils.split(
+        final CharSequence[] charSequencesFromSpanned = SpannableStringUtils.split(
                 spannableString, " ", true /* preserveTrailingEmptySegmengs */);
-        final CharSequence[] charSequencesFromString = StringUtils.split(
+        final CharSequence[] charSequencesFromString = SpannableStringUtils.split(
                 spannableString.toString(), " ", true /* preserveTrailingEmptySegmengs */);
 
 
@@ -456,44 +458,44 @@
     }
 
     public void testSplitCharSequencePreserveTrailingEmptySegmengs() {
-        assertEquals(1, StringUtils.split("", " ",
+        assertEquals(1, SpannableStringUtils.split("", " ",
                 false /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(1, StringUtils.split(new SpannedString(""), " ",
+        assertEquals(1, SpannableStringUtils.split(new SpannedString(""), " ",
                 false /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(1, StringUtils.split("", " ",
+        assertEquals(1, SpannableStringUtils.split("", " ",
                 true /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(1, StringUtils.split(new SpannedString(""), " ",
+        assertEquals(1, SpannableStringUtils.split(new SpannedString(""), " ",
                 true /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(0, StringUtils.split(" ", " ",
+        assertEquals(0, SpannableStringUtils.split(" ", " ",
                 false /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(0, StringUtils.split(new SpannedString(" "), " ",
+        assertEquals(0, SpannableStringUtils.split(new SpannedString(" "), " ",
                 false /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(2, StringUtils.split(" ", " ",
+        assertEquals(2, SpannableStringUtils.split(" ", " ",
                 true /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(2, StringUtils.split(new SpannedString(" "), " ",
+        assertEquals(2, SpannableStringUtils.split(new SpannedString(" "), " ",
                 true /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(3, StringUtils.split("a b c  ", " ",
+        assertEquals(3, SpannableStringUtils.split("a b c  ", " ",
                 false /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(3, StringUtils.split(new SpannedString("a b c  "), " ",
+        assertEquals(3, SpannableStringUtils.split(new SpannedString("a b c  "), " ",
                 false /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(5, StringUtils.split("a b c  ", " ",
+        assertEquals(5, SpannableStringUtils.split("a b c  ", " ",
                 true /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(5, StringUtils.split(new SpannedString("a b c  "), " ",
+        assertEquals(5, SpannableStringUtils.split(new SpannedString("a b c  "), " ",
                 true /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(6, StringUtils.split("a     b ", " ",
+        assertEquals(6, SpannableStringUtils.split("a     b ", " ",
                 false /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(6, StringUtils.split(new SpannedString("a     b "), " ",
+        assertEquals(6, SpannableStringUtils.split(new SpannedString("a     b "), " ",
                 false /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(7, StringUtils.split("a     b ", " ",
+        assertEquals(7, SpannableStringUtils.split("a     b ", " ",
                 true /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(7, StringUtils.split(new SpannedString("a     b "), " ",
+        assertEquals(7, SpannableStringUtils.split(new SpannedString("a     b "), " ",
                 true /* preserveTrailingEmptySegmengs */).length);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
index dfc3fec..54f478f 100644
--- a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
@@ -23,6 +23,7 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodManager;
 import com.android.inputmethod.latin.RichInputMethodSubtype;
 
@@ -36,6 +37,7 @@
 
     private RichInputMethodManager mRichImm;
     private Resources mRes;
+    private InputMethodSubtype mSavedAddtionalSubtypes[];
 
     InputMethodSubtype EN_US;
     InputMethodSubtype EN_GB;
@@ -45,6 +47,8 @@
     InputMethodSubtype FR_CH;
     InputMethodSubtype DE;
     InputMethodSubtype DE_CH;
+    InputMethodSubtype HI;
+    InputMethodSubtype SR;
     InputMethodSubtype ZZ;
     InputMethodSubtype DE_QWERTY;
     InputMethodSubtype FR_QWERTZ;
@@ -54,17 +58,27 @@
     InputMethodSubtype ZZ_AZERTY;
     InputMethodSubtype ZZ_PC;
 
-    // This is a preliminary subtype and may not exist.
-    InputMethodSubtype HI_LATN;
+    // These are preliminary subtypes and may not exist.
+    InputMethodSubtype HI_LATN; // Hinglish
+    InputMethodSubtype SR_LATN; // Serbian Latin
+    InputMethodSubtype HI_LATN_DVORAK; // Hinglis Dvorak
+    InputMethodSubtype SR_LATN_QWERTY; // Serbian Latin Qwerty
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         final Context context = getContext();
+        mRes = context.getResources();
         RichInputMethodManager.init(context);
         mRichImm = RichInputMethodManager.getInstance();
-        mRes = context.getResources();
-        SubtypeLocaleUtils.init(context);
+
+        // Save and reset additional subtypes
+        mSavedAddtionalSubtypes = mRichImm.getAdditionalSubtypes(context);
+        final InputMethodSubtype[] predefinedAddtionalSubtypes =
+                AdditionalSubtypeUtils.createAdditionalSubtypesArray(
+                        AdditionalSubtypeUtils.createPrefSubtypes(
+                                mRes.getStringArray(R.array.predefined_subtypes)));
+        mRichImm.setAdditionalInputMethodSubtypes(predefinedAddtionalSubtypes);
 
         final InputMethodInfo imi = mRichImm.getInputMethodInfoOfThisIme();
         final int subtypeCount = imi.getSubtypeCount();
@@ -89,6 +103,10 @@
                 Locale.GERMAN.toString(), "qwertz");
         DE_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 "de_CH", "swiss");
+        HI = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "hi", "hindi");
+        SR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "sr", "south_slavic");
         ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 SubtypeLocaleUtils.NO_LANGUAGE, "qwerty");
         DE_QWERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
@@ -107,6 +125,22 @@
                 SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty");
 
         HI_LATN = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet("hi_ZZ", "qwerty");
+        if (HI_LATN != null) {
+            HI_LATN_DVORAK = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
+                    "hi_ZZ", "dvorak");
+        }
+        SR_LATN = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet("sr_ZZ", "serbian_qwertz");
+        if (SR_LATN != null) {
+            SR_LATN_QWERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
+                    "sr_ZZ", "qwerty");
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Restore additional subtypes.
+        mRichImm.setAdditionalInputMethodSubtypes(mSavedAddtionalSubtypes);
+        super.tearDown();
     }
 
     public void testAllFullDisplayName() {
@@ -139,11 +173,9 @@
         assertEquals("fr_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CH));
         assertEquals("de", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE));
         assertEquals("de_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE_CH));
+        assertEquals("hi", "hindi", SubtypeLocaleUtils.getKeyboardLayoutSetName(HI));
+        assertEquals("sr", "south_slavic", SubtypeLocaleUtils.getKeyboardLayoutSetName(SR));
         assertEquals("zz", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ));
-        // This is a preliminary subtype and may not exist.
-        if (HI_LATN != null) {
-            assertEquals("hi_ZZ", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(HI_LATN));
-        }
 
         assertEquals("de qwerty", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE_QWERTY));
         assertEquals("fr qwertz", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_QWERTZ));
@@ -155,28 +187,46 @@
                 SubtypeLocaleUtils.getKeyboardLayoutSetName(ES_US_COLEMAK));
         assertEquals("zz azerty", "azerty",
                 SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ_AZERTY));
+
+        // These are preliminary subtypes and may not exist.
+        if (HI_LATN != null) {
+            assertEquals("hi_ZZ", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(HI_LATN));
+            assertEquals("hi_ZZ dvorak", "dvorak",
+                    SubtypeLocaleUtils.getKeyboardLayoutSetName(HI_LATN_DVORAK));
+        }
+        if (SR_LATN != null) {
+            assertEquals("sr_ZZ", "serbian_qwertz",
+                    SubtypeLocaleUtils.getKeyboardLayoutSetName(SR_LATN));
+            assertEquals("sr_ZZ qwerty", "qwerty",
+                    SubtypeLocaleUtils.getKeyboardLayoutSetName(SR_LATN_QWERTY));
+        }
     }
 
     // InputMethodSubtype's display name in system locale (en_US).
-    //        isAdditionalSubtype (T=true, F=false)
-    // locale layout  |  display name
-    // ------ ------- - ----------------------
-    //  en_US qwerty  F  English (US)            exception
-    //  en_GB qwerty  F  English (UK)            exception
-    //  es_US spanish F  Spanish (US)            exception
-    //  fr    azerty  F  French
-    //  fr_CA qwerty  F  French (Canada)
-    //  fr_CH swiss   F  French (Switzerland)
-    //  de    qwertz  F  German
-    //  de_CH swiss   F  German (Switzerland)
-    //  hi_ZZ qwerty  F  Hinglish
-    //  zz    qwerty  F  Alphabet (QWERTY)
-    //  fr    qwertz  T  French (QWERTZ)
-    //  de    qwerty  T  German (QWERTY)
-    //  en_US azerty  T  English (US) (AZERTY)   exception
-    //  en_UK dvorak  T  English (UK) (Dvorak)   exception
-    //  es_US colemak T  Spanish (US) (Colemak)  exception
-    //  zz    pc      T  Alphabet (PC)
+    //               isAdditionalSubtype (T=true, F=false)
+    // locale layout         |  display name
+    // ------ -------------- - ----------------------
+    //  en_US qwerty         F  English (US)            exception
+    //  en_GB qwerty         F  English (UK)            exception
+    //  es_US spanish        F  Spanish (US)            exception
+    //  fr    azerty         F  French
+    //  fr_CA qwerty         F  French (Canada)
+    //  fr_CH swiss          F  French (Switzerland)
+    //  de    qwertz         F  German
+    //  de_CH swiss          F  German (Switzerland)
+    //  hi    hindi          F  Hindi
+    //  hi_ZZ qwerty         F  Hinglish                exception
+    //  sr    south_slavic   F  Serbian
+    //  sr_ZZ serbian_qwertz F  Serbian (Latin)         exception
+    //  zz    qwerty         F  Alphabet (QWERTY)
+    //  fr    qwertz         T  French (QWERTZ)
+    //  de    qwerty         T  German (QWERTY)
+    //  en_US azerty         T  English (US) (AZERTY)   exception
+    //  en_UK dvorak         T  English (UK) (Dvorak)   exception
+    //  es_US colemak        T  Spanish (US) (Colemak)  exception
+    //  hi_ZZ dvorak         T  Hinglish (Dvorka)       exception
+    //  sr_ZZ qwerty         T  Serbian (QWERTY)        exception
+    //  zz    pc             T  Alphabet (PC)
 
     public void testPredefinedSubtypesInEnglishSystemLocale() {
         final RunInLocale<Void> tests = new RunInLocale<Void>() {
@@ -198,13 +248,21 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
                 assertEquals("de_CH", "German (Switzerland)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH));
+                assertEquals("hi", "Hindi",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI));
+                assertEquals("sr", "Serbian",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR));
                 assertEquals("zz", "Alphabet (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
-                // This is a preliminary subtype and may not exist.
+                // These are preliminary subtypes and may not exist.
                 if (HI_LATN != null) {
                     assertEquals("hi_ZZ", "Hinglish",
                             SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN));
                 }
+                if (SR_LATN != null) {
+                    assertEquals("sr_ZZ", "Serbian (Latin)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN));
+                }
                 return null;
             }
         };
@@ -229,6 +287,15 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_AZERTY));
                 assertEquals("zz pc", "Alphabet (PC)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
+                // These are preliminary subtypes and may not exist.
+                if (HI_LATN_DVORAK != null) {
+                    assertEquals("hi_ZZ", "Hinglish (Dvorak)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN_DVORAK));
+                }
+                if (SR_LATN_QWERTY != null) {
+                    assertEquals("sr_ZZ", "Serbian (QWERTY)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN_QWERTY));
+                }
                 return null;
             }
         };
@@ -239,22 +306,27 @@
     //        isAdditionalSubtype (T=true, F=false)
     // locale layout  |  display name
     // ------ ------- - ----------------------
-    //  en_US qwerty  F  Anglais (États-Unis)            exception
-    //  en_GB qwerty  F  Anglais (Royaume-Uni)            exception
-    //  es_US spanish F  Espagnol (États-Unis)            exception
-    //  fr    azerty  F  Français
-    //  fr_CA qwerty  F  Français (Canada)
-    //  fr_CH swiss   F  Français (Suisse)
-    //  de    qwertz  F  Allemand
-    //  de_CH swiss   F  Allemand (Suisse)
-    //  hi_ZZ qwerty  F  Hinglish
-    //  zz    qwerty  F  Alphabet latin (QWERTY)
-    //  fr    qwertz  T  Français (QWERTZ)
-    //  de    qwerty  T  Allemand (QWERTY)
-    //  en_US azerty  T  Anglais (États-Unis) (AZERTY)   exception
-    //  en_UK dvorak  T  Anglais (Royaume-Uni) (Dvorak)   exception
-    //  es_US colemak T  Espagnol (États-Unis) (Colemak)  exception
-    //  zz    pc      T  Alphabet latin (PC)
+    //  en_US qwerty         F  Anglais (États-Unis)             exception
+    //  en_GB qwerty         F  Anglais (Royaume-Uni)            exception
+    //  es_US spanish        F  Espagnol (États-Unis)            exception
+    //  fr    azerty         F  Français
+    //  fr_CA qwerty         F  Français (Canada)
+    //  fr_CH swiss          F  Français (Suisse)
+    //  de    qwertz         F  Allemand
+    //  de_CH swiss          F  Allemand (Suisse)
+    //  hi    hindi          F  Hindi                            exception
+    //  hi_ZZ qwerty         F  Hindi/Anglais                    exception
+    //  sr    south_slavic   F  Serbe                            exception
+    //  sr_ZZ serbian_qwertz F  Serbe (latin)                    exception
+    //  zz    qwerty         F  Alphabet latin (QWERTY)
+    //  fr    qwertz         T  Français (QWERTZ)
+    //  de    qwerty         T  Allemand (QWERTY)
+    //  en_US azerty         T  Anglais (États-Unis) (AZERTY)    exception
+    //  en_UK dvorak         T  Anglais (Royaume-Uni) (Dvorak)   exception
+    //  es_US colemak        T  Espagnol (États-Unis) (Colemak)  exception
+    //  hi_ZZ dvorak         T  Hindi/Anglais (Dvorka)           exception
+    //  sr_ZZ qwerty         T  Serbe (QWERTY)                   exception
+    //  zz    pc             T  Alphabet latin (PC)
 
     public void testPredefinedSubtypesInFrenchSystemLocale() {
         final RunInLocale<Void> tests = new RunInLocale<Void>() {
@@ -276,13 +348,21 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
                 assertEquals("de_CH", "Allemand (Suisse)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH));
+                assertEquals("hi", "Hindi",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI));
+                assertEquals("sr", "Serbe",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR));
                 assertEquals("zz", "Alphabet latin (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
-                // This is a preliminary subtype and may not exist.
+                // These are preliminary subtypes and may not exist.
                 if (HI_LATN != null) {
                     assertEquals("hi_ZZ", "Hindi/Anglais",
                             SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN));
                 }
+                if (SR_LATN != null) {
+                    assertEquals("sr_ZZ", "Serbe (latin)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN));
+                }
                 return null;
             }
         };
@@ -307,12 +387,77 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_AZERTY));
                 assertEquals("zz pc", "Alphabet latin (PC)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
+                // These are preliminary subtypes and may not exist.
+                if (HI_LATN_DVORAK != null) {
+                    assertEquals("hi_ZZ", "Hindi/Anglais (Dvorak)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN_DVORAK));
+                }
+                if (SR_LATN_QWERTY != null) {
+                    assertEquals("sr_ZZ", "Serbe (QWERTY)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN_QWERTY));
+                }
                 return null;
             }
         };
         tests.runInLocale(mRes, Locale.FRENCH);
     }
 
+    // InputMethodSubtype's display name in system locale (hi).
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout  |  display name
+    // ------ ------- - ----------------------
+    //  hi    hindi   F  हिन्दी
+    //  hi_ZZ qwerty  F  हिंग्लिश
+    //  hi_ZZ dvorak  T  हिंग्लिश (Dvorak)
+
+    public void testHinglishSubtypesInHindiSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job (final Resources res) {
+                assertEquals("hi", "हिन्दी",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI));
+                // These are preliminary subtypes and may not exist.
+                if (HI_LATN != null) {
+                    assertEquals("hi_ZZ", "हिंग्लिश",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN));
+                    assertEquals("hi_ZZ", "हिंग्लिश (Dvorak)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN_DVORAK));
+                }
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, new Locale("hi"));
+    }
+
+    // InputMethodSubtype's display name in system locale (sr).
+    //               isAdditionalSubtype (T=true, F=false)
+    // locale layout         |  display name
+    // ------ -------------- - ----------------------
+    //  sr    south_slavic   F  Српски
+    //  sr_ZZ serbian_qwertz F  српски (латиница)
+    //  sr_ZZ qwerty         T  српски (QWERTY)
+
+    public void testSerbianLatinSubtypesInSerbianSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job (final Resources res) {
+                assertEquals("sr", "Српски",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR));
+                // These are preliminary subtypes and may not exist.
+                if (SR_LATN != null) {
+                    // TODO: Uncommented because of the current translation of these strings
+                    // in Seriban are described in Latin script.
+//                    assertEquals("sr_ZZ", "српски (латиница)",
+//                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN));
+//                    assertEquals("sr_ZZ", "српски (QWERTY)",
+//                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN_QWERTY));
+                }
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, new Locale("sr"));
+    }
+
     public void testIsRtlLanguage() {
         // Known Right-to-Left language subtypes.
         final InputMethodSubtype ARABIC = mRichImm
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index 09af9a0..81c0706 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -47,7 +47,6 @@
         latin/BinaryDictionary.java \
         latin/DicTraverseSession.java \
         latin/Dictionary.java \
-        latin/InputPointers.java \
         latin/LastComposedWord.java \
         latin/NgramContext.java \
         latin/SuggestedWords.java \
@@ -59,9 +58,7 @@
         latin/utils/CoordinateUtils.java \
         latin/utils/FileUtils.java \
         latin/utils/JniUtils.java \
-        latin/utils/LocaleUtils.java \
-        latin/utils/ResizableIntArray.java \
-        latin/utils/StringUtils.java
+        latin/utils/LocaleUtils.java
 
 LATINIME_OVERRIDABLE_SRC_FILES_FOR_DICTTOOL := \
         latin/define/DebugFlags.java