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

Commit 6fc5bb64 authored by tom hsu's avatar tom hsu Committed by danielwbhuang
Browse files

[Panlingual] Revamp the panlingual UI in Settings.

  - Create a Activity to contain AppLocaleDetail and
    LocalePickerWithRegion
  - Update the Entry from apps language page

Bug: 223089715
Test: local test pass
Change-Id: Id01e93f3df32412c7323ca577a149009eb1862ad
parent 2e82606f
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -852,7 +852,7 @@
        </activity>

        <activity
            android:name=".Settings$AppLocalePickerActivity"
            android:name=".localepicker.AppLocalePickerActivity"
            android:label="@string/app_locale_picker_title"
            android:exported="true" >
            <intent-filter>
@@ -860,8 +860,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
@@ -108,8 +108,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 −307
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,229 +124,47 @@ 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);
                    }
                });
            });
            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 static boolean isCountrySuggestedLocale(String localeCountry,
                String simCountry,
                String networkCountry) {
            return ((!simCountry.isEmpty() && simCountry.equals(localeCountry))
                    || (!networkCountry.isEmpty() && networkCountry.equals(localeCountry)));
        }

        @VisibleForTesting
        void handleSupportedLocales() {
            mProcessedSupportedLocales.addAll(mAppSupportedLocales);
            if (mProcessedSuggestedLocales != null || !mProcessedSuggestedLocales.isEmpty()) {
                mProcessedSuggestedLocales.retainAll(mProcessedSupportedLocales);
                mProcessedSupportedLocales.removeAll(mProcessedSuggestedLocales);
            }
        }

        private void clearLocalesData() {
            mProcessedSuggestedLocales.clear();
            mProcessedSupportedLocales.clear();
        }

        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));
                }
            }
            return appSupportedLocales;
        }

        /** Gets per app's default locale */
        public static Locale getAppDefaultLocale(Context context, String packageName) {
            LocaleManager localeManager = context.getSystemService(LocaleManager.class);
    private ApplicationInfo getApplicationInfo(String packageName, int userId) {
        ApplicationInfo applicationInfo;
        try {
                LocaleList localeList = (localeManager == null)
                        ? null : localeManager.getApplicationLocales(packageName);
                return localeList == null ? null : localeList.get(0);
            } catch (IllegalArgumentException e) {
                Log.w(TAG, "package name : " + packageName + " is not correct. " + e);
            }
            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;
        }

        /** 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;
    private boolean hasAppSupportedLocales() {
        LocaleList localeList = getPackageLocales();
        return (localeList != null && localeList.size() > 0) || getAssetLocales().length > 0;
    }

        @VisibleForTesting
        String[] getAssetLocales() {
    private String[] getAssetLocales() {
        try {
                PackageManager packageManager = mContext.getPackageManager();
            PackageManager packageManager = getContext().getPackageManager();
            String[] locales = packageManager.getResourcesForApplication(
                    packageManager.getPackageInfo(mPackageName, PackageManager.MATCH_ALL)
                            .applicationInfo).getAssets().getNonSystemLocales();
@@ -418,11 +184,10 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen
        return new String[0];
    }

        @VisibleForTesting
        LocaleList getPackageLocales() {
    private LocaleList getPackageLocales() {
        try {
            LocaleConfig localeConfig =
                        new LocaleConfig(mContext.createPackageContext(mPackageName, 0));
                    new LocaleConfig(getContext().createPackageContext(mPackageName, 0));
            if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) {
                return localeConfig.getSupportedLocales();
            }
@@ -431,5 +196,32 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen
        }
        return null;
    }

    /** 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)
                    ? null : localeManager.getApplicationLocales(packageName);
            return localeList == null ? null : localeList.get(0);
        } catch (IllegalArgumentException e) {
            Log.w(TAG, "package name : " + packageName + " is not correct. " + 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