Loading core/java/com/android/internal/app/AppLocaleCollector.java +42 −19 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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); } } Loading @@ -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); Loading Loading @@ -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) { Loading @@ -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) { Loading core/java/com/android/internal/app/LocalePickerWithRegion.java +4 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading core/java/com/android/internal/app/LocaleStore.java +119 −35 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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) { Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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: Loading Loading @@ -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( Loading @@ -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()); Loading @@ -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. */ Loading tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java +34 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading @@ -77,6 +83,21 @@ public class AppLocaleCollectorTest { initAppSupportedLocale()); } @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(); Loading @@ -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); Loading @@ -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; } Loading @@ -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() { Loading @@ -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() { Loading tests/Internal/src/com/android/internal/app/LocaleStoreTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -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())); } } Loading Loading
core/java/com/android/internal/app/AppLocaleCollector.java +42 −19 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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); } } Loading @@ -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); Loading Loading @@ -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) { Loading @@ -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) { Loading
core/java/com/android/internal/app/LocalePickerWithRegion.java +4 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading
core/java/com/android/internal/app/LocaleStore.java +119 −35 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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) { Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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: Loading Loading @@ -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( Loading @@ -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()); Loading @@ -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. */ Loading
tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java +34 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 { Loading @@ -77,6 +83,21 @@ public class AppLocaleCollectorTest { initAppSupportedLocale()); } @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(); Loading @@ -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); Loading @@ -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; } Loading @@ -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() { Loading @@ -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() { Loading
tests/Internal/src/com/android/internal/app/LocaleStoreTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -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())); } } Loading