Loading AndroidManifest.xml +1 −3 Original line number Diff line number Diff line Loading @@ -864,7 +864,7 @@ </activity> <activity android:name=".Settings$AppLocalePickerActivity" android:name=".localepicker.AppLocalePickerActivity" android:label="@string/app_locale_picker_title" android:exported="true" > <intent-filter> Loading @@ -872,8 +872,6 @@ <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="package" /> </intent-filter> <meta-data android:name="com.android.settings.FRAGMENT_CLASS" android:value="com.android.settings.applications.appinfo.AppLocaleDetails" /> </activity> <activity Loading res/layout/app_locale_picker.xml 0 → 100644 +33 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- 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. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/app_locale_detail_container" android:orientation="vertical"> <FrameLayout android:id="@+id/app_locale_detail" android:layout_width="match_parent" android:layout_height="wrap_content"/> <FrameLayout android:id="@+id/app_locale_picker_with_region" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> No newline at end of file res/xml/app_locale_details.xml +1 −15 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/app_locale_picker_title"> <com.android.settingslib.widget.LayoutPreference android:key="app_locale_description" android:layout="@layout/app_locale_details_description" Loading @@ -26,19 +27,4 @@ settings:allowDividerBelow="true" settings:searchable="false"/> <PreferenceCategory android:key="category_key_suggested_languages" android:title="@string/suggested_app_locales_title" > <com.android.settingslib.widget.RadioButtonPreference android:key="system_default_locale" android:title="@string/preference_of_system_locale_title" android:order="-10000"/> </PreferenceCategory> <PreferenceCategory android:key="category_key_all_languages" android:title="@string/all_supported_app_locales_title" /> </PreferenceScreen> src/com/android/settings/Settings.java +0 −2 Original line number Diff line number Diff line Loading @@ -111,8 +111,6 @@ public class Settings extends SettingsActivity { public static class InputMethodAndSubtypeEnablerActivity extends SettingsActivity { /* empty */ } public static class SpellCheckersSettingsActivity extends SettingsActivity { /* empty */ } public static class LocalePickerActivity extends SettingsActivity { /* empty */ } /** Activity for the App locale details settings. */ public static class AppLocalePickerActivity extends SettingsActivity { /* empty */ } public static class LanguageAndInputSettingsActivity extends SettingsActivity { /* empty */ } public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ } public static class DarkThemeSettingsActivity extends SettingsActivity { /* empty */ } Loading src/com/android/settings/applications/appinfo/AppLocaleDetails.java +99 −298 Original line number Diff line number Diff line Loading @@ -22,73 +22,64 @@ import android.app.LocaleConfig; import android.app.LocaleManager; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Bundle; import android.os.LocaleList; import android.telephony.TelephonyManager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.RadioButtonPreference; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Locale; /** * A fragment to show the current app locale info and help the user to select the expected locale. * TODO(b/223503670): Implement the unittest. * A fragment to show the current app locale info. */ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreference.OnClickListener { public class AppLocaleDetails extends SettingsPreferenceFragment { private static final String TAG = "AppLocaleDetails"; private static final String CATEGORY_KEY_SUGGESTED_LANGUAGES = "category_key_suggested_languages"; private static final String CATEGORY_KEY_ALL_LANGUAGES = "category_key_all_languages"; private static final String KEY_APP_DESCRIPTION = "app_locale_description"; @VisibleForTesting static final String KEY_SYSTEM_DEFAULT_LOCALE = "system_default_locale"; private boolean mCreated = false; @VisibleForTesting AppLocaleDetailsHelper mAppLocaleDetailsHelper; private PreferenceGroup mGroupOfSuggestedLocales; private PreferenceGroup mGroupOfSupportedLocales; private String mPackageName; private LayoutPreference mPrefOfDescription; private RadioButtonPreference mDefaultPreference; /** * Create a instance of AppLocaleDetails. * @param packageName Indicates which application need to show the locale picker. */ public static AppLocaleDetails newInstance(String packageName) { AppLocaleDetails appLocaleDetails = new AppLocaleDetails(); Bundle bundle = new Bundle(); bundle.putString(AppInfoBase.ARG_PACKAGE_NAME, packageName); appLocaleDetails.setArguments(bundle); return appLocaleDetails; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.app_locale_details); mAppLocaleDetailsHelper = new AppLocaleDetailsHelper(getContext(), mPackageName); mGroupOfSuggestedLocales = getPreferenceScreen().findPreference(CATEGORY_KEY_SUGGESTED_LANGUAGES); mGroupOfSupportedLocales = getPreferenceScreen().findPreference(CATEGORY_KEY_ALL_LANGUAGES); mPrefOfDescription = getPreferenceScreen().findPreference(KEY_APP_DESCRIPTION); Bundle bundle = getArguments(); mPackageName = bundle.getString(AppInfoBase.ARG_PACKAGE_NAME, ""); mDefaultPreference = (RadioButtonPreference) getPreferenceScreen() .findPreference(KEY_SYSTEM_DEFAULT_LOCALE); mDefaultPreference.setOnClickListener(this); if (mPackageName.isEmpty()) { Log.d(TAG, "No package name."); finish(); } } // Override here so we don't have an empty screen Loading @@ -96,8 +87,8 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // if we don't have a package info, show a page saying this is unsupported if (mPackageInfo == null) { // if we don't have a package, show a page saying this is unsupported if (mPackageName.isEmpty()) { return inflater.inflate(R.layout.manage_applications_apps_unsupported, null); } return super.onCreateView(inflater, container, savedInstanceState); Loading @@ -105,46 +96,19 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen @Override public void onResume() { // Update Locales first, before refresh ui. mAppLocaleDetailsHelper.handleAllLocalesData(); super.onResume(); mDefaultPreference.setSummary(Locale.getDefault().getDisplayName(Locale.getDefault())); } @Override protected boolean refreshUi() { refreshUiInternal(); return true; super.onResume(); } @VisibleForTesting void refreshUiInternal() { if (mAppLocaleDetailsHelper.getSupportedLocales().isEmpty()) { private void refreshUiInternal() { if (!hasAppSupportedLocales()) { Log.d(TAG, "No supported language."); mGroupOfSuggestedLocales.setVisible(false); mGroupOfSupportedLocales.setVisible(false); mPrefOfDescription.setVisible(true); TextView description = (TextView) mPrefOfDescription.findViewById(R.id.description); description.setText(getContext().getString(R.string.no_multiple_language_supported, Locale.getDefault().getDisplayName(Locale.getDefault()))); return; } resetLocalePreferences(); Locale appLocale = AppLocaleDetailsHelper.getAppDefaultLocale(getContext(), mPackageName); // Sets up default locale preference. mGroupOfSuggestedLocales.addPreference(mDefaultPreference); mDefaultPreference.setChecked(appLocale == null); // Sets up suggested locales of per app. setLanguagesPreference(mGroupOfSuggestedLocales, mAppLocaleDetailsHelper.getSuggestedLocales(), appLocale); // Sets up supported locales of per app. setLanguagesPreference(mGroupOfSupportedLocales, mAppLocaleDetailsHelper.getSupportedLocales(), appLocale); } private void resetLocalePreferences() { mGroupOfSuggestedLocales.removeAll(); mGroupOfSupportedLocales.removeAll(); } @Override Loading @@ -152,22 +116,6 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen return SettingsEnums.APPS_LOCALE_LIST; } @Override protected AlertDialog createDialog(int id, int errorCode) { return null; } @Override public void onRadioButtonClicked(RadioButtonPreference pref) { String key = pref.getKey(); if (KEY_SYSTEM_DEFAULT_LOCALE.equals(key)) { mAppLocaleDetailsHelper.setAppDefaultLocale(LocaleList.forLanguageTags("")); } else { mAppLocaleDetailsHelper.setAppDefaultLocale(key); } refreshUi(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Loading @@ -176,183 +124,77 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen return; } mCreated = true; if (mPackageInfo == null) { if (mPackageName == null) { return; } // Creates a head icon button of app on this page. final Activity activity = getActivity(); ApplicationInfo applicationInfo = getApplicationInfo(mPackageName, getContext().getUserId()); final Preference pref = EntityHeaderController .newInstance(activity, this, null /* header */) .setRecyclerView(getListView(), getSettingsLifecycle()) .setIcon(Utils.getBadgedIcon(getContext(), mPackageInfo.applicationInfo)) .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm)) .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo)) .setIcon(Utils.getBadgedIcon(getContext(), applicationInfo)) .setLabel(applicationInfo.loadLabel(getContext().getPackageManager())) .setIsInstantApp(AppUtils.isInstant(applicationInfo)) .setPackageName(mPackageName) .setUid(mPackageInfo.applicationInfo.uid) .setUid(applicationInfo.uid) .setHasAppInfoLink(true) .setButtonActions(ActionType.ACTION_NONE, ActionType.ACTION_NONE) .done(activity, getPrefContext()); getPreferenceScreen().addPreference(pref); } /** * TODO (b209962418) Do a performance test to low end device. * @return Return the summary to show the current app's language. */ public static CharSequence getSummary(Context context, String packageName) { Locale appLocale = AppLocaleDetailsHelper.getAppDefaultLocale(context, packageName); if (appLocale == null) { Locale systemLocale = Locale.getDefault(); return context.getString(R.string.preference_of_system_locale_summary, systemLocale.getDisplayName(systemLocale)); } else { return appLocale.getDisplayName(appLocale); } } private void setLanguagesPreference(PreferenceGroup group, Collection<Locale> locales, Locale appLocale) { if (locales == null) { return; } for (Locale locale : locales) { if (locale == null) { continue; } RadioButtonPreference pref = new RadioButtonPreference(getContext()); pref.setTitle(locale.getDisplayName(locale)); pref.setKey(locale.toLanguageTag()); // Will never be checked if appLocale is null // aka if there is no per-app locale pref.setChecked(locale.equals(appLocale)); pref.setOnClickListener(this); group.addPreference(pref); } } @VisibleForTesting static class AppLocaleDetailsHelper { private String mPackageName; private Context mContext; private TelephonyManager mTelephonyManager; private LocaleManager mLocaleManager; private Collection<Locale> mProcessedSuggestedLocales = new ArrayList<>(); private Collection<Locale> mProcessedSupportedLocales = new ArrayList<>(); private Collection<Locale> mAppSupportedLocales = new ArrayList<>(); AppLocaleDetailsHelper(Context context, String packageName) { mContext = context; mPackageName = packageName; mTelephonyManager = context.getSystemService(TelephonyManager.class); mLocaleManager = context.getSystemService(LocaleManager.class); mAppSupportedLocales = getAppSupportedLocales(); } /** Handle suggested and supported locales for UI display. */ public void handleAllLocalesData() { clearLocalesData(); handleSuggestedLocales(); handleSupportedLocales(); } /** Gets suggested locales in the app. */ public Collection<Locale> getSuggestedLocales() { return mProcessedSuggestedLocales; } /** Gets supported locales in the app. */ public Collection<Locale> getSupportedLocales() { return mProcessedSupportedLocales; } @VisibleForTesting void handleSuggestedLocales() { Locale appLocale = getAppDefaultLocale(mContext, mPackageName); // 1st locale in suggested languages group. for (Locale supportedlocale : mAppSupportedLocales) { if (compareLocale(supportedlocale, appLocale)) { mProcessedSuggestedLocales.add(appLocale); break; } } // 2nd and 3rd locale in suggested languages group. String simCountry = mTelephonyManager.getSimCountryIso().toUpperCase(Locale.US); String networkCountry = mTelephonyManager.getNetworkCountryIso().toUpperCase(Locale.US); mAppSupportedLocales.forEach(supportedlocale -> { String localeCountry = supportedlocale.getCountry().toUpperCase(Locale.US); if (!compareLocale(supportedlocale, appLocale) && isCountrySuggestedLocale(localeCountry, simCountry, networkCountry)) { mProcessedSuggestedLocales.add(supportedlocale); } }); // Other locales in suggested languages group. Collection<Locale> supportedSystemLocales = new HashSet<>(); getCurrentSystemLocales().forEach(systemLocale -> { mAppSupportedLocales.forEach(supportedLocale -> { if (compareLocale(systemLocale, supportedLocale)) { supportedSystemLocales.add(supportedLocale); private ApplicationInfo getApplicationInfo(String packageName, int userId) { ApplicationInfo applicationInfo; try { applicationInfo = getContext().getPackageManager() .getApplicationInfoAsUser(packageName, /* flags= */ 0, userId); return applicationInfo; } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Application info not found for: " + packageName); return null; } }); }); supportedSystemLocales.removeAll(mProcessedSuggestedLocales); mProcessedSuggestedLocales.addAll(supportedSystemLocales); } @VisibleForTesting static boolean compareLocale(Locale source, Locale target) { if (source == null && target == null) { return true; } else if (source != null && target != null) { return LocaleList.matchesLanguageAndScript(source, target); } else { return false; } private boolean hasAppSupportedLocales() { LocaleList localeList = getPackageLocales(); return (localeList != null && localeList.size() > 0) || getAssetLocales().length > 0; } private static boolean isCountrySuggestedLocale(String localeCountry, String simCountry, String networkCountry) { return ((!simCountry.isEmpty() && simCountry.equals(localeCountry)) || (!networkCountry.isEmpty() && networkCountry.equals(localeCountry))); private String[] getAssetLocales() { try { PackageManager packageManager = getContext().getPackageManager(); String[] locales = packageManager.getResourcesForApplication( packageManager.getPackageInfo(mPackageName, PackageManager.MATCH_ALL) .applicationInfo).getAssets().getNonSystemLocales(); if (locales == null) { Log.i(TAG, "[" + mPackageName + "] locales are null."); } @VisibleForTesting void handleSupportedLocales() { mProcessedSupportedLocales.addAll(mAppSupportedLocales); if (mProcessedSuggestedLocales != null || !mProcessedSuggestedLocales.isEmpty()) { mProcessedSuggestedLocales.retainAll(mProcessedSupportedLocales); mProcessedSupportedLocales.removeAll(mProcessedSuggestedLocales); if (locales.length <= 0) { Log.i(TAG, "[" + mPackageName + "] locales length is 0."); return new String[0]; } String locale = locales[0]; Log.i(TAG, "First asset locale - [" + mPackageName + "] " + locale); return locales; } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); } private void clearLocalesData() { mProcessedSuggestedLocales.clear(); mProcessedSupportedLocales.clear(); return new String[0]; } private Collection<Locale> getAppSupportedLocales() { Collection<Locale> appSupportedLocales = new ArrayList<>(); LocaleList localeList = getPackageLocales(); if (localeList != null && localeList.size() > 0) { for (int i = 0; i < localeList.size(); i++) { appSupportedLocales.add(localeList.get(i)); } } else { String[] languages = getAssetLocales(); for (String language : languages) { appSupportedLocales.add(Locale.forLanguageTag(language)); private LocaleList getPackageLocales() { try { LocaleConfig localeConfig = new LocaleConfig(getContext().createPackageContext(mPackageName, 0)); if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) { return localeConfig.getSupportedLocales(); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); } return appSupportedLocales; return null; } /** Gets per app's default locale */ Loading @@ -368,59 +210,18 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen return null; } /** Sets per app's default language to system. */ public void setAppDefaultLocale(String languageTag) { if (languageTag.isEmpty()) { Log.w(TAG, "[setAppDefaultLocale] No language tag."); return; } setAppDefaultLocale(LocaleList.forLanguageTags(languageTag)); } /** Sets per app's default language to system. */ public void setAppDefaultLocale(LocaleList localeList) { if (mLocaleManager == null) { Log.w(TAG, "LocaleManager is null, and cannot set the app locale up."); return; } mLocaleManager.setApplicationLocales(mPackageName, localeList); } @VisibleForTesting Collection<Locale> getCurrentSystemLocales() { LocaleList localeList = Resources.getSystem().getConfiguration().getLocales(); Collection<Locale> systemLocales = new ArrayList<>(); for (int i = 0; i < localeList.size(); i++) { systemLocales.add(localeList.get(i)); } return systemLocales; } @VisibleForTesting String[] getAssetLocales() { try { PackageManager packageManager = mContext.getPackageManager(); return packageManager.getResourcesForApplication( packageManager.getPackageInfo(mPackageName, PackageManager.MATCH_ALL) .applicationInfo).getAssets().getNonSystemLocales(); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); } return new String[0]; } @VisibleForTesting LocaleList getPackageLocales() { try { LocaleConfig localeConfig = new LocaleConfig(mContext.createPackageContext(mPackageName, 0)); if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) { return localeConfig.getSupportedLocales(); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); } return null; /** * TODO (b209962418) Do a performance test to low end device. * @return Return the summary to show the current app's language. */ public static CharSequence getSummary(Context context, String packageName) { Locale appLocale = getAppDefaultLocale(context, packageName); if (appLocale == null) { Locale systemLocale = Locale.getDefault(); return context.getString(R.string.preference_of_system_locale_summary, systemLocale.getDisplayName(systemLocale)); } else { return appLocale.getDisplayName(appLocale); } } } Loading
AndroidManifest.xml +1 −3 Original line number Diff line number Diff line Loading @@ -864,7 +864,7 @@ </activity> <activity android:name=".Settings$AppLocalePickerActivity" android:name=".localepicker.AppLocalePickerActivity" android:label="@string/app_locale_picker_title" android:exported="true" > <intent-filter> Loading @@ -872,8 +872,6 @@ <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="package" /> </intent-filter> <meta-data android:name="com.android.settings.FRAGMENT_CLASS" android:value="com.android.settings.applications.appinfo.AppLocaleDetails" /> </activity> <activity Loading
res/layout/app_locale_picker.xml 0 → 100644 +33 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- 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. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/app_locale_detail_container" android:orientation="vertical"> <FrameLayout android:id="@+id/app_locale_detail" android:layout_width="match_parent" android:layout_height="wrap_content"/> <FrameLayout android:id="@+id/app_locale_picker_with_region" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> No newline at end of file
res/xml/app_locale_details.xml +1 −15 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/app_locale_picker_title"> <com.android.settingslib.widget.LayoutPreference android:key="app_locale_description" android:layout="@layout/app_locale_details_description" Loading @@ -26,19 +27,4 @@ settings:allowDividerBelow="true" settings:searchable="false"/> <PreferenceCategory android:key="category_key_suggested_languages" android:title="@string/suggested_app_locales_title" > <com.android.settingslib.widget.RadioButtonPreference android:key="system_default_locale" android:title="@string/preference_of_system_locale_title" android:order="-10000"/> </PreferenceCategory> <PreferenceCategory android:key="category_key_all_languages" android:title="@string/all_supported_app_locales_title" /> </PreferenceScreen>
src/com/android/settings/Settings.java +0 −2 Original line number Diff line number Diff line Loading @@ -111,8 +111,6 @@ public class Settings extends SettingsActivity { public static class InputMethodAndSubtypeEnablerActivity extends SettingsActivity { /* empty */ } public static class SpellCheckersSettingsActivity extends SettingsActivity { /* empty */ } public static class LocalePickerActivity extends SettingsActivity { /* empty */ } /** Activity for the App locale details settings. */ public static class AppLocalePickerActivity extends SettingsActivity { /* empty */ } public static class LanguageAndInputSettingsActivity extends SettingsActivity { /* empty */ } public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ } public static class DarkThemeSettingsActivity extends SettingsActivity { /* empty */ } Loading
src/com/android/settings/applications/appinfo/AppLocaleDetails.java +99 −298 Original line number Diff line number Diff line Loading @@ -22,73 +22,64 @@ import android.app.LocaleConfig; import android.app.LocaleManager; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Bundle; import android.os.LocaleList; import android.telephony.TelephonyManager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.RadioButtonPreference; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Locale; /** * A fragment to show the current app locale info and help the user to select the expected locale. * TODO(b/223503670): Implement the unittest. * A fragment to show the current app locale info. */ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreference.OnClickListener { public class AppLocaleDetails extends SettingsPreferenceFragment { private static final String TAG = "AppLocaleDetails"; private static final String CATEGORY_KEY_SUGGESTED_LANGUAGES = "category_key_suggested_languages"; private static final String CATEGORY_KEY_ALL_LANGUAGES = "category_key_all_languages"; private static final String KEY_APP_DESCRIPTION = "app_locale_description"; @VisibleForTesting static final String KEY_SYSTEM_DEFAULT_LOCALE = "system_default_locale"; private boolean mCreated = false; @VisibleForTesting AppLocaleDetailsHelper mAppLocaleDetailsHelper; private PreferenceGroup mGroupOfSuggestedLocales; private PreferenceGroup mGroupOfSupportedLocales; private String mPackageName; private LayoutPreference mPrefOfDescription; private RadioButtonPreference mDefaultPreference; /** * Create a instance of AppLocaleDetails. * @param packageName Indicates which application need to show the locale picker. */ public static AppLocaleDetails newInstance(String packageName) { AppLocaleDetails appLocaleDetails = new AppLocaleDetails(); Bundle bundle = new Bundle(); bundle.putString(AppInfoBase.ARG_PACKAGE_NAME, packageName); appLocaleDetails.setArguments(bundle); return appLocaleDetails; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.app_locale_details); mAppLocaleDetailsHelper = new AppLocaleDetailsHelper(getContext(), mPackageName); mGroupOfSuggestedLocales = getPreferenceScreen().findPreference(CATEGORY_KEY_SUGGESTED_LANGUAGES); mGroupOfSupportedLocales = getPreferenceScreen().findPreference(CATEGORY_KEY_ALL_LANGUAGES); mPrefOfDescription = getPreferenceScreen().findPreference(KEY_APP_DESCRIPTION); Bundle bundle = getArguments(); mPackageName = bundle.getString(AppInfoBase.ARG_PACKAGE_NAME, ""); mDefaultPreference = (RadioButtonPreference) getPreferenceScreen() .findPreference(KEY_SYSTEM_DEFAULT_LOCALE); mDefaultPreference.setOnClickListener(this); if (mPackageName.isEmpty()) { Log.d(TAG, "No package name."); finish(); } } // Override here so we don't have an empty screen Loading @@ -96,8 +87,8 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // if we don't have a package info, show a page saying this is unsupported if (mPackageInfo == null) { // if we don't have a package, show a page saying this is unsupported if (mPackageName.isEmpty()) { return inflater.inflate(R.layout.manage_applications_apps_unsupported, null); } return super.onCreateView(inflater, container, savedInstanceState); Loading @@ -105,46 +96,19 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen @Override public void onResume() { // Update Locales first, before refresh ui. mAppLocaleDetailsHelper.handleAllLocalesData(); super.onResume(); mDefaultPreference.setSummary(Locale.getDefault().getDisplayName(Locale.getDefault())); } @Override protected boolean refreshUi() { refreshUiInternal(); return true; super.onResume(); } @VisibleForTesting void refreshUiInternal() { if (mAppLocaleDetailsHelper.getSupportedLocales().isEmpty()) { private void refreshUiInternal() { if (!hasAppSupportedLocales()) { Log.d(TAG, "No supported language."); mGroupOfSuggestedLocales.setVisible(false); mGroupOfSupportedLocales.setVisible(false); mPrefOfDescription.setVisible(true); TextView description = (TextView) mPrefOfDescription.findViewById(R.id.description); description.setText(getContext().getString(R.string.no_multiple_language_supported, Locale.getDefault().getDisplayName(Locale.getDefault()))); return; } resetLocalePreferences(); Locale appLocale = AppLocaleDetailsHelper.getAppDefaultLocale(getContext(), mPackageName); // Sets up default locale preference. mGroupOfSuggestedLocales.addPreference(mDefaultPreference); mDefaultPreference.setChecked(appLocale == null); // Sets up suggested locales of per app. setLanguagesPreference(mGroupOfSuggestedLocales, mAppLocaleDetailsHelper.getSuggestedLocales(), appLocale); // Sets up supported locales of per app. setLanguagesPreference(mGroupOfSupportedLocales, mAppLocaleDetailsHelper.getSupportedLocales(), appLocale); } private void resetLocalePreferences() { mGroupOfSuggestedLocales.removeAll(); mGroupOfSupportedLocales.removeAll(); } @Override Loading @@ -152,22 +116,6 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen return SettingsEnums.APPS_LOCALE_LIST; } @Override protected AlertDialog createDialog(int id, int errorCode) { return null; } @Override public void onRadioButtonClicked(RadioButtonPreference pref) { String key = pref.getKey(); if (KEY_SYSTEM_DEFAULT_LOCALE.equals(key)) { mAppLocaleDetailsHelper.setAppDefaultLocale(LocaleList.forLanguageTags("")); } else { mAppLocaleDetailsHelper.setAppDefaultLocale(key); } refreshUi(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Loading @@ -176,183 +124,77 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen return; } mCreated = true; if (mPackageInfo == null) { if (mPackageName == null) { return; } // Creates a head icon button of app on this page. final Activity activity = getActivity(); ApplicationInfo applicationInfo = getApplicationInfo(mPackageName, getContext().getUserId()); final Preference pref = EntityHeaderController .newInstance(activity, this, null /* header */) .setRecyclerView(getListView(), getSettingsLifecycle()) .setIcon(Utils.getBadgedIcon(getContext(), mPackageInfo.applicationInfo)) .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm)) .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo)) .setIcon(Utils.getBadgedIcon(getContext(), applicationInfo)) .setLabel(applicationInfo.loadLabel(getContext().getPackageManager())) .setIsInstantApp(AppUtils.isInstant(applicationInfo)) .setPackageName(mPackageName) .setUid(mPackageInfo.applicationInfo.uid) .setUid(applicationInfo.uid) .setHasAppInfoLink(true) .setButtonActions(ActionType.ACTION_NONE, ActionType.ACTION_NONE) .done(activity, getPrefContext()); getPreferenceScreen().addPreference(pref); } /** * TODO (b209962418) Do a performance test to low end device. * @return Return the summary to show the current app's language. */ public static CharSequence getSummary(Context context, String packageName) { Locale appLocale = AppLocaleDetailsHelper.getAppDefaultLocale(context, packageName); if (appLocale == null) { Locale systemLocale = Locale.getDefault(); return context.getString(R.string.preference_of_system_locale_summary, systemLocale.getDisplayName(systemLocale)); } else { return appLocale.getDisplayName(appLocale); } } private void setLanguagesPreference(PreferenceGroup group, Collection<Locale> locales, Locale appLocale) { if (locales == null) { return; } for (Locale locale : locales) { if (locale == null) { continue; } RadioButtonPreference pref = new RadioButtonPreference(getContext()); pref.setTitle(locale.getDisplayName(locale)); pref.setKey(locale.toLanguageTag()); // Will never be checked if appLocale is null // aka if there is no per-app locale pref.setChecked(locale.equals(appLocale)); pref.setOnClickListener(this); group.addPreference(pref); } } @VisibleForTesting static class AppLocaleDetailsHelper { private String mPackageName; private Context mContext; private TelephonyManager mTelephonyManager; private LocaleManager mLocaleManager; private Collection<Locale> mProcessedSuggestedLocales = new ArrayList<>(); private Collection<Locale> mProcessedSupportedLocales = new ArrayList<>(); private Collection<Locale> mAppSupportedLocales = new ArrayList<>(); AppLocaleDetailsHelper(Context context, String packageName) { mContext = context; mPackageName = packageName; mTelephonyManager = context.getSystemService(TelephonyManager.class); mLocaleManager = context.getSystemService(LocaleManager.class); mAppSupportedLocales = getAppSupportedLocales(); } /** Handle suggested and supported locales for UI display. */ public void handleAllLocalesData() { clearLocalesData(); handleSuggestedLocales(); handleSupportedLocales(); } /** Gets suggested locales in the app. */ public Collection<Locale> getSuggestedLocales() { return mProcessedSuggestedLocales; } /** Gets supported locales in the app. */ public Collection<Locale> getSupportedLocales() { return mProcessedSupportedLocales; } @VisibleForTesting void handleSuggestedLocales() { Locale appLocale = getAppDefaultLocale(mContext, mPackageName); // 1st locale in suggested languages group. for (Locale supportedlocale : mAppSupportedLocales) { if (compareLocale(supportedlocale, appLocale)) { mProcessedSuggestedLocales.add(appLocale); break; } } // 2nd and 3rd locale in suggested languages group. String simCountry = mTelephonyManager.getSimCountryIso().toUpperCase(Locale.US); String networkCountry = mTelephonyManager.getNetworkCountryIso().toUpperCase(Locale.US); mAppSupportedLocales.forEach(supportedlocale -> { String localeCountry = supportedlocale.getCountry().toUpperCase(Locale.US); if (!compareLocale(supportedlocale, appLocale) && isCountrySuggestedLocale(localeCountry, simCountry, networkCountry)) { mProcessedSuggestedLocales.add(supportedlocale); } }); // Other locales in suggested languages group. Collection<Locale> supportedSystemLocales = new HashSet<>(); getCurrentSystemLocales().forEach(systemLocale -> { mAppSupportedLocales.forEach(supportedLocale -> { if (compareLocale(systemLocale, supportedLocale)) { supportedSystemLocales.add(supportedLocale); private ApplicationInfo getApplicationInfo(String packageName, int userId) { ApplicationInfo applicationInfo; try { applicationInfo = getContext().getPackageManager() .getApplicationInfoAsUser(packageName, /* flags= */ 0, userId); return applicationInfo; } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Application info not found for: " + packageName); return null; } }); }); supportedSystemLocales.removeAll(mProcessedSuggestedLocales); mProcessedSuggestedLocales.addAll(supportedSystemLocales); } @VisibleForTesting static boolean compareLocale(Locale source, Locale target) { if (source == null && target == null) { return true; } else if (source != null && target != null) { return LocaleList.matchesLanguageAndScript(source, target); } else { return false; } private boolean hasAppSupportedLocales() { LocaleList localeList = getPackageLocales(); return (localeList != null && localeList.size() > 0) || getAssetLocales().length > 0; } private static boolean isCountrySuggestedLocale(String localeCountry, String simCountry, String networkCountry) { return ((!simCountry.isEmpty() && simCountry.equals(localeCountry)) || (!networkCountry.isEmpty() && networkCountry.equals(localeCountry))); private String[] getAssetLocales() { try { PackageManager packageManager = getContext().getPackageManager(); String[] locales = packageManager.getResourcesForApplication( packageManager.getPackageInfo(mPackageName, PackageManager.MATCH_ALL) .applicationInfo).getAssets().getNonSystemLocales(); if (locales == null) { Log.i(TAG, "[" + mPackageName + "] locales are null."); } @VisibleForTesting void handleSupportedLocales() { mProcessedSupportedLocales.addAll(mAppSupportedLocales); if (mProcessedSuggestedLocales != null || !mProcessedSuggestedLocales.isEmpty()) { mProcessedSuggestedLocales.retainAll(mProcessedSupportedLocales); mProcessedSupportedLocales.removeAll(mProcessedSuggestedLocales); if (locales.length <= 0) { Log.i(TAG, "[" + mPackageName + "] locales length is 0."); return new String[0]; } String locale = locales[0]; Log.i(TAG, "First asset locale - [" + mPackageName + "] " + locale); return locales; } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); } private void clearLocalesData() { mProcessedSuggestedLocales.clear(); mProcessedSupportedLocales.clear(); return new String[0]; } private Collection<Locale> getAppSupportedLocales() { Collection<Locale> appSupportedLocales = new ArrayList<>(); LocaleList localeList = getPackageLocales(); if (localeList != null && localeList.size() > 0) { for (int i = 0; i < localeList.size(); i++) { appSupportedLocales.add(localeList.get(i)); } } else { String[] languages = getAssetLocales(); for (String language : languages) { appSupportedLocales.add(Locale.forLanguageTag(language)); private LocaleList getPackageLocales() { try { LocaleConfig localeConfig = new LocaleConfig(getContext().createPackageContext(mPackageName, 0)); if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) { return localeConfig.getSupportedLocales(); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); } return appSupportedLocales; return null; } /** Gets per app's default locale */ Loading @@ -368,59 +210,18 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen return null; } /** Sets per app's default language to system. */ public void setAppDefaultLocale(String languageTag) { if (languageTag.isEmpty()) { Log.w(TAG, "[setAppDefaultLocale] No language tag."); return; } setAppDefaultLocale(LocaleList.forLanguageTags(languageTag)); } /** Sets per app's default language to system. */ public void setAppDefaultLocale(LocaleList localeList) { if (mLocaleManager == null) { Log.w(TAG, "LocaleManager is null, and cannot set the app locale up."); return; } mLocaleManager.setApplicationLocales(mPackageName, localeList); } @VisibleForTesting Collection<Locale> getCurrentSystemLocales() { LocaleList localeList = Resources.getSystem().getConfiguration().getLocales(); Collection<Locale> systemLocales = new ArrayList<>(); for (int i = 0; i < localeList.size(); i++) { systemLocales.add(localeList.get(i)); } return systemLocales; } @VisibleForTesting String[] getAssetLocales() { try { PackageManager packageManager = mContext.getPackageManager(); return packageManager.getResourcesForApplication( packageManager.getPackageInfo(mPackageName, PackageManager.MATCH_ALL) .applicationInfo).getAssets().getNonSystemLocales(); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); } return new String[0]; } @VisibleForTesting LocaleList getPackageLocales() { try { LocaleConfig localeConfig = new LocaleConfig(mContext.createPackageContext(mPackageName, 0)); if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) { return localeConfig.getSupportedLocales(); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); } return null; /** * TODO (b209962418) Do a performance test to low end device. * @return Return the summary to show the current app's language. */ public static CharSequence getSummary(Context context, String packageName) { Locale appLocale = getAppDefaultLocale(context, packageName); if (appLocale == null) { Locale systemLocale = Locale.getDefault(); return context.getString(R.string.preference_of_system_locale_summary, systemLocale.getDisplayName(systemLocale)); } else { return appLocale.getDisplayName(appLocale); } } }