diff options
author | Yohei Yukawa <yukawa@google.com> | 2014-11-01 21:04:30 +0900 |
---|---|---|
committer | Yohei Yukawa <yukawa@google.com> | 2014-11-06 15:28:06 +0900 |
commit | b21220efae92a56ff7b4b781fa614a6e3a8a3007 (patch) | |
tree | 60bfb9473ff210c79cb6569adae534cef48020f0 | |
parent | 8b26674aa139965005ff3ba7d284d7d557c2de61 (diff) | |
download | frameworks_base-b21220efae92a56ff7b4b781fa614a6e3a8a3007.zip frameworks_base-b21220efae92a56ff7b4b781fa614a6e3a8a3007.tar.gz frameworks_base-b21220efae92a56ff7b4b781fa614a6e3a8a3007.tar.bz2 |
Minimize the number of default enabled IMEs part 4
This is a follow up CL for recent attempt to minimize
the number of default enabled IMEs.
- part1: I831502db502f4073c9c2f50ce7705a4e45e2e1e3
- part2: Ife93d909fb8a24471c425c903e2b7048826e17a3
- part3: I6571d464a46453934f0a8f5e79018a67a9a3c845
It turned out that the changes made in part2 and part3 are
a bit overkill, and users will see no software keyboards
in some particular situations. The problem we missed in
the previous CLs is the fact that
InputMethodInfo#isDefault is indeed a locale-dependent
value, hence it may vary depending on the system locale.
Existing unittests also failed to abstract such
locale-dependent nature.
In order to addresses that regression, the selection logic
is a bit widely reorganized in this CL. Now the logic is
implemented as a series of fallback rules.
Also, unit tests are updated to be able to 1) test the
order of the enabled IMEs, and 2) emulate the
locale-dependent behavior of InputMethodInfo#isDefault
to enrich test cases.
BUG: 17347871
BUG: 18192576
Change-Id: I871ccda787eb0f1099ba3574356c1da4b33681f3
-rw-r--r-- | core/java/com/android/internal/inputmethod/InputMethodUtils.java | 310 | ||||
-rw-r--r-- | core/tests/inputmethodtests/src/android/os/InputMethodTest.java | 181 |
2 files changed, 325 insertions, 166 deletions
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index ac915d1..183527c 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -16,6 +16,8 @@ package com.android.internal.inputmethod; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.ContentResolver; import android.content.Context; @@ -34,7 +36,9 @@ import android.view.textservice.SpellCheckerInfo; import android.view.textservice.TextServicesManager; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -115,8 +119,8 @@ public class InputMethodUtils { } /** - * @deprecated Use {@link Locale} returned from - * {@link #getFallbackLocaleForDefaultIme(ArrayList)} instead. + * @deprecated Use {@link #isSystemImeThatHasSubtypeOf(InputMethodInfo, Context, boolean, + * Locale, boolean, String)} instead. */ @Deprecated public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) { @@ -126,25 +130,60 @@ public class InputMethodUtils { return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD); } + private static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi, + final Context context, final boolean checkDefaultAttribute, + @Nullable final Locale requiredLocale, final boolean checkCountry, + final String requiredSubtypeMode) { + if (!isSystemIme(imi)) { + return false; + } + if (checkDefaultAttribute && !imi.isDefault(context)) { + return false; + } + if (!containsSubtypeOf(imi, requiredLocale, checkCountry, requiredSubtypeMode)) { + return false; + } + return true; + } + + @Nullable public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis, final Context context) { + // At first, find the fallback locale from the IMEs that are declared as "default" in the + // current locale. Note that IME developers can declare an IME as "default" only for + // some particular locales but "not default" for other locales. 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)) { + if (isSystemImeThatHasSubtypeOf(imis.get(i), context, + true /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) { + return fallbackLocale; + } + } + } + // If no fallback locale is found in the above condition, find fallback locales regardless + // of the "default" attribute as a last resort. + for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) { + for (int i = 0; i < imis.size(); ++i) { + if (isSystemImeThatHasSubtypeOf(imis.get(i), context, + false /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) { return fallbackLocale; } } } + Slog.w(TAG, "Found no fallback locale. imis=" + Arrays.toString(imis.toArray())); return null; } - private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi) { + private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi, + final Context context, final boolean checkDefaultAttribute) { if (!isSystemIme(imi)) { return false; } + if (checkDefaultAttribute && !imi.isDefault(context)) { + return false; + } if (!imi.isAuxiliaryIme()) { return false; } @@ -166,98 +205,184 @@ public class InputMethodUtils { } } - 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); + private static final class InputMethodListBuilder { + // Note: We use LinkedHashSet instead of android.util.ArraySet because the enumeration + // order can have non-trivial effect in the call sites. + @NonNull + private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>(); - if (!isSystemReady) { - final ArrayList<InputMethodInfo> retval = new ArrayList<>(); + public InputMethodListBuilder fillImes(final ArrayList<InputMethodInfo> imis, + final Context context, final boolean checkDefaultAttribute, + @Nullable final Locale locale, final boolean checkCountry, + final String requiredSubtypeMode) { for (int i = 0; i < imis.size(); ++i) { final InputMethodInfo imi = imis.get(i); - // 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); + if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale, + checkCountry, requiredSubtypeMode)) { + mInputMethodSet.add(imi); } } - return retval; + return this; } - // 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 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); - 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); + // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be + // documented more clearly. + public InputMethodListBuilder fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis, + final Context context) { + // If one or more auxiliary input methods are available, OK to stop populating the list. + for (final InputMethodInfo imi : mInputMethodSet) { + if (imi.isAuxiliaryIme()) { + return this; + } } - systemLocaleKeyboardImeFound |= isSystemLocaleKeyboardIme; - } - - // System locale country doesn't match any IMEs, try to find IMEs in a country-agnostic - // way. - if (!systemLocaleKeyboardImeFound) { + boolean added = false; 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 (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context, + true /* checkDefaultAttribute */)) { + mInputMethodSet.add(imi); + added = true; } - if (isImeThatHasSubtypeOf(imi, systemLocale, true /* ignoreCountry */, - SUBTYPE_MODE_ANY)) { - retval.add(imi); + } + if (added) { + return this; + } + for (int i = 0; i < imis.size(); ++i) { + final InputMethodInfo imi = imis.get(i); + if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context, + false /* checkDefaultAttribute */)) { + mInputMethodSet.add(imi); } } + return this; } - // If one or more auxiliary input methods are available, OK to stop populating the list. - for (int i = 0; i < retval.size(); ++i) { - if (retval.get(i).isAuxiliaryIme()) { - return retval; - } + public boolean isEmpty() { + return mInputMethodSet.isEmpty(); } - for (int i = 0; i < imis.size(); ++i) { - final InputMethodInfo imi = imis.get(i); - if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi)) { - retval.add(imi); - } + + @NonNull + public ArrayList<InputMethodInfo> build() { + return new ArrayList<>(mInputMethodSet); } - return retval; } - 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); + private static InputMethodListBuilder getMinimumKeyboardSetWithoutSystemLocale( + final ArrayList<InputMethodInfo> imis, final Context context, + @Nullable final Locale fallbackLocale) { + // Before the system becomes ready, we pick up at least one keyboard in the following order. + // The first user (device owner) falls into this category. + // 1. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true + // 2. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true + // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false + // 4. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false + // TODO: We should check isAsciiCapable instead of relying on fallbackLocale. + + final InputMethodListBuilder builder = new InputMethodListBuilder(); + builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale, + false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale, + false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray()) + + " fallbackLocale=" + fallbackLocale); + return builder; + } + + private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale( + final ArrayList<InputMethodInfo> imis, final Context context, + @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) { + // Once the system becomes ready, we pick up at least one keyboard in the following order. + // Secondary users fall into this category in general. + // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true + // 2. checkDefaultAttribute: true, locale: systemLocale, checkCountry: false + // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true + // 4. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false + // 5. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true + // 6. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false + // TODO: We should check isAsciiCapable instead of relying on fallbackLocale. + + final InputMethodListBuilder builder = new InputMethodListBuilder(); + builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale, + true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale, + false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale, + false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale, + false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray()) + + " systemLocale=" + systemLocale + " fallbackLocale=" + fallbackLocale); + return builder; + } + + public static ArrayList<InputMethodInfo> getDefaultEnabledImes(final Context context, + final boolean isSystemReady, final ArrayList<InputMethodInfo> imis) { + final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context); + if (!isSystemReady) { + // When the system is not ready, the system locale is not stable and reliable. Hence + // we will pick up IMEs that support software keyboard based on the fallback locale. + // Also pick up suitable IMEs regardless of the software keyboard support. + // (e.g. Voice IMEs) + return getMinimumKeyboardSetWithoutSystemLocale(imis, context, fallbackLocale) + .fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SUBTYPE_MODE_ANY) + .build(); + } + + // When the system is ready, we will primarily rely on the system locale, but also keep + // relying on the fallback locale as a last resort. + // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs), + // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic" + // subtype) + final Locale systemLocale = getSystemLocaleFromContext(context); + return getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale) + .fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale, + true /* checkCountry */, SUBTYPE_MODE_ANY) + .fillAuxiliaryImes(imis, context) + .build(); } /** - * @deprecated Use {@link #isSystemIme(InputMethodInfo)} and - * {@link InputMethodInfo#isDefault(Context)} and - * {@link #isImeThatHasSubtypeOf(InputMethodInfo, Locale, boolean, String))} instead. + * @deprecated Use {@link #isSystemImeThatHasSubtypeOf(InputMethodInfo, Context, boolean, + * Locale, boolean, String)} instead. */ @Deprecated public static boolean isValidSystemDefaultIme( @@ -285,22 +410,25 @@ public class InputMethodUtils { } public static boolean containsSubtypeOf(final InputMethodInfo imi, - final Locale locale, final boolean ignoreCountry, final String mode) { + @Nullable final Locale locale, final boolean checkCountry, final String mode) { + if (locale == null) { + return false; + } 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 { + if (checkCountry) { // TODO: Use {@link Locale#toLanguageTag()} and // {@link Locale#forLanguageTag(languageTag)} instead. if (!TextUtils.equals(subtype.getLocale(), locale.toString())) { continue; } + } else { + final Locale subtypeLocale = new Locale(getLanguageFromLocaleString( + subtype.getLocale())); + if (!subtypeLocale.getLanguage().equals(locale.getLanguage())) { + continue; + } } if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) || mode.equalsIgnoreCase(subtype.getMode())) { @@ -465,19 +593,9 @@ public class InputMethodUtils { return applicableSubtypes; } - private static List<InputMethodSubtype> getEnabledInputMethodSubtypeList( - Context context, InputMethodInfo imi, List<InputMethodSubtype> enabledSubtypes, - boolean allowsImplicitlySelectedSubtypes) { - if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) { - enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - context.getResources(), imi); - } - return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes); - } - /** * Returns the language component of a given locale string. - * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(languageTag)} + * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)} */ public static String getLanguageFromLocaleString(String locale) { final int idx = locale.indexOf('_'); diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodTest.java index 577dd64..1557918 100644 --- a/core/tests/inputmethodtests/src/android/os/InputMethodTest.java +++ b/core/tests/inputmethodtests/src/android/os/InputMethodTest.java @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Objects; public class InputMethodTest extends InstrumentationTestCase { private static final boolean IS_AUX = true; @@ -46,84 +47,104 @@ public class InputMethodTest extends InstrumentationTestCase { private static final Locale LOCALE_EN_IN = new Locale("en", "IN"); private static final Locale LOCALE_HI = new Locale("hi"); private static final Locale LOCALE_JA_JP = new Locale("ja", "JP"); + private static final Locale LOCALE_ZH_CN = new Locale("zh", "CN"); + private static final Locale LOCALE_ZH_TW = new Locale("zh", "TW"); private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; private static final String SUBTYPE_MODE_VOICE = "voice"; @SmallTest public void testVoiceImes() throws Exception { // locale: en_US - assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US, !IS_SYSTEM_READY, - "DummyDefaultEnKeyboardIme"); - assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US, !IS_SYSTEM_READY, - "DummyDefaultEnKeyboardIme"); - assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US, IS_SYSTEM_READY, - "DummyDefaultAutoVoiceIme", "DummyDefaultEnKeyboardIme"); - assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US, IS_SYSTEM_READY, - "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", - "DummyDefaultEnKeyboardIme"); + assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US, + !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); + assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US, + !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme"); + assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US, + IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); + assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US, + IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0", + "DummyNonDefaultAutoVoiceIme1"); // locale: en_GB - assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB, !IS_SYSTEM_READY, - "DummyDefaultEnKeyboardIme"); - assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB, !IS_SYSTEM_READY, - "DummyDefaultEnKeyboardIme"); - assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB, IS_SYSTEM_READY, - "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); - assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB, IS_SYSTEM_READY, - "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", - "DummyDefaultEnKeyboardIme"); + assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB, + !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); + assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB, + !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme"); + assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB, + IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); + assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB, + IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0", + "DummyNonDefaultAutoVoiceIme1"); // locale: ja_JP - assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP, !IS_SYSTEM_READY, - "DummyDefaultEnKeyboardIme"); - assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP, !IS_SYSTEM_READY, - "DummyDefaultEnKeyboardIme"); - assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP, IS_SYSTEM_READY, - "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); - assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP, IS_SYSTEM_READY, - "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", - "DummyDefaultEnKeyboardIme"); + assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP, + !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); + assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP, + !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme"); + assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP, + IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); + assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP, + IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0", + "DummyNonDefaultAutoVoiceIme1"); } @SmallTest public void testKeyboardImes() throws Exception { // locale: en_US - assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_US, !IS_SYSTEM_READY, - "com.android.apps.inputmethod.latin"); - assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_US, IS_SYSTEM_READY, - "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin"); + assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US, + !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin"); + assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US, + IS_SYSTEM_READY, "com.android.apps.inputmethod.latin", + "com.android.apps.inputmethod.voice"); // locale: en_GB - assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_GB, !IS_SYSTEM_READY, - "com.android.apps.inputmethod.latin"); - assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_GB, IS_SYSTEM_READY, - "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin"); + assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB, + !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin"); + assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB, + IS_SYSTEM_READY, "com.android.apps.inputmethod.latin", + "com.android.apps.inputmethod.voice"); // locale: en_IN - assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_IN, !IS_SYSTEM_READY, - "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"); + assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN, + !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin"); + assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN, + IS_SYSTEM_READY, "com.android.apps.inputmethod.hindi", + "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice"); // locale: hi - assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_HI, !IS_SYSTEM_READY, - "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"); + assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI, + !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin"); + assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI, + IS_SYSTEM_READY, "com.android.apps.inputmethod.hindi", + "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice"); // locale: ja_JP - assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_JA_JP, !IS_SYSTEM_READY, - "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.japanese"); + assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP, + !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin"); + assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP, + IS_SYSTEM_READY, "com.android.apps.inputmethod.japanese", + "com.android.apps.inputmethod.voice"); + + // locale: zh_CN + assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN, + !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin"); + assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN, + IS_SYSTEM_READY, "com.android.apps.inputmethod.pinyin", + "com.android.apps.inputmethod.voice"); + + // locale: zh_TW + // Note: In this case, no IME is suitable for the system locale. Hence we will pick up a + // fallback IME regardless of the "default" attribute. + assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW, + !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin"); + assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW, + IS_SYSTEM_READY, "com.android.apps.inputmethod.latin", + "com.android.apps.inputmethod.voice"); } @SmallTest public void testParcelable() throws Exception { - final ArrayList<InputMethodInfo> originalList = getSamplePreinstalledImes(); + final ArrayList<InputMethodInfo> originalList = getSamplePreinstalledImes("en-rUS"); final List<InputMethodInfo> clonedList = cloneViaParcel(originalList); assertNotNull(clonedList); final List<InputMethodInfo> clonedClonedList = cloneViaParcel(clonedList); @@ -139,11 +160,14 @@ public class InputMethodTest extends InstrumentationTestCase { } private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes, - final Locale systemLocale, final boolean isSystemReady, String... imeNames) { + final Locale systemLocale, final boolean isSystemReady, String... expectedImeNames) { final Context context = getInstrumentation().getTargetContext(); - assertEquals(new HashSet<String>(Arrays.asList(imeNames)), - getPackageNames(callGetDefaultEnabledImesUnderWithLocale(context, - isSystemReady, preinstalledImes, systemLocale))); + final String[] actualImeNames = getPackageNames(callGetDefaultEnabledImesUnderWithLocale( + context, isSystemReady, preinstalledImes, systemLocale)); + assertEquals(expectedImeNames.length, actualImeNames.length); + for (int i = 0; i < expectedImeNames.length; ++i) { + assertEquals(expectedImeNames[i], actualImeNames[i]); + } } private static List<InputMethodInfo> cloneViaParcel(final List<InputMethodInfo> list) { @@ -172,11 +196,10 @@ public class InputMethodTest extends InstrumentationTestCase { } } - private HashSet<String> getPackageNames(final ArrayList<InputMethodInfo> imis) { - final HashSet<String> packageNames = new HashSet<>(); - for (final InputMethodInfo imi : imis) { - final String actualPackageName = imi.getPackageName(); - packageNames.add(actualPackageName); + private String[] getPackageNames(final ArrayList<InputMethodInfo> imis) { + final String[] packageNames = new String[imis.size()]; + for (int i = 0; i < imis.size(); ++i) { + packageNames[i] = imis.get(i).getPackageName(); } return packageNames; } @@ -278,21 +301,34 @@ public class InputMethodTest extends InstrumentationTestCase { return preinstalledImes; } - private static ArrayList<InputMethodInfo> getSamplePreinstalledImes() { + private static boolean contains(final String[] textList, final String textToBeChecked) { + if (textList == null) { + return false; + } + for (final String text : textList) { + if (Objects.equals(textToBeChecked, text)) { + return true; + } + } + return false; + } + + private static ArrayList<InputMethodInfo> getSamplePreinstalledImes(final String localeString) { ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>(); // a dummy Voice IME { + final boolean isDefaultIme = false; final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); subtypes.add(createDummyInputMethodSubtype("", SUBTYPE_MODE_VOICE, IS_AUX, IS_AUTO, !IS_ASCII_CAPABLE)); preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.voice", - "com.android.inputmethod.voice", "DummyVoiceIme", IS_AUX, IS_DEFAULT, + "com.android.inputmethod.voice", "DummyVoiceIme", IS_AUX, isDefaultIme, subtypes)); } - // a dummy Hindi IME { + final boolean isDefaultIme = contains(new String[]{ "hi", "en-rIN" }, localeString); final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); // TODO: This subtype should be marked as IS_ASCII_CAPABLE subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX, @@ -300,32 +336,36 @@ public class InputMethodTest extends InstrumentationTestCase { subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_AUTO, !IS_ASCII_CAPABLE)); preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.hindi", - "com.android.inputmethod.hindi", "DummyHindiIme", !IS_AUX, IS_DEFAULT, + "com.android.inputmethod.hindi", "DummyHindiIme", !IS_AUX, isDefaultIme, subtypes)); } // a dummy Pinyin IME { + final boolean isDefaultIme = contains(new String[]{ "zh-rCN" }, localeString); final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); subtypes.add(createDummyInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_AUTO, !IS_ASCII_CAPABLE)); - preinstalledImes.add(createDummyInputMethodInfo("ccom.android.apps.inputmethod.pinyin", - "com.android.apps.inputmethod.pinyin", "DummyPinyinIme", !IS_AUX, IS_DEFAULT, + preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.pinyin", + "com.android.apps.inputmethod.pinyin", "DummyPinyinIme", !IS_AUX, isDefaultIme, subtypes)); } - // a dummy Korian IME + // a dummy Korean IME { + final boolean isDefaultIme = contains(new String[]{ "ko" }, localeString); final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); subtypes.add(createDummyInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_AUTO, !IS_ASCII_CAPABLE)); preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.korean", - "com.android.apps.inputmethod.korean", "DummyKorianIme", !IS_AUX, IS_DEFAULT, + "com.android.apps.inputmethod.korean", "DummyKoreanIme", !IS_AUX, isDefaultIme, subtypes)); } // a dummy Latin IME { + final boolean isDefaultIme = contains( + new String[]{ "en-rUS", "en-rGB", "en-rIN", "en", "hi" }, localeString); final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_AUTO, IS_ASCII_CAPABLE)); @@ -336,12 +376,13 @@ public class InputMethodTest extends InstrumentationTestCase { subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_AUTO, IS_ASCII_CAPABLE)); preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.latin", - "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT, + "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, isDefaultIme, subtypes)); } // a dummy Japanese IME { + final boolean isDefaultIme = contains(new String[]{ "ja", "ja-rJP" }, localeString); final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); subtypes.add(createDummyInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_AUTO, !IS_ASCII_CAPABLE)); @@ -349,7 +390,7 @@ public class InputMethodTest extends InstrumentationTestCase { !IS_AUTO, !IS_ASCII_CAPABLE)); preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.japanese", "com.android.apps.inputmethod.japanese", "DummyJapaneseIme", !IS_AUX, - IS_DEFAULT, subtypes)); + isDefaultIme, subtypes)); } return preinstalledImes; |