Merge "[Locale Preference] Seperate region and numbering system." into udc-dev

This commit is contained in:
TreeHugger Robot
2023-03-14 03:49:02 +00:00
committed by Android (Google) Code Review
5 changed files with 279 additions and 40 deletions

View File

@@ -20,6 +20,7 @@ import android.annotation.IntRange;
import android.compat.annotation.UnsupportedAppUsage;
import android.icu.text.CaseMap;
import android.icu.text.ListFormatter;
import android.icu.text.NumberingSystem;
import android.icu.util.ULocale;
import android.os.LocaleList;
import android.text.TextUtils;
@@ -172,6 +173,21 @@ public class LocaleHelper {
return lfn.format((Object[]) localeNames);
}
/**
* Returns numbering system value of a locale for display in the provided locale.
*
* @param locale The locale whose key value is displayed.
* @param displayLocale The locale in which to display the key value.
* @return The string of numbering system.
*/
public static String getDisplayNumberingSystemKeyValue(
Locale locale, Locale displayLocale) {
ULocale uLocale = new ULocale.Builder()
.setUnicodeLocaleKeyword("nu", NumberingSystem.getInstance(locale).getName())
.build();
return uLocale.getDisplayKeywordValue("numbers", ULocale.forLocale(displayLocale));
}
/**
* Adds the likely subtags for a provided locale ID.
*

View File

@@ -61,6 +61,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
private int mTopDistance = 0;
private CharSequence mTitle = null;
private OnActionExpandListener mOnActionExpandListener;
private boolean mIsNumberingSystem = false;
/**
* Other classes can register to be notified when a locale was selected.
@@ -90,6 +91,18 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
boolean hasSpecificPackageName();
}
private static LocalePickerWithRegion createNumberingSystemPicker(
LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
boolean translatedOnly, OnActionExpandListener onActionExpandListener,
LocaleCollectorBase localePickerCollector) {
LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
localePicker.setOnActionExpandListener(onActionExpandListener);
localePicker.setIsNumberingSystem(true);
boolean shouldShowTheList = localePicker.setListener(listener, parent,
translatedOnly, localePickerCollector);
return shouldShowTheList ? localePicker : null;
}
private static LocalePickerWithRegion createCountryPicker(
LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
boolean translatedOnly, OnActionExpandListener onActionExpandListener,
@@ -128,6 +141,10 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
return localePicker;
}
private void setIsNumberingSystem(boolean isNumberingSystem) {
mIsNumberingSystem = isNumberingSystem;
}
/**
* Sets the listener and initializes the locale list.
*
@@ -184,6 +201,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
final boolean hasSpecificPackageName =
mLocalePickerCollector != null && mLocalePickerCollector.hasSpecificPackageName();
mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, hasSpecificPackageName);
mAdapter.setNumberingSystemMode(mIsNumberingSystem);
final LocaleHelper.LocaleInfoComparator comp =
new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
mAdapter.sort(comp);
@@ -213,7 +231,6 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
@Override
public void onResume() {
super.onResume();
if (mParentLocale != null) {
getActivity().setTitle(mParentLocale.getFullNameNative());
} else {
@@ -250,16 +267,28 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
// Special case for resetting the app locale to equal the system locale.
boolean isSystemLocale = locale.isSystemLocale();
boolean isRegionLocale = locale.getParent() != null;
boolean mayHaveDifferentNumberingSystem = locale.hasNumberingSystems();
if (isSystemLocale || isRegionLocale) {
if (isSystemLocale
|| (isRegionLocale && !mayHaveDifferentNumberingSystem)
|| mIsNumberingSystem) {
if (mListener != null) {
mListener.onLocaleSelected(locale);
}
returnToParentFrame();
} else {
LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
mListener, locale, mTranslatedOnly /* translate only */,
mOnActionExpandListener, this.mLocalePickerCollector);
LocalePickerWithRegion selector;
if (mayHaveDifferentNumberingSystem) {
selector =
LocalePickerWithRegion.createNumberingSystemPicker(
mListener, locale, mTranslatedOnly /* translate only */,
mOnActionExpandListener, this.mLocalePickerCollector);
} else {
selector = LocalePickerWithRegion.createCountryPicker(
mListener, locale, mTranslatedOnly /* translate only */,
mOnActionExpandListener, this.mLocalePickerCollector);
}
if (selector != null) {
getFragmentManager().beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)

View File

@@ -39,6 +39,9 @@ import java.util.Locale;
import java.util.Set;
public class LocaleStore {
private static final int TIER_LANGUAGE = 1;
private static final int TIER_REGION = 2;
private static final int TIER_NUMBERING = 3;
private static final HashMap<String, LocaleInfo> sLocaleCache = new HashMap<>();
private static final String TAG = LocaleStore.class.getSimpleName();
private static boolean sFullyInitialized = false;
@@ -68,10 +71,13 @@ public class LocaleStore {
private String mFullCountryNameNative;
private String mLangScriptKey;
private boolean mHasNumberingSystems;
private LocaleInfo(Locale locale) {
this.mLocale = locale;
this.mId = locale.toLanguageTag();
this.mParent = getParent(locale);
this.mHasNumberingSystems = false;
this.mIsChecked = false;
this.mSuggestionFlags = SUGGESTION_TYPE_NONE;
this.mIsTranslated = false;
@@ -93,6 +99,11 @@ public class LocaleStore {
.build();
}
/** Return true if there are any same locales with different numbering system. */
public boolean hasNumberingSystems() {
return mHasNumberingSystems;
}
@Override
public String toString() {
return mId;
@@ -195,6 +206,10 @@ public class LocaleStore {
}
}
String getNumberingSystem() {
return LocaleHelper.getDisplayNumberingSystemKeyValue(mLocale, mLocale);
}
String getContentDescription(boolean countryMode) {
if (countryMode) {
return getFullCountryNameInUiLanguage();
@@ -383,6 +398,12 @@ public class LocaleStore {
final boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
Set<Locale> numberSystemLocaleList = new HashSet<>();
for (String localeId : LocalePicker.getSupportedLocales(context)) {
if (Locale.forLanguageTag(localeId).getUnicodeLocaleType("nu") != null) {
numberSystemLocaleList.add(Locale.forLanguageTag(localeId));
}
}
for (String localeId : LocalePicker.getSupportedLocales(context)) {
if (localeId.isEmpty()) {
throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
@@ -403,6 +424,12 @@ public class LocaleStore {
if (simCountries.contains(li.getLocale().getCountry())) {
li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
}
numberSystemLocaleList.forEach(l -> {
if (li.getLocale().stripExtensions().equals(l.stripExtensions())) {
li.mHasNumberingSystems = true;
}
});
sLocaleCache.put(li.getId(), li);
final Locale parent = li.getParent();
if (parent != null) {
@@ -445,20 +472,43 @@ public class LocaleStore {
sFullyInitialized = true;
}
private static int getLevel(Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
if (ignorables.contains(li.getId())) return 0;
if (li.mIsPseudo) return 2;
if (translatedOnly && !li.isTranslated()) return 0;
if (li.getParent() != null) return 2;
return 0;
private static boolean isShallIgnore(
Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
if (ignorables.stream().anyMatch(tag ->
Locale.forLanguageTag(tag).stripExtensions()
.equals(li.getLocale().stripExtensions()))) {
return true;
}
if (li.mIsPseudo) return false;
if (translatedOnly && !li.isTranslated()) return true;
if (li.getParent() != null) return false;
return true;
}
private static int getLocaleTier(LocaleInfo parent) {
if (parent == null) {
return TIER_LANGUAGE;
} else if (parent.getLocale().getCountry().isEmpty()) {
return TIER_REGION;
} else {
return TIER_NUMBERING;
}
}
/**
* Returns a list of locales for language or region selection.
*
* If the parent is null, then it is the language list.
*
* If it is not null, then the list will contain all the locales that belong to that parent.
* Example: if the parent is "ar", then the region list will contain all Arabic locales.
* (this is not language based, but language-script, so that it works for zh-Hant and so on.
* (this is not language based, but language-script, so that it works for zh-Hant and so on.)
*
* If it is not null and has country, then the list will contain all locales with that parent's
* language and country, i.e. containing alternate numbering systems.
*
* Example: if the parent is "ff-Adlm-BF", then the numbering list will contain all
* Fula (Adlam, Burkina Faso) i.e. "ff-Adlm-BF" and "ff-Adlm-BF-u-nu-latn"
*/
@UnsupportedAppUsage
public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
@@ -478,28 +528,49 @@ public class LocaleStore {
*/
public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) {
fillCache(context);
String parentId = parent == null ? null : parent.getId();
HashSet<LocaleInfo> result = new HashSet<>();
if (context != null) {
fillCache(context);
}
HashMap<String, LocaleInfo> supportedLcoaleInfos =
explicitLocales == null
? sLocaleCache
: convertExplicitLocales(explicitLocales, sLocaleCache.values());
return getTierLocales(ignorables, parent, translatedOnly, supportedLcoaleInfos);
}
private static Set<LocaleInfo> getTierLocales(
Set<String> ignorables,
LocaleInfo parent,
boolean translatedOnly,
HashMap<String, LocaleInfo> supportedLcoaleInfos) {
boolean hasTargetParent = parent != null;
String parentId = hasTargetParent ? parent.getId() : null;
HashSet<LocaleInfo> result = new HashSet<>();
for (LocaleStore.LocaleInfo li : supportedLcoaleInfos.values()) {
int level = getLevel(ignorables, li, translatedOnly);
if (level == 2) {
if (parent != null) { // region selection
if (parentId.equals(li.getParent().toLanguageTag())) {
result.add(li);
}
} else { // language selection
if (isShallIgnore(ignorables, li, translatedOnly)) {
continue;
}
switch(getLocaleTier(parent)) {
case TIER_LANGUAGE:
if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) {
result.add(li);
} else {
result.add(getLocaleInfo(li.getParent()));
result.add(getLocaleInfo(li.getParent(), supportedLcoaleInfos));
}
}
break;
case TIER_REGION:
if (parentId.equals(li.getParent().toLanguageTag())) {
result.add(getLocaleInfo(
li.getLocale().stripExtensions(), supportedLcoaleInfos));
}
break;
case TIER_NUMBERING:
if (parent.getLocale().stripExtensions()
.equals(li.getLocale().stripExtensions())) {
result.add(li);
}
break;
}
}
return result;
@@ -538,18 +609,21 @@ public class LocaleStore {
}
private static LocaleList matchLocaleFromSupportedLocaleList(
LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
LocaleList explicitLocales, Collection<LocaleInfo> localeInfos) {
if (localeInfos == null) {
return explicitLocales;
}
//TODO: Adds a function for unicode extension if needed.
Locale[] resultLocales = new Locale[explicitLocales.size()];
for (int i = 0; i < explicitLocales.size(); i++) {
Locale locale = explicitLocales.get(i).stripExtensions();
Locale locale = explicitLocales.get(i);
if (!TextUtils.isEmpty(locale.getCountry())) {
for (LocaleInfo localeInfo :localeinfo) {
for (LocaleInfo localeInfo :localeInfos) {
if (LocaleList.matchesLanguageAndScript(locale, localeInfo.getLocale())
&& TextUtils.equals(locale.getCountry(),
localeInfo.getLocale().getCountry())) {
resultLocales[i] = localeInfo.getLocale();
continue;
break;
}
}
}
@@ -562,18 +636,23 @@ public class LocaleStore {
@UnsupportedAppUsage
public static LocaleInfo getLocaleInfo(Locale locale) {
return getLocaleInfo(locale, sLocaleCache);
}
private static LocaleInfo getLocaleInfo(
Locale locale, HashMap<String, LocaleInfo> localeInfos) {
String id = locale.toLanguageTag();
LocaleInfo result;
if (!sLocaleCache.containsKey(id)) {
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();
if (sLocaleCache.containsKey(filteredLocale.toLanguageTag())) {
if (localeInfos.containsKey(filteredLocale.toLanguageTag())) {
result = new LocaleInfo(locale);
LocaleInfo localeInfo = sLocaleCache.get(filteredLocale.toLanguageTag());
LocaleInfo localeInfo = localeInfos.get(filteredLocale.toLanguageTag());
// This locale is included in supported locales, so follow the settings
// of supported locales.
result.mIsPseudo = localeInfo.mIsPseudo;
@@ -582,9 +661,9 @@ public class LocaleStore {
return result;
}
result = new LocaleInfo(locale);
sLocaleCache.put(id, result);
localeInfos.put(id, result);
} else {
result = sLocaleCache.get(id);
result = localeInfos.get(id);
}
return result;
}

View File

@@ -64,6 +64,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {
protected ArrayList<LocaleStore.LocaleInfo> mOriginalLocaleOptions;
protected int mSuggestionCount;
protected final boolean mCountryMode;
protected boolean mIsNumberingMode;
protected LayoutInflater mInflater;
protected Locale mDisplayLocale = null;
@@ -89,6 +90,14 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {
}
}
public void setNumberingSystemMode(boolean isNumberSystemMode) {
mIsNumberingMode = isNumberSystemMode;
}
public boolean getIsForNumberingSystem() {
return mIsNumberingMode;
}
@Override
public boolean areAllItemsEnabled() {
return false;
@@ -209,7 +218,6 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {
if (convertView == null && mInflater == null) {
mInflater = LayoutInflater.from(parent.getContext());
}
int itemType = getItemViewType(position);
View itemView = getNewViewIfNeeded(convertView, parent, itemType, position);
switch (itemType) {
@@ -217,13 +225,13 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {
case TYPE_HEADER_ALL_OTHERS:
TextView textView = (TextView) itemView;
if (itemType == TYPE_HEADER_SUGGESTED) {
if (mCountryMode) {
if (mCountryMode && !mIsNumberingMode) {
setTextTo(textView, R.string.language_picker_regions_section_suggested);
} else {
setTextTo(textView, R.string.language_picker_section_suggested);
}
} else {
if (mCountryMode) {
if (mCountryMode && !mIsNumberingMode) {
setTextTo(textView, R.string.region_picker_section_all);
} else {
setTextTo(textView, R.string.language_picker_section_all);
@@ -419,9 +427,11 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {
private void updateTextView(View convertView, TextView text, int position) {
LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
text.setText(item.getLabel(mCountryMode));
text.setText(mIsNumberingMode
? item.getNumberingSystem() : item.getLabel(mCountryMode));
text.setTextLocale(item.getLocale());
text.setContentDescription(item.getContentDescription(mCountryMode));
text.setContentDescription(mIsNumberingMode
? item.getNumberingSystem() : item.getContentDescription(mCountryMode));
if (mCountryMode) {
int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
//noinspection ResourceType

View File

@@ -35,6 +35,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IllformedLocaleException;
import java.util.List;
import java.util.Locale;
@@ -141,14 +142,118 @@ public class LocaleStoreTest {
HashMap<String, LocaleInfo> result =
LocaleStore.convertExplicitLocales(locales, supportedLocale);
assertEquals("en", result.get("en").getId());
assertEquals("en-US", result.get("en-US").getId());
assertNull(result.get("en-Latn-US"));
}
@Test
public void getLevelLocales_languageTier_returnAllSupportLanguages() {
LocaleList testSupportedLocales =
LocaleList.forLanguageTags(
"en-US,zh-Hant-TW,ja-JP,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,bn-IN");
Set<String> ignorableLocales = new HashSet<>();
ignorableLocales.add("zh-Hant-HK");
LocaleInfo parent = null;
Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
null, ignorableLocales, parent, false, testSupportedLocales);
assertEquals(5, localeInfos.size());
localeInfos.forEach(localeInfo -> {
assertTrue(localeInfo.getLocale().getCountry().isEmpty());
});
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("en")));
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("zh-Hant")));
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("ja")));
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("bn")));
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("ks-Arab")));
}
@Test
public void getLevelLocales_regionTierAndParentIsEn_returnEnLocales() {
LocaleList testSupportedLocales =
LocaleList.forLanguageTags(
"en-US,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN");
Set<String> ignorableLocales = new HashSet<>();
ignorableLocales.add("zh-Hant-HK");
LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("en"));
Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
null, ignorableLocales, parent, false, testSupportedLocales);
assertEquals(3, localeInfos.size());
localeInfos.forEach(localeInfo -> {
assertEquals("en", localeInfo.getLocale().getLanguage());
});
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("en-US")));
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("en-GB")));
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("en-ZA")));
}
@Test
public void getLevelLocales_numberingTierAndParentIsBnIn_returnBnInLocales() {
LocaleList testSupportedLocales =
LocaleList.forLanguageTags(
"en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
Set<String> ignorableLocales = new HashSet<>();
ignorableLocales.add("zh-Hant-HK");
LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn"));
Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
null, ignorableLocales, parent, false, testSupportedLocales);
assertEquals(1, localeInfos.size());
assertEquals("bn-IN", localeInfos.iterator().next().getLocale().toLanguageTag());
}
@Test
public void getLevelLocales_regionTierAndParentIsBnInAndIgnoreBn_returnEmpty() {
LocaleList testSupportedLocales =
LocaleList.forLanguageTags(
"en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
Set<String> ignorableLocales = new HashSet<>();
ignorableLocales.add("bn-IN");
LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN"));
Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
null, ignorableLocales, parent, false, testSupportedLocales);
assertEquals(0, localeInfos.size());
}
@Test
public void getLevelLocales_regionTierAndParentIsBnIn_returnBnLocaleFamily() {
LocaleList testSupportedLocales =
LocaleList.forLanguageTags(
"en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
Set<String> ignorableLocales = new HashSet<>();
ignorableLocales.add("en-US");
LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN"));
Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
null, ignorableLocales, parent, false, testSupportedLocales);
assertEquals(3, localeInfos.size());
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-adlm")));
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-arab")));
assertTrue(localeInfos.stream().anyMatch(
info -> info.getLocale().toLanguageTag().equals("bn-IN")));
}
private ArrayList<LocaleInfo> getFakeSupportedLocales() {
String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB"};
String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB", "en-US-u-nu-arab"};
ArrayList<LocaleInfo> supportedLocales = new ArrayList<>();
for (String localeTag : locales) {
supportedLocales.add(LocaleStore.fromLocale(Locale.forLanguageTag(localeTag)));