Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dc489241 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Minimize the number of default enabled IMEs part 3

With this CL, the behavior of getDefaultEnabledImes() changes
as follows:

- Previously system IMEs are always enabled by default as long
  as it is a software keyboard that supports En_* subtype. With
  this CL, getDefaultEnabledImes() relies on the locale returned
  from getFallbackLocaleForDefaultIme() instead of calling
  isSystemImeThatHasEnglishKeyboardSubtype() to minimize the
  number of enabled IMEs.
- Previously default enabled system IMEs are chosen in a
  country-agnostic way. As a result, "en_IN" is enabled even
  when the system locale is "en_US". With this CL, the system
  first tries to find IMEs with taking the coutnry into account,
  and use the country-agnostic way when and only when fallback
  logic is required.

BUG: 17347871
Change-Id: I6571d464a46453934f0a8f5e79018a67a9a3c845
parent c68f2762
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -416,6 +416,9 @@ public final class InputMethodInfo implements Parcelable {
            return true;
        }
        try {
            if (getIsDefaultResourceId() == 0) {
                return false;
            }
            final Resources res = context.createPackageContext(getPackageName(), 0).getResources();
            return res.getBoolean(getIsDefaultResourceId());
        } catch (NameNotFoundException e) {
+134 −11
Original line number Diff line number Diff line
@@ -53,6 +53,17 @@ public class InputMethodUtils {
    private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
            "EnabledWhenDefaultIsNotAsciiCapable";
    private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
    /**
     * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
     * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
     * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
     * doesn't automatically match {@code Locale("en", "IN")}.
     */
    private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = {
        Locale.ENGLISH, // "en"
        Locale.US, // "en_US"
        Locale.UK, // "en_GB"
    };

    private InputMethodUtils() {
        // This utility class is not publicly instantiable.
@@ -102,6 +113,11 @@ public class InputMethodUtils {
                & ApplicationInfo.FLAG_SYSTEM) != 0;
    }

    /**
     * @deprecated Use {@link Locale} returned from
     * {@link #getFallbackLocaleForDefaultIme(ArrayList)} instead.
     */
    @Deprecated
    public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) {
        if (!isSystemIme(imi)) {
            return false;
@@ -109,6 +125,21 @@ public class InputMethodUtils {
        return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD);
    }

    public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis,
            final Context context) {
        for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
            for (int i = 0; i < imis.size(); ++i) {
                final InputMethodInfo imi = imis.get(i);
                if (isSystemIme(imi) && imi.isDefault(context) &&
                        containsSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */,
                                SUBTYPE_MODE_KEYBOARD)) {
                    return fallbackLocale;
                }
            }
        }
        return null;
    }

    private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi) {
        if (!isSystemIme(imi)) {
            return false;
@@ -126,37 +157,85 @@ public class InputMethodUtils {
        return false;
    }

    public static Locale getSystemLocaleFromContext(final Context context) {
        try {
            return context.getResources().getConfiguration().locale;
        } catch (Resources.NotFoundException ex) {
            return null;
        }
    }

    public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
            Context context, boolean isSystemReady, ArrayList<InputMethodInfo> imis) {
        // OK to store null in fallbackLocale because isImeThatHasSubtypeOf() is null-tolerant.
        final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);

        if (!isSystemReady) {
            final ArrayList<InputMethodInfo> retval = new ArrayList<>();
            for (int i = 0; i < imis.size(); ++i) {
                final InputMethodInfo imi = imis.get(i);
                if (isSystemImeThatHasEnglishKeyboardSubtype(imi)) {
                // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
                if (isSystemIme(imi) && imi.isDefault(context) &&
                        isImeThatHasSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */,
                                SUBTYPE_MODE_KEYBOARD)) {
                    retval.add(imi);
                }
            }
            return retval;
        }

        // OK to store null in fallbackLocale because isImeThatHasSubtypeOf() is null-tolerant.
        final Locale systemLocale = getSystemLocaleFromContext(context);
        // TODO: Use LinkedHashSet to simplify the code.
        final ArrayList<InputMethodInfo> retval = new ArrayList<>();
        boolean auxilialyImeAdded = false;
        boolean systemLocaleKeyboardImeFound = false;

        // First, try to find IMEs with taking the system locale country into consideration.
        for (int i = 0; i < imis.size(); ++i) {
            final InputMethodInfo imi = imis.get(i);
            // TODO: We should check isAsciiCapable instead of relying on
            // isSystemImeThatHasEnglishKeyboardSubtype().
            if (isValidSystemDefaultIme(isSystemReady, imi, context)
                    || isSystemImeThatHasEnglishKeyboardSubtype(imi)) {
            if (!isSystemIme(imi) || !imi.isDefault(context)) {
                continue;
            }
            final boolean isSystemLocaleKeyboardIme = isImeThatHasSubtypeOf(imi, systemLocale,
                    false /* ignoreCountry */, SUBTYPE_MODE_KEYBOARD);
            // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
            // TODO: Use LinkedHashSet to simplify the code.
            if (isSystemLocaleKeyboardIme ||
                    isImeThatHasSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */,
                            SUBTYPE_MODE_ANY)) {
                retval.add(imi);
                if (imi.isAuxiliaryIme()) {
                    auxilialyImeAdded = true;
            }
            systemLocaleKeyboardImeFound |= isSystemLocaleKeyboardIme;
        }

        // System locale country doesn't match any IMEs, try to find IMEs in a country-agnostic
        // way.
        if (!systemLocaleKeyboardImeFound) {
            for (int i = 0; i < imis.size(); ++i) {
                final InputMethodInfo imi = imis.get(i);
                if (!isSystemIme(imi) || !imi.isDefault(context)) {
                    continue;
                }
                if (isImeThatHasSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */,
                        SUBTYPE_MODE_KEYBOARD)) {
                    // IMEs that have fallback locale are already added in the previous loop. We
                    // don't need to add them again here.
                    // TODO: Use LinkedHashSet to simplify the code.
                    continue;
                }
                if (isImeThatHasSubtypeOf(imi, systemLocale, true /* ignoreCountry */,
                        SUBTYPE_MODE_ANY)) {
                    retval.add(imi);
                }
            }
        }

        // If one or more auxiliary input methods are available, OK to stop populating the list.
        if (auxilialyImeAdded) {
        for (int i = 0; i < retval.size(); ++i) {
            if (retval.get(i).isAuxiliaryIme()) {
                return retval;
            }
        }
        for (int i = 0; i < imis.size(); ++i) {
            final InputMethodInfo imi = imis.get(i);
            if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi)) {
@@ -166,7 +245,20 @@ public class InputMethodUtils {
        return retval;
    }

    // TODO: Rename isSystemDefaultImeThatHasCurrentLanguageSubtype
    public static boolean isImeThatHasSubtypeOf(final InputMethodInfo imi,
            final Locale locale, final boolean ignoreCountry, final String mode) {
        if (locale == null) {
            return false;
        }
        return containsSubtypeOf(imi, locale, ignoreCountry, mode);
    }

    /**
     * @deprecated Use {@link #isSystemIme(InputMethodInfo)} and
     * {@link InputMethodInfo#isDefault(Context)} and
     * {@link #isImeThatHasSubtypeOf(InputMethodInfo, Locale, boolean, String))} instead.
     */
    @Deprecated
    public static boolean isValidSystemDefaultIme(
            boolean isSystemReady, InputMethodInfo imi, Context context) {
        if (!isSystemReady) {
@@ -191,6 +283,36 @@ public class InputMethodUtils {
        return false;
    }

    public static boolean containsSubtypeOf(final InputMethodInfo imi,
            final Locale locale, final boolean ignoreCountry, final String mode) {
        final int N = imi.getSubtypeCount();
        for (int i = 0; i < N; ++i) {
            final InputMethodSubtype subtype = imi.getSubtypeAt(i);
            if (ignoreCountry) {
                final Locale subtypeLocale = new Locale(getLanguageFromLocaleString(
                        subtype.getLocale()));
                if (!subtypeLocale.getLanguage().equals(locale.getLanguage())) {
                    continue;
                }
            } else {
                // TODO: Use {@link Locale#toLanguageTag()} and
                // {@link Locale#forLanguageTag(languageTag)} instead.
                if (!TextUtils.equals(subtype.getLocale(), locale.toString())) {
                    continue;
                }
            }
            if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
                    mode.equalsIgnoreCase(subtype.getMode())) {
                return true;
            }
        }
        return false;
    }

    /**
     * @deprecated Use {@link #containsSubtypeOf(InputMethodInfo, Locale, boolean, String)} instead.
     */
    @Deprecated
    public static boolean containsSubtypeOf(InputMethodInfo imi, String language, String mode) {
        final int N = imi.getSubtypeCount();
        for (int i = 0; i < N; ++i) {
@@ -354,6 +476,7 @@ public class InputMethodUtils {

    /**
     * Returns the language component of a given locale string.
     * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(languageTag)}
     */
    public static String getLanguageFromLocaleString(String locale) {
        final int idx = locale.indexOf('_');
+8 −11
Original line number Diff line number Diff line
@@ -79,7 +79,6 @@ public class InputMethodTest extends InstrumentationTestCase {
        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP, !IS_SYSTEM_READY,
                "DummyDefaultEnKeyboardIme");
        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP, IS_SYSTEM_READY,
                "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1",
                "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP, IS_SYSTEM_READY,
                "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1",
@@ -90,38 +89,36 @@ public class InputMethodTest extends InstrumentationTestCase {
    public void testKeyboardImes() throws Exception {
        // locale: en_US
        assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_US, !IS_SYSTEM_READY,
                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.hindi");
                "com.android.apps.inputmethod.latin");
        assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_US, IS_SYSTEM_READY,
                "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin",
                "com.android.apps.inputmethod.hindi");
                "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin");

        // locale: en_GB
        assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_GB, !IS_SYSTEM_READY,
                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.hindi");
                "com.android.apps.inputmethod.latin");
        assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_GB, IS_SYSTEM_READY,
                "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin",
                "com.android.apps.inputmethod.hindi");
                "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin");

        // locale: en_IN
        assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_IN, !IS_SYSTEM_READY,
                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.hindi");
                "com.android.apps.inputmethod.latin");
        assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_IN, IS_SYSTEM_READY,
                "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin",
                "com.android.apps.inputmethod.hindi");

        // locale: hi
        assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_HI, !IS_SYSTEM_READY,
                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.hindi");
                "com.android.apps.inputmethod.latin");
        assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_HI, IS_SYSTEM_READY,
                "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin",
                "com.android.apps.inputmethod.hindi");

        // locale: ja_JP
        assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_JA_JP, !IS_SYSTEM_READY,
                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.hindi");
                "com.android.apps.inputmethod.latin");
        assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_JA_JP, IS_SYSTEM_READY,
                "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin",
                "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.japanese");
                "com.android.apps.inputmethod.japanese");
    }

    @SmallTest