Merge "Check all regexp patterns in ResourceUtils.getDeviceOverrideValue"
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..2c700e5 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.appendToCommaConcatenatedTextIfNotExists(
                     UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + layoutDisplayName, extraValue);
         } else {
             layoutDisplayNameExtraValue = extraValue;
         }
-        final String additionalSubtypeExtraValue = StringUtils.appendToCsvIfNotExists(
-                IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue);
+        final String additionalSubtypeExtraValue =
+                StringUtils.appendToCommaConcatenatedTextIfNotExists(
+                        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.removeFromCommaConcatenatedTextIfExists(
+                layoutExtraValue, StringUtils.removeFromCommaConcatenatedTextIfExists(
+                        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..8f98e3a 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -199,6 +199,7 @@
         if (editorInfo == null) return false;
         final String findingKey = (packageName != null) ? packageName + "." + key
                 : key;
-        return StringUtils.containsInCsv(findingKey, editorInfo.privateImeOptions);
+        return StringUtils.containsInCommaConcatenatedText(
+                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/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 615b2df..1ad8def 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 ab050d7..fa90ba2 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -35,33 +35,50 @@
         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(","));
+    private static final String SEPARATOR_FOR_COMMA_CONCATENATED_TEXT = ",";
+
+    public static boolean containsInCommaConcatenatedText(final String text,
+            final String extraValues) {
+        if (TextUtils.isEmpty(extraValues)) {
+            return false;
+        }
+        return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_CONCATENATED_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 appendToCommaConcatenatedTextIfNotExists(final String text,
+            final String extraValues) {
+        if (TextUtils.isEmpty(extraValues)) {
+            return text;
+        }
+        if (containsInCommaConcatenatedText(text, extraValues)) {
+            return extraValues;
+        }
+        return extraValues + SEPARATOR_FOR_COMMA_CONCATENATED_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 removeFromCommaConcatenatedTextIfExists(final String text,
+            final String extraValues) {
+        if (TextUtils.isEmpty(extraValues)) {
+            return "";
+        }
+        final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_CONCATENATED_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_CONCATENATED_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/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index 29e790a..e980683 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.containsInCommaConcatenatedText("key", null));
+        assertFalse("empty", StringUtils.containsInCommaConcatenatedText("key", ""));
+        assertFalse("not in 1 element",
+                StringUtils.containsInCommaConcatenatedText("key", "key1"));
+        assertFalse("not in 2 elements",
+                StringUtils.containsInCommaConcatenatedText("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.containsInCommaConcatenatedText("key", "key"));
+        assertTrue("in 2 elements", StringUtils.containsInCommaConcatenatedText("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.appendToCommaConcatenatedTextIfNotExists("key", null));
+        assertEquals("empty", "key",
+                StringUtils.appendToCommaConcatenatedTextIfNotExists("key", ""));
 
         assertEquals("not in 1 element", "key1,key",
-                StringUtils.appendToCsvIfNotExists("key", "key1"));
+                StringUtils.appendToCommaConcatenatedTextIfNotExists("key", "key1"));
         assertEquals("not in 2 elements", "key1,key2,key",
-                StringUtils.appendToCsvIfNotExists("key", "key1,key2"));
+                StringUtils.appendToCommaConcatenatedTextIfNotExists("key", "key1,key2"));
 
         assertEquals("in 1 element", "key",
-                StringUtils.appendToCsvIfNotExists("key", "key"));
+                StringUtils.appendToCommaConcatenatedTextIfNotExists("key", "key"));
         assertEquals("in 2 elements at position 1", "key,key2",
-                StringUtils.appendToCsvIfNotExists("key", "key,key2"));
+                StringUtils.appendToCommaConcatenatedTextIfNotExists("key", "key,key2"));
         assertEquals("in 2 elements at position 2", "key1,key",
-                StringUtils.appendToCsvIfNotExists("key", "key1,key"));
+                StringUtils.appendToCommaConcatenatedTextIfNotExists("key", "key1,key"));
         assertEquals("in 3 elements at position 2", "key1,key,key3",
-                StringUtils.appendToCsvIfNotExists("key", "key1,key,key3"));
+                StringUtils.appendToCommaConcatenatedTextIfNotExists("key", "key1,key,key3"));
     }
 
-    public void testRemoveFromCsvIfExists() {
-        assertEquals("null", "", StringUtils.removeFromCsvIfExists("key", null));
-        assertEquals("empty", "", StringUtils.removeFromCsvIfExists("key", ""));
+    public void testRemoveFromExtraValuesIfExists() {
+        assertEquals("null", "", StringUtils.removeFromCommaConcatenatedTextIfExists("key", null));
+        assertEquals("empty", "", StringUtils.removeFromCommaConcatenatedTextIfExists("key", ""));
 
         assertEquals("not in 1 element", "key1",
-                StringUtils.removeFromCsvIfExists("key", "key1"));
+                StringUtils.removeFromCommaConcatenatedTextIfExists("key", "key1"));
         assertEquals("not in 2 elements", "key1,key2",
-                StringUtils.removeFromCsvIfExists("key", "key1,key2"));
+                StringUtils.removeFromCommaConcatenatedTextIfExists("key", "key1,key2"));
 
         assertEquals("in 1 element", "",
-                StringUtils.removeFromCsvIfExists("key", "key"));
+                StringUtils.removeFromCommaConcatenatedTextIfExists("key", "key"));
         assertEquals("in 2 elements at position 1", "key2",
-                StringUtils.removeFromCsvIfExists("key", "key,key2"));
+                StringUtils.removeFromCommaConcatenatedTextIfExists("key", "key,key2"));
         assertEquals("in 2 elements at position 2", "key1",
-                StringUtils.removeFromCsvIfExists("key", "key1,key"));
+                StringUtils.removeFromCommaConcatenatedTextIfExists("key", "key1,key"));
         assertEquals("in 3 elements at position 2", "key1,key3",
-                StringUtils.removeFromCsvIfExists("key", "key1,key,key3"));
+                StringUtils.removeFromCommaConcatenatedTextIfExists("key", "key1,key,key3"));
 
         assertEquals("in 3 elements at position 1,2,3", "",
-                StringUtils.removeFromCsvIfExists("key", "key,key,key"));
+                StringUtils.removeFromCommaConcatenatedTextIfExists("key", "key,key,key"));
         assertEquals("in 5 elements at position 2,4", "key1,key3,key5",
-                StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5"));
+                StringUtils.removeFromCommaConcatenatedTextIfExists(
+                        "key", "key1,key,key3,key,key5"));
     }