summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorCalvin Pan <calvinpan@google.com>2022-03-15 06:23:08 +0800
committerCalvin Pan <calvinpan@google.com>2022-03-21 14:48:09 +0800
commit780ff7127a5d7b47c3346a33dc601a456b2efccb (patch)
treef75941c7c6c3eecc35fb83e2aee3cc8f2cab7fe3 /core/java
parentf77ad16906ab3d819096a63ada08fcd793ec8cc1 (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')
-rw-r--r--core/java/com/android/internal/app/AppLocaleStore.java85
-rw-r--r--core/java/com/android/internal/app/LocaleHelper.java6
-rw-r--r--core/java/com/android/internal/app/LocalePickerWithRegion.java78
-rw-r--r--core/java/com/android/internal/app/LocaleStore.java49
-rw-r--r--core/java/com/android/internal/app/SuggestedLocaleAdapter.java28
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;
+ }
}