Merge "Fix NoClassDefFoundError due to CursorAnchorInfo in K"
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 4a218d5..6a63bfd 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -62,6 +62,7 @@
     private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
 
     private DictionaryGroup[] mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
+    private DictionaryGroup mMostProbableDictionaryGroup = mDictionaryGroups[0];
     private boolean mIsUserDictEnabled = false;
     private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
     // To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
@@ -126,9 +127,16 @@
      * A group of dictionaries that work together for a single language.
      */
     private static class DictionaryGroup {
+        // TODO: Run evaluation to determine a reasonable value for these constants. The current
+        // values are ad-hoc and chosen without any particular care or methodology.
+        public static final float WEIGHT_FOR_MOST_PROBABLE_LANGUAGE = 1.0f;
+        public static final float WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.95f;
+        public static final float WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.6f;
+
         public final Locale mLocale;
         private Dictionary mMainDict;
-        public float mWeightForLocale = 1.0f;
+        public float mWeightForTypingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
+        public float mWeightForGesturingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
         public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
                 new ConcurrentHashMap<>();
 
@@ -214,25 +222,52 @@
         mPersonalizationHelper.updateEnabledSubtypes(enabledSubtypes);
     }
 
+    // TODO: remove this, it's confusing with seamless multiple language switching
     public void setIsMonolingualUser(final boolean isMonolingualUser) {
         mPersonalizationHelper.setIsMonolingualUser(isMonolingualUser);
     }
 
-    // TODO: remove this, replace with version returning multiple locales
-    public Locale getLocale() {
-        return mDictionaryGroups[0].mLocale;
+    public boolean isActive() {
+        return null != mDictionaryGroups[0].mLocale;
     }
 
     /**
-     * Returns the primary locale among all currently active locales. BE CAREFUL using this.
+     * Returns the most probable locale among all currently active locales. BE CAREFUL using this.
      *
      * DO NOT USE THIS just because it's convenient. Use it when it's correct, for example when
      * choosing what dictionary to put a word in, or when changing the capitalization of a typed
      * string.
-     * @return the primary active locale
+     * @return the most probable locale
      */
-    public Locale getPrimaryLocale() {
-        return mDictionaryGroups[0].mLocale;
+    public Locale getMostProbableLocale() {
+        return getDictionaryGroupForMostProbableLanguage().mLocale;
+    }
+
+    public Locale[] getLocales() {
+        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+        final Locale[] locales = new Locale[dictionaryGroups.length];
+        for (int i = 0; i < dictionaryGroups.length; ++i) {
+            locales[i] = dictionaryGroups[i].mLocale;
+        }
+        return locales;
+    }
+
+    private DictionaryGroup getDictionaryGroupForMostProbableLanguage() {
+        return mMostProbableDictionaryGroup;
+    }
+
+    public void switchMostProbableLanguage(final Locale locale) {
+        final DictionaryGroup newMostProbableDictionaryGroup =
+                findDictionaryGroupWithLocale(mDictionaryGroups, locale);
+        mMostProbableDictionaryGroup.mWeightForTypingInLocale =
+                DictionaryGroup.WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE;
+        mMostProbableDictionaryGroup.mWeightForGesturingInLocale =
+                DictionaryGroup.WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE;
+        newMostProbableDictionaryGroup.mWeightForTypingInLocale =
+                DictionaryGroup.WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
+        newMostProbableDictionaryGroup.mWeightForGesturingInLocale =
+                DictionaryGroup.WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
+        mMostProbableDictionaryGroup = newMostProbableDictionaryGroup;
     }
 
     private static ExpandableBinaryDictionary getSubDict(final String dictType,
@@ -256,11 +291,11 @@
         }
     }
 
-    public void resetDictionaries(final Context context, final Locale newLocale,
+    public void resetDictionaries(final Context context, final Locale[] newLocales,
             final boolean useContactsDict, final boolean usePersonalizedDicts,
             final boolean forceReloadMainDictionary,
             final DictionaryInitializationListener listener) {
-        resetDictionariesWithDictNamePrefix(context, newLocale, useContactsDict,
+        resetDictionariesWithDictNamePrefix(context, newLocales, useContactsDict,
                 usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */);
     }
 
@@ -274,20 +309,13 @@
         return null;
     }
 
-    private DictionaryGroup getDictionaryGroupForActiveLanguage() {
-        // TODO: implement this
-        return mDictionaryGroups[0];
-    }
-
     public void resetDictionariesWithDictNamePrefix(final Context context,
-            final Locale newLocaleToUse,
+            final Locale[] newLocales,
             final boolean useContactsDict, final boolean usePersonalizedDicts,
             final boolean forceReloadMainDictionary,
             final DictionaryInitializationListener listener,
             final String dictNamePrefix) {
         final HashMap<Locale, ArrayList<String>> existingDictsToCleanup = new HashMap<>();
-        // TODO: use several locales
-        final Locale[] newLocales = new Locale[] { newLocaleToUse };
         // TODO: Make subDictTypesToUse configurable by resource or a static final list.
         final HashSet<String> subDictTypesToUse = new HashSet<>();
         subDictTypesToUse.add(Dictionary.TYPE_USER);
@@ -359,6 +387,7 @@
         synchronized (mLock) {
             oldDictionaryGroups = mDictionaryGroups;
             mDictionaryGroups = newDictionaryGroups;
+            mMostProbableDictionaryGroup = newDictionaryGroups[0];
             mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
             if (hasAtLeastOneUninitializedMainDictionary()) {
                 asyncReloadUninitializedMainDictionaries(context, newLocales, listener);
@@ -448,13 +477,15 @@
             dictionaryGroups[i] = new DictionaryGroup(locale, mainDictionary, subDicts);
         }
         mDictionaryGroups = dictionaryGroups;
+        mMostProbableDictionaryGroup = dictionaryGroups[0];
     }
 
     public void closeDictionaries() {
         final DictionaryGroup[] dictionaryGroups;
         synchronized (mLock) {
             dictionaryGroups = mDictionaryGroups;
-            mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
+            mMostProbableDictionaryGroup = new DictionaryGroup();
+            mDictionaryGroups = new DictionaryGroup[] { mMostProbableDictionaryGroup };
         }
         for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
             for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
@@ -469,7 +500,7 @@
 
     @UsedForTesting
     public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
-        return mDictionaryGroups[0].getSubDict(dictName);
+        return mMostProbableDictionaryGroup.getSubDict(dictName);
     }
 
     // The main dictionaries are loaded asynchronously.  Don't cache the return value
@@ -542,17 +573,18 @@
     }
 
     public void addWordToUserDictionary(final Context context, final String word) {
-        final Locale locale = getLocale();
+        final Locale locale = getMostProbableLocale();
         if (locale == null) {
             return;
         }
+        // TODO: add a toast telling what language this is being added to?
         UserBinaryDictionary.addWordToUserDictionary(context, locale, word);
     }
 
     public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
             final NgramContext ngramContext, final int timeStampInSeconds,
             final boolean blockPotentiallyOffensive) {
-        final DictionaryGroup dictionaryGroup = getDictionaryGroupForActiveLanguage();
+        final DictionaryGroup dictionaryGroup = getDictionaryGroupForMostProbableLanguage();
         final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
         NgramContext ngramContextForCurrentWord = ngramContext;
         for (int i = 0; i < words.length; i++) {
@@ -620,7 +652,7 @@
 
     private void removeWord(final String dictName, final String word) {
         final ExpandableBinaryDictionary dictionary =
-                getDictionaryGroupForActiveLanguage().getSubDict(dictName);
+                getDictionaryGroupForMostProbableLanguage().getSubDict(dictName);
         if (dictionary != null) {
             dictionary.removeUnigramEntryDynamically(word);
         }
@@ -645,10 +677,13 @@
             for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
                 final Dictionary dictionary = dictionaryGroup.getDict(dictType);
                 if (null == dictionary) continue;
+                final float weightForLocale = composer.isBatchMode()
+                        ? dictionaryGroup.mWeightForGesturingInLocale
+                        : dictionaryGroup.mWeightForTypingInLocale;
                 final ArrayList<SuggestedWordInfo> dictionarySuggestions =
                         dictionary.getSuggestions(composer, ngramContext, proximityInfo,
                                 settingsValuesForSuggestion, sessionId,
-                                dictionaryGroup.mWeightForLocale, weightOfLangModelVsSpatialModel);
+                                weightForLocale, weightOfLangModelVsSpatialModel);
                 if (null == dictionarySuggestions) continue;
                 suggestionResults.addAll(dictionarySuggestions);
                 if (null != suggestionResults.mRawSuggestions) {
@@ -747,7 +782,8 @@
             final SpacingAndPunctuations spacingAndPunctuations,
             final AddMultipleDictionaryEntriesCallback callback) {
         mPersonalizationHelper.addEntriesToPersonalizationDictionariesToUpdate(
-                getLocale(), personalizationDataChunk, spacingAndPunctuations, callback);
+                getMostProbableLocale(), personalizationDataChunk, spacingAndPunctuations,
+                callback);
     }
 
     @UsedForTesting
@@ -756,7 +792,7 @@
         // TODO: we're inserting the phrase into the dictionary for the active language. Rethink
         // this a bit from a theoretical point of view.
         final ExpandableBinaryDictionary contextualDict =
-                getDictionaryGroupForActiveLanguage().getSubDict(Dictionary.TYPE_CONTEXTUAL);
+                getDictionaryGroupForMostProbableLanguage().getSubDict(Dictionary.TYPE_CONTEXTUAL);
         if (contextualDict == null) {
             return;
         }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
index ff4a6bd..1b33d91 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
@@ -101,7 +101,7 @@
 
     private void resetDictionariesForLocaleLocked(final DictionaryFacilitator dictionaryFacilitator,
             final Locale locale) {
-        dictionaryFacilitator.resetDictionariesWithDictNamePrefix(mContext, locale,
+        dictionaryFacilitator.resetDictionariesWithDictNamePrefix(mContext, new Locale[] { locale },
                 mUseContactsDictionary, false /* usePersonalizedDicts */,
                 false /* forceReloadMainDictionary */, null /* listener */,
                 mDictionaryNamePrefix);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index b168cc9..2be99a9 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -675,9 +675,7 @@
     // TODO: make sure the current settings always have the right locales, and read from them
     private void resetDictionaryFacilitatorForLocale(final Locale[] locales) {
         final SettingsValues settingsValues = mSettings.getCurrent();
-        // TODO: pass the array instead
-        final Locale locale = locales[0];
-        mDictionaryFacilitator.resetDictionaries(this /* context */, locale,
+        mDictionaryFacilitator.resetDictionaries(this /* context */, locales,
                 settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
                 false /* forceReloadMainDictionary */, this);
         if (settingsValues.mAutoCorrectionEnabledPerUserSettings) {
@@ -692,7 +690,7 @@
     /* package private */ void resetSuggestMainDict() {
         final SettingsValues settingsValues = mSettings.getCurrent();
         mDictionaryFacilitator.resetDictionaries(this /* context */,
-                mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
+                mDictionaryFacilitator.getLocales(), settingsValues.mUseContactsDict,
                 settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this);
     }
 
@@ -1632,7 +1630,7 @@
         }
         final String wordToShow;
         if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) {
-            wordToShow = word.toLowerCase(mDictionaryFacilitator.getPrimaryLocale());
+            wordToShow = word.toLowerCase(mDictionaryFacilitator.getMostProbableLocale());
         } else {
             wordToShow = word;
         }
@@ -1898,7 +1896,7 @@
     @UsedForTesting
     /* package for test */ void replaceDictionariesForTest(final Locale locale) {
         final SettingsValues settingsValues = mSettings.getCurrent();
-        mDictionaryFacilitator.resetDictionaries(this, locale,
+        mDictionaryFacilitator.resetDictionaries(this, new Locale[] { locale },
             settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
             false /* forceReloadMainDictionary */, this /* listener */);
     }
@@ -1917,7 +1915,7 @@
     }
 
     public void dumpDictionaryForDebug(final String dictName) {
-        if (mDictionaryFacilitator.getLocale() == null) {
+        if (!mDictionaryFacilitator.isActive()) {
             resetDictionaryFacilitatorIfNecessary();
         }
         mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index d2d9b9b..e181237 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -55,10 +55,6 @@
         mDictionaryFacilitator = dictionaryFacilitator;
     }
 
-    public Locale getLocale() {
-        return mDictionaryFacilitator.getLocale();
-    }
-
     public void setAutoCorrectionThreshold(final float threshold) {
         mAutoCorrectionThreshold = threshold;
     }
@@ -136,7 +132,10 @@
                 SESSION_ID_TYPING);
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
                 getTransformedSuggestedWordInfoList(wordComposer, suggestionResults,
-                        trailingSingleQuotesCount, mDictionaryFacilitator.getLocale());
+                        trailingSingleQuotesCount,
+                        // For transforming suggestions that don't come for any dictionary, we
+                        // use the currently most probable locale as it's our best bet.
+                        mDictionaryFacilitator.getMostProbableLocale());
         final boolean didRemoveTypedWord =
                 SuggestedWordInfo.removeDups(wordComposer.getTypedWord(), suggestionsContainer);
 
@@ -216,7 +215,8 @@
         final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
                 wordComposer, ngramContext, proximityInfo, settingsValuesForSuggestion,
                 SESSION_ID_GESTURE);
-        final Locale defaultLocale = mDictionaryFacilitator.getLocale();
+        // For transforming words that don't come from a dictionary, because it's our best bet
+        final Locale defaultLocale = mDictionaryFacilitator.getMostProbableLocale();
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
                 new ArrayList<>(suggestionResults);
         final int suggestionsCount = suggestionsContainer.size();
diff --git a/tests/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCacheTests.java b/tests/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCacheTests.java
index 5af30e5..ed3929d 100644
--- a/tests/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCacheTests.java
+++ b/tests/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCacheTests.java
@@ -50,15 +50,15 @@
     private void testGetFacilitator(final DictionaryFacilitatorLruCache cache) {
         final DictionaryFacilitator dictionaryFacilitatorEnUs = cache.get(Locale.US);
         assertNotNull(dictionaryFacilitatorEnUs);
-        assertEquals(Locale.US, dictionaryFacilitatorEnUs.getLocale());
+        assertTrue(dictionaryFacilitatorEnUs.isForLocales(new Locale[] { Locale.US }));
 
         final DictionaryFacilitator dictionaryFacilitatorFr = cache.get(Locale.FRENCH);
         assertNotNull(dictionaryFacilitatorEnUs);
-        assertEquals(Locale.FRENCH, dictionaryFacilitatorFr.getLocale());
+        assertTrue(dictionaryFacilitatorFr.isForLocales(new Locale[] { Locale.FRENCH }));
 
         final DictionaryFacilitator dictionaryFacilitatorDe = cache.get(Locale.GERMANY);
         assertNotNull(dictionaryFacilitatorDe);
-        assertEquals(Locale.GERMANY, dictionaryFacilitatorDe.getLocale());
+        assertTrue(dictionaryFacilitatorDe.isForLocales(new Locale[] { Locale.GERMANY }));
     }
 
     public void testSetUseContactsDictionary() {