Loading core/java/com/android/internal/app/LocaleHelper.java +16 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. * Loading core/java/com/android/internal/app/LocalePickerWithRegion.java +34 −5 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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, Loading Loading @@ -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. * Loading Loading @@ -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); Loading Loading @@ -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 { Loading Loading @@ -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( 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) Loading core/java/com/android/internal/app/LocaleStore.java +107 −28 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -195,6 +206,10 @@ public class LocaleStore { } } String getNumberingSystem() { return LocaleHelper.getDisplayNumberingSystemKeyValue(mLocale, mLocale); } String getContentDescription(boolean countryMode) { if (countryMode) { return getFullCountryNameInUiLanguage(); Loading Loading @@ -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"); Loading @@ -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) { Loading Loading @@ -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, Loading @@ -478,28 +528,49 @@ public class LocaleStore { */ public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables, LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) { if (context != null) { fillCache(context); String parentId = parent == null ? null : parent.getId(); HashSet<LocaleInfo> result = new HashSet<>(); } 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); if (isShallIgnore(ignorables, li, translatedOnly)) { continue; } } else { // language selection 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; Loading Loading @@ -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; } } } Loading @@ -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; Loading @@ -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; } Loading core/java/com/android/internal/app/SuggestedLocaleAdapter.java +15 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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); Loading Loading @@ -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 Loading tests/Internal/src/com/android/internal/app/LocaleStoreTest.java +107 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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))); Loading Loading
core/java/com/android/internal/app/LocaleHelper.java +16 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. * Loading
core/java/com/android/internal/app/LocalePickerWithRegion.java +34 −5 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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, Loading Loading @@ -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. * Loading Loading @@ -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); Loading Loading @@ -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 { Loading Loading @@ -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( 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) Loading
core/java/com/android/internal/app/LocaleStore.java +107 −28 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -195,6 +206,10 @@ public class LocaleStore { } } String getNumberingSystem() { return LocaleHelper.getDisplayNumberingSystemKeyValue(mLocale, mLocale); } String getContentDescription(boolean countryMode) { if (countryMode) { return getFullCountryNameInUiLanguage(); Loading Loading @@ -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"); Loading @@ -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) { Loading Loading @@ -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, Loading @@ -478,28 +528,49 @@ public class LocaleStore { */ public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables, LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) { if (context != null) { fillCache(context); String parentId = parent == null ? null : parent.getId(); HashSet<LocaleInfo> result = new HashSet<>(); } 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); if (isShallIgnore(ignorables, li, translatedOnly)) { continue; } } else { // language selection 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; Loading Loading @@ -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; } } } Loading @@ -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; Loading @@ -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; } Loading
core/java/com/android/internal/app/SuggestedLocaleAdapter.java +15 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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); Loading Loading @@ -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 Loading
tests/Internal/src/com/android/internal/app/LocaleStoreTest.java +107 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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))); Loading