Merge "[ML2] Small refactoring"
diff --git a/java/res/values-ne-rNP/strings.xml b/java/res/values-ne-rNP/strings.xml
index c7c95ae..1764e76 100644
--- a/java/res/values-ne-rNP/strings.xml
+++ b/java/res/values-ne-rNP/strings.xml
@@ -80,7 +80,7 @@
     <string name="help_and_feedback" msgid="5328219371839879161">"मद्दत र प्रतिक्रिया"</string>
     <string name="select_language" msgid="3693815588777926848">"इनपुट भाषाहरू"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"बचत गर्न पुनः छुनुहोस्"</string>
-    <string name="hint_add_to_dictionary_without_word" msgid="3040385779511255101">"बचत गर्न यहाँ छुनुहोस्"</string>
+    <string name="hint_add_to_dictionary_without_word" msgid="3040385779511255101">"सुरक्षित गर्न यहाँ छुनुहोस्"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"उपलब्ध शब्दकोश"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"किबोर्ड थिम"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"अंग्रेजी (युके)"</string>
diff --git a/java/res/values-ta-rIN/strings-talkback-descriptions.xml b/java/res/values-ta-rIN/strings-talkback-descriptions.xml
index 911e1a5..0ef0720 100644
--- a/java/res/values-ta-rIN/strings-talkback-descriptions.xml
+++ b/java/res/values-ta-rIN/strings-talkback-descriptions.xml
@@ -58,7 +58,7 @@
     <string name="keyboard_mode_date" msgid="6597407244976713364">"தேதி"</string>
     <string name="keyboard_mode_date_time" msgid="3642804408726668808">"தேதி மற்றும் நேரம்"</string>
     <string name="keyboard_mode_email" msgid="1239682082047693644">"மின்னஞ்சல்"</string>
-    <string name="keyboard_mode_im" msgid="3812086215529493501">"செய்தியிடல்"</string>
+    <string name="keyboard_mode_im" msgid="3812086215529493501">"மெசேஜ்"</string>
     <string name="keyboard_mode_number" msgid="5395042245837996809">"எண்"</string>
     <string name="keyboard_mode_phone" msgid="2486230278064523665">"ஃபோன்"</string>
     <string name="keyboard_mode_text" msgid="9138789594969187494">"உரை"</string>
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 7294813..02d5edd 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -57,6 +57,13 @@
         @SuppressWarnings("dep-ann")
         public static final String FORCE_ASCII = "forceAscii";
 
+        /**
+         * The private IME option used to suppress the floating gesture preview for a given text
+         * field. This overrides the corresponding keyboard settings preference.
+         * {@link com.android.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled}
+         */
+        public static final String NO_FLOATING_GESTURE_PREVIEW = "noGestureFloatingPreview";
+
         private ImeOption() {
             // This utility class is not publicly instantiable.
         }
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index ebe4361..782e182 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.latin;
 
+import static com.android.inputmethod.latin.Constants.ImeOption.NO_FLOATING_GESTURE_PREVIEW;
 import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE;
 import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT;
 
@@ -42,6 +43,12 @@
     final public boolean mApplicationSpecifiedCompletionOn;
     final public boolean mShouldInsertSpacesAutomatically;
     final public boolean mShouldShowVoiceInputKey;
+    /**
+     * Whether the floating gesture preview should be disabled. If true, this should override the
+     * corresponding keyboard settings preference, always suppressing the floating preview text.
+     * {@link com.android.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled}
+     */
+    final public boolean mDisableGestureFloatingPreviewText;
     final private int mInputType;
     final private EditorInfo mEditorInfo;
     final private String mPackageNameForPrivateImeOptions;
@@ -76,6 +83,7 @@
             mApplicationSpecifiedCompletionOn = false;
             mShouldInsertSpacesAutomatically = false;
             mShouldShowVoiceInputKey = false;
+            mDisableGestureFloatingPreviewText = false;
             return;
         }
         // inputClass == InputType.TYPE_CLASS_TEXT
@@ -107,6 +115,9 @@
                 || hasNoMicrophoneKeyOption();
         mShouldShowVoiceInputKey = !noMicrophone;
 
+        mDisableGestureFloatingPreviewText = InputAttributes.inPrivateImeOptions(
+                mPackageNameForPrivateImeOptions, NO_FLOATING_GESTURE_PREVIEW, editorInfo);
+
         // If it's a browser edit field and auto correct is not ON explicitly, then
         // disable auto correction, but keep suggestions on.
         // If NO_SUGGESTIONS is set, don't do prediction.
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f8df120..c55acd4 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -555,6 +555,7 @@
         KeyboardSwitcher.init(this);
         AudioAndHapticFeedbackManager.init(this);
         AccessibilityUtils.init(this);
+        mStatsUtilsManager.onCreate(this /* context */);
         super.onCreate();
 
         mHandler.onCreate();
@@ -586,7 +587,6 @@
         registerReceiver(mDictionaryDumpBroadcastReceiver, dictDumpFilter);
 
         DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this);
-        mStatsUtilsManager.onCreate(this /* context */);
         StatsUtils.onCreate(mSettings.getCurrent(), mRichImm);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 91a2b67..e91862d 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -162,8 +162,8 @@
                 autoCorrectionThresholdRawValue);
         mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
         mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
-        mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
-                Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
+        mGestureFloatingPreviewTextEnabled = !mInputAttributes.mDisableGestureFloatingPreviewText
+                && prefs.getBoolean(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
         mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res);
         mAutoCorrectionEnabledPerUserSettings = mAutoCorrectEnabled
                 && !mInputAttributes.mInputTypeNoAutoCorrect;
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
index 1a4fa63..e10571e 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
@@ -20,12 +20,14 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.text.InputType;
 import android.util.Log;
 import android.util.LruCache;
+import android.util.Pair;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -49,16 +51,15 @@
             DistracterFilterCheckingExactMatchesAndSuggestions.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private static final int MAX_DISTRACTERS_CACHE_SIZE = 512;
+    private static final int MAX_DISTRACTERS_CACHE_SIZE = 1024;
 
     private final Context mContext;
-    private final Map<Locale, InputMethodSubtype> mLocaleToSubtypeMap;
-    private final Map<Locale, Keyboard> mLocaleToKeyboardMap;
+    private final ConcurrentHashMap<Locale, InputMethodSubtype> mLocaleToSubtypeCache;
+    private final ConcurrentHashMap<Locale, Keyboard> mLocaleToKeyboardCache;
     private final DictionaryFacilitatorLruCache mDictionaryFacilitatorLruCache;
-    private final LruCache<String, Boolean> mDistractersCache;
-    // TODO: Remove and support multiple locales at the same time.
-    private Locale mCurrentLocale;
-    private Keyboard mKeyboard;
+    // The key is a pair of a locale and a word. The value indicates the word is a distracter to
+    // words of the locale.
+    private final LruCache<Pair<Locale, String>, Boolean> mDistractersCache;
     private final Object mLock = new Object();
 
     // If the score of the top suggestion exceeds this value, the tested word (e.g.,
@@ -78,20 +79,17 @@
     public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context,
             final DictionaryFacilitatorLruCache dictionaryFacilitatorLruCache) {
         mContext = context;
-        mLocaleToSubtypeMap = new HashMap<>();
-        mLocaleToKeyboardMap = new HashMap<>();
+        mLocaleToSubtypeCache = new ConcurrentHashMap<>();
+        mLocaleToKeyboardCache = new ConcurrentHashMap<>();
         mDictionaryFacilitatorLruCache = dictionaryFacilitatorLruCache;
         mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
-        mCurrentLocale = null;
-        mKeyboard = null;
     }
 
     @Override
     public void close() {
-        mLocaleToKeyboardMap.clear();
+        mLocaleToSubtypeCache.clear();
+        mLocaleToKeyboardCache.clear();
         mDistractersCache.evictAll();
-        mCurrentLocale = null;
-        mKeyboard = null;
     }
 
     @Override
@@ -108,29 +106,36 @@
                 newLocaleToSubtypeMap.put(locale, subtype);
             }
         }
-        if (mLocaleToSubtypeMap.equals(newLocaleToSubtypeMap)) {
+        if (mLocaleToSubtypeCache.equals(newLocaleToSubtypeMap)) {
             // Enabled subtypes have not been changed.
             return;
         }
-        synchronized (mLock) {
-            mLocaleToSubtypeMap.clear();
-            mLocaleToSubtypeMap.putAll(newLocaleToSubtypeMap);
-            mLocaleToKeyboardMap.clear();
+        // Update subtype and keyboard map for locales that are in the current mapping.
+        for (final Locale locale: mLocaleToSubtypeCache.keySet()) {
+            if (newLocaleToSubtypeMap.containsKey(locale)) {
+                final InputMethodSubtype newSubtype = newLocaleToSubtypeMap.remove(locale);
+                if (newSubtype.equals(newLocaleToSubtypeMap.get(locale))) {
+                    // Mapping has not been changed.
+                    continue;
+                }
+                mLocaleToSubtypeCache.replace(locale, newSubtype);
+            } else {
+                mLocaleToSubtypeCache.remove(locale);
+            }
+            mLocaleToKeyboardCache.remove(locale);
         }
+        // Add locales that are not in the current mapping.
+        mLocaleToSubtypeCache.putAll(newLocaleToSubtypeMap);
     }
 
-    private void loadKeyboardForLocale(final Locale newLocale) {
-        final Keyboard cachedKeyboard = mLocaleToKeyboardMap.get(newLocale);
+    private Keyboard getKeyboardForLocale(final Locale locale) {
+        final Keyboard cachedKeyboard = mLocaleToKeyboardCache.get(locale);
         if (cachedKeyboard != null) {
-            mKeyboard = cachedKeyboard;
-            return;
+            return cachedKeyboard;
         }
-        final InputMethodSubtype subtype;
-        synchronized (mLock) {
-            subtype = mLocaleToSubtypeMap.get(newLocale);
-        }
+        final InputMethodSubtype subtype = mLocaleToSubtypeCache.get(locale);
         if (subtype == null) {
-            return;
+            return null;
         }
         final EditorInfo editorInfo = new EditorInfo();
         editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
@@ -143,7 +148,9 @@
         builder.setSubtype(new RichInputMethodSubtype(subtype));
         builder.setIsSpellChecker(false /* isSpellChecker */);
         final KeyboardLayoutSet layoutSet = builder.build();
-        mKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
+        final Keyboard newKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
+        mLocaleToKeyboardCache.put(locale, newKeyboard);
+        return newKeyboard;
     }
 
     /**
@@ -161,24 +168,18 @@
         if (locale == null) {
             return false;
         }
-        if (!locale.equals(mCurrentLocale)) {
-            synchronized (mLock) {
-                if (!mLocaleToSubtypeMap.containsKey(locale)) {
-                    Log.e(TAG, "Locale " + locale + " is not enabled.");
-                    // TODO: Investigate what we should do for disabled locales.
-                    return false;
-                }
-                mCurrentLocale = locale;
-                loadKeyboardForLocale(locale);
-                mDistractersCache.evictAll();
-            }
+        if (!mLocaleToSubtypeCache.containsKey(locale)) {
+            Log.e(TAG, "Locale " + locale + " is not enabled.");
+            // TODO: Investigate what we should do for disabled locales.
+            return false;
         }
         final DictionaryFacilitator dictionaryFacilitator =
                 mDictionaryFacilitatorLruCache.get(locale);
         if (DEBUG) {
             Log.d(TAG, "testedWord: " + testedWord);
         }
-        final Boolean isCachedDistracter = mDistractersCache.get(testedWord);
+        final Pair<Locale, String> cacheKey = new Pair<>(locale, testedWord);
+        final Boolean isCachedDistracter = mDistractersCache.get(cacheKey);
         if (isCachedDistracter != null && isCachedDistracter) {
             if (DEBUG) {
                 Log.d(TAG, "isDistracter: true (cache hit)");
@@ -189,8 +190,8 @@
         final boolean isDistracterCheckedByGetMaxFreqencyOfExactMatches =
                 checkDistracterUsingMaxFreqencyOfExactMatches(dictionaryFacilitator, testedWord);
         if (isDistracterCheckedByGetMaxFreqencyOfExactMatches) {
-            // Add the word to the cache.
-            mDistractersCache.put(testedWord, Boolean.TRUE);
+            // Add the pair of locale and word to the cache.
+            mDistractersCache.put(cacheKey, Boolean.TRUE);
             return true;
         }
         final boolean isValidWord = dictionaryFacilitator.isValidWord(testedWord,
@@ -203,11 +204,12 @@
             return false;
         }
 
+        final Keyboard keyboard = getKeyboardForLocale(locale);
         final boolean isDistracterCheckedByGetSuggestion =
-                checkDistracterUsingGetSuggestions(dictionaryFacilitator, testedWord);
+                checkDistracterUsingGetSuggestions(dictionaryFacilitator, keyboard, testedWord);
         if (isDistracterCheckedByGetSuggestion) {
-            // Add the word to the cache.
-            mDistractersCache.put(testedWord, Boolean.TRUE);
+            // Add the pair of locale and word to the cache.
+            mDistractersCache.put(cacheKey, Boolean.TRUE);
             return true;
         }
         return false;
@@ -229,8 +231,9 @@
     }
 
     private boolean checkDistracterUsingGetSuggestions(
-            final DictionaryFacilitator dictionaryFacilitator, final String testedWord) {
-        if (mKeyboard == null) {
+            final DictionaryFacilitator dictionaryFacilitator, final Keyboard keyboard,
+            final String testedWord) {
+        if (keyboard == null) {
             return false;
         }
         final SettingsValuesForSuggestion settingsValuesForSuggestion =
@@ -243,24 +246,24 @@
                 testedWord;
         final WordComposer composer = new WordComposer();
         final int[] codePoints = StringUtils.toCodePointArray(testedWord);
-
+        final int[] coordinates = keyboard.getCoordinates(codePoints);
+        composer.setComposingWord(codePoints, coordinates);
+        final SuggestionResults suggestionResults;
         synchronized (mLock) {
-            final int[] coordinates = mKeyboard.getCoordinates(codePoints);
-            composer.setComposingWord(codePoints, coordinates);
-            final SuggestionResults suggestionResults = dictionaryFacilitator.getSuggestionResults(
-                    composer, PrevWordsInfo.EMPTY_PREV_WORDS_INFO, mKeyboard.getProximityInfo(),
+            suggestionResults = dictionaryFacilitator.getSuggestionResults(
+                    composer, PrevWordsInfo.EMPTY_PREV_WORDS_INFO, keyboard.getProximityInfo(),
                     settingsValuesForSuggestion, 0 /* sessionId */);
-            if (suggestionResults.isEmpty()) {
-                return false;
-            }
-            final SuggestedWordInfo firstSuggestion = suggestionResults.first();
-            final boolean isDistractor = suggestionExceedsDistracterThreshold(
-                    firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
-            if (DEBUG) {
-                Log.d(TAG, "isDistracter: " + isDistractor);
-            }
-            return isDistractor;
         }
+        if (suggestionResults.isEmpty()) {
+            return false;
+        }
+        final SuggestedWordInfo firstSuggestion = suggestionResults.first();
+        final boolean isDistractor = suggestionExceedsDistracterThreshold(
+                firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
+        if (DEBUG) {
+            Log.d(TAG, "isDistracter: " + isDistractor);
+        }
+        return isDistractor;
     }
 
     private static boolean suggestionExceedsDistracterThreshold(final SuggestedWordInfo suggestion,