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
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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,36 +157,84 @@ 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) {
|
||||
return retval;
|
||||
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);
|
||||
@@ -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('_');
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user