Merge "Fix unit test assert helper method"
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index 9ce5051..ad6bead 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -37,7 +37,7 @@
         <item>MODEL=(SAMSUNG-)?GT-I(930[05][NT]?|9308):MANUFACTURER=samsung,8</item>
         <item>MODEL=(SAMSUNG-)?SGH-(T999[V]?|I747[M]?|N064|N035):MANUFACTURER=samsung,8</item>
         <item>MODEL=(SAMSUNG-)?SCH-(J021|R530|I535|I939):MANUFACTURER=samsung,8</item>
-        <item>MODEL=(SAMSUNG-)?(SCL21|SC-06D|SC-03E]):MANUFACTURER=samsung,8</item>
+        <item>MODEL=(SAMSUNG-)?(SCL21|SC-06D|SC-03E):MANUFACTURER=samsung,8</item>
         <item>MODEL=(SAMSUNG-)?(SHV-210[KLS]?|SPH-L710):MANUFACTURER=samsung,8</item>
         <!-- LG Optimus G -->
         <item>MODEL=LG-E97[013]|LS970|L-01E:MANUFACTURER=LGE,15</item>
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index c5bd624..83f1090 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -335,10 +335,6 @@
         }
     }
 
-    public boolean isInMomentarySwitchState() {
-        return mState.isInMomentarySwitchState();
-    }
-
     /**
      * Updates state machine to figure out when to automatically switch back to the previous mode.
      */
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 34464f6..7493df8 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -315,9 +315,8 @@
             default:
                 final int longpressTimeout =
                         Settings.getInstance().getCurrent().mKeyLongpressTimeout;
-                if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
-                    // We use longer timeout for sliding finger input started from the symbols
-                    // mode key.
+                if (tracker.isInSlidingKeyInputFromModifier()) {
+                    // We use longer timeout for sliding finger input started from the modifier key.
                     delay = longpressTimeout * 3;
                 } else {
                     delay = longpressTimeout;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index f18d5ed..9f6374b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -574,11 +574,6 @@
         }
     }
 
-    public boolean isInMomentarySwitchState() {
-        return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
-                || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
-    }
-
     private static boolean isSpaceCharacter(final int c) {
         return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
     }
diff --git a/java/src/com/android/inputmethod/latin/AdditionalFeaturesSettingUtils.java b/java/src/com/android/inputmethod/latin/AdditionalFeaturesSettingUtils.java
new file mode 100644
index 0000000..0fdaea5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AdditionalFeaturesSettingUtils.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethodcommon.InputMethodSettingsFragment;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Utility class for managing additional features settings.
+ */
+public class AdditionalFeaturesSettingUtils {
+    public static final int ADDITIONAL_FEATURES_SETTINGS_SIZE = 0;
+
+    private AdditionalFeaturesSettingUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static void addAdditionalFeaturesPreferences(
+            final Context context, final InputMethodSettingsFragment settingsFragment) {
+        // do nothing.
+    }
+
+    public static void readAdditionalFeaturesPreferencesIntoArray(
+            final SharedPreferences prefs, final int[] additionalFeaturesPreferences) {
+        // do nothing.
+    }
+
+    public static int[] getAdditionalNativeSuggestOptions() {
+        return Settings.getInstance().getCurrent().mAdditionalFeaturesSettingValues;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
index 99b95ea..85b14d8 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -49,13 +49,14 @@
                 && SubtypeLocale.isExceptionalLocale(localeString)) {
             final String layoutDisplayName = SubtypeLocale.getKeyboardLayoutSetDisplayName(
                     keyboardLayoutSetName);
-            layoutDisplayNameExtraValue = StringUtils.appendToCsvIfNotExists(
+            layoutDisplayNameExtraValue = StringUtils.appendToCommaSplittableTextIfNotExists(
                     UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + layoutDisplayName, extraValue);
         } else {
             layoutDisplayNameExtraValue = extraValue;
         }
-        final String additionalSubtypeExtraValue = StringUtils.appendToCsvIfNotExists(
-                IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue);
+        final String additionalSubtypeExtraValue =
+                StringUtils.appendToCommaSplittableTextIfNotExists(
+                        IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue);
         final int nameId = SubtypeLocale.getSubtypeNameId(localeString, keyboardLayoutSetName);
         return new InputMethodSubtype(nameId, R.drawable.ic_subtype_keyboard,
                 localeString, KEYBOARD_MODE,
@@ -66,8 +67,9 @@
         final String localeString = subtype.getLocale();
         final String keyboardLayoutSetName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
         final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName;
-        final String extraValue = StringUtils.removeFromCsvIfExists(layoutExtraValue,
-                StringUtils.removeFromCsvIfExists(IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue()));
+        final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists(
+                layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists(
+                        IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue()));
         final String basePrefSubtype = localeString + LOCALE_AND_LAYOUT_SEPARATOR
                 + keyboardLayoutSetName;
         return extraValue.isEmpty() ? basePrefSubtype
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index c644a77..aad129d 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -136,6 +136,8 @@
         final InputPointers ips = composer.getInputPointers();
         final int inputSize = isGesture ? ips.getPointerSize() : composerSize;
         mNativeSuggestOptions.setIsGesture(isGesture);
+        mNativeSuggestOptions.setAdditionalFeaturesOptions(
+                AdditionalFeaturesSettingUtils.getAdditionalNativeSuggestOptions());
         // proximityInfo and/or prevWordForBigrams may not be null.
         final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
                 getTraverseSession(sessionId).getSession(), ips.getXCoordinates(),
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index dd58db5..1f673e9 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -199,6 +199,6 @@
         if (editorInfo == null) return false;
         final String findingKey = (packageName != null) ? packageName + "." + key
                 : key;
-        return StringUtils.containsInCsv(findingKey, editorInfo.privateImeOptions);
+        return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 592db35..cebc93c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -2369,9 +2369,11 @@
         // Please note that if mSuggest is null, it means that everything is off: suggestion
         // and correction, so we shouldn't try to show the hint
         final boolean showingAddToDictionaryHint =
-                SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind && mSuggest != null
-                // If the suggestion is not in the dictionary, the hint should be shown.
-                && !AutoCorrection.isValidWord(mSuggest, suggestion, true);
+                (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
+                        || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
+                        && mSuggest != null
+                        // If the suggestion is not in the dictionary, the hint should be shown.
+                        && !AutoCorrection.isValidWord(mSuggest, suggestion, true);
 
         if (mSettings.isInternal()) {
             Stats.onSeparator((char)Constants.CODE_SPACE,
diff --git a/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java
index 4425f07..2915513 100644
--- a/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java
+++ b/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java
@@ -22,7 +22,8 @@
     private static final int USE_FULL_EDIT_DISTANCE = 1;
     private static final int OPTIONS_SIZE = 2;
 
-    private final int[] mOptions = new int[OPTIONS_SIZE];
+    private final int[] mOptions = new int[OPTIONS_SIZE
+            + AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
 
     public void setIsGesture(final boolean value) {
         setBooleanOption(IS_GESTURE, value);
@@ -32,6 +33,12 @@
         setBooleanOption(USE_FULL_EDIT_DISTANCE, value);
     }
 
+    public void setAdditionalFeaturesOptions(final int[] additionalOptions) {
+        for (int i = 0; i < additionalOptions.length; i++) {
+            setIntegerOption(OPTIONS_SIZE + i, additionalOptions[i]);
+        }
+    }
+
     public int[] getOptions() {
         return mOptions;
     }
@@ -39,4 +46,8 @@
     private void setBooleanOption(final int key, final boolean value) {
         mOptions[key] = value ? 1 : 0;
     }
+
+    private void setIntegerOption(final int key, final int value) {
+        mOptions[key] = value;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java
index a9fba53..0eb8b4f 100644
--- a/java/src/com/android/inputmethod/latin/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java
@@ -27,6 +27,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.regex.PatternSyntaxException;
 
 public final class ResourceUtils {
     private static final String TAG = ResourceUtils.class.getSimpleName();
@@ -83,22 +84,39 @@
             return overrideValue;
         }
 
-        final String defaultValue = findDefaultConstant(overrideArray);
-        // The defaultValue might be an empty string.
-        if (defaultValue == null) {
-            Log.w(TAG, "Couldn't find override value nor default value:"
-                    + " resource="+ res.getResourceEntryName(overrideResId)
-                    + " build=" + sBuildKeyValuesDebugString);
-        } else {
-            Log.i(TAG, "Found default value:"
-                    + " resource="+ res.getResourceEntryName(overrideResId)
-                    + " build=" + sBuildKeyValuesDebugString
-                    + " default=" + defaultValue);
+        String defaultValue = null;
+        try {
+            defaultValue = findDefaultConstant(overrideArray);
+            // The defaultValue might be an empty string.
+            if (defaultValue == null) {
+                Log.w(TAG, "Couldn't find override value nor default value:"
+                        + " resource="+ res.getResourceEntryName(overrideResId)
+                        + " build=" + sBuildKeyValuesDebugString);
+            } else {
+                Log.i(TAG, "Found default value:"
+                        + " resource="+ res.getResourceEntryName(overrideResId)
+                        + " build=" + sBuildKeyValuesDebugString
+                        + " default=" + defaultValue);
+            }
+        } catch (final DeviceOverridePatternSyntaxError e) {
+            Log.w(TAG, "Syntax error, ignored", e);
         }
         sDeviceOverrideValueMap.put(key, defaultValue);
         return defaultValue;
     }
 
+    @SuppressWarnings("serial")
+    static class DeviceOverridePatternSyntaxError extends Exception {
+        public DeviceOverridePatternSyntaxError(final String message, final String expression) {
+            this(message, expression, null);
+        }
+
+        public DeviceOverridePatternSyntaxError(final String message, final String expression,
+                final Throwable throwable) {
+            super(message + ": " + expression, throwable);
+        }
+    }
+
     /**
      * Find the condition that fulfills specified key value pairs from an array of
      * "condition,constant", and return the corresponding string constant. A condition is
@@ -123,10 +141,12 @@
         if (conditionConstantArray == null || keyValuePairs == null) {
             return null;
         }
+        String foundValue = null;
         for (final String conditionConstant : conditionConstantArray) {
             final int posComma = conditionConstant.indexOf(',');
             if (posComma < 0) {
-                throw new RuntimeException("Array element has no comma: " + conditionConstant);
+                Log.w(TAG, "Array element has no comma: " + conditionConstant);
+                continue;
             }
             final String condition = conditionConstant.substring(0, posComma);
             if (condition.isEmpty()) {
@@ -134,44 +154,59 @@
                 // {@link #findConstantForDefault(String[])}.
                 continue;
             }
-            if (fulfillsCondition(keyValuePairs, condition)) {
-                return conditionConstant.substring(posComma + 1);
+            try {
+                if (fulfillsCondition(keyValuePairs, condition)) {
+                    // Take first match
+                    if (foundValue == null) {
+                        foundValue = conditionConstant.substring(posComma + 1);
+                    }
+                    // And continue walking through all conditions.
+                }
+            } catch (final DeviceOverridePatternSyntaxError e) {
+                Log.w(TAG, "Syntax error, ignored", e);
             }
         }
-        return null;
+        return foundValue;
     }
 
     private static boolean fulfillsCondition(final HashMap<String,String> keyValuePairs,
-            final String condition) {
+            final String condition) throws DeviceOverridePatternSyntaxError {
         final String[] patterns = condition.split(":");
         // Check all patterns in a condition are true
+        boolean matchedAll = true;
         for (final String pattern : patterns) {
             final int posEqual = pattern.indexOf('=');
             if (posEqual < 0) {
-                throw new RuntimeException("Pattern has no '=': " + condition);
+                throw new DeviceOverridePatternSyntaxError("Pattern has no '='", condition);
             }
             final String key = pattern.substring(0, posEqual);
             final String value = keyValuePairs.get(key);
             if (value == null) {
-                throw new RuntimeException("Found unknown key: " + condition);
+                throw new DeviceOverridePatternSyntaxError("Unknown key", condition);
             }
             final String patternRegexpValue = pattern.substring(posEqual + 1);
-            if (!value.matches(patternRegexpValue)) {
-                return false;
+            try {
+                if (!value.matches(patternRegexpValue)) {
+                    matchedAll = false;
+                    // And continue walking through all patterns.
+                }
+            } catch (final PatternSyntaxException e) {
+                throw new DeviceOverridePatternSyntaxError("Syntax error", condition, e);
             }
         }
-        return true;
+        return matchedAll;
     }
 
     @UsedForTesting
-    static String findDefaultConstant(final String[] conditionConstantArray) {
+    static String findDefaultConstant(final String[] conditionConstantArray)
+            throws DeviceOverridePatternSyntaxError {
         if (conditionConstantArray == null) {
             return null;
         }
         for (final String condition : conditionConstantArray) {
             final int posComma = condition.indexOf(',');
             if (posComma < 0) {
-                throw new RuntimeException("Array element has no comma: " + condition);
+                throw new DeviceOverridePatternSyntaxError("Array element has no comma", condition);
             }
             if (posComma == 0) { // condition is empty.
                 return condition.substring(posComma + 1);
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 835ef7b..4d49372 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -207,6 +207,8 @@
 
         if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) {
             removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen());
+        } else {
+            AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this);
         }
 
         setupKeyLongpressTimeoutSettings(prefs, res);
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index eb311b9..0910244 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -80,6 +80,10 @@
     private final boolean mVoiceKeyEnabled;
     private final boolean mVoiceKeyOnMain;
 
+    // Setting values for additional features
+    public final int[] mAdditionalFeaturesSettingValues =
+            new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
+
     // Debug settings
     public final boolean mIsInternal;
 
@@ -149,6 +153,8 @@
                 Settings.PREF_SHOW_SUGGESTIONS_SETTING,
                 res.getString(R.string.prefs_suggestion_visibility_default_value));
         mSuggestionVisibility = createSuggestionVisibility(res, showSuggestionsSetting);
+        AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
+                prefs, mAdditionalFeaturesSettingValues);
         mIsInternal = Settings.isInternal(prefs);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 6a7f99a..c2fd4fb 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -35,33 +35,55 @@
         return text.codePointCount(0, text.length());
     }
 
-    public static boolean containsInArray(final String key, final String[] array) {
+    public static boolean containsInArray(final String text, final String[] array) {
         for (final String element : array) {
-            if (key.equals(element)) return true;
+            if (text.equals(element)) return true;
         }
         return false;
     }
 
-    public static boolean containsInCsv(final String key, final String csv) {
-        if (TextUtils.isEmpty(csv)) return false;
-        return containsInArray(key, csv.split(","));
+    /**
+     * Comma-Splittable Text is similar to Comma-Separated Values (CSV) but has much simpler syntax.
+     * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain
+     * a comma character in it.
+     */
+    private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ",";
+
+    public static boolean containsInCommaSplittableText(final String text,
+            final String extraValues) {
+        if (TextUtils.isEmpty(extraValues)) {
+            return false;
+        }
+        return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
     }
 
-    public static String appendToCsvIfNotExists(final String key, final String csv) {
-        if (TextUtils.isEmpty(csv)) return key;
-        if (containsInCsv(key, csv)) return csv;
-        return csv + "," + key;
+    public static String appendToCommaSplittableTextIfNotExists(final String text,
+            final String extraValues) {
+        if (TextUtils.isEmpty(extraValues)) {
+            return text;
+        }
+        if (containsInCommaSplittableText(text, extraValues)) {
+            return extraValues;
+        }
+        return extraValues + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + text;
     }
 
-    public static String removeFromCsvIfExists(final String key, final String csv) {
-        if (TextUtils.isEmpty(csv)) return "";
-        final String[] elements = csv.split(",");
-        if (!containsInArray(key, elements)) return csv;
+    public static String removeFromCommaSplittableTextIfExists(final String text,
+            final String extraValues) {
+        if (TextUtils.isEmpty(extraValues)) {
+            return "";
+        }
+        final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
+        if (!containsInArray(text, elements)) {
+            return extraValues;
+        }
         final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
         for (final String element : elements) {
-            if (!key.equals(element)) result.add(element);
+            if (!text.equals(element)) {
+                result.add(element);
+            }
         }
-        return TextUtils.join(",", result);
+        return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index dfddb0f..1f45327 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -132,7 +132,10 @@
         public static final int KIND_APP_DEFINED = 6; // Suggested by the application
         public static final int KIND_SHORTCUT = 7; // A shortcut
         public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input)
-        public static final int KIND_RESUMED = 9; // A resumed suggestion (comes from a span)
+        // KIND_RESUMED: A resumed suggestion (comes from a span, currently this type is used only
+        // in java for re-correction)
+        public static final int KIND_RESUMED = 9;
+        public static final int KIND_OOV_CORRECTION = 10; // Most probable string correction
 
         public static final int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags
         public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
index 93687e1..a446672 100644
--- a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
+++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
@@ -23,6 +23,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 public class AccountUtils {
     private AccountUtils() {
@@ -44,4 +45,22 @@
         }
         return retval;
     }
+
+    /**
+     * Get all device accounts having specified domain name.
+     * @param context application context
+     * @param domain domain name used for filtering
+     * @return List of account names that contain the specified domain name
+     */
+    public static List<String> getDeviceAccountsWithDomain(
+            final Context context, final String domain) {
+        final ArrayList<String> retval = new ArrayList<String>();
+        final String atDomain = "@" + domain.toLowerCase(Locale.ROOT);
+        for (final Account account : getAccounts(context)) {
+            if (account.name.toLowerCase(Locale.ROOT).endsWith(atDomain)) {
+                retval.add(account.name);
+            }
+        }
+        return retval;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 1113939..9764610 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -250,10 +250,12 @@
         }
 
         private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords,
-                final int pos) {
-            final String word = suggestedWords.getWord(pos);
-            final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect();
-            final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid;
+                final int indexInSuggestedWords) {
+            final String word = suggestedWords.getWord(indexInSuggestedWords);
+            final boolean isAutoCorrect = indexInSuggestedWords == 1
+                    && suggestedWords.willAutoCorrect();
+            final boolean isTypedWordValid = indexInSuggestedWords == 0
+                    && suggestedWords.mTypedWordValid;
             if (!isAutoCorrect && !isTypedWordValid)
                 return word;
 
@@ -270,28 +272,31 @@
             return spannedWord;
         }
 
-        private int getWordPosition(final int index, final SuggestedWords suggestedWords) {
+        private int getIndexInSuggestedWords(final int indexInStrip,
+                final SuggestedWords suggestedWords) {
             // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
             // suggestions.
-            final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0;
-            if (index == mCenterSuggestionIndex) {
-                return centerPos;
-            } else if (index == centerPos) {
+            final int mostImportantIndexInSuggestedWords = suggestedWords.willAutoCorrect() ? 1 : 0;
+            if (indexInStrip == mCenterSuggestionIndex) {
+                return mostImportantIndexInSuggestedWords;
+            } else if (indexInStrip == mostImportantIndexInSuggestedWords) {
                 return mCenterSuggestionIndex;
             } else {
-                return index;
+                return indexInStrip;
             }
         }
 
-        private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords,
-                final int pos) {
+        private int getSuggestionTextColor(final int indexInStrip,
+                final SuggestedWords suggestedWords) {
+            final int indexInSuggestedWords = getIndexInSuggestedWords(
+                    indexInStrip, suggestedWords);
             // TODO: Need to revisit this logic with bigram suggestions
-            final boolean isSuggested = (pos != 0);
+            final boolean isSuggested = (indexInSuggestedWords != 0);
 
             final int color;
-            if (index == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) {
+            if (indexInStrip == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) {
                 color = mColorAutoCorrect;
-            } else if (index == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) {
+            } else if (indexInStrip == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) {
                 color = mColorValidTypedWord;
             } else if (isSuggested) {
                 color = mColorSuggested;
@@ -301,7 +306,7 @@
             if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
                 // If we auto-correct, then the autocorrection is in slot 0 and the typed word
                 // is in slot 1.
-                if (index == mCenterSuggestionIndex
+                if (indexInStrip == mCenterSuggestionIndex
                         && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet(
                                 suggestedWords.getWord(1), suggestedWords.getWord(0))) {
                     return 0xFFFF0000;
@@ -338,67 +343,101 @@
             setupTexts(suggestedWords, countInStrip);
             mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
             int x = 0;
-            for (int index = 0; index < countInStrip; index++) {
-                final int pos = getWordPosition(index, suggestedWords);
-
-                if (index != 0) {
-                    final View divider = mDividers.get(pos);
+            for (int indexInStrip = 0; indexInStrip < countInStrip; indexInStrip++) {
+                if (indexInStrip != 0) {
+                    final View divider = mDividers.get(indexInStrip);
                     // Add divider if this isn't the left most suggestion in suggestions strip.
                     addDivider(stripView, divider);
                     x += divider.getMeasuredWidth();
                 }
 
-                final CharSequence styled = mTexts.get(pos);
-                final TextView word = mWords.get(pos);
-                if (index == mCenterSuggestionIndex && mMoreSuggestionsAvailable) {
-                    // TODO: This "more suggestions hint" should have nicely designed icon.
-                    word.setCompoundDrawablesWithIntrinsicBounds(
-                            null, null, null, mMoreSuggestionsHint);
-                    // HACK: To align with other TextView that has no compound drawables.
-                    word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight());
-                } else {
-                    word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
-                }
-
-                // Disable this suggestion if the suggestion is null or empty.
-                word.setEnabled(!TextUtils.isEmpty(styled));
-                word.setTextColor(getSuggestionTextColor(index, suggestedWords, pos));
-                final int width = getSuggestionWidth(index, stripWidth);
-                final CharSequence text = getEllipsizedText(styled, width, word.getPaint());
-                final float scaleX = word.getTextScaleX();
-                word.setText(text); // TextView.setText() resets text scale x to 1.0.
-                word.setTextScaleX(scaleX);
+                final int width = getSuggestionWidth(indexInStrip, stripWidth);
+                final TextView word = layoutWord(suggestedWords, indexInStrip, width);
                 stripView.addView(word);
-                setLayoutWeight(
-                        word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT);
+                setLayoutWeight(word, getSuggestionWeight(indexInStrip),
+                        ViewGroup.LayoutParams.MATCH_PARENT);
                 x += word.getMeasuredWidth();
 
-                if (DBG && pos < suggestedWords.size()) {
-                    final String debugInfo = Utils.getDebugInfo(suggestedWords, pos);
-                    if (debugInfo != null) {
-                        final TextView info = mInfos.get(pos);
-                        info.setText(debugInfo);
-                        placer.addView(info);
-                        info.measure(ViewGroup.LayoutParams.WRAP_CONTENT,
-                                ViewGroup.LayoutParams.WRAP_CONTENT);
-                        final int infoWidth = info.getMeasuredWidth();
-                        final int y = info.getMeasuredHeight();
-                        ViewLayoutUtils.placeViewAt(
-                                info, x - infoWidth, y, infoWidth, info.getMeasuredHeight());
-                    }
+                if (DBG) {
+                    layoutDebugInfo(suggestedWords, indexInStrip, placer, x);
                 }
             }
         }
 
-        private int getSuggestionWidth(final int index, final int maxWidth) {
+        /**
+         * Format appropriately the suggested word indirectly specified by
+         * <code>indexInStrip</code> as text in a corresponding {@link TextView}. When the
+         * suggested word doesn't exist, the corresponding {@link TextView} will be disabled
+         * and never respond to user interaction. The suggested word may be shrunk or ellipsized to
+         * fit in the specified width.
+         *
+         * The <code>indexInStrip</code> argument is the index in the suggestion strip. The indices
+         * increase towards the right for LTR scripts and the left for RTL scripts, starting with 0.
+         * The index of the most important suggestion is in {@link #mCenterSuggestionIndex}. This
+         * usually doesn't match the index in <code>suggedtedWords</code> -- see
+         * {@link #getIndexInSuggestedWords(int,SuggestedWords)}.
+         *
+         * @param suggestedWords the list of suggestions.
+         * @param indexInStrip the in the suggestion strip.
+         * @param width the maximum width for layout in pixels.
+         * @return the {@link TextView} containing the suggested word appropriately formatted.
+         */
+        private TextView layoutWord(final SuggestedWords suggestedWords, final int indexInStrip,
+                final int width) {
+            final int indexInSuggestedWords = getIndexInSuggestedWords(
+                    indexInStrip, suggestedWords);
+            final CharSequence styled = mTexts.get(indexInSuggestedWords);
+            final TextView word = mWords.get(indexInSuggestedWords);
+            if (indexInStrip == mCenterSuggestionIndex && mMoreSuggestionsAvailable) {
+                // TODO: This "more suggestions hint" should have a nicely designed icon.
+                word.setCompoundDrawablesWithIntrinsicBounds(
+                        null, null, null, mMoreSuggestionsHint);
+                // HACK: Align with other TextViews that have no compound drawables.
+                word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight());
+            } else {
+                word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+            }
+
+            // Disable this suggestion if the suggestion is null or empty.
+            word.setEnabled(!TextUtils.isEmpty(styled));
+            word.setTextColor(getSuggestionTextColor(indexInStrip, suggestedWords));
+            final CharSequence text = getEllipsizedText(styled, width, word.getPaint());
+            final float scaleX = word.getTextScaleX();
+            word.setText(text); // TextView.setText() resets text scale x to 1.0.
+            word.setTextScaleX(scaleX);
+            return word;
+        }
+
+        private void layoutDebugInfo(final SuggestedWords suggestedWords, final int indexInStrip,
+                final ViewGroup placer, final int x) {
+            final int indexInSuggestedWords = getIndexInSuggestedWords(
+                    indexInStrip, suggestedWords);
+            if (indexInSuggestedWords >= suggestedWords.size()) {
+                return;
+            }
+            final String debugInfo = Utils.getDebugInfo(suggestedWords, indexInSuggestedWords);
+            if (debugInfo == null) {
+                return;
+            }
+            final TextView info = mInfos.get(indexInSuggestedWords);
+            info.setText(debugInfo);
+            placer.addView(info);
+            info.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+            final int infoWidth = info.getMeasuredWidth();
+            final int y = info.getMeasuredHeight();
+            ViewLayoutUtils.placeViewAt(
+                    info, x - infoWidth, y, infoWidth, info.getMeasuredHeight());
+        }
+
+        private int getSuggestionWidth(final int indexInStrip, final int maxWidth) {
             final int paddings = mPadding * mSuggestionsCountInStrip;
             final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
             final int availableWidth = maxWidth - paddings - dividers;
-            return (int)(availableWidth * getSuggestionWeight(index));
+            return (int)(availableWidth * getSuggestionWeight(indexInStrip));
         }
 
-        private float getSuggestionWeight(final int index) {
-            if (index == mCenterSuggestionIndex) {
+        private float getSuggestionWeight(final int indexInStrip) {
+            if (indexInStrip == mCenterSuggestionIndex) {
                 return mCenterSuggestionWeight;
             } else {
                 // TODO: Revisit this for cases of 5 or more suggestions
@@ -422,16 +461,16 @@
         private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords,
                 final ViewGroup stripView) {
             final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
-            for (int index = 0; index < countInStrip; index++) {
-                if (index != 0) {
+            for (int indexInStrip = 0; indexInStrip < countInStrip; indexInStrip++) {
+                if (indexInStrip != 0) {
                     // Add divider if this isn't the left most suggestion in suggestions strip.
-                    addDivider(stripView, mDividers.get(index));
+                    addDivider(stripView, mDividers.get(indexInStrip));
                 }
 
-                final TextView word = mWords.get(index);
+                final TextView word = mWords.get(indexInStrip);
                 word.setEnabled(true);
                 word.setTextColor(mColorAutoCorrect);
-                final String text = suggestedWords.getWord(index);
+                final String text = suggestedWords.getWord(indexInStrip);
                 word.setText(text);
                 word.setTextScaleX(1.0f);
                 word.setCompoundDrawables(null, null, null, null);
diff --git a/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java b/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java
index ed16846..c915522 100644
--- a/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java
@@ -19,25 +19,41 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.inputmethod.latin.ResourceUtils.DeviceOverridePatternSyntaxError;
+
 import java.util.HashMap;
 
 @SmallTest
 public class ResourceUtilsTests extends AndroidTestCase {
     public void testFindDefaultConstant() {
         final String[] nullArray = null;
-        assertNull(ResourceUtils.findDefaultConstant(nullArray));
-
         final String[] emptyArray = {};
-        assertNull(ResourceUtils.findDefaultConstant(emptyArray));
-
         final String[] array = {
-            "HARDWARE=grouper,0.3",
-            "HARDWARE=mako,0.4",
-            ",defaultValue1",
-            "HARDWARE=manta,0.2",
-            ",defaultValue2",
+                "HARDWARE=grouper,0.3",
+                "HARDWARE=mako,0.4",
+                ",defaultValue1",
+                "HARDWARE=manta,0.2",
+                ",defaultValue2",
         };
-        assertEquals(ResourceUtils.findDefaultConstant(array), "defaultValue1");
+
+        try {
+            assertNull(ResourceUtils.findDefaultConstant(nullArray));
+            assertNull(ResourceUtils.findDefaultConstant(emptyArray));
+            assertEquals(ResourceUtils.findDefaultConstant(array), "defaultValue1");
+        } catch (final DeviceOverridePatternSyntaxError e) {
+            fail(e.getMessage());
+        }
+
+        final String[] errorArray = {
+            "HARDWARE=grouper,0.3",
+            "no_comma"
+        };
+        try {
+            final String defaultValue = ResourceUtils.findDefaultConstant(errorArray);
+            fail("exception should be thrown: defaultValue=" + defaultValue);
+        } catch (final DeviceOverridePatternSyntaxError e) {
+            assertEquals("Array element has no comma: no_comma", e.getMessage());
+        }
     }
 
     public void testFindConstantForKeyValuePairsSimple() {
@@ -67,33 +83,23 @@
 
         final HashMap<String,String> keyValues = CollectionUtils.newHashMap();
         keyValues.put(HARDWARE_KEY, "grouper");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3");
+        assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "mako");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4");
+        assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "manta");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2");
+        assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
 
-        try {
-            keyValues.clear();
-            keyValues.put("hardware", "grouper");
-            final String constant = ResourceUtils.findConstantForKeyValuePairs(keyValues, array);
-            fail("condition without HARDWARE must fail: constant=" + constant);
-        } catch (final RuntimeException e) {
-            assertEquals(e.getMessage(), "Found unknown key: HARDWARE=grouper");
-        }
+        keyValues.clear();
+        keyValues.put("hardware", "grouper");
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
+
         keyValues.clear();
         keyValues.put(HARDWARE_KEY, "MAKO");
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "mantaray");
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
 
-        try {
-            final String constant = ResourceUtils.findConstantForKeyValuePairs(
-                    emptyKeyValue, array);
-            fail("emptyCondition shouldn't match: constant=" + constant);
-        } catch (final RuntimeException e) {
-            assertEquals(e.getMessage(), "Found unknown key: HARDWARE=grouper");
-        }
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(emptyKeyValue, array));
     }
 
     public void testFindConstantForKeyValuePairsCombined() {
@@ -102,6 +108,8 @@
         final String MANUFACTURER_KEY = "MANUFACTURER";
         final String[] array = {
             ",defaultValue",
+            "no_comma",
+            "error_pattern,0.1",
             "HARDWARE=grouper:MANUFACTURER=asus,0.3",
             "HARDWARE=mako:MODEL=Nexus 4,0.4",
             "HARDWARE=manta:MODEL=Nexus 10:MANUFACTURER=samsung,0.2"
@@ -117,25 +125,25 @@
         keyValues.put(HARDWARE_KEY, "grouper");
         keyValues.put(MODEL_KEY, "Nexus 7");
         keyValues.put(MANUFACTURER_KEY, "asus");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3");
+        assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
 
         keyValues.clear();
         keyValues.put(HARDWARE_KEY, "mako");
         keyValues.put(MODEL_KEY, "Nexus 4");
         keyValues.put(MANUFACTURER_KEY, "LGE");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4");
+        assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
 
         keyValues.clear();
         keyValues.put(HARDWARE_KEY, "manta");
         keyValues.put(MODEL_KEY, "Nexus 10");
         keyValues.put(MANUFACTURER_KEY, "samsung");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2");
+        assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
         keyValues.put(HARDWARE_KEY, "mantaray");
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray), "0.2");
+        assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
     }
 
     public void testFindConstantForKeyValuePairsRegexp() {
@@ -144,6 +152,8 @@
         final String MANUFACTURER_KEY = "MANUFACTURER";
         final String[] array = {
             ",defaultValue",
+            "no_comma",
+            "HARDWARE=error_regexp:MANUFACTURER=error[regexp,0.1",
             "HARDWARE=grouper|tilapia:MANUFACTURER=asus,0.3",
             "HARDWARE=[mM][aA][kK][oO]:MODEL=Nexus 4,0.4",
             "HARDWARE=manta.*:MODEL=Nexus 10:MANUFACTURER=samsung,0.2"
@@ -153,24 +163,24 @@
         keyValues.put(HARDWARE_KEY, "grouper");
         keyValues.put(MODEL_KEY, "Nexus 7");
         keyValues.put(MANUFACTURER_KEY, "asus");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3");
+        assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "tilapia");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3");
+        assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
 
         keyValues.clear();
         keyValues.put(HARDWARE_KEY, "mako");
         keyValues.put(MODEL_KEY, "Nexus 4");
         keyValues.put(MANUFACTURER_KEY, "LGE");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4");
+        assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "MAKO");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4");
+        assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
 
         keyValues.clear();
         keyValues.put(HARDWARE_KEY, "manta");
         keyValues.put(MODEL_KEY, "Nexus 10");
         keyValues.put(MANUFACTURER_KEY, "samsung");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2");
+        assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "mantaray");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2");
+        assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index 29e790a..4f26098 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -40,57 +40,62 @@
         }));
     }
 
-    public void testContainsInCsv() {
-        assertFalse("null", StringUtils.containsInCsv("key", null));
-        assertFalse("empty", StringUtils.containsInCsv("key", ""));
-        assertFalse("not in 1 element", StringUtils.containsInCsv("key", "key1"));
-        assertFalse("not in 2 elements", StringUtils.containsInCsv("key", "key1,key2"));
+    public void testContainsInExtraValues() {
+        assertFalse("null", StringUtils.containsInCommaSplittableText("key", null));
+        assertFalse("empty", StringUtils.containsInCommaSplittableText("key", ""));
+        assertFalse("not in 1 element",
+                StringUtils.containsInCommaSplittableText("key", "key1"));
+        assertFalse("not in 2 elements",
+                StringUtils.containsInCommaSplittableText("key", "key1,key2"));
 
-        assertTrue("in 1 element", StringUtils.containsInCsv("key", "key"));
-        assertTrue("in 2 elements", StringUtils.containsInCsv("key", "key1,key"));
+        assertTrue("in 1 element", StringUtils.containsInCommaSplittableText("key", "key"));
+        assertTrue("in 2 elements", StringUtils.containsInCommaSplittableText("key", "key1,key"));
     }
 
-    public void testAppendToCsvIfNotExists() {
-        assertEquals("null", "key", StringUtils.appendToCsvIfNotExists("key", null));
-        assertEquals("empty", "key", StringUtils.appendToCsvIfNotExists("key", ""));
+    public void testAppendToExtraValuesIfNotExists() {
+        assertEquals("null", "key",
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", null));
+        assertEquals("empty", "key",
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", ""));
 
         assertEquals("not in 1 element", "key1,key",
-                StringUtils.appendToCsvIfNotExists("key", "key1"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1"));
         assertEquals("not in 2 elements", "key1,key2,key",
-                StringUtils.appendToCsvIfNotExists("key", "key1,key2"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key2"));
 
         assertEquals("in 1 element", "key",
-                StringUtils.appendToCsvIfNotExists("key", "key"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key"));
         assertEquals("in 2 elements at position 1", "key,key2",
-                StringUtils.appendToCsvIfNotExists("key", "key,key2"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key,key2"));
         assertEquals("in 2 elements at position 2", "key1,key",
-                StringUtils.appendToCsvIfNotExists("key", "key1,key"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key"));
         assertEquals("in 3 elements at position 2", "key1,key,key3",
-                StringUtils.appendToCsvIfNotExists("key", "key1,key,key3"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key,key3"));
     }
 
-    public void testRemoveFromCsvIfExists() {
-        assertEquals("null", "", StringUtils.removeFromCsvIfExists("key", null));
-        assertEquals("empty", "", StringUtils.removeFromCsvIfExists("key", ""));
+    public void testRemoveFromExtraValuesIfExists() {
+        assertEquals("null", "", StringUtils.removeFromCommaSplittableTextIfExists("key", null));
+        assertEquals("empty", "", StringUtils.removeFromCommaSplittableTextIfExists("key", ""));
 
         assertEquals("not in 1 element", "key1",
-                StringUtils.removeFromCsvIfExists("key", "key1"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key1"));
         assertEquals("not in 2 elements", "key1,key2",
-                StringUtils.removeFromCsvIfExists("key", "key1,key2"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key1,key2"));
 
         assertEquals("in 1 element", "",
-                StringUtils.removeFromCsvIfExists("key", "key"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key"));
         assertEquals("in 2 elements at position 1", "key2",
-                StringUtils.removeFromCsvIfExists("key", "key,key2"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key,key2"));
         assertEquals("in 2 elements at position 2", "key1",
-                StringUtils.removeFromCsvIfExists("key", "key1,key"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key1,key"));
         assertEquals("in 3 elements at position 2", "key1,key3",
-                StringUtils.removeFromCsvIfExists("key", "key1,key,key3"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key1,key,key3"));
 
         assertEquals("in 3 elements at position 1,2,3", "",
-                StringUtils.removeFromCsvIfExists("key", "key,key,key"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key,key,key"));
         assertEquals("in 5 elements at position 2,4", "key1,key3,key5",
-                StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5"));
+                StringUtils.removeFromCommaSplittableTextIfExists(
+                        "key", "key1,key,key3,key,key5"));
     }