diff options
| author | Calvin Pan <calvinpan@google.com> | 2022-03-15 06:23:08 +0800 |
|---|---|---|
| committer | Calvin Pan <calvinpan@google.com> | 2022-03-21 14:48:09 +0800 |
| commit | 780ff7127a5d7b47c3346a33dc601a456b2efccb (patch) | |
| tree | f75941c7c6c3eecc35fb83e2aee3cc8f2cab7fe3 /core/java | |
| parent | f77ad16906ab3d819096a63ada08fcd793ec8cc1 (diff) | |
Reuse LocalePickerWithRegion in app language selection
- (LocalePickerWithRegion) Only list the language that supported by app
- Add "System language" option
- Add current app locale into suggestion list
- Sort locales by these priorities:
1. Current app's locale
2. System language
3. Suggested language
4. Supported languages
Bug: 223090003
Test: Manual
- (system languages page)
Check Settings-> System -> Languages -> Languages
- (pre-apps language page)
Check Settings-> System -> Languages -> Apps Languages
- Add unit test in follow CL(tracing in b/224747202)
Change-Id: I8dc9f73811dbf653b32773fb98ddb2e4656a4432
Diffstat (limited to 'core/java')
5 files changed, 230 insertions, 16 deletions
diff --git a/core/java/com/android/internal/app/AppLocaleStore.java b/core/java/com/android/internal/app/AppLocaleStore.java new file mode 100644 index 000000000000..76e58988eedf --- /dev/null +++ b/core/java/com/android/internal/app/AppLocaleStore.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 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.app.LocaleConfig; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.LocaleList; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Locale; + +public class AppLocaleStore { + private static final String TAG = AppLocaleStore.class.getSimpleName(); + + public static ArrayList<Locale> getAppSupportedLocales(Context context, String packageName) { + ArrayList<Locale> appSupportedLocales = new ArrayList<>(); + LocaleList packageLocaleList = getPackageLocales(context, packageName); + + if (packageLocaleList != null && packageLocaleList.size() > 0) { + for (int i = 0; i < packageLocaleList.size(); i++) { + appSupportedLocales.add(packageLocaleList.get(i)); + } + Log.d(TAG, "getAppSupportedLocales from LocaleConfig. Size: " + + appSupportedLocales.size()); + } else { + String[] languages = getAssetLocales(context, packageName); + for (String language : languages) { + appSupportedLocales.add(Locale.forLanguageTag(language)); + } + Log.d(TAG, "getAppSupportedLocales from asset. Size: " + + appSupportedLocales.size()); + } + return appSupportedLocales; + } + + private static LocaleList getPackageLocales(Context context, String packageName) { + try { + LocaleConfig localeConfig = + new LocaleConfig(context.createPackageContext(packageName, 0)); + if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) { + return localeConfig.getSupportedLocales(); + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Can not found the package name : " + packageName + " / " + e); + } + return null; + } + + private static String[] getAssetLocales(Context context, String packageName) { + try { + PackageManager packageManager = context.getPackageManager(); + String[] locales = packageManager.getResourcesForApplication( + packageManager.getPackageInfo(packageName, PackageManager.MATCH_ALL) + .applicationInfo).getAssets().getNonSystemLocales(); + if (locales == null) { + Log.i(TAG, "[" + packageName + "] locales are null."); + return new String[0]; + } else if (locales.length <= 0) { + Log.i(TAG, "[" + packageName + "] locales length is 0."); + return new String[0]; + } + return locales; + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Can not found the package name : " + packageName + " / " + e); + } + return new String[0]; + } + +} diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java index 707286e0fc4c..57bd3f945358 100644 --- a/core/java/com/android/internal/app/LocaleHelper.java +++ b/core/java/com/android/internal/app/LocaleHelper.java @@ -234,7 +234,11 @@ public class LocaleHelper { public int compare(LocaleStore.LocaleInfo lhs, LocaleStore.LocaleInfo rhs) { // We don't care about the various suggestion types, just "suggested" (!= 0) // and "all others" (== 0) - if (lhs.isSuggested() == rhs.isSuggested()) { + if (lhs.isAppCurrentLocale() || rhs.isAppCurrentLocale()) { + return lhs.isAppCurrentLocale() ? -1 : 1; + } else if (lhs.isSystemLocale() || rhs.isSystemLocale()) { + return lhs.isSystemLocale() ? -1 : 1; + } else if (lhs.isSuggested() == rhs.isSuggested()) { // They are in the same "bucket" (suggested / others), so we compare the text return mCollator.compare( removePrefixForCompare(lhs.getLocale(), lhs.getLabel(mCountryMode)), diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index b4ae56f23443..e5b33f9abd83 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -23,6 +23,7 @@ import android.content.Context; import android.os.Bundle; import android.os.LocaleList; import android.text.TextUtils; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -32,6 +33,7 @@ import android.widget.SearchView; import com.android.internal.R; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Locale; @@ -45,6 +47,7 @@ import java.util.Set; * default locale.</p> */ public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener { + private static final String TAG = LocalePickerWithRegion.class.getSimpleName();; private static final String PARENT_FRAGMENT_NAME = "localeListEditor"; private SuggestedLocaleAdapter mAdapter; @@ -57,6 +60,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O private boolean mPreviousSearchHadFocus = false; private int mFirstVisiblePosition = 0; private int mTopDistance = 0; + private String mAppPackageName; /** * Other classes can register to be notified when a locale was selected. @@ -73,17 +77,25 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O private static LocalePickerWithRegion createCountryPicker(Context context, LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, - boolean translatedOnly) { + boolean translatedOnly, String appPackageName) { LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); boolean shouldShowTheList = localePicker.setListener(context, listener, parent, - translatedOnly); + translatedOnly, appPackageName); return shouldShowTheList ? localePicker : null; } public static LocalePickerWithRegion createLanguagePicker(Context context, LocaleSelectedListener listener, boolean translatedOnly) { LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); - localePicker.setListener(context, listener, /* parent */ null, translatedOnly); + localePicker.setListener(context, listener, /* parent */ null, translatedOnly, null); + return localePicker; + } + + public static LocalePickerWithRegion createLanguagePicker(Context context, + LocaleSelectedListener listener, boolean translatedOnly, String appPackageName) { + LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); + localePicker.setListener( + context, listener, /* parent */ null, translatedOnly, appPackageName); return localePicker; } @@ -101,20 +113,32 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O * "pretending" it was selected, and return false.</p> */ private boolean setListener(Context context, LocaleSelectedListener listener, - LocaleStore.LocaleInfo parent, boolean translatedOnly) { + LocaleStore.LocaleInfo parent, boolean translatedOnly, String appPackageName) { this.mParentLocale = parent; this.mListener = listener; this.mTranslatedOnly = translatedOnly; + this.mAppPackageName = appPackageName; setRetainInstance(true); final HashSet<String> langTagsToIgnore = new HashSet<>(); - if (!translatedOnly) { + LocaleStore.LocaleInfo appCurrentLocale = + LocaleStore.getAppCurrentLocaleInfo(context, appPackageName); + boolean isForCountryMode = parent != null; + + if (!TextUtils.isEmpty(appPackageName) && !isForCountryMode) { + if (appCurrentLocale != null) { + Log.d(TAG, "appCurrentLocale: " + appCurrentLocale.getLocale().toLanguageTag()); + langTagsToIgnore.add(appCurrentLocale.getLocale().toLanguageTag()); + } else { + Log.d(TAG, "appCurrentLocale is null"); + } + } else if (!translatedOnly) { final LocaleList userLocales = LocalePicker.getLocales(); final String[] langTags = userLocales.toLanguageTags().split(","); Collections.addAll(langTagsToIgnore, langTags); } - if (parent != null) { + if (isForCountryMode) { mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore, parent, translatedOnly); if (mLocaleList.size() <= 1) { @@ -127,10 +151,39 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore, null /* no parent */, translatedOnly); } + Log.d(TAG, "mLocaleList size: " + mLocaleList.size()); + + // Adding current locale and system default option into suggestion list + if(!TextUtils.isEmpty(appPackageName)) { + if (appCurrentLocale != null && !isForCountryMode) { + mLocaleList.add(appCurrentLocale); + } + filterTheLanguagesNotSupportedInApp(context, appPackageName); + if (!isForCountryMode) { + mLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo()); + } + } return true; } + private void filterTheLanguagesNotSupportedInApp(Context context, String appPackageName) { + ArrayList<Locale> supportedLocales = + AppLocaleStore.getAppSupportedLocales(context, appPackageName); + + Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>(); + for(LocaleStore.LocaleInfo li: mLocaleList) { + for(Locale l: supportedLocales) { + if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) { + filteredList.add(li); + } + } + } + Log.d(TAG, "mLocaleList after app-supported filter: " + filteredList.size()); + + mLocaleList = filteredList; + } + private void returnToParentFrame() { getFragmentManager().popBackStack(PARENT_FRAGMENT_NAME, FragmentManager.POP_BACK_STACK_INCLUSIVE); @@ -151,7 +204,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O final boolean countryMode = mParentLocale != null; final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault(); - mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode); + mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, mAppPackageName); final LocaleHelper.LocaleInfoComparator comp = new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode); mAdapter.sort(comp); @@ -212,17 +265,22 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O @Override public void onListItemClick(ListView l, View v, int position, long id) { + SuggestedLocaleAdapter adapter = (SuggestedLocaleAdapter) getListAdapter(); final LocaleStore.LocaleInfo locale = - (LocaleStore.LocaleInfo) getListAdapter().getItem(position); + (LocaleStore.LocaleInfo) adapter.getItem(position); + // Special case for resetting the app locale to equal the system locale. + boolean isSystemLocale = locale.isSystemLocale(); + boolean isRegionLocale = locale.getParent() != null; - if (locale.getParent() != null) { + if (isSystemLocale || isRegionLocale) { if (mListener != null) { mListener.onLocaleSelected(locale); } returnToParentFrame(); } else { LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker( - getContext(), mListener, locale, mTranslatedOnly /* translate only */); + getContext(), mListener, locale, mTranslatedOnly /* translate only */, + adapter.getAppPackageName()); 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 1c5ca593ebba..cea8eaa3ee7f 100644 --- a/core/java/com/android/internal/app/LocaleStore.java +++ b/core/java/com/android/internal/app/LocaleStore.java @@ -16,11 +16,13 @@ package com.android.internal.app; +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.util.Log; import java.io.Serializable; import java.util.HashMap; @@ -31,12 +33,17 @@ import java.util.Set; public class LocaleStore { 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 { private static final int SUGGESTION_TYPE_NONE = 0; private static final int SUGGESTION_TYPE_SIM = 1 << 0; private static final int SUGGESTION_TYPE_CFG = 1 << 1; + // Only for per-app language picker + private static final int SUGGESTION_TYPE_CURRENT = 1 << 2; + // Only for per-app language picker + private static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3; private final Locale mLocale; private final Locale mParent; @@ -189,6 +196,14 @@ public class LocaleStore { 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; + } } private static Set<String> getSimCountries(Context context) { @@ -239,6 +254,40 @@ public class LocaleStore { } } + public static LocaleInfo getAppCurrentLocaleInfo(Context context, String appPackageName) { + 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 localeInfo = new LocaleInfo(locale); + localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT; + localeInfo.mIsTranslated = true; + return localeInfo; + } + } catch (IllegalArgumentException e) { + Log.d(TAG, "IllegalArgumentException ", e); + } + return null; + } + + /** + * 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() { + LocaleInfo systemDefaultInfo = new LocaleInfo(""); + systemDefaultInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SYSTEM_LANGUAGE; + 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). diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index 46f47a31441c..10855f488940 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.text.TextUtils; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -64,10 +65,14 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { private Locale mDisplayLocale = null; // used to potentially cache a modified Context that uses mDisplayLocale private Context mContextOverride = null; + private String mAppPackageName; - public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) { + public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode, + String appPackageName) { mCountryMode = countryMode; mLocaleOptions = new ArrayList<>(localeOptions.size()); + mAppPackageName = appPackageName; + for (LocaleStore.LocaleInfo li : localeOptions) { if (li.isSuggested()) { mSuggestionCount++; @@ -195,11 +200,20 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { TextView text = (TextView) convertView.findViewById(R.id.locale); LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position); - text.setText(item.getLabel(mCountryMode)); - text.setTextLocale(item.getLocale()); - text.setContentDescription(item.getContentDescription(mCountryMode)); + Locale layoutLocale; + if (item.isSystemLocale()) { + // show system default option + text.setText(R.string.system_locale_title); + text.setTextLocale(Locale.getDefault()); + layoutLocale = Locale.getDefault(); + } else { + text.setText(item.getLabel(mCountryMode)); + text.setTextLocale(item.getLocale()); + text.setContentDescription(item.getContentDescription(mCountryMode)); + layoutLocale = item.getParent(); + } if (mCountryMode) { - int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent()); + int layoutDir = TextUtils.getLayoutDirectionFromLocale(layoutLocale); //noinspection ResourceType convertView.setLayoutDirection(layoutDir); text.setTextDirection(layoutDir == View.LAYOUT_DIRECTION_RTL @@ -316,4 +330,8 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { public Filter getFilter() { return new FilterByNativeAndUiNames(); } + + public String getAppPackageName() { + return mAppPackageName; + } } |
