From 9c372194f82310c4c4a09caa4d8f79868598cbb1 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Tue, 20 Mar 2018 22:54:56 -0700 Subject: [PATCH] Make sure at least one non-aux IME is enabled Imagine the scenario where three IMEs are installed, and only the last two are enabled: IME A: * a pre-installed IME * has a subtype [mode="keyboard" && isAuxiliary=false] * disabled by user (hence not included in ENABLED_INPUT_METHODS) IME B: * a pre-installed IME * has a subtype [isAuxiliary=true] * currently enabled (included in ENABLED_INPUT_METHODS) IME X: * not a pre-installed IME * has a subtype [mode="keyboard" && isAuxiliary=false] * currently enabled (included in ENABLED_INPUT_METHODS) In this scenario, when the IME X is uninstalled, the current implementation of InputMethodManagerService (IMMS) does not try to hard reset enabled IMEs because there is still one enabled IME, even though it is an auxiliary IME. This can, however, be problematic because an auxiliary IME is just a supplemental IME and user may not be able to easily access the UI to re-enable non-auxiliary IME such as IME A. For instance, on the lock screen there is no way to manually re-enable the IME A. With this CL, every time the available IME list is updated, IMMS ensures that at least one non-auxiliary IME is enabled. If no non-auxiliary IME is enabled, then IMMS tries to pick up one from the pre-installed IME by using the same logic when choosing the default enabled IMEs for the hard-reset scenario. Bug: 71509065 Fix: 71509065 Test: atest FrameworksCoreTests:com.android.internal.inputmethod.InputMethodUtilsTest Test: Manually verified in the above scenario Change-Id: I88c69f548526b35f0e4ba37489365b2433373b04 --- .../inputmethod/InputMethodUtils.java | 22 +++++++---- .../inputmethod/InputMethodUtilsTest.java | 39 +++++++++++++++++++ .../server/InputMethodManagerService.java | 17 ++++++-- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index d3b4dbf1bd827..9a082ec21b3de 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -320,8 +320,8 @@ public class InputMethodUtils { return builder; } - public static ArrayList getDefaultEnabledImes(final Context context, - final ArrayList imis) { + public static ArrayList getDefaultEnabledImes( + Context context, ArrayList imis, boolean onlyMinimum) { final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context); // We will primarily rely on the system locale, but also keep relying on the fallback locale // as a last resort. @@ -329,11 +329,19 @@ public class InputMethodUtils { // 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(); + final InputMethodListBuilder builder = + getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale); + if (!onlyMinimum) { + builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale, + true /* checkCountry */, SUBTYPE_MODE_ANY) + .fillAuxiliaryImes(imis, context); + } + return builder.build(); + } + + public static ArrayList getDefaultEnabledImes( + Context context, ArrayList imis) { + return getDefaultEnabledImes(context, imis, false /* onlyMinimum */); } public static Locale constructLocaleFromString(String localeStr) { diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java index e6ac68294564c..1fd24e3f237ec 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java @@ -99,6 +99,10 @@ public class InputMethodUtilsTest { assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1"); + assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US, + "DummyDefaultEnKeyboardIme"); + assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US, + "DummyDefaultEnKeyboardIme"); // locale: en_GB assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB, @@ -106,6 +110,10 @@ public class InputMethodUtilsTest { assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1"); + assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB, + "DummyDefaultEnKeyboardIme"); + assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB, + "DummyDefaultEnKeyboardIme"); // locale: ja_JP assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP, @@ -113,6 +121,10 @@ public class InputMethodUtilsTest { assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1"); + assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP, + "DummyDefaultEnKeyboardIme"); + assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP, + "DummyDefaultEnKeyboardIme"); } @Test @@ -120,34 +132,49 @@ public class InputMethodUtilsTest { // locale: en_US assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US, "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice"); + assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US, + "com.android.apps.inputmethod.latin"); // locale: en_GB assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB, "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice"); + assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB, + "com.android.apps.inputmethod.latin"); // locale: en_IN assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN, "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice"); + assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN, + "com.android.apps.inputmethod.hindi", + "com.android.apps.inputmethod.latin"); // locale: hi assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI, "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice"); + assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("hi"), LOCALE_HI, + "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin"); // locale: ja_JP assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP, "com.android.apps.inputmethod.japanese", "com.android.apps.inputmethod.voice"); + assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP, + "com.android.apps.inputmethod.japanese"); // locale: zh_CN assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN, "com.android.apps.inputmethod.pinyin", "com.android.apps.inputmethod.voice"); + assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN, + "com.android.apps.inputmethod.pinyin"); // 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, "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice"); + assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW, + "com.android.apps.inputmethod.latin"); } @Test @@ -785,6 +812,18 @@ public class InputMethodUtilsTest { } } + private void assertDefaultEnabledMinimumImes(final ArrayList preinstalledImes, + final Locale systemLocale, String... expectedImeNames) { + final Context context = createTargetContextWithLocales(new LocaleList(systemLocale)); + final String[] actualImeNames = getPackageNames( + InputMethodUtils.getDefaultEnabledImes(context, preinstalledImes, + true /* onlyMinimum */)); + assertEquals(expectedImeNames.length, actualImeNames.length); + for (int i = 0; i < expectedImeNames.length; ++i) { + assertEquals(expectedImeNames[i], actualImeNames[i]); + } + } + private static List cloneViaParcel(final List list) { Parcel p = null; try { diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 5b57c14c11153..f6ff359d8c192 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -3663,16 +3663,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + boolean reenableMinimumNonAuxSystemImes = false; // TODO: The following code should find better place to live. if (!resetDefaultEnabledIme) { boolean enabledImeFound = false; + boolean enabledNonAuxImeFound = false; final List enabledImes = mSettings.getEnabledInputMethodListLocked(); final int N = enabledImes.size(); for (int i = 0; i < N; ++i) { final InputMethodInfo imi = enabledImes.get(i); if (mMethodList.contains(imi)) { enabledImeFound = true; - break; + if (!imi.isAuxiliaryIme()) { + enabledNonAuxImeFound = true; + break; + } } } if (!enabledImeFound) { @@ -3681,12 +3686,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } resetDefaultEnabledIme = true; resetSelectedInputMethodAndSubtypeLocked(""); + } else if (!enabledNonAuxImeFound) { + if (DEBUG) { + Slog.i(TAG, "All the enabled non-Aux IMEs are gone. Do partial reset."); + } + reenableMinimumNonAuxSystemImes = true; } } - if (resetDefaultEnabledIme) { + if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) { final ArrayList defaultEnabledIme = - InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList); + InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList, + reenableMinimumNonAuxSystemImes); final int N = defaultEnabledIme.size(); for (int i = 0; i < N; ++i) { final InputMethodInfo imi = defaultEnabledIme.get(i);