diff options
5 files changed, 279 insertions, 40 deletions
diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java index 57bd3f945358..d521866bccdb 100644 --- a/core/java/com/android/internal/app/LocaleHelper.java +++ b/core/java/com/android/internal/app/LocaleHelper.java @@ -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; @@ -173,6 +174,21 @@ public class LocaleHelper { } /** + * 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. * * @param locale the locale to maximize. diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index 685bd9a9b2d2..5dfc0eafd47e 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -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) diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java index 8b41829154e1..bcbfdc96a18f 100644 --- a/core/java/com/android/internal/app/LocaleStore.java +++ b/core/java/com/android/internal/app/LocaleStore.java @@ -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; } diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index a61a6d7d241b..08de4dfbe1c4 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -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 diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java index e704cc29976e..46ebfede9a42 100644 --- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java +++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java @@ -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))); |