Add an option for the implicitly selected subtype

Bug: 5057886
Change-Id: Iddde4724891501b4f18cade6a3d2c64b6124e58a
diff --git a/api/current.txt b/api/current.txt
index 27787b5..c35ffda 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -709,6 +709,7 @@
     field public static final int overScrollFooter = 16843459; // 0x10102c3
     field public static final int overScrollHeader = 16843458; // 0x10102c2
     field public static final int overScrollMode = 16843457; // 0x10102c1
+    field public static final int overridesImplicitlyEnabledSubtype = 16843696; // 0x10103b0
     field public static final int packageNames = 16843649; // 0x1010381
     field public static final int padding = 16842965; // 0x10100d5
     field public static final int paddingBottom = 16842969; // 0x10100d9
@@ -24531,6 +24532,7 @@
   }
 
   public final class InputMethodSubtype implements android.os.Parcelable {
+    ctor public InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean);
     method public boolean containsExtraValueKey(java.lang.String);
     method public int describeContents();
     method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
@@ -24541,6 +24543,7 @@
     method public java.lang.String getMode();
     method public int getNameResId();
     method public boolean isAuxiliary();
+    method public boolean overridesImplicitlyEnabledSubtype();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
   }
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 4ec4ff9..0119d03 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -164,7 +164,9 @@
                             a.getString(com.android.internal.R.styleable
                                     .InputMethod_Subtype_imeSubtypeExtraValue),
                             a.getBoolean(com.android.internal.R.styleable
-                                    .InputMethod_Subtype_isAuxiliary, false));
+                                    .InputMethod_Subtype_isAuxiliary, false),
+                            a.getBoolean(com.android.internal.R.styleable
+                                    .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false));
                     mSubtypes.add(subtype);
                 }
             }
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 8c51680..5670432 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -42,6 +42,7 @@
     private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
 
     private final boolean mIsAuxiliary;
+    private final boolean mOverridesImplicitlyEnabledSubtype;
     private final int mSubtypeHashCode;
     private final int mSubtypeIconResId;
     private final int mSubtypeNameResId;
@@ -57,11 +58,12 @@
      * @param locale The locale supported by the subtype
      * @param mode The mode supported by the subtype
      * @param extraValue The extra value of the subtype
+     * @param isAuxiliary true when this subtype is one shot subtype.
      * @hide
      */
-    public InputMethodSubtype(
-            int nameId, int iconId, String locale, String mode, String extraValue) {
-        this(nameId, iconId, locale, mode, extraValue, false);
+    public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
+            boolean isAuxiliary) {
+        this(nameId, iconId, locale, mode, extraValue, false, false);
     }
 
     /**
@@ -72,18 +74,21 @@
      * @param mode The mode supported by the subtype
      * @param extraValue The extra value of the subtype
      * @param isAuxiliary true when this subtype is one shot subtype.
-     * @hide
+     * @param overridesImplicitlyEnabledSubtype true when this subtype should be selected by default
+     * if no other subtypes are selected explicitly. Note that a subtype with this parameter being
+     * true will not be shown in the subtypes list.
      */
     public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
-            boolean isAuxiliary) {
+            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
         mSubtypeNameResId = nameId;
         mSubtypeIconResId = iconId;
         mSubtypeLocale = locale != null ? locale : "";
         mSubtypeMode = mode != null ? mode : "";
         mSubtypeExtraValue = extraValue != null ? extraValue : "";
         mIsAuxiliary = isAuxiliary;
+        mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
         mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
-                mIsAuxiliary);
+                mIsAuxiliary, mOverridesImplicitlyEnabledSubtype);
     }
 
     InputMethodSubtype(Parcel source) {
@@ -97,8 +102,9 @@
         s = source.readString();
         mSubtypeExtraValue = s != null ? s : "";
         mIsAuxiliary = (source.readInt() == 1);
+        mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
         mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
-                mIsAuxiliary);
+                mIsAuxiliary, mOverridesImplicitlyEnabledSubtype);
     }
 
     /**
@@ -146,6 +152,14 @@
     }
 
     /**
+     * @return true when this subtype is selected by default if no other subtypes are selected
+     * explicitly. Note that a subtype that returns true will not be shown in the subtypes list.
+     */
+    public boolean overridesImplicitlyEnabledSubtype() {
+        return mOverridesImplicitlyEnabledSubtype;
+    }
+
+    /**
      * @param context Context will be used for getting Locale and PackageManager.
      * @param packageName The package name of the IME
      * @param appInfo The application info of the IME
@@ -244,6 +258,7 @@
         dest.writeString(mSubtypeMode);
         dest.writeString(mSubtypeExtraValue);
         dest.writeInt(mIsAuxiliary ? 1 : 0);
+        dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
     }
 
     public static final Parcelable.Creator<InputMethodSubtype> CREATOR
@@ -276,8 +291,9 @@
     }
 
     private static int hashCodeInternal(String locale, String mode, String extraValue,
-            boolean isAuxiliary) {
-        return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary});
+            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
+        return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
+                overridesImplicitlyEnabledSubtype});
     }
 
     /**
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index b50c063..0bf5b0a 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2237,6 +2237,10 @@
              InputMethodManager#switchToLastInputMethod will ignore auxiliary subtypes when it
              chooses a target subtype. -->
         <attr name="isAuxiliary" format="boolean" />
+        <!-- Set true when this subtype should be selected by default if no other subtypes are
+             selected explicitly. Note that a subtype with this parameter being true will
+             not be shown in the subtypes list. -->
+        <attr name="overridesImplicitlyEnabledSubtype" format="boolean" />
         <!-- The extra value of the subtype. This string can be any string and will be passed to
              the IME when the framework calls the IME with the subtype.  -->
         <attr name="imeSubtypeExtraValue" format="string" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6988f6b..bc2b907 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2010,4 +2010,5 @@
   <public type="attr" name="targetDescriptions" />
   <public type="attr" name="directionDescriptions" />
 
+  <public type="attr" name="overridesImplicitlyEnabledSubtype" />
 </resources>
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index bb831f5..6aa95c1 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -655,7 +655,7 @@
         List<InputMethodSubtype> enabledSubtypes =
                 mSettings.getEnabledInputMethodSubtypeListLocked(imi);
         if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
-            enabledSubtypes = getApplicableSubtypesLocked(mRes, getSubtypes(imi));
+            enabledSubtypes = getImplicitlyApplicableSubtypesLocked(mRes, imi);
         }
         return InputMethodSubtype.sort(mContext, 0, imi, enabledSubtypes);
     }
@@ -1903,6 +1903,20 @@
         return subtypes;
     }
 
+
+    private static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
+            InputMethodInfo imi, String mode) {
+        ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+        final int subtypeCount = imi.getSubtypeCount();
+        for (int i = 0; i < subtypeCount; ++i) {
+            final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+            if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
+                subtypes.add(subtype);
+            }
+        }
+        return subtypes;
+    }
+
     private boolean chooseNewDefaultIMELocked() {
         List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
         if (enabled != null && enabled.size() > 0) {
@@ -2357,8 +2371,9 @@
         return NOT_A_SUBTYPE_ID;
     }
 
-    private static ArrayList<InputMethodSubtype> getApplicableSubtypesLocked(
-            Resources res, List<InputMethodSubtype> subtypes) {
+    private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+            Resources res, InputMethodInfo imi) {
+        final List<InputMethodSubtype> subtypes = getSubtypes(imi);
         final String systemLocale = res.getConfiguration().locale.toString();
         if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
         HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
@@ -2366,6 +2381,19 @@
         final int N = subtypes.size();
         boolean containsKeyboardSubtype = false;
         for (int i = 0; i < N; ++i) {
+            // scan overriding implicitly enabled subtypes.
+            InputMethodSubtype subtype = subtypes.get(i);
+            if (subtype.overridesImplicitlyEnabledSubtype()) {
+                final String mode = subtype.getMode();
+                if (!applicableModeAndSubtypesMap.containsKey(mode)) {
+                    applicableModeAndSubtypesMap.put(mode, subtype);
+                }
+            }
+        }
+        if (applicableModeAndSubtypesMap.size() > 0) {
+            return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
+        }
+        for (int i = 0; i < N; ++i) {
             InputMethodSubtype subtype = subtypes.get(i);
             final String locale = subtype.getLocale();
             final String mode = subtype.getMode();
@@ -2489,16 +2517,21 @@
                 subtype = findLastResortApplicableSubtypeLocked(
                         mRes, enabledSubtypes, mode, null, true);
             }
+            final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
+                    getOverridingImplicitlyEnabledSubtypes(imi, mode);
+            final ArrayList<InputMethodSubtype> subtypesForSearch =
+                    overridingImplicitlyEnabledSubtypes.isEmpty()
+                            ? getSubtypes(imi) : overridingImplicitlyEnabledSubtypes;
             // 4. Search by the current subtype's locale from all subtypes.
             if (subtype == null && mCurrentSubtype != null) {
                 subtype = findLastResortApplicableSubtypeLocked(
-                        mRes, getSubtypes(imi), mode, mCurrentSubtype.getLocale(), false);
+                        mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
             }
             // 5. Search by the system locale from all subtypes.
             // 6. Search the first enabled subtype matched with mode from all subtypes.
             if (subtype == null) {
                 subtype = findLastResortApplicableSubtypeLocked(
-                        mRes, getSubtypes(imi), mode, null, true);
+                        mRes, subtypesForSearch, mode, null, true);
             }
             if (subtype != null) {
                 if (imiId.equals(mCurMethodId)) {
@@ -2945,12 +2978,12 @@
                     if (explicitlyEnabledSubtypes.size() == 0) {
                         // If there are no explicitly enabled subtypes, applicable subtypes are
                         // enabled implicitly.
-                        InputMethodInfo ime = mMethodMap.get(imeId);
+                        InputMethodInfo imi = mMethodMap.get(imeId);
                         // If IME is enabled and no subtypes are enabled, applicable subtypes
                         // are enabled implicitly, so needs to treat them to be enabled.
-                        if (ime != null && ime.getSubtypeCount() > 0) {
+                        if (imi != null && imi.getSubtypeCount() > 0) {
                             List<InputMethodSubtype> implicitlySelectedSubtypes =
-                                    getApplicableSubtypesLocked(mRes, getSubtypes(ime));
+                                    getImplicitlyApplicableSubtypesLocked(mRes, imi);
                             if (implicitlySelectedSubtypes != null) {
                                 final int N = implicitlySelectedSubtypes.size();
                                 for (int i = 0; i < N; ++i) {