diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index b1813a141b2b9e0cf6191dfb9bd702647ae757b9..ba449eeb3e0b34e6739c66709402729abe2c02d7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -53,7 +53,9 @@ public final class KeySpecParser {
     private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
 
     // Constants for parsing.
-    private static final char LABEL_END = '|';
+    private static final char COMMA = ',';
+    private static final char BACKSLASH = '\\';
+    private static final char VERTICAL_BAR = '|';
     private static final String PREFIX_TEXT = "!text/";
     static final String PREFIX_ICON = "!icon/";
     private static final String PREFIX_CODE = "!code/";
@@ -64,6 +66,59 @@ public final class KeySpecParser {
         // Intentional empty constructor for utility class.
     }
 
+    /**
+     * Split the text containing multiple key specifications separated by commas into an array of
+     * key specifications.
+     * A key specification can contain a character escaped by the backslash character, including a
+     * comma character.
+     * Note that an empty key specification will be eliminated from the result array.
+     *
+     * @param text the text containing multiple key specifications.
+     * @return an array of key specification text. Null if the specified <code>text</code> is empty
+     * or has no key specifications.
+     */
+    public static String[] splitKeySpecs(final String text) {
+        final int size = text.length();
+        if (size == 0) {
+            return null;
+        }
+        // Optimization for one-letter key specification.
+        if (size == 1) {
+            return text.charAt(0) == COMMA ? null : new String[] { text };
+        }
+
+        ArrayList<String> list = null;
+        int start = 0;
+        // The characters in question in this loop are COMMA and BACKSLASH. These characters never
+        // match any high or low surrogate character. So it is OK to iterate through with char
+        // index.
+        for (int pos = 0; pos < size; pos++) {
+            final char c = text.charAt(pos);
+            if (c == COMMA) {
+                // Skip empty entry.
+                if (pos - start > 0) {
+                    if (list == null) {
+                        list = CollectionUtils.newArrayList();
+                    }
+                    list.add(text.substring(start, pos));
+                }
+                // Skip comma
+                start = pos + 1;
+            } else if (c == BACKSLASH) {
+                // Skip escape character and escaped character.
+                pos++;
+            }
+        }
+        final String remain = (size - start > 0) ? text.substring(start) : null;
+        if (list == null) {
+            return remain != null ? new String[] { remain } : null;
+        }
+        if (remain != null) {
+            list.add(remain);
+        }
+        return list.toArray(new String[list.size()]);
+    }
+
     private static boolean hasIcon(final String moreKeySpec) {
         return moreKeySpec.startsWith(PREFIX_ICON);
     }
@@ -78,14 +133,14 @@ public final class KeySpecParser {
     }
 
     private static String parseEscape(final String text) {
-        if (text.indexOf(Constants.CSV_ESCAPE) < 0) {
+        if (text.indexOf(BACKSLASH) < 0) {
             return text;
         }
         final int length = text.length();
         final StringBuilder sb = new StringBuilder();
         for (int pos = 0; pos < length; pos++) {
             final char c = text.charAt(pos);
-            if (c == Constants.CSV_ESCAPE && pos + 1 < length) {
+            if (c == BACKSLASH && pos + 1 < length) {
                 // Skip escape char
                 pos++;
                 sb.append(text.charAt(pos));
@@ -97,20 +152,20 @@ public final class KeySpecParser {
     }
 
     private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
-        if (moreKeySpec.indexOf(Constants.CSV_ESCAPE, start) < 0) {
-            final int end = moreKeySpec.indexOf(LABEL_END, start);
+        if (moreKeySpec.indexOf(BACKSLASH, start) < 0) {
+            final int end = moreKeySpec.indexOf(VERTICAL_BAR, start);
             if (end == 0) {
-                throw new KeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec);
+                throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + moreKeySpec);
             }
             return end;
         }
         final int length = moreKeySpec.length();
         for (int pos = start; pos < length; pos++) {
             final char c = moreKeySpec.charAt(pos);
-            if (c == Constants.CSV_ESCAPE && pos + 1 < length) {
+            if (c == BACKSLASH && pos + 1 < length) {
                 // Skip escape char
                 pos++;
-            } else if (c == LABEL_END) {
+            } else if (c == VERTICAL_BAR) {
                 return pos;
             }
         }
@@ -136,9 +191,9 @@ public final class KeySpecParser {
             return null;
         }
         if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
-            throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
+            throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec);
         }
-        return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1));
+        return parseEscape(moreKeySpec.substring(end + /* VERTICAL_BAR */1));
     }
 
     static String getOutputText(final String moreKeySpec) {
@@ -169,7 +224,7 @@ public final class KeySpecParser {
         if (hasCode(moreKeySpec)) {
             final int end = indexOfLabelEnd(moreKeySpec, 0);
             if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
-                throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
+                throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec);
             }
             return parseCode(moreKeySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED);
         }
@@ -204,7 +259,7 @@ public final class KeySpecParser {
 
     public static int getIconId(final String moreKeySpec) {
         if (moreKeySpec != null && hasIcon(moreKeySpec)) {
-            final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length());
+            final int end = moreKeySpec.indexOf(VERTICAL_BAR, PREFIX_ICON.length());
             final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length())
                     : moreKeySpec.substring(PREFIX_ICON.length(), end);
             return KeyboardIconsSet.getIconId(name);
@@ -351,7 +406,7 @@ public final class KeySpecParser {
                     final String name = text.substring(pos + prefixLen, end);
                     sb.append(textsSet.getText(name));
                     pos = end - 1;
-                } else if (c == Constants.CSV_ESCAPE) {
+                } else if (c == BACKSLASH) {
                     if (sb != null) {
                         // Append both escape character and escaped character.
                         sb.append(text.substring(pos, Math.min(pos + 2, size)));
@@ -366,7 +421,6 @@ public final class KeySpecParser {
                 text = sb.toString();
             }
         } while (sb != null);
-
         return text;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
index 5db3ebbd17174c64bd7a227738ebe4d7c6a47cb6..f65056948bab140a8724b03f82b3f93acc237af5 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -18,8 +18,6 @@ package com.android.inputmethod.keyboard.internal;
 
 import android.content.res.TypedArray;
 
-import com.android.inputmethod.latin.StringUtils;
-
 public abstract class KeyStyle {
     private final KeyboardTextsSet mTextsSet;
 
@@ -42,7 +40,7 @@ public abstract class KeyStyle {
     protected String[] parseStringArray(final TypedArray a, final int index) {
         if (a.hasValue(index)) {
             final String text = KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
-            return StringUtils.parseCsvString(text);
+            return KeySpecParser.splitKeySpecs(text);
         }
         return null;
     }
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 86bb255621f0ade9931529844cf42882bbb07717..64c14d32f17aa8170f54b0c5c615ad0e758ad948 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -215,10 +215,6 @@ public final class Constants {
         }
     }
 
-    // Constants for CSV parsing.
-    public static final char CSV_SEPARATOR = ',';
-    public static final char CSV_ESCAPE = '\\';
-
     private Constants() {
         // This utility class is not publicly instantiable.
     }
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 1ad8def16a8e886f4138249de98dee1ace1d6319..09102447f041e2c6b3d9fed4146b7675054a9cbb 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -100,7 +100,7 @@ public final class SettingsValues {
         mWordConnectors =
                 StringUtils.toCodePointArray(res.getString(R.string.symbols_word_connectors));
         Arrays.sort(mWordConnectors);
-        final String[] suggestPuncsSpec = StringUtils.parseCsvString(res.getString(
+        final String[] suggestPuncsSpec = KeySpecParser.splitKeySpecs(res.getString(
                 R.string.suggested_punctuations));
         mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
         mWordSeparators = res.getString(R.string.symbols_word_separators);
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 988b7f60ec097c4e41d73c08d953c0dbc71ca253..c2fd4fb32c571f9751fe5e18fd78151df769690f 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -153,44 +153,6 @@ public final class StringUtils {
         return codePoints;
     }
 
-    public static String[] parseCsvString(final String text) {
-        final int size = text.length();
-        if (size == 0) {
-            return null;
-        }
-        if (codePointCount(text) == 1) {
-            return text.codePointAt(0) == Constants.CSV_SEPARATOR ? null : new String[] { text };
-        }
-
-        ArrayList<String> list = null;
-        int start = 0;
-        for (int pos = 0; pos < size; pos++) {
-            final char c = text.charAt(pos);
-            if (c == Constants.CSV_SEPARATOR) {
-                // Skip empty entry.
-                if (pos - start > 0) {
-                    if (list == null) {
-                        list = CollectionUtils.newArrayList();
-                    }
-                    list.add(text.substring(start, pos));
-                }
-                // Skip comma
-                start = pos + 1;
-            } else if (c == Constants.CSV_ESCAPE) {
-                // Skip escape character and escaped character.
-                pos++;
-            }
-        }
-        final String remain = (size - start > 0) ? text.substring(start) : null;
-        if (list == null) {
-            return remain != null ? new String[] { remain } : null;
-        }
-        if (remain != null) {
-            list.add(remain);
-        }
-        return list.toArray(new String[list.size()]);
-    }
-
     // This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE.
     public static int getCapitalizationType(final String text) {
         // If the first char is not uppercase, then the word is either all lower case or
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java
similarity index 95%
rename from tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
rename to tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java
index 9014e7cc8285a397691c254c5081a7dbba004786..7a87f3722fafde8736839dbbda7009934a8bfb55 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java
@@ -21,7 +21,6 @@ import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.StringUtils;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -29,7 +28,7 @@ import java.util.Arrays;
 import java.util.Locale;
 
 @MediumTest
-public class KeySpecParserCsvTests extends InstrumentationTestCase {
+public class KeySpecParserSplitTests extends InstrumentationTestCase {
     private final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
 
     @Override
@@ -78,7 +77,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
     private void assertTextArray(final String message, final String value,
             final String ... expectedArray) {
         final String resolvedActual = KeySpecParser.resolveTextReference(value, mTextsSet);
-        final String[] actual = StringUtils.parseCsvString(resolvedActual);
+        final String[] actual = KeySpecParser.splitKeySpecs(resolvedActual);
         final String[] expected = (expectedArray.length == 0) ? null : expectedArray;
         assertArrayEquals(message, expected, actual);
     }
@@ -101,7 +100,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
     private static final String SURROGATE1 = PAIR1 + PAIR2;
     private static final String SURROGATE2 = PAIR1 + PAIR2 + PAIR3;
 
-    public void testParseCsvTextZero() {
+    public void testSplitZero() {
         assertTextArray("Empty string", "");
         assertTextArray("Empty entry", ",");
         assertTextArray("Empty entry at beginning", ",a", "a");
@@ -110,7 +109,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
         assertTextArray("Empty entries with escape", ",a,b\\,c,,d,", "a", "b\\,c", "d");
     }
 
-    public void testParseCsvTextSingle() {
+    public void testSplitSingle() {
         assertTextArray("Single char", "a", "a");
         assertTextArray("Surrogate pair", PAIR1, PAIR1);
         assertTextArray("Single escape", "\\", "\\");
@@ -139,7 +138,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
         assertTextArray("Incomplete resource reference 4", "!" + SURROGATE2, "!" + SURROGATE2);
     }
 
-    public void testParseCsvTextSingleEscaped() {
+    public void testSplitSingleEscaped() {
         assertTextArray("Escaped char", "\\a", "\\a");
         assertTextArray("Escaped surrogate pair", "\\" + PAIR1, "\\" + PAIR1);
         assertTextArray("Escaped comma", "\\,", "\\,");
@@ -174,7 +173,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
         assertTextArray("Escaped !TEXT/NAME", "\\!TEXT/EMPTY_STRING", "\\!TEXT/EMPTY_STRING");
     }
 
-    public void testParseCsvTextMulti() {
+    public void testSplitMulti() {
         assertTextArray("Multiple chars", "a,b,c", "a", "b", "c");
         assertTextArray("Multiple chars", "a,b,\\c", "a", "b", "\\c");
         assertTextArray("Multiple chars and escape at beginning and end",
@@ -189,7 +188,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
                 " abc ", " def ", " ghi ");
     }
 
-    public void testParseCsvTextMultiEscaped() {
+    public void testSplitMultiEscaped() {
         assertTextArray("Multiple chars with comma", "a,\\,,c", "a", "\\,", "c");
         assertTextArray("Multiple chars with comma surrounded by spaces", " a , \\, , c ",
                 " a ", " \\, ", " c ");
@@ -208,17 +207,17 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
                 "\\!", "\\!TEXT/EMPTY_STRING");
     }
 
-    public void testParseCsvResourceError() {
+    public void testSplitResourceError() {
         assertError("Incomplete resource name", "!text/", "!text/");
         assertError("Non existing resource", "!text/non_existing");
     }
 
-    public void testParseCsvResourceZero() {
+    public void testSplitResourceZero() {
         assertTextArray("Empty string",
                 "!text/empty_string");
     }
 
-    public void testParseCsvResourceSingle() {
+    public void testSplitResourceSingle() {
         assertTextArray("Single char",
                 "!text/single_char", "a");
         assertTextArray("Space",
@@ -240,7 +239,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
                 "\\\\!text/single_char", "\\\\a");
     }
 
-    public void testParseCsvResourceSingleEscaped() {
+    public void testSplitResourceSingleEscaped() {
         assertTextArray("Escaped char",
                 "!text/escaped_char", "\\a");
         assertTextArray("Escaped comma",
@@ -267,7 +266,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
                 "!text/escaped_label_with_escape", "a\\\\c");
     }
 
-    public void testParseCsvResourceMulti() {
+    public void testSplitResourceMulti() {
         assertTextArray("Multiple chars",
                 "!text/multiple_chars", "a", "b", "c");
         assertTextArray("Multiple chars surrounded by spaces",
@@ -279,7 +278,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
                 "!text/multiple_labels_surrounded_by_spaces", " abc ", " def ", " ghi ");
     }
 
-    public void testParseCsvResourcetMultiEscaped() {
+    public void testSplitResourcetMultiEscaped() {
         assertTextArray("Multiple chars with comma",
                 "!text/multiple_chars_with_comma",
                 "a", "\\,", "c");
@@ -300,7 +299,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
                 " ab\\\\ ", " d\\\\\\, ", " g\\,i ");
     }
 
-    public void testParseMultipleResources() {
+    public void testSplitMultipleResources() {
         assertTextArray("Literals and resources",
                 "1,!text/multiple_chars,z", "1", "a", "b", "c", "z");
         assertTextArray("Literals and resources and escape at end",
@@ -322,7 +321,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
                 "abcabc", "def", "ghi");
     }
 
-    public void testParseIndirectReference() {
+    public void testSplitIndirectReference() {
         assertTextArray("Indirect",
                 "!text/indirect_string", "a", "b", "c");
         assertTextArray("Indirect with literal",
@@ -331,7 +330,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase {
                 "!text/indirect2_string", "a", "b", "c");
     }
 
-    public void testParseInfiniteIndirectReference() {
+    public void testSplitInfiniteIndirectReference() {
         assertError("Infinite indirection",
                 "1,!text/infinite_indirection,2", "1", "infinite", "<infinite>", "loop", "2");
     }