[Panlingual] numbering system and suggestion flag

1. Add numbering system for App's suggested languages.
2. Make the sugesiton flag include all sources like system, ime,
   app, sim.

Bug: 248514263
Test: atest AppLpcaleCollectorTest
Change-Id: I1342e0dbd0eabb4d59f69b5bae9c7c4fe6b72637
This commit is contained in:
Allen Su
2023-04-24 17:53:23 +00:00
parent 820fe8475a
commit 4369917786
5 changed files with 200 additions and 62 deletions

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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.
*/

View File

@@ -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 {
@@ -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();
@@ -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() {

View File

@@ -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()));
}
}