blob: b3b06037ec24c465694bc8d91b55a167ec1f8092 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.app;
import android.annotation.IntDef;
import android.app.LocaleManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.LocaleList;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
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.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IllformedLocaleException;
import java.util.List;
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;
public static class LocaleInfo implements Serializable {
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
// 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
// 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
// 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;
private boolean mIsTranslated;
private boolean mIsPseudo;
private boolean mIsChecked; // Used by the LocaleListEditor to mark entries for deletion
// Combination of flags for various reasons to show a locale as a suggestion.
// Can be SIM, location, etc.
// Set to public to be accessible during runtime from the test app.
@VisibleForTesting public int mSuggestionFlags;
private String mFullNameNative;
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;
this.mIsPseudo = false;
}
private LocaleInfo(String localeId) {
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;
}
return new Locale.Builder()
.setLocale(locale)
.setRegion("")
.setExtension(Locale.UNICODE_LOCALE_EXTENSION, "")
.build();
}
/** Return true if there are any same locales with different numbering system. */
public boolean hasNumberingSystems() {
return mHasNumberingSystems;
}
@Override
public String toString() {
return mId;
}
@UnsupportedAppUsage
public Locale getLocale() {
return mLocale;
}
@UnsupportedAppUsage
public Locale getParent() {
return mParent;
}
/**
* TODO: This method may rename to be more generic i.e. toLanguageTag().
*/
@UnsupportedAppUsage
public String getId() {
return mId;
}
public boolean isTranslated() {
return mIsTranslated;
}
public void setTranslated(boolean isTranslated) {
mIsTranslated = isTranslated;
}
public boolean isSuggested() {
if (!mIsTranslated) { // Never suggest an untranslated locale
return false;
}
return mSuggestionFlags != SUGGESTION_TYPE_NONE;
}
/**
* 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) {
Locale locale = mLocale.stripExtensions();
mFullNameNative =
LocaleHelper.getDisplayName(locale, locale, true /* sentence case */);
}
return mFullNameNative;
}
public String getFullCountryNameNative() {
if (mFullCountryNameNative == null) {
mFullCountryNameNative = LocaleHelper.getDisplayCountry(mLocale, mLocale);
}
return mFullCountryNameNative;
}
String getFullCountryNameInUiLanguage() {
// We don't cache the UI name because the default locale keeps changing
return LocaleHelper.getDisplayCountry(mLocale);
}
/** Returns the name of the locale in the language of the UI.
* It is used for search, but never shown.
* For instance German will show as "Deutsch" in the list, but we will also search for
* "allemand" if the system UI is in French.
*/
@UnsupportedAppUsage
public String getFullNameInUiLanguage() {
Locale locale = mLocale.stripExtensions();
// We don't cache the UI name because the default locale keeps changing
return LocaleHelper.getDisplayName(locale, true /* sentence case */);
}
private String getLangScriptKey() {
if (mLangScriptKey == null) {
Locale baseLocale = new Locale.Builder()
.setLocale(mLocale)
.setExtension(Locale.UNICODE_LOCALE_EXTENSION, "")
.build();
Locale parentWithScript = getParent(LocaleHelper.addLikelySubtags(baseLocale));
mLangScriptKey =
(parentWithScript == null)
? mLocale.toLanguageTag()
: parentWithScript.toLanguageTag();
}
return mLangScriptKey;
}
String getLabel(boolean countryMode) {
if (countryMode) {
return getFullCountryNameNative();
} else {
return getFullNameNative();
}
}
String getNumberingSystem() {
return LocaleHelper.getDisplayNumberingSystemKeyValue(mLocale, mLocale);
}
String getContentDescription(boolean countryMode) {
if (countryMode) {
return getFullCountryNameInUiLanguage();
} else {
return getFullNameInUiLanguage();
}
}
public boolean getChecked() {
return mIsChecked;
}
public void setChecked(boolean checked) {
mIsChecked = checked;
}
public boolean isAppCurrentLocale() {
return (mSuggestionFlags & SUGGESTION_TYPE_CURRENT) > 0;
}
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) {
Set<String> result = new HashSet<>();
TelephonyManager tm = context.getSystemService(TelephonyManager.class);
if (tm != null) {
String iso = tm.getSimCountryIso().toUpperCase(Locale.US);
if (!iso.isEmpty()) {
result.add(iso);
}
iso = tm.getNetworkCountryIso().toUpperCase(Locale.US);
if (!iso.isEmpty()) {
result.add(iso);
}
}
return result;
}
/*
* This method is added for SetupWizard, to force an update of the suggested locales
* when the SIM is initialized.
*
* <p>When the device is freshly started, it sometimes gets to the language selection
* before the SIM is properly initialized.
* So at the time the cache is filled, the info from the SIM might not be available.
* The SetupWizard has a SimLocaleMonitor class to detect onSubscriptionsChanged events.
* SetupWizard will call this function when that happens.</p>
*
* <p>TODO: decide if it is worth moving such kind of monitoring in this shared code.
* The user might change the SIM or might cross border and connect to a network
* in a different country, without restarting the Settings application or the phone.</p>
*/
public static void updateSimCountries(Context context) {
Set<String> simCountries = getSimCountries(context);
for (LocaleInfo li : sLocaleCache.values()) {
// This method sets the suggestion flags for the (new) SIM locales, but it does not
// try to clean up the old flags. After all, if the user replaces a German SIM
// with a French one, it is still possible that they are speaking German.
// So both French and German are reasonable suggestions.
if (simCountries.contains(li.getLocale().getCountry())) {
li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
}
}
}
/**
* Get the application's activated locale.
*
* @param context UI activity's context.
* @param appPackageName The application's package name.
* @param isAppSelected True if the application is selected in the UI; false otherwise.
* @return A LocaleInfo with the application's activated locale.
*/
public static LocaleInfo getAppActivatedLocaleInfo(Context context, String appPackageName,
boolean isAppSelected) {
if (appPackageName == null) {
return null;
}
LocaleManager localeManager = context.getSystemService(LocaleManager.class);
try {
LocaleList localeList = (localeManager == null)
? null : localeManager.getApplicationLocales(appPackageName);
Locale locale = localeList == null ? null : localeList.get(0);
if (locale != null) {
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;
}
return localeInfo;
}
} catch (IllegalArgumentException e) {
Log.d(TAG, "IllegalArgumentException ", e);
}
return null;
}
/**
* Transform IME's language tag to LocaleInfo.
*
* @param list A list which includes IME's subtype.
* @return A LocaleInfo set which includes IME's language tags.
*/
public static Set<LocaleInfo> transformImeLanguageTagToLocaleInfo(
List<InputMethodSubtype> list) {
Set<LocaleInfo> imeLocales = new HashSet<>();
Set<String> languageTagSet = new HashSet<>();
for (InputMethodSubtype subtype : list) {
String languageTag = subtype.getLanguageTag();
if (!languageTagSet.contains(languageTag)) {
languageTagSet.add(languageTag);
Locale locale = Locale.forLanguageTag(languageTag);
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 locale that removes all extensions except for the numbering system.
*/
public static Set<LocaleInfo> getSystemCurrentLocales() {
Set<LocaleInfo> localeList = new HashSet<>();
LocaleList systemLangList = LocaleList.getDefault();
for(int i = 0; i < systemLangList.size(); i++) {
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;
}
/**
* The "system default" is special case for per-app picker. Intentionally keep the locale
* empty to let activity know "system default" been selected.
*/
public static LocaleInfo getSystemDefaultLocaleInfo(boolean hasAppLanguage) {
LocaleInfo systemDefaultInfo = new LocaleInfo("");
systemDefaultInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SYSTEM_LANGUAGE;
if (hasAppLanguage) {
systemDefaultInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT;
}
systemDefaultInfo.mIsTranslated = true;
return systemDefaultInfo;
}
/*
* Show all the languages supported for a country in the suggested list.
* This is also handy for devices without SIM (tablets).
*/
private static void addSuggestedLocalesForRegion(Locale locale) {
if (locale == null) {
return;
}
final String country = locale.getCountry();
if (country.isEmpty()) {
return;
}
for (LocaleInfo li : sLocaleCache.values()) {
if (country.equals(li.getLocale().getCountry())) {
// We don't need to differentiate between manual and SIM suggestions
li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
}
}
}
@UnsupportedAppUsage
public static void fillCache(Context context) {
if (sFullyInitialized) {
return;
}
Set<String> simCountries = getSimCountries(context);
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");
}
LocaleInfo li = new LocaleInfo(localeId);
if (LocaleList.isPseudoLocale(li.getLocale())) {
if (isInDeveloperMode) {
li.setTranslated(true);
li.mIsPseudo = true;
li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
} else {
// Do not display pseudolocales unless in development mode.
continue;
}
}
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) {
String parentId = parent.toLanguageTag();
if (!sLocaleCache.containsKey(parentId)) {
sLocaleCache.put(parentId, new LocaleInfo(parent));
}
}
}
// TODO: See if we can reuse what LocaleList.matchScore does
final HashSet<String> localizedLocales = new HashSet<>();
for (String localeId : LocalePicker.getSystemAssetLocales()) {
LocaleInfo li = new LocaleInfo(localeId);
final String country = li.getLocale().getCountry();
// All this is to figure out if we should suggest a country
if (!country.isEmpty()) {
LocaleInfo cachedLocale = null;
if (sLocaleCache.containsKey(li.getId())) { // the simple case, e.g. fr-CH
cachedLocale = sLocaleCache.get(li.getId());
} else { // e.g. zh-TW localized, zh-Hant-TW in cache
final String langScriptCtry = li.getLangScriptKey() + "-" + country;
if (sLocaleCache.containsKey(langScriptCtry)) {
cachedLocale = sLocaleCache.get(langScriptCtry);
}
}
if (cachedLocale != null) {
cachedLocale.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CFG;
}
}
localizedLocales.add(li.getLangScriptKey());
}
for (LocaleInfo li : sLocaleCache.values()) {
li.setTranslated(localizedLocales.contains(li.getLangScriptKey()));
}
addSuggestedLocalesForRegion(Locale.getDefault());
sFullyInitialized = true;
}
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.)
*
* 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,
LocaleInfo parent, boolean translatedOnly) {
return getLevelLocales(context, ignorables, parent, translatedOnly, null);
}
/**
* @param explicitLocales Indicates only the locales within this list should be shown in the
* locale picker.
*
* 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.
*/
public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) {
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> supportedLocaleInfos) {
boolean hasTargetParent = parent != null;
String parentId = hasTargetParent ? parent.getId() : null;
HashSet<LocaleInfo> result = new HashSet<>();
for (LocaleStore.LocaleInfo li : supportedLocaleInfos.values()) {
if (isShallIgnore(ignorables, li, translatedOnly)) {
continue;
}
switch(getLocaleTier(parent)) {
case TIER_LANGUAGE:
if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) {
result.add(li);
} else {
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())) {
Locale locale = li.getLocale().stripExtensions();
LocaleInfo localeInfo = getLocaleInfo(locale, supportedLocaleInfos);
addLocaleInfoToMap(locale, localeInfo, supportedLocaleInfos);
result.add(localeInfo);
}
break;
case TIER_NUMBERING:
if (parent.getLocale().stripExtensions()
.equals(li.getLocale().stripExtensions())) {
result.add(li);
}
break;
}
}
return result;
}
/** Converts string array of explicit locales to HashMap */
public static HashMap<String, LocaleInfo> convertExplicitLocales(
LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
// Trys to find the matched locale within android supported locales. If there is no matched
// locale, it will still keep the unsupported lcoale in list.
// Note: This currently does not support unicode extension check.
LocaleList localeList = matchLocaleFromSupportedLocaleList(
explicitLocales, localeinfo);
HashMap<String, LocaleInfo> localeInfos = new HashMap<>();
for (int i = 0; i < localeList.size(); i++) {
Locale locale = localeList.get(i);
if (locale.toString().isEmpty()) {
throw new IllformedLocaleException("Bad locale entry");
}
LocaleInfo li = new LocaleInfo(locale);
if (localeInfos.containsKey(li.getId())) {
continue;
}
localeInfos.put(li.getId(), li);
Locale parent = li.getParent();
if (parent != null) {
String parentId = parent.toLanguageTag();
if (!localeInfos.containsKey(parentId)) {
localeInfos.put(parentId, new LocaleInfo(parent));
}
}
}
return localeInfos;
}
private static LocaleList matchLocaleFromSupportedLocaleList(
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);
if (!TextUtils.isEmpty(locale.getCountry())) {
for (LocaleInfo localeInfo :localeInfos) {
if (LocaleList.matchesLanguageAndScript(locale, localeInfo.getLocale())
&& TextUtils.equals(locale.getCountry(),
localeInfo.getLocale().getCountry())) {
resultLocales[i] = localeInfo.getLocale();
break;
}
}
}
if (resultLocales[i] == null) {
resultLocales[i] = locale;
}
}
return new LocaleList(resultLocales);
}
@UnsupportedAppUsage
public static LocaleInfo getLocaleInfo(Locale locale) {
LocaleInfo localeInfo = getLocaleInfo(locale, sLocaleCache);
addLocaleInfoToMap(locale, localeInfo, sLocaleCache);
return localeInfo;
}
private static LocaleInfo getLocaleInfo(
Locale locale, HashMap<String, LocaleInfo> localeInfos) {
String id = locale.toLanguageTag();
LocaleInfo result;
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 = getLocaleWithOnlyNumberingSystem(locale);
if (localeInfos.containsKey(filteredLocale.toLanguageTag())) {
result = new LocaleInfo(locale);
LocaleInfo localeInfo = localeInfos.get(filteredLocale.toLanguageTag());
// This locale is included in supported locales, so follow the settings
// of supported locales.
result.mIsPseudo = localeInfo.mIsPseudo;
result.mIsTranslated = localeInfo.mIsTranslated;
result.mHasNumberingSystems = localeInfo.mHasNumberingSystems;
result.mSuggestionFlags = localeInfo.mSuggestionFlags;
return result;
}
result = new LocaleInfo(locale);
} 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.
*/
@UnsupportedAppUsage
@VisibleForTesting
public static LocaleInfo fromLocale(Locale locale) {
return new LocaleInfo(locale);
}
}