Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7eb32dec authored by Tom Hsu's avatar Tom Hsu Committed by Android (Google) Code Review
Browse files

Merge "[Panlingual] Revamp the panlingual UI in Settings." into tm-dev

parents 028e8295 9be043e7
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -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>
@@ -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
+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
+1 −15
Original line number Diff line number Diff line
@@ -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"
@@ -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>
+0 −2
Original line number Diff line number Diff line
@@ -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 */ }
+99 −298
Original line number Diff line number Diff line
@@ -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
@@ -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);
@@ -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
@@ -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);
@@ -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 */
@@ -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