/* * Copyright (C) 2021 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.settings.applications.appinfo; import static com.android.settings.widget.EntityHeaderController.ActionType; import android.app.Activity; import android.app.LocaleManager; import android.app.settings.SettingsEnums; import android.content.Context; 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.internal.app.LocalePicker; import com.android.internal.app.LocalePicker.LocaleInfo; import com.android.settings.R; 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 com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; /** * A fragment to show the current app locale info and help the user to select the expected locale. */ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreference.OnClickListener { 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"; private boolean mCreated = false; private AppLocaleDetailsHelper mAppLocaleDetailsHelper; private PreferenceGroup mGroupOfSuggestedLocales; private PreferenceGroup mGroupOfSupportedLocales; private LayoutPreference mPrefOfDescription; @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); } // Override here so we don't have an empty screen @Override 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) { return inflater.inflate(R.layout.manage_applications_apps_unsupported, null); } return super.onCreateView(inflater, container, savedInstanceState); } @Override public void onResume() { // Update Locales first, before refresh ui. mAppLocaleDetailsHelper.handleAllLocalesData(); super.onResume(); } @Override protected boolean refreshUi() { if (mAppLocaleDetailsHelper.getSupportedLocales().isEmpty()) { Log.d(TAG, "No supported language."); mGroupOfSuggestedLocales.setVisible(false); mGroupOfSupportedLocales.setVisible(false); mPrefOfDescription.setVisible(true); TextView description = (TextView) mPrefOfDescription.findViewById(R.id.description); Locale locale = mAppLocaleDetailsHelper.getCurrentSystemLocales().get(0); description.setText(getContext().getString(R.string.no_multiple_language_supported, locale.getDisplayName(locale))); return true; } mGroupOfSuggestedLocales.removeAll(); mGroupOfSupportedLocales.removeAll(); Locale appLocale = AppLocaleDetailsHelper.getAppDefaultLocale(getContext(), mPackageName); setLanguagesPreference(mGroupOfSuggestedLocales, mAppLocaleDetailsHelper.getSuggestedLocales(), appLocale); setLanguagesPreference(mGroupOfSupportedLocales, mAppLocaleDetailsHelper.getSupportedLocales(), appLocale); return true; } @Override public int getMetricsCategory() { return SettingsEnums.APPS_LOCALE_LIST; } @Override protected AlertDialog createDialog(int id, int errorCode) { return null; } @Override public void onRadioButtonClicked(RadioButtonPreference pref) { mAppLocaleDetailsHelper.setAppDefaultLocale(pref.getKey()); refreshUi(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (mCreated) { Log.w(TAG, "onActivityCreated: ignoring duplicate call"); return; } mCreated = true; if (mPackageInfo == null) { return; } // Creates a head icon button of app on this page. final Activity activity = getActivity(); 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)) .setPackageName(mPackageName) .setUid(mPackageInfo.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); return appLocale == null ? "" : appLocale.getDisplayName(appLocale); } private void setLanguagesPreference(PreferenceGroup group, Collection locales, Locale appLocale) { if (locales == null) { return; } for (Locale locale : locales) { RadioButtonPreference pref = new RadioButtonPreference(getContext()); pref.setTitle(locale.getDisplayName(locale)); pref.setKey(locale.toLanguageTag()); 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 mSuggestedLocales = new ArrayList<>(); private Collection mSupportedLocales = new ArrayList<>(); AppLocaleDetailsHelper(Context context, String packageName) { mContext = context; mPackageName = packageName; mTelephonyManager = context.getSystemService(TelephonyManager.class); mLocaleManager = context.getSystemService(LocaleManager.class); } /** Handle suggested and supported locales for UI display. */ public void handleAllLocalesData() { clearLocalesData(); handleSuggestedLocales(); handleSupportedLocales(); } /** Gets suggested locales in the app. */ public Collection getSuggestedLocales() { return mSuggestedLocales; } /** Gets supported locales in the app. */ public Collection getSupportedLocales() { return mSupportedLocales; } @VisibleForTesting void handleSuggestedLocales() { LocaleList currentSystemLocales = getCurrentSystemLocales(); Locale appLocale = getAppDefaultLocale(mContext, mPackageName); String simCountry = mTelephonyManager.getSimCountryIso().toUpperCase(Locale.US); String networkCountry = mTelephonyManager.getNetworkCountryIso().toUpperCase(Locale.US); // 1st locale in suggested languages group. if (appLocale != null) { mSuggestedLocales.add(appLocale); } // 2nd locale in suggested languages group. final List localeInfos = LocalePicker.getAllAssetLocales(mContext, false); for (LocaleInfo localeInfo : localeInfos) { Locale locale = localeInfo.getLocale(); String localeCountry = locale.getCountry().toUpperCase(Locale.US); if (!compareLocale(locale, appLocale) && isCountrySuggestedLocale(localeCountry, simCountry, networkCountry)) { mSuggestedLocales.add(locale); } } // Other locales in suggested languages group. for (int i = 0; i < currentSystemLocales.size(); i++) { Locale locale = currentSystemLocales.get(i); boolean isInSuggestedLocales = false; for (int j = 0; j < mSuggestedLocales.size(); j++) { Locale suggestedLocale = Iterables.get(mSuggestedLocales, j); if (compareLocale(locale, suggestedLocale)) { isInSuggestedLocales = true; break; } } if (!isInSuggestedLocales) { mSuggestedLocales.add(locale); } } } @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 static boolean isCountrySuggestedLocale(String localeCountry, String simCountry, String networkCountry) { return ((!simCountry.isEmpty() && simCountry.equals(localeCountry)) || (!networkCountry.isEmpty() && networkCountry.equals(localeCountry))); } @VisibleForTesting void handleSupportedLocales() { //TODO Waiting for PackageManager api String[] languages = getAssetSystemLocales(); for (String language : languages) { mSupportedLocales.add(Locale.forLanguageTag(language)); } if (mSuggestedLocales != null || !mSuggestedLocales.isEmpty()) { mSupportedLocales.removeAll(mSuggestedLocales); } } private void clearLocalesData() { mSuggestedLocales.clear(); mSupportedLocales.clear(); } /** Gets per app's default locale */ public static Locale getAppDefaultLocale(Context context, String packageName) { LocaleManager localeManager = context.getSystemService(LocaleManager.class); try { LocaleList localeList = (localeManager == null) ? new LocaleList() : localeManager.getApplicationLocales(packageName); return localeList.isEmpty() ? null : localeList.get(0); } catch (IllegalArgumentException e) { Log.w(TAG, "package name : " + packageName + " is not correct. " + e); } 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 LocaleList getCurrentSystemLocales() { return Resources.getSystem().getConfiguration().getLocales(); } @VisibleForTesting String[] getAssetSystemLocales() { 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 : " + e); } return new String[0]; } } }