diff options
5 files changed, 200 insertions, 62 deletions
diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java index 7fe1e3ae5dba..7cf428ad97a5 100644 --- a/core/java/com/android/internal/app/AppLocaleCollector.java +++ b/core/java/com/android/internal/app/AppLocaleCollector.java @@ -154,12 +154,16 @@ public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollecto } /** - * Get the locales that system activates. - * @return A set which includes all the locales that system activates. + * Get a list of system locale that removes all extensions except for the numbering system. */ @VisibleForTesting - public List<LocaleStore.LocaleInfo> getSystemCurrentLocale() { - return LocaleStore.getSystemCurrentLocaleInfo(); + public List<LocaleStore.LocaleInfo> getSystemCurrentLocales() { + List<LocaleStore.LocaleInfo> sysLocales = LocaleStore.getSystemCurrentLocales(); + return sysLocales.stream().filter( + // For the locale to be added into the suggestion area, its country could not be + // empty. + info -> info.getLocale().getCountry().length() > 0).collect( + Collectors.toList()); } @Override @@ -220,12 +224,15 @@ public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollecto // Add current system language into suggestion list if (!isForCountryMode) { - boolean isCurrentLocale, isInAppOrIme; - for (LocaleStore.LocaleInfo localeInfo : getSystemCurrentLocale()) { + boolean isCurrentLocale, existsInApp, existsInIme; + for (LocaleStore.LocaleInfo localeInfo : getSystemCurrentLocales()) { isCurrentLocale = mAppCurrentLocale != null && localeInfo.getLocale().equals(mAppCurrentLocale.getLocale()); - isInAppOrIme = existsInAppOrIme(localeInfo.getLocale()); - if (!isCurrentLocale && !isInAppOrIme) { + // Add the system suggestion flag if the localeInfo exists in mAllAppActiveLocales + // and mImeLocales. + existsInApp = addSystemSuggestionFlag(localeInfo, mAllAppActiveLocales); + existsInIme = addSystemSuggestionFlag(localeInfo, mImeLocales); + if (!isCurrentLocale && !existsInApp && !existsInIme) { appLocaleList.add(localeInfo); } } @@ -248,6 +255,8 @@ public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollecto // Filter out the locale with the same language and country // like zh-TW vs zh-Hant-TW. localeSet = filterSameLanguageAndCountry(localeSet, suggestedSet); + // Add IME suggestion flag if the locale is supported by IME. + localeSet = addImeSuggestionFlag(localeSet); } appLocaleList.addAll(localeSet); suggestedSet.addAll(localeSet); @@ -284,6 +293,31 @@ public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollecto Collectors.toSet()); } + private boolean addSystemSuggestionFlag(LocaleStore.LocaleInfo localeInfo, + Set<LocaleStore.LocaleInfo> appLocaleSet) { + for (LocaleStore.LocaleInfo info : appLocaleSet) { + if (info.getLocale().equals(localeInfo.getLocale())) { + info.extendSuggestionOfType( + LocaleStore.LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE); + return true; + } + } + return false; + } + + private Set<LocaleStore.LocaleInfo> addImeSuggestionFlag( + Set<LocaleStore.LocaleInfo> localeSet) { + for (LocaleStore.LocaleInfo localeInfo : localeSet) { + for (LocaleStore.LocaleInfo imeLocale : mImeLocales) { + if (imeLocale.getLocale().equals(localeInfo.getLocale())) { + localeInfo.extendSuggestionOfType( + LocaleStore.LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE); + } + } + } + return localeSet; + } + private Set<LocaleStore.LocaleInfo> filterSameLanguageAndCountry( Set<LocaleStore.LocaleInfo> newLocaleList, Set<LocaleStore.LocaleInfo> existingLocaleList) { @@ -306,17 +340,6 @@ public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollecto return result; } - private boolean existsInAppOrIme(Locale locale) { - boolean existInApp = mAllAppActiveLocales.stream().anyMatch( - localeInfo -> localeInfo.getLocale().equals(locale)); - if (existInApp) { - return true; - } else { - return mImeLocales.stream().anyMatch( - localeInfo -> localeInfo.getLocale().equals(locale)); - } - } - private Set<LocaleStore.LocaleInfo> filterSupportedLocales( Set<LocaleStore.LocaleInfo> suggestedLocales, HashSet<Locale> appSupportedLocales) { diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index 5dfc0eafd47e..27eebbef6245 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -270,6 +270,10 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O boolean mayHaveDifferentNumberingSystem = locale.hasNumberingSystems(); if (isSystemLocale + // The suggeseted locale would contain the country code except an edge case for + // SUGGESTION_TYPE_CURRENT where the application itself set the preferred locale. + // In this case, onLocaleSelected() will still set the app locale. + || locale.isSuggested() || (isRegionLocale && !mayHaveDifferentNumberingSystem) || mIsNumberingSystem) { if (mListener != null) { diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java index d07058daad8a..f4b858f87413 100644 --- a/core/java/com/android/internal/app/LocaleStore.java +++ b/core/java/com/android/internal/app/LocaleStore.java @@ -16,6 +16,7 @@ package com.android.internal.app; +import android.annotation.IntDef; import android.app.LocaleManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -29,6 +30,8 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.VisibleForTesting; import java.io.Serializable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -47,15 +50,42 @@ public class LocaleStore { private static boolean sFullyInitialized = false; public static class LocaleInfo implements Serializable { - @VisibleForTesting static final int SUGGESTION_TYPE_NONE = 0; - @VisibleForTesting static final int SUGGESTION_TYPE_SIM = 1 << 0; - @VisibleForTesting static final int SUGGESTION_TYPE_CFG = 1 << 1; + public static final int SUGGESTION_TYPE_NONE = 0; + // A mask used to identify the suggested locale is from SIM. + public static final int SUGGESTION_TYPE_SIM = 1 << 0; + // A mask used to identify the suggested locale is from the config. + public static final int SUGGESTION_TYPE_CFG = 1 << 1; // Only for per-app language picker - @VisibleForTesting static final int SUGGESTION_TYPE_CURRENT = 1 << 2; + // A mask used to identify the suggested locale is from the same application's current + // configured locale. + public static final int SUGGESTION_TYPE_CURRENT = 1 << 2; // Only for per-app language picker - @VisibleForTesting static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3; + // A mask used to identify the suggested locale is the system default language. + public static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3; // Only for per-app language picker - @VisibleForTesting static final int SUGGESTION_TYPE_OTHER_APP_LANGUAGE = 1 << 4; + // A mask used to identify the suggested locale is from other applications' configured + // locales. + public static final int SUGGESTION_TYPE_OTHER_APP_LANGUAGE = 1 << 4; + // Only for per-app language picker + // A mask used to identify the suggested locale is what the active IME supports. + public static final int SUGGESTION_TYPE_IME_LANGUAGE = 1 << 5; + // Only for per-app language picker + // A mask used to identify the suggested locale is in the current system languages. + public static final int SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE = 1 << 6; + /** @hide */ + @IntDef(prefix = { "SUGGESTION_TYPE_" }, value = { + SUGGESTION_TYPE_NONE, + SUGGESTION_TYPE_SIM, + SUGGESTION_TYPE_CFG, + SUGGESTION_TYPE_CURRENT, + SUGGESTION_TYPE_SYSTEM_LANGUAGE, + SUGGESTION_TYPE_OTHER_APP_LANGUAGE, + SUGGESTION_TYPE_IME_LANGUAGE, + SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SuggestionType {} + private final Locale mLocale; private final Locale mParent; private final String mId; @@ -88,6 +118,17 @@ public class LocaleStore { this(Locale.forLanguageTag(localeId)); } + private LocaleInfo(LocaleInfo localeInfo) { + this.mLocale = localeInfo.getLocale(); + this.mId = localeInfo.getId(); + this.mParent = localeInfo.getParent(); + this.mHasNumberingSystems = localeInfo.mHasNumberingSystems; + this.mIsChecked = localeInfo.getChecked(); + this.mSuggestionFlags = localeInfo.mSuggestionFlags; + this.mIsTranslated = localeInfo.isTranslated(); + this.mIsPseudo = localeInfo.mIsPseudo; + } + private static Locale getParent(Locale locale) { if (locale.getCountry().isEmpty()) { return null; @@ -142,13 +183,31 @@ public class LocaleStore { return mSuggestionFlags != SUGGESTION_TYPE_NONE; } - private boolean isSuggestionOfType(int suggestionMask) { + /** + * Check whether the LocaleInfo is suggested by a specific mask + * + * @param suggestionMask The mask which is used to identify the suggestion flag. + * @return true if the locale is suggested by a specific suggestion flag. Otherwise, false. + */ + public boolean isSuggestionOfType(int suggestionMask) { if (!mIsTranslated) { // Never suggest an untranslated locale return false; } return (mSuggestionFlags & suggestionMask) == suggestionMask; } + /** + * Extend the locale's suggestion type + * + * @param suggestionMask The mask to extend the suggestion flag + */ + public void extendSuggestionOfType(@SuggestionType int suggestionMask) { + if (!mIsTranslated) { // Never suggest an untranslated locale + return; + } + mSuggestionFlags |= suggestionMask; + } + @UnsupportedAppUsage public String getFullNameNative() { if (mFullNameNative == null) { @@ -186,14 +245,14 @@ public class LocaleStore { private String getLangScriptKey() { if (mLangScriptKey == null) { Locale baseLocale = new Locale.Builder() - .setLocale(mLocale) - .setExtension(Locale.UNICODE_LOCALE_EXTENSION, "") - .build(); + .setLocale(mLocale) + .setExtension(Locale.UNICODE_LOCALE_EXTENSION, "") + .build(); Locale parentWithScript = getParent(LocaleHelper.addLikelySubtags(baseLocale)); mLangScriptKey = (parentWithScript == null) - ? mLocale.toLanguageTag() - : parentWithScript.toLanguageTag(); + ? mLocale.toLanguageTag() + : parentWithScript.toLanguageTag(); } return mLangScriptKey; } @@ -233,6 +292,10 @@ public class LocaleStore { public boolean isSystemLocale() { return (mSuggestionFlags & SUGGESTION_TYPE_SYSTEM_LANGUAGE) > 0; } + + public boolean isInCurrentSystemLocales() { + return (mSuggestionFlags & SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE) > 0; + } } private static Set<String> getSimCountries(Context context) { @@ -304,13 +367,13 @@ public class LocaleStore { Locale locale = localeList == null ? null : localeList.get(0); if (locale != null) { - LocaleInfo localeInfo = new LocaleInfo(locale); + LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache); + LocaleInfo localeInfo = new LocaleInfo(cacheInfo); if (isAppSelected) { localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT; } else { localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE; } - localeInfo.mIsTranslated = true; return localeInfo; } } catch (IllegalArgumentException e) { @@ -329,26 +392,27 @@ public class LocaleStore { List<InputMethodSubtype> list) { Set<LocaleInfo> imeLocales = new HashSet<>(); for (InputMethodSubtype subtype : list) { - LocaleInfo localeInfo = new LocaleInfo(subtype.getLanguageTag()); - localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE; - localeInfo.mIsTranslated = true; + Locale locale = Locale.forLanguageTag(subtype.getLanguageTag()); + LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache); + LocaleInfo localeInfo = new LocaleInfo(cacheInfo); + localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE; imeLocales.add(localeInfo); } return imeLocales; } /** - * Returns a list of system languages with LocaleInfo + * Returns a list of system locale that removes all extensions except for the numbering system. */ - public static List<LocaleInfo> getSystemCurrentLocaleInfo() { + public static List<LocaleInfo> getSystemCurrentLocales() { List<LocaleInfo> localeList = new ArrayList<>(); - LocaleList systemLangList = LocaleList.getDefault(); for(int i = 0; i < systemLangList.size(); i++) { - LocaleInfo systemLocaleInfo = new LocaleInfo(systemLangList.get(i)); - systemLocaleInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM; - systemLocaleInfo.mIsTranslated = true; - localeList.add(systemLocaleInfo); + Locale sysLocale = getLocaleWithOnlyNumberingSystem(systemLangList.get(i)); + LocaleInfo cacheInfo = getLocaleInfo(sysLocale, sLocaleCache); + LocaleInfo localeInfo = new LocaleInfo(cacheInfo); + localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE; + localeList.add(localeInfo); } return localeList; } @@ -542,12 +606,12 @@ public class LocaleStore { Set<String> ignorables, LocaleInfo parent, boolean translatedOnly, - HashMap<String, LocaleInfo> supportedLcoaleInfos) { + HashMap<String, LocaleInfo> supportedLocaleInfos) { boolean hasTargetParent = parent != null; String parentId = hasTargetParent ? parent.getId() : null; HashSet<LocaleInfo> result = new HashSet<>(); - for (LocaleStore.LocaleInfo li : supportedLcoaleInfos.values()) { + for (LocaleStore.LocaleInfo li : supportedLocaleInfos.values()) { if (isShallIgnore(ignorables, li, translatedOnly)) { continue; } @@ -556,13 +620,18 @@ public class LocaleStore { if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) { result.add(li); } else { - result.add(getLocaleInfo(li.getParent(), supportedLcoaleInfos)); + Locale locale = li.getParent(); + LocaleInfo localeInfo = getLocaleInfo(locale, supportedLocaleInfos); + addLocaleInfoToMap(locale, localeInfo, supportedLocaleInfos); + result.add(localeInfo); } break; case TIER_REGION: if (parentId.equals(li.getParent().toLanguageTag())) { - result.add(getLocaleInfo( - li.getLocale().stripExtensions(), supportedLcoaleInfos)); + Locale locale = li.getLocale().stripExtensions(); + LocaleInfo localeInfo = getLocaleInfo(locale, supportedLocaleInfos); + addLocaleInfoToMap(locale, localeInfo, supportedLocaleInfos); + result.add(localeInfo); } break; case TIER_NUMBERING: @@ -636,7 +705,9 @@ public class LocaleStore { @UnsupportedAppUsage public static LocaleInfo getLocaleInfo(Locale locale) { - return getLocaleInfo(locale, sLocaleCache); + LocaleInfo localeInfo = getLocaleInfo(locale, sLocaleCache); + addLocaleInfoToMap(locale, localeInfo, sLocaleCache); + return localeInfo; } private static LocaleInfo getLocaleInfo( @@ -646,10 +717,7 @@ public class LocaleStore { if (!localeInfos.containsKey(id)) { // Locale preferences can modify the language tag to current system languages, so we // need to check the input locale without extra u extension except numbering system. - Locale filteredLocale = new Locale.Builder() - .setLocale(locale.stripExtensions()) - .setUnicodeLocaleKeyword("nu", locale.getUnicodeLocaleType("nu")) - .build(); + Locale filteredLocale = getLocaleWithOnlyNumberingSystem(locale); if (localeInfos.containsKey(filteredLocale.toLanguageTag())) { result = new LocaleInfo(locale); LocaleInfo localeInfo = localeInfos.get(filteredLocale.toLanguageTag()); @@ -662,13 +730,29 @@ public class LocaleStore { return result; } result = new LocaleInfo(locale); - localeInfos.put(id, result); } else { result = localeInfos.get(id); } return result; } + private static Locale getLocaleWithOnlyNumberingSystem(Locale locale) { + return new Locale.Builder() + .setLocale(locale.stripExtensions()) + .setUnicodeLocaleKeyword("nu", locale.getUnicodeLocaleType("nu")) + .build(); + } + + private static void addLocaleInfoToMap(Locale locale, LocaleInfo localeInfo, + HashMap<String, LocaleInfo> map) { + if (!map.containsKey(locale.toLanguageTag())) { + Locale localeWithNumberingSystem = getLocaleWithOnlyNumberingSystem(locale); + if (!map.containsKey(localeWithNumberingSystem.toLanguageTag())) { + map.put(locale.toLanguageTag(), localeInfo); + } + } + } + /** * API for testing. */ diff --git a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java index b24ac3cae795..7d9a6a56262c 100644 --- a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java +++ b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java @@ -19,11 +19,14 @@ package com.android.internal.app; import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import android.os.LocaleList; + import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -62,6 +65,9 @@ public class AppLocaleCollectorTest { private static final int CURRENT = LocaleInfo.SUGGESTION_TYPE_CURRENT; private static final int SYSTEM = LocaleInfo.SUGGESTION_TYPE_SYSTEM_LANGUAGE; private static final int OTHERAPP = LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE; + private static final int IME = LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE; + private static final int SYSTEM_AVAILABLE = + LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE; @Before public void setUp() throws Exception { @@ -78,6 +84,21 @@ public class AppLocaleCollectorTest { } @Test + public void testGetSystemCurrentLocales() { + LocaleList.setDefault( + LocaleList.forLanguageTags("en-US-u-mu-fahrenhe,ar-JO-u-mu-fahrenhe-nu-latn")); + + List<LocaleStore.LocaleInfo> list = + mAppLocaleCollector.getSystemCurrentLocales(); + + LocaleList expected = LocaleList.forLanguageTags("en-US,ar-JO-u-nu-latn"); + assertEquals(list.size(), expected.size()); + for (LocaleStore.LocaleInfo info : list) { + assertTrue(expected.indexOf(info.getLocale()) != -1); + } + } + + @Test public void testGetSupportedLocaleList() { doReturn(mAppCurrentLocale).when(mAppLocaleCollector).getAppCurrentLocale(); doReturn(mResult).when(mAppLocaleCollector).getAppSupportedLocales(); @@ -85,7 +106,8 @@ public class AppLocaleCollectorTest { doReturn(mImeLocales).when(mAppLocaleCollector).getActiveImeLocales(); doReturn(mSystemSupportedLocales).when(mAppLocaleCollector).getSystemSupportedLocale( anyObject(), eq(null), eq(true)); - doReturn(mSystemCurrentLocales).when(mAppLocaleCollector).getSystemCurrentLocale(); + doReturn(mSystemCurrentLocales).when( + mAppLocaleCollector).getSystemCurrentLocales(); Set<LocaleInfo> result = mAppLocaleCollector.getSupportedLocaleList(null, true, false); @@ -106,8 +128,10 @@ public class AppLocaleCollectorTest { map.put("ko", NONE); // The locale App and system support. map.put("en-AU", OTHERAPP); // The locale other App activates and current App supports. map.put("en-CA", OTHERAPP); // The locale other App activates and current App supports. - map.put("ja-JP", OTHERAPP); // The locale other App activates and current App supports. - map.put("zh-Hant-TW", SIM); // The locale system activates. + map.put("en-IN", IME); // The locale IME supports. + map.put("ja-JP", + OTHERAPP | SYSTEM_AVAILABLE | IME); // The locale exists in OTHERAPP, SYSTEM and IME + map.put("zh-Hant-TW", SYSTEM_AVAILABLE); // The locale system activates. map.put(createLocaleInfo("", SYSTEM).getId(), SYSTEM); // System language title return map; } @@ -124,9 +148,10 @@ public class AppLocaleCollectorTest { } private List<LocaleInfo> initSystemCurrentLocales() { - return List.of(createLocaleInfo("zh-Hant-TW", SIM), + return List.of(createLocaleInfo("zh-Hant-TW", SYSTEM_AVAILABLE), + createLocaleInfo("ja-JP", SYSTEM_AVAILABLE), // will be filtered because current App activates this locale. - createLocaleInfo("en-US", SIM)); + createLocaleInfo("en-US", SYSTEM_AVAILABLE)); } private Set<LocaleInfo> initAllAppActivatedLocales() { @@ -141,9 +166,11 @@ public class AppLocaleCollectorTest { private Set<LocaleInfo> initImeLocales() { return Set.of( // will be filtered because system activates zh-Hant-TW. - createLocaleInfo("zh-TW", OTHERAPP), + createLocaleInfo("zh-TW", IME), // will be filtered because current App's activats this locale. - createLocaleInfo("en-US", OTHERAPP)); + createLocaleInfo("en-US", IME), + createLocaleInfo("ja-JP", IME), + createLocaleInfo("en-IN", IME)); } private HashSet<Locale> initAppSupportedLocale() { diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java index 46ebfede9a42..f6568816b4f7 100644 --- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java +++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java @@ -57,7 +57,7 @@ public class LocaleStoreTest { Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP"); assertEquals(localeSet.size(), expectedLanguageTag.size()); for (LocaleInfo info : localeSet) { - assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE); + assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE); assertTrue(expectedLanguageTag.contains(info.getId())); } } |