diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index 62e149afab7f1..4e48e45d018ec 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -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 sSubtypeToLocale = + new LocaleUtils.LocaleExtractor() { + @Override + public Locale get(InputMethodSubtype source) { + return source != null ? source.getLocaleObject() : null; + } + }; + @VisibleForTesting public static ArrayList getImplicitlyApplicableSubtypesLocked( Resources res, InputMethodInfo imi) { final List 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 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 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 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 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; } diff --git a/core/java/com/android/internal/inputmethod/LocaleUtils.java b/core/java/com/android/internal/inputmethod/LocaleUtils.java new file mode 100644 index 0000000000000..99bb4cbea14ad --- /dev/null +++ b/core/java/com/android/internal/inputmethod/LocaleUtils.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.inputmethod; + +import com.android.internal.annotations.VisibleForTesting; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.LocaleList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +public final class LocaleUtils { + + @VisibleForTesting + public interface LocaleExtractor { + @Nullable + Locale get(@Nullable T source); + } + + @Nullable + private static String getLanguage(@Nullable Locale locale) { + if (locale == null) { + return null; + } + return locale.getLanguage(); + } + + /** + * Filters the given items based on language preferences. + * + *

For each language found in {@code preferredLanguages}, this method tries to copy at most + * one best-match item from {@code source} to {@code dest}. For example, if + * {@code "en-GB", "ja", "en-AU", "fr-CA", "en-IN"} is specified to {@code preferredLanguages}, + * this method tries to copy at most one English locale, at most one Japanese, and at most one + * French locale from {@code source} to {@code dest}. Here the best matching English locale + * will be searched from {@code source} as follows. + *

    + *
  1. The first instance in {@code sources} that exactly matches {@code "en-GB"}
  2. + *
  3. The first instance in {@code sources} that exactly matches {@code "en-AU"}
  4. + *
  5. The first instance in {@code sources} that exactly matches {@code "en-IN"}
  6. + *
  7. The first instance in {@code sources} that partially matches {@code "en"}
  8. + *
+ *

Then this method iterates the same algorithm for Japanese then French.

+ * + * @param sources Source items to be filtered. + * @param extractor Type converter from the source items to {@link Locale} object. + * @param preferredLanguages Ordered list of locales with which the input items will be + * filtered. + * @param dest Destination into which the filtered items will be added. + * @param Type of the data items. + */ + @VisibleForTesting + public static void filterByLanguage( + @NonNull List sources, + @NonNull LocaleExtractor extractor, + @NonNull LocaleList preferredLanguages, + @NonNull ArrayList dest) { + final Locale[] availableLocales = new Locale[sources.size()]; + for (int i = 0; i < availableLocales.length; ++i) { + availableLocales[i] = extractor.get(sources.get(i)); + } + final Locale[] sortedPreferredLanguages = new Locale[preferredLanguages.size()]; + if (sortedPreferredLanguages.length > 0) { + int nextIndex = 0; + final int N = preferredLanguages.size(); + languageLoop: + for (int i = 0; i < N; ++i) { + final String language = getLanguage(preferredLanguages.get(i)); + for (int j = 0; j < nextIndex; ++j) { + if (TextUtils.equals(getLanguage(sortedPreferredLanguages[j]), language)) { + continue languageLoop; + } + } + for (int j = i; j < N; ++j) { + final Locale locale = preferredLanguages.get(j); + if (TextUtils.equals(language, getLanguage(locale))) { + sortedPreferredLanguages[nextIndex] = locale; + ++nextIndex; + } + } + } + } + + + for (int languageIndex = 0; languageIndex < sortedPreferredLanguages.length;) { + // Finding the range. + final String language = getLanguage(sortedPreferredLanguages[languageIndex]); + int nextLanguageIndex = languageIndex; + for (; nextLanguageIndex < sortedPreferredLanguages.length; ++nextLanguageIndex) { + final Locale locale = sortedPreferredLanguages[nextLanguageIndex]; + if (!TextUtils.equals(getLanguage(locale), language)) { + break; + } + } + + // Check exact match + boolean found = false; + for (int i = languageIndex; !found && i < nextLanguageIndex; ++i) { + final Locale locale = sortedPreferredLanguages[i]; + for (int j = 0; j < availableLocales.length; ++j) { + if (!Objects.equals(locale, availableLocales[j])) { + continue; + } + dest.add(sources.get(j)); + found = true; + break; + } + } + + if (!found) { + // No exact match. Use language match. + for (int j = 0; j < availableLocales.length; ++j) { + if (!TextUtils.equals(language, getLanguage(availableLocales[j]))) { + continue; + } + dest.add(sources.get(j)); + break; + } + } + languageIndex = nextLanguageIndex; + } + } +} \ No newline at end of file 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 f962a4334933c..ac020e48355ac 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java @@ -21,6 +21,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.os.Parcel; import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.SmallTest; @@ -36,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; @@ -186,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); @@ -233,9 +241,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_EN_US)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_EN_US), imi); assertEquals(1, result.size()); verifyEquality(autoSubtype, result.get(0)); } @@ -257,9 +263,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_EN_US)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_EN_US), imi); verifyEquality(nonAutoEnUS, result.get(0)); } @@ -279,9 +283,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_EN_GB)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_EN_GB), imi); assertEquals(1, result.size()); verifyEquality(nonAutoEnGB, result.get(0)); } @@ -303,9 +305,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_FR)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_FR), imi); assertEquals(1, result.size()); verifyEquality(nonAutoFrCA, result.get(0)); } @@ -323,9 +323,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_FR_CA)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_FR_CA), imi); assertEquals(1, result.size()); verifyEquality(nonAutoFrCA, result.get(0)); } @@ -344,9 +342,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_JA_JP)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_JA_JP), imi); assertEquals(3, result.size()); verifyEquality(nonAutoJa, result.get(0)); verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, result.get(1)); @@ -364,9 +360,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_FIL_PH)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_FIL_PH), imi); assertEquals(1, result.size()); verifyEquality(nonAutoFil, result.get(0)); } @@ -384,9 +378,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_FI)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_FI), imi); assertEquals(1, result.size()); verifyEquality(nonAutoJa, result.get(0)); } @@ -402,9 +394,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_IN)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_IN), imi); assertEquals(1, result.size()); verifyEquality(nonAutoIn, result.get(0)); } @@ -418,9 +408,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_ID)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_ID), imi); assertEquals(1, result.size()); verifyEquality(nonAutoIn, result.get(0)); } @@ -434,9 +422,7 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_IN)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_IN), imi); assertEquals(1, result.size()); verifyEquality(nonAutoId, result.get(0)); } @@ -450,12 +436,36 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { subtypes); final ArrayList result = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( - createTargetContextWithLocales(new LocaleList(LOCALE_ID)) - .getResources(), - imi); + getResourcesForLocales(LOCALE_ID), imi); 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 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 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 @@ -638,6 +648,10 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { .createConfigurationContext(resourceConfiguration); } + private Resources getResourcesForLocales(Locale... locales) { + return createTargetContextWithLocales(new LocaleList(locales)).getResources(); + } + private String[] getPackageNames(final ArrayList imis) { final String[] packageNames = new String[imis.size()]; for (int i = 0; i < imis.size(); ++i) { diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/LocaleUtilsTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/LocaleUtilsTest.java new file mode 100644 index 0000000000000..b9c2da75f55c8 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/inputmethod/LocaleUtilsTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.inputmethod; + +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.LocaleList; + +import java.util.ArrayList; +import java.util.Locale; + +public class LocaleUtilsTest extends InstrumentationTestCase { + + private static final LocaleUtils.LocaleExtractor sIdentityMapper = + new LocaleUtils.LocaleExtractor() { + @Override + public Locale get(Locale source) { + return source; + } + }; + + @SmallTest + public void testFilterByLanguageEmptyLanguageList() throws Exception { + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(Locale.forLanguageTag("en-US")); + availableLocales.add(Locale.forLanguageTag("fr-CA")); + availableLocales.add(Locale.forLanguageTag("in")); + availableLocales.add(Locale.forLanguageTag("ja")); + availableLocales.add(Locale.forLanguageTag("fil")); + + final LocaleList preferredLocales = LocaleList.getEmptyLocaleList(); + + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(0, dest.size()); + } + + @SmallTest + public void testFilterByLanguageEmptySource() throws Exception { + final ArrayList availableLocales = new ArrayList<>(); + + final LocaleList preferredLocales = LocaleList.forLanguageTags("fr,en-US,ja-JP"); + + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(0, dest.size()); + } + + @SmallTest + public void testFilterByLanguageNullAvailableLocales() throws Exception { + { + final LocaleList preferredLocales = + LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN"); + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(null); + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(0, dest.size()); + } + { + final LocaleList preferredLocales = + LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN"); + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(null); + availableLocales.add(null); + availableLocales.add(null); + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(0, dest.size()); + } + { + final LocaleList preferredLocales = + LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN"); + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(null); + availableLocales.add(Locale.forLanguageTag("en-US")); + availableLocales.add(null); + availableLocales.add(null); + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(1, dest.size()); + assertEquals(availableLocales.get(1), dest.get(0)); // "en-US" + } + { + final LocaleList preferredLocales = + LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN"); + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(null); + availableLocales.add(Locale.forLanguageTag("en")); + availableLocales.add(null); + availableLocales.add(null); + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(1, dest.size()); + assertEquals(availableLocales.get(1), dest.get(0)); // "en" + } + { + final LocaleList preferredLocales = + LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN"); + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(null); + availableLocales.add(Locale.forLanguageTag("ja-JP")); + availableLocales.add(null); + availableLocales.add(null); + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(0, dest.size()); + } + } + + @SmallTest + public void testFilterByLanguage() throws Exception { + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(Locale.forLanguageTag("en-US")); + availableLocales.add(Locale.forLanguageTag("fr-CA")); + availableLocales.add(Locale.forLanguageTag("in")); + availableLocales.add(Locale.forLanguageTag("ja")); + availableLocales.add(Locale.forLanguageTag("fil")); + + final LocaleList preferredLocales = LocaleList.forLanguageTags("fr,en-US,ja-JP"); + + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(3, dest.size()); + assertEquals(availableLocales.get(1), dest.get(0)); // "fr-CA" + assertEquals(availableLocales.get(0), dest.get(1)); // "en-US" + assertEquals(availableLocales.get(3), dest.get(2)); // "ja" + } + + @SmallTest + public void testFilterByLanguageTheSameLanguage() throws Exception { + { + final LocaleList preferredLocales = + LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN"); + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(Locale.forLanguageTag("fr-CA")); + availableLocales.add(Locale.forLanguageTag("en-US")); + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(1, dest.size()); + assertEquals(availableLocales.get(1), dest.get(0)); // "en-US" + } + { + final LocaleList preferredLocales = + LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN"); + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(Locale.forLanguageTag("fr-CA")); + availableLocales.add(Locale.forLanguageTag("en")); + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(1, dest.size()); + assertEquals(availableLocales.get(1), dest.get(0)); // "en" + } + { + final LocaleList preferredLocales = + LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN"); + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(Locale.forLanguageTag("fr-CA")); + availableLocales.add(Locale.forLanguageTag("en-CA")); + availableLocales.add(Locale.forLanguageTag("en-IN")); + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(1, dest.size()); + assertEquals(availableLocales.get(2), dest.get(0)); // "en-IN" + } + { + final LocaleList preferredLocales = + LocaleList.forLanguageTags("en-AU,en-GB,en-US,en-IN"); + final ArrayList availableLocales = new ArrayList<>(); + availableLocales.add(Locale.forLanguageTag("fr-CA")); + availableLocales.add(Locale.forLanguageTag("en-CA")); + availableLocales.add(Locale.forLanguageTag("en-NZ")); + availableLocales.add(Locale.forLanguageTag("en-BZ")); + final ArrayList dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(1, dest.size()); + assertEquals(availableLocales.get(1), dest.get(0)); // "en-CA" + } + } +} diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 5ba8bd555237f..63c9822d82c8c 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -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) {