Use LocaleList for implicitly enabled subtypes.

There are two major changes in this CL:

  1. Now IMMS resets its internal state whenever the system locale list
     is changed, rather than just checking the primary system locale.
  2. For software keyboard subtypes,
     InputMethodUtils#getImplicitlyApplicableSubtypesLocked() now takes
     the entire system locale list into account when determining what
     subtypes should be enabled by default when the user does not
     explicitly enable one or more subtypes.

Bug: 27129703
Change-Id: Iaf179d60c12b9a98b4f097e2449471c4184e049b
This commit is contained in:
Yohei Yukawa
2016-02-24 18:27:04 -08:00
parent 102ff0726d
commit e985c240e3
3 changed files with 97 additions and 45 deletions

View File

@@ -32,6 +32,7 @@ import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LocaleList;
import android.util.Pair;
import android.util.Printer;
import android.util.Slog;
@@ -486,18 +487,29 @@ public class InputMethodUtils {
return NOT_A_SUBTYPE_ID;
}
private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
new LocaleUtils.LocaleExtractor<InputMethodSubtype>() {
@Override
public Locale get(InputMethodSubtype source) {
return source != null ? source.getLocaleObject() : null;
}
};
@VisibleForTesting
public static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
Resources res, InputMethodInfo imi) {
final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
final String systemLocale = res.getConfiguration().locale.toString();
final LocaleList systemLocales = res.getConfiguration().getLocales();
final String systemLocale = systemLocales.get(0).toString();
if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>();
final String systemLanguage = res.getConfiguration().locale.getLanguage();
final int numSubtypes = subtypes.size();
// Handle overridesImplicitlyEnabledSubtype mechanism.
final String systemLanguage = systemLocales.get(0).getLanguage();
final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new HashMap<>();
final int N = subtypes.size();
for (int i = 0; i < N; ++i) {
for (int i = 0; i < numSubtypes; ++i) {
// scan overriding implicitly enabled subtypes.
InputMethodSubtype subtype = subtypes.get(i);
final InputMethodSubtype subtype = subtypes.get(i);
if (subtype.overridesImplicitlyEnabledSubtype()) {
final String mode = subtype.getMode();
if (!applicableModeAndSubtypesMap.containsKey(mode)) {
@@ -508,42 +520,46 @@ public class InputMethodUtils {
if (applicableModeAndSubtypesMap.size() > 0) {
return new ArrayList<>(applicableModeAndSubtypesMap.values());
}
for (int i = 0; i < N; ++i) {
final ArrayList<InputMethodSubtype> keyboardSubtypes = new ArrayList<>();
for (int i = 0; i < numSubtypes; ++i) {
final InputMethodSubtype subtype = subtypes.get(i);
final String locale = subtype.getLocale();
final String mode = subtype.getMode();
final String language = getLanguageFromLocaleString(locale);
// When system locale starts with subtype's locale, that subtype will be applicable
// for system locale. We need to make sure the languages are the same, to prevent
// locales like "fil" (Filipino) being matched by "fi" (Finnish).
//
// For instance, it's clearly applicable for cases like system locale = en_US and
// subtype = en, but it is not necessarily considered applicable for cases like system
// locale = en and subtype = en_US.
//
// We just call systemLocale.startsWith(locale) in this function because there is no
// need to find applicable subtypes aggressively unlike
// findLastResortApplicableSubtypeLocked.
//
// TODO: This check is broken. It won't take scripts into account and doesn't
// account for the mandatory conversions performed by Locale#toString.
if (language.equals(systemLanguage) && systemLocale.startsWith(locale)) {
final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
// If more applicable subtypes are contained, skip.
if (applicableSubtype != null) {
if (systemLocale.equals(applicableSubtype.getLocale())) continue;
if (!systemLocale.equals(locale)) continue;
if (TextUtils.equals(SUBTYPE_MODE_KEYBOARD, subtype.getMode())) {
keyboardSubtypes.add(subtype);
} else {
final Locale locale = subtype.getLocaleObject();
final String mode = subtype.getMode();
// TODO: Take secondary system locales into consideration.
if (locale != null && locale.equals(systemLanguage)) {
final InputMethodSubtype applicableSubtype =
applicableModeAndSubtypesMap.get(mode);
// If more applicable subtypes are contained, skip.
if (applicableSubtype != null) {
if (systemLocale.equals(applicableSubtype.getLocaleObject())) continue;
if (!systemLocale.equals(locale)) continue;
}
applicableModeAndSubtypesMap.put(mode, subtype);
}
applicableModeAndSubtypesMap.put(mode, subtype);
}
}
final InputMethodSubtype keyboardSubtype
= applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD);
final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<>(
applicableModeAndSubtypesMap.values());
if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
for (int i = 0; i < N; ++i) {
final InputMethodSubtype subtype = subtypes.get(i);
final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<>();
LocaleUtils.filterByLanguage(keyboardSubtypes, sSubtypeToLocale, systemLocales,
applicableSubtypes);
boolean hasAsciiCapableKeyboard = false;
final int numApplicationSubtypes = applicableSubtypes.size();
for (int i = 0; i < numApplicationSubtypes; ++i) {
final InputMethodSubtype subtype = applicableSubtypes.get(i);
if (subtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
hasAsciiCapableKeyboard = true;
break;
}
}
if (!hasAsciiCapableKeyboard) {
final int numKeyboardSubtypes = keyboardSubtypes.size();
for (int i = 0; i < numKeyboardSubtypes; ++i) {
final InputMethodSubtype subtype = keyboardSubtypes.get(i);
final String mode = subtype.getMode();
if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
@@ -551,13 +567,16 @@ public class InputMethodUtils {
}
}
}
if (keyboardSubtype == null) {
if (applicableSubtypes.isEmpty()) {
InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
if (lastResortKeyboardSubtype != null) {
applicableSubtypes.add(lastResortKeyboardSubtype);
}
}
applicableSubtypes.addAll(applicableModeAndSubtypesMap.values());
return applicableSubtypes;
}

View File

@@ -37,6 +37,10 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isIn;
import static org.hamcrest.Matchers.not;
public class InputMethodUtilsTest extends InstrumentationTestCase {
private static final boolean IS_AUX = true;
private static final boolean IS_DEFAULT = true;
@@ -187,6 +191,9 @@ public class InputMethodUtilsTest extends InstrumentationTestCase {
final InputMethodSubtype nonAutoEnGB = createDummyInputMethodSubtype("en_GB",
SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
final InputMethodSubtype nonAutoEnIN = createDummyInputMethodSubtype("en_IN",
SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
final InputMethodSubtype nonAutoFrCA = createDummyInputMethodSubtype("fr_CA",
SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
@@ -433,6 +440,32 @@ public class InputMethodUtilsTest extends InstrumentationTestCase {
assertEquals(1, result.size());
verifyEquality(nonAutoId, result.get(0));
}
// If there is no automatic subtype (overridesImplicitlyEnabledSubtype:true) and the system
// provides multiple locales, we try to enable multiple subtypes.
{
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
subtypes.add(nonAutoEnUS);
subtypes.add(nonAutoFrCA);
subtypes.add(nonAutoIn);
subtypes.add(nonAutoJa);
subtypes.add(nonAutoFil);
subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
final InputMethodInfo imi = createDummyInputMethodInfo(
"com.android.apps.inputmethod.latin",
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi);
assertThat(nonAutoFrCA, isIn(result));
assertThat(nonAutoEnUS, isIn(result));
assertThat(nonAutoJa, isIn(result));
assertThat(nonAutoIn, not(isIn(result)));
assertThat(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, not(isIn(result)));
assertThat(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, not(isIn(result)));
}
}
@SmallTest

View File

@@ -95,6 +95,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.EventLog;
import android.util.LocaleList;
import android.util.LruCache;
import android.util.Pair;
import android.util.PrintWriterPrinter;
@@ -135,7 +136,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
/**
* This class provides a system service that manages input methods.
@@ -446,7 +446,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private View mSwitchingDialogTitleView;
private InputMethodInfo[] mIms;
private int[] mSubtypeIds;
private Locale mLastSystemLocale;
private LocaleList mLastSystemLocales;
private boolean mShowImeWithHardKeyboard;
private boolean mAccessibilityRequestingNoSoftKeyboard;
private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
@@ -949,15 +949,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// not system ready
return;
}
final Locale newLocale = mRes.getConfiguration().locale;
final LocaleList newLocales = mRes.getConfiguration().getLocales();
if (!updateOnlyWhenLocaleChanged
|| (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
|| (newLocales != null && !newLocales.equals(mLastSystemLocales))) {
if (!updateOnlyWhenLocaleChanged) {
hideCurrentInputLocked(0, null);
resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_RESET_IME);
}
if (DEBUG) {
Slog.i(TAG, "Locale has been changed to " + newLocale);
Slog.i(TAG, "LocaleList has been changed to " + newLocales);
}
buildInputMethodListLocked(resetDefaultEnabledIme);
if (!updateOnlyWhenLocaleChanged) {
@@ -972,7 +972,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
resetDefaultImeLocked(mContext);
}
updateFromSettingsLocked(true);
mLastSystemLocale = newLocale;
mLastSystemLocales = newLocales;
if (!updateOnlyWhenLocaleChanged) {
try {
startInputInnerLocked();
@@ -1079,7 +1079,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mSettings.getEnabledInputMethodListLocked(),
mSettings.getCurrentUserId(), mContext.getBasePackageName());
}
mLastSystemLocale = mRes.getConfiguration().locale;
mLastSystemLocales = mRes.getConfiguration().getLocales();
try {
startInputInnerLocked();
} catch (RuntimeException e) {