From 809c93214bd85f038c3abb09d8dee60f778b7746 Mon Sep 17 00:00:00 2001
From: Yohei Yukawa <yukawa@google.com>
Date: Fri, 22 Jan 2016 02:10:35 -0800
Subject: [PATCH] Experimental automatic language switching support.

With this CL, LatinIME switches the current subtype from its enabled
subtypes based on the first locale in EditorInfo#hintLocales.

This functionality is still experimental, and will be triggered only
when EditorInfo#hintLocales is specified by the application.

Bug: 22859862
Change-Id: Ibd0559b370d8aa0d50d1bada8ecfdac0ed8db898
---
 .../android/inputmethod/latin/LatinIME.java   | 30 +++++++++++++-
 .../latin/RichInputMethodManager.java         | 41 +++++++++++++++++++
 2 files changed, 70 insertions(+), 1 deletion(-)

diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 55af62ee48..c2d9f965fb 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -54,7 +54,9 @@ import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
 import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils;
 import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater;
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
@@ -176,8 +178,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8;
         private static final int MSG_DEALLOCATE_MEMORY = 9;
         private static final int MSG_RESUME_SUGGESTIONS_FOR_START_INPUT = 10;
+        private static final int MSG_SWITCH_LANGUAGE_AUTOMATICALLY = 11;
         // Update this when adding new messages
-        private static final int MSG_LAST = MSG_RESUME_SUGGESTIONS_FOR_START_INPUT;
+        private static final int MSG_LAST = MSG_SWITCH_LANGUAGE_AUTOMATICALLY;
 
         private static final int ARG1_NOT_GESTURE_INPUT = 0;
         private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
@@ -271,6 +274,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
             case MSG_DEALLOCATE_MEMORY:
                 latinIme.deallocateMemory();
                 break;
+            case MSG_SWITCH_LANGUAGE_AUTOMATICALLY:
+                latinIme.switchLanguage((InputMethodSubtype)msg.obj);
+                break;
             }
         }
 
@@ -389,6 +395,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
             obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget();
         }
 
+        public void postSwitchLanguage(final InputMethodSubtype subtype) {
+            obtainMessage(MSG_SWITCH_LANGUAGE_AUTOMATICALLY, subtype).sendToTarget();
+        }
+
         // Working variables for the following methods.
         private boolean mIsOrientationChanging;
         private boolean mPendingSuccessiveImsCallback;
@@ -795,6 +805,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
 
     void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) {
         super.onStartInput(editorInfo, restarting);
+
+        // If the primary hint language does not match the current subtype language, then try
+        // to switch to the primary hint language.
+        // TODO: Support all the locales in EditorInfo#hintLocales.
+        final Locale primaryHintLocale = EditorInfoCompatUtils.getPrimaryHintLocale(editorInfo);
+        if (primaryHintLocale == null) {
+            return;
+        }
+        final InputMethodSubtype newSubtype = mRichImm.findSubtypeByLocale(primaryHintLocale);
+        if (newSubtype == null || newSubtype.equals(mRichImm.getCurrentSubtype().getRawSubtype())) {
+            return;
+        }
+        mHandler.postSwitchLanguage(newSubtype);
     }
 
     @SuppressWarnings("deprecation")
@@ -1301,6 +1324,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         return mOptionsDialog != null && mOptionsDialog.isShowing();
     }
 
+    public void switchLanguage(final InputMethodSubtype subtype) {
+        final IBinder token = getWindow().getWindow().getAttributes().token;
+        mRichImm.setInputMethodAndSubtype(token, subtype);
+    }
+
     // TODO: Revise the language switch key behavior to make it much smarter and more reasonable.
     public void switchToNextSubtype() {
         final IBinder token = getWindow().getWindow().getAttributes().token;
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index ef946c8bc9..3beb51d68d 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -32,6 +32,7 @@ import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
 import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils;
@@ -428,6 +429,46 @@ public class RichInputMethodManager {
         return null;
     }
 
+    public InputMethodSubtype findSubtypeByLocale(final Locale locale) {
+        // Find the best subtype based on a straightforward matching algorithm.
+        // TODO: Use LocaleList#getFirstMatch() instead.
+        final List<InputMethodSubtype> subtypes =
+                getMyEnabledInputMethodSubtypeList(true /* allowsImplicitlySelectedSubtypes */);
+        final int count = subtypes.size();
+        for (int i = 0; i < count; ++i) {
+            final InputMethodSubtype subtype = subtypes.get(i);
+            final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
+            if (subtypeLocale.equals(locale)) {
+                return subtype;
+            }
+        }
+        for (int i = 0; i < count; ++i) {
+            final InputMethodSubtype subtype = subtypes.get(i);
+            final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
+            if (subtypeLocale.getLanguage().equals(locale.getLanguage()) &&
+                    subtypeLocale.getCountry().equals(locale.getCountry()) &&
+                    subtypeLocale.getVariant().equals(locale.getVariant())) {
+                return subtype;
+            }
+        }
+        for (int i = 0; i < count; ++i) {
+            final InputMethodSubtype subtype = subtypes.get(i);
+            final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
+            if (subtypeLocale.getLanguage().equals(locale.getLanguage()) &&
+                    subtypeLocale.getCountry().equals(locale.getCountry())) {
+                return subtype;
+            }
+        }
+        for (int i = 0; i < count; ++i) {
+            final InputMethodSubtype subtype = subtypes.get(i);
+            final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
+            if (subtypeLocale.getLanguage().equals(locale.getLanguage())) {
+                return subtype;
+            }
+        }
+        return null;
+    }
+
     public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) {
         mImmWrapper.mImm.setInputMethodAndSubtype(
                 token, getInputMethodIdOfThisIme(), subtype);
-- 
GitLab