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

Commit 54064774 authored by Allen Su's avatar Allen Su Committed by Android (Google) Code Review
Browse files

Merge "Migrate LocaleNotification to main trunk" into main

parents 9eb3b3db f22d5e98
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ android_library {
        "androidx.lifecycle_lifecycle-runtime",
        "androidx.lifecycle_lifecycle-runtime-ktx",
        "androidx.lifecycle_lifecycle-viewmodel",
        "gson",
        "guava",
        "jsr305",
        "net-utils-framework-common",
+2 −0
Original line number Diff line number Diff line
@@ -2796,6 +2796,8 @@
                android:exported="true"
                android:permission="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

        <receiver android:name=".localepicker.NotificationCancelReceiver" />

        <activity android:name="Settings$ApnEditorActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:exported="true"
+80 −46
Original line number Diff line number Diff line
@@ -18,13 +18,17 @@ package com.android.settings.localepicker;

import android.app.FragmentTransaction;
import android.app.LocaleManager;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.MenuItem;
@@ -32,8 +36,7 @@ import android.view.View;
import android.widget.FrameLayout;
import android.widget.ListView;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.core.app.NotificationCompat;
import androidx.core.view.ViewCompat;

import com.android.internal.app.LocalePickerWithRegion;
@@ -43,19 +46,23 @@ import com.android.settings.applications.AppLocaleUtil;
import com.android.settings.applications.appinfo.AppLocaleDetails;
import com.android.settings.core.SettingsBaseActivity;

import java.util.Locale;

public class AppLocalePickerActivity extends SettingsBaseActivity
        implements LocalePickerWithRegion.LocaleSelectedListener, MenuItem.OnActionExpandListener {
    private static final String TAG = AppLocalePickerActivity.class.getSimpleName();
    private static final String CHANNEL_ID_SUGGESTION = "suggestion";
    private static final String CHANNEL_ID_SUGGESTION_TO_USER = "Locale suggestion";
    private static final String EXTRA_SYSTEM_LOCALE_DIALOG_TYPE = "system_locale_dialog_type";
    private static final String LOCALE_SUGGESTION = "locale_suggestion";
    static final boolean ENABLED = false;
    static final String EXTRA_APP_LOCALE = "app_locale";
    private static final String PROP_SYSTEM_LOCALE_SUGGESTION = "android.system.locale.suggestion";
    private static final boolean ENABLED = false;
    static final String EXTRA_NOTIFICATION_ID = "notification_id";
    static final String PROP_SYSTEM_LOCALE_SUGGESTION = "android.system.locale.suggestion";

    private String mPackageName;
    private LocalePickerWithRegion mLocalePickerWithRegion;
    private AppLocaleDetails mAppLocaleDetails;
    private View mAppLocaleDetailContainer;
    private NotificationController mNotificationController;

    @Override
    public void onCreate(Bundle savedInstanceState) {
@@ -81,6 +88,7 @@ public class AppLocalePickerActivity extends SettingsBaseActivity

        setTitle(R.string.app_locale_picker_title);
        getActionBar().setDisplayHomeAsUpEnabled(true);
        mNotificationController = NotificationController.getInstance(this);

        mLocalePickerWithRegion = LocalePickerWithRegion.createLanguagePicker(
                this,
@@ -146,52 +154,78 @@ public class AppLocalePickerActivity extends SettingsBaseActivity
        if (!SystemProperties.getBoolean(PROP_SYSTEM_LOCALE_SUGGESTION, ENABLED)) {
            return;
        }
        String languageTag = localeInfo.getLocale().toLanguageTag();
        if (isInSystemLocale(languageTag) || localeInfo.isAppCurrentLocale()) {
        String localeTag = localeInfo.getLocale().toLanguageTag();
        if (LocaleUtils.isInSystemLocale(localeTag) || localeInfo.isAppCurrentLocale()) {
            return;
        }
        String intentAction = getString(R.string.config_app_locale_intent_action);
        if (!TextUtils.isEmpty(intentAction)) {
        try {
                PackageManager packageManager = getPackageManager();
                ApplicationInfo info = packageManager.getApplicationInfo(mPackageName,
                        PackageManager.GET_META_DATA);
                Intent intent = new Intent(intentAction)
                        .putExtra(Intent.EXTRA_UID, info.uid)
                        .putExtra(EXTRA_APP_LOCALE, languageTag);
                if (intent.resolveActivity(packageManager) != null) {
                    mStartForResult.launch(intent);
            int uid = getPackageManager().getApplicationInfo(mPackageName,
                    PackageManager.GET_META_DATA).uid;
            boolean launchNotification = mNotificationController.shouldTriggerNotification(
                    uid, localeTag);
            if (launchNotification) {
                triggerNotification(
                        mNotificationController.getNotificationId(localeTag),
                        getString(R.string.title_system_locale_addition,
                                localeInfo.getFullNameNative()),
                        getString(R.string.desc_system_locale_addition),
                        localeTag);
            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "Unable to find info for package: " + mPackageName);
        }
    }
    }

    // Invoke startActivityFroResult so that the calling package can be shared via the intent.
    private ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            result -> {
            }
    );
    private void triggerNotification(
            int notificationId,
            String title,
            String description,
            String localeTag) {
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        final boolean channelExist =
                notificationManager.getNotificationChannel(CHANNEL_ID_SUGGESTION) != null;

    /**
     * Checks if the localeTag is in the system locale. Since in the current design, the system
     * language list would not show two locales with the same language and region but different
     * numbering system. So, during the comparison, the extension has to be stripped.
     *
     * @param languageTag A language tag
     * @return true if the locale is in the system locale. Otherwise, false.
     */
    private static boolean isInSystemLocale(String languageTag) {
        LocaleList systemLocales = LocaleList.getDefault();
        Locale locale = Locale.forLanguageTag(languageTag).stripExtensions();
        for (int i = 0; i < systemLocales.size(); i++) {
            if (locale.equals(systemLocales.get(i).stripExtensions())) {
                return true;
        // Create an alert channel if it does not exist
        if (!channelExist) {
            NotificationChannel channel =
                    new NotificationChannel(
                            CHANNEL_ID_SUGGESTION,
                            CHANNEL_ID_SUGGESTION_TO_USER,
                            NotificationManager.IMPORTANCE_DEFAULT);
            channel.setSound(/* sound */ null, /* audioAttributes */ null); // silent notification
            notificationManager.createNotificationChannel(channel);
        }

        final NotificationCompat.Builder builder =
                new NotificationCompat.Builder(this, CHANNEL_ID_SUGGESTION)
                        .setSmallIcon(R.drawable.ic_settings_language)
                        .setAutoCancel(true)
                        .setContentTitle(title)
                        .setContentText(description)
                        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                        .setContentIntent(
                                createPendingIntent(localeTag, notificationId, false))
                        .setDeleteIntent(
                                createPendingIntent(localeTag, notificationId, true));
        notificationManager.notify(notificationId, builder.build());
    }
        return false;

    private PendingIntent createPendingIntent(String locale, int notificationId,
            boolean isDeleteIntent) {
        Intent intent = isDeleteIntent
                ? new Intent(this, NotificationCancelReceiver.class)
                : new Intent(Settings.ACTION_LOCALE_SETTINGS)
                        .putExtra(EXTRA_SYSTEM_LOCALE_DIALOG_TYPE, LOCALE_SUGGESTION)
                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

        intent.putExtra(EXTRA_APP_LOCALE, locale)
                .putExtra(EXTRA_NOTIFICATION_ID, notificationId);
        int flag = PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT;
        int elapsedTime = (int) SystemClock.elapsedRealtimeNanos();

        return isDeleteIntent
                ? PendingIntent.getBroadcast(this, elapsedTime, intent, flag)
                : PendingIntent.getActivity(this, elapsedTime, intent, flag);
    }

    private View launchAppLocaleDetailsPage() {
+19 −4
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment {

    static final int DIALOG_CONFIRM_SYSTEM_DEFAULT = 1;
    static final int DIALOG_NOT_AVAILABLE_LOCALE = 2;
    static final int DIALOG_ADD_SYSTEM_LOCALE = 3;

    static final String ARG_DIALOG_TYPE = "arg_dialog_type";
    static final String ARG_TARGET_LOCALE = "arg_target_locale";
@@ -95,7 +96,8 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment {
            mShouldKeepDialog = savedInstanceState.getBoolean(ARG_SHOW_DIALOG, false);
            // Keep the dialog if user rotates the device, otherwise close the confirm system
            // default dialog only when user changes the locale.
            if (type == DIALOG_CONFIRM_SYSTEM_DEFAULT && !mShouldKeepDialog) {
            if ((type == DIALOG_CONFIRM_SYSTEM_DEFAULT || type == DIALOG_ADD_SYSTEM_LOCALE)
                    && !mShouldKeepDialog) {
                dismiss();
            }
        }
@@ -192,7 +194,8 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment {

        @Override
        public void onClick(DialogInterface dialog, int which) {
            if (mDialogType == DIALOG_CONFIRM_SYSTEM_DEFAULT) {
            if (mDialogType == DIALOG_CONFIRM_SYSTEM_DEFAULT
                    || mDialogType == DIALOG_ADD_SYSTEM_LOCALE) {
                int result = Activity.RESULT_CANCELED;
                boolean changed = false;
                if (which == DialogInterface.BUTTON_POSITIVE) {
@@ -201,9 +204,12 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment {
                }
                Intent intent = new Intent();
                Bundle bundle = new Bundle();
                bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
                bundle.putInt(ARG_DIALOG_TYPE, mDialogType);
                bundle.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE, mLocaleInfo);
                intent.putExtras(bundle);
                mParent.onActivityResult(DIALOG_CONFIRM_SYSTEM_DEFAULT, result, intent);
                mParent.onActivityResult(mDialogType, result, intent);
                mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CHANGE_LANGUAGE,
                        changed);
            }
            mShouldKeepDialog = false;
        }
@@ -227,6 +233,15 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment {
                    dialogContent.mMessage = mContext.getString(R.string.desc_unavailable_locale);
                    dialogContent.mPositiveButton = mContext.getString(R.string.okay);
                    break;
                case DIALOG_ADD_SYSTEM_LOCALE:
                    dialogContent.mTitle = String.format(mContext.getString(
                                    R.string.title_system_locale_addition),
                            mLocaleInfo.getFullNameNative());
                    dialogContent.mMessage = mContext.getString(
                            R.string.desc_system_locale_addition);
                    dialogContent.mPositiveButton = mContext.getString(R.string.add);
                    dialogContent.mNegativeButton = mContext.getString(R.string.cancel);
                    break;
                default:
                    break;
            }
+58 −78
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.settings.localepicker;
import static android.os.UserManager.DISALLOW_CONFIG_LOCALE;

import static com.android.settings.localepicker.AppLocalePickerActivity.EXTRA_APP_LOCALE;
import static com.android.settings.localepicker.AppLocalePickerActivity.EXTRA_NOTIFICATION_ID;
import static com.android.settings.localepicker.LocaleDialogFragment.DIALOG_ADD_SYSTEM_LOCALE;
import static com.android.settings.localepicker.LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT;

import android.app.Activity;
@@ -29,6 +31,7 @@ import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -59,7 +62,6 @@ import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.LayoutPreference;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

@@ -68,20 +70,22 @@ import java.util.Locale;
 */
@SearchIndexable
public class LocaleListEditor extends RestrictedSettingsFragment implements View.OnTouchListener {
    private static final String TAG = LocaleListEditor.class.getSimpleName();
    protected static final String INTENT_LOCALE_KEY = "localeInfo";

    private static final String TAG = LocaleListEditor.class.getSimpleName();
    private static final String CFGKEY_REMOVE_MODE = "localeRemoveMode";
    private static final String CFGKEY_REMOVE_DIALOG = "showingLocaleRemoveDialog";
    private static final String CFGKEY_ADD_LOCALE = "localeAdded";
    private static final int MENU_ID_REMOVE = Menu.FIRST + 1;
    private static final int REQUEST_LOCALE_PICKER = 0;

    private static final String INDEX_KEY_ADD_LANGUAGE = "add_language";
    private static final String KEY_LANGUAGES_PICKER = "languages_picker";
    private static final String TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT = "dialog_confirm_system_default";
    private static final String TAG_DIALOG_NOT_AVAILABLE = "dialog_not_available_locale";
    static final String EXTRA_SYSTEM_LOCALE_DIALOG_TYPE = "system_locale_dialog_type";
    private static final String TAG_DIALOG_ADD_SYSTEM_LOCALE = "dialog_add_system_locale";
    private static final String EXTRA_SYSTEM_LOCALE_DIALOG_TYPE = "system_locale_dialog_type";
    private static final String LOCALE_SUGGESTION = "locale_suggestion";
    private static final int MENU_ID_REMOVE = Menu.FIRST + 1;
    private static final int REQUEST_LOCALE_PICKER = 0;
    private static final int INVALID_NOTIFICATION_ID = -1;

    private LocaleDragAndDropAdapter mAdapter;
    private Menu mMenu;
@@ -170,9 +174,10 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View
        if (mShowingRemoveDialog) {
            showRemoveLocaleWarningDialog();
        }
        if (shouldShowConfirmationDialog() && !mLocaleAdditionMode) {
            getActivity().setResult(Activity.RESULT_OK);
        Log.d(TAG, "LocaleAdditionMode:" + mLocaleAdditionMode);
        if (!mLocaleAdditionMode && shouldShowConfirmationDialog()) {
            showDialogForAddedLocale();
            mLocaleAdditionMode = true;
        }
    }

@@ -236,16 +241,17 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View
                mAdapter.notifyListChanged(localeInfo);
            }
            mAdapter.setCacheItemList();
        } else if (requestCode == DIALOG_ADD_SYSTEM_LOCALE) {
            if (resultCode == Activity.RESULT_OK) {
                localeInfo = (LocaleStore.LocaleInfo) data.getExtras().getSerializable(
                        LocaleDialogFragment.ARG_TARGET_LOCALE);
                String preferencesTags = Settings.System.getString(
                        getContext().getContentResolver(),
                        Settings.System.LOCALE_PREFERENCES);
                mAdapter.addLocale(mayAppendUnicodeTags(localeInfo, preferencesTags));
            }
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mSuggestionDialog != null) {
            mSuggestionDialog.dismiss();
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    @VisibleForTesting
@@ -276,31 +282,42 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View
        Intent intent = this.getIntent();
        String dialogType = intent.getStringExtra(EXTRA_SYSTEM_LOCALE_DIALOG_TYPE);
        String localeTag = intent.getStringExtra(EXTRA_APP_LOCALE);
        if (!isAllowedPackage()
                || isNullOrEmpty(dialogType)
                || isNullOrEmpty(localeTag)
                || !LOCALE_SUGGESTION.equals(dialogType)
        int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, INVALID_NOTIFICATION_ID);
        if (!isDialogFeatureEnabled()
                || !isValidNotificationId(localeTag, notificationId)
                || !isValidDialogType(dialogType)
                || !isValidLocale(localeTag)
                || isInSystemLocale(localeTag)) {
            getActivity().setResult(Activity.RESULT_CANCELED);
                || LocaleUtils.isInSystemLocale(localeTag)) {
            return false;
        }
        getActivity().setResult(Activity.RESULT_OK);
        return true;
    }

    private boolean isAllowedPackage() {
        List<String> allowList = Arrays.asList(getContext().getResources().getStringArray(
                R.array.allowed_packages_for_locale_confirmation_diallog));
        String callingPackage = getActivity().getCallingPackage();
        return !isNullOrEmpty(callingPackage) && allowList.contains(callingPackage);
    private boolean isDialogFeatureEnabled() {
        return SystemProperties.getBoolean(AppLocalePickerActivity.PROP_SYSTEM_LOCALE_SUGGESTION,
                AppLocalePickerActivity.ENABLED);
    }

    private boolean isValidNotificationId(String localeTag, long id) {
        if (id == -1) {
            return false;
        }
        return id == getNotificationController().getNotificationId(localeTag);
    }

    private static boolean isNullOrEmpty(String str) {
        return str == null || str.isEmpty();
    @VisibleForTesting
    NotificationController getNotificationController() {
        return NotificationController.getInstance(getContext());
    }

    private boolean isValidDialogType(String type) {
        return LOCALE_SUGGESTION.equals(type);
    }

    private boolean isValidLocale(String tag) {
        if (TextUtils.isEmpty(tag)) {
            return false;
        }
        String[] systemLocales = getSupportedLocales();
        for (String systemTag : systemLocales) {
            if (systemTag.equals(tag)) {
@@ -310,63 +327,26 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View
        return false;
    }

    protected String[] getSupportedLocales() {
    @VisibleForTesting
    String[] getSupportedLocales() {
        return LocalePicker.getSupportedLocales(getContext());
    }

    /**
     *  Check if the localeTag is in the system locale. Since in the current design, the system
     *  language list would not show two locales with the same language and region but different
     *  numbering system. So, during the comparison, the u extension has to be stripped out.
     *
     * @param languageTag A language tag
     * @return true if the locale is in the system locale. Otherwise, false.
     */
    private boolean isInSystemLocale(String languageTag) {
        LocaleList systemLocales = LocaleList.getDefault();
        Locale locale = Locale.forLanguageTag(languageTag).stripExtensions();
        for (int i = 0; i < systemLocales.size(); i++) {
            if (systemLocales.get(i).stripExtensions().equals(locale)) {
                return true;
            }
        }
        return false;
    }

    private void showDialogForAddedLocale() {
        Log.d(TAG, "Show confirmation dialog");
        Intent intent = this.getIntent();
        String dialogType = intent.getStringExtra(EXTRA_SYSTEM_LOCALE_DIALOG_TYPE);
        String appLocaleTag = intent.getStringExtra(EXTRA_APP_LOCALE);
        Log.d(TAG, "Dialog suggested locale: " + appLocaleTag);

        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(
                Locale.forLanguageTag(appLocaleTag));
        if (LOCALE_SUGGESTION.equals(dialogType)) {
            AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
            customizeLayout(dialogBuilder, localeInfo.getFullNameNative());
            dialogBuilder
                    .setPositiveButton(R.string.add, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            mLocaleAdditionMode = true;
                            String preferencesTags = Settings.System.getString(
                                    getContext().getContentResolver(),
                                    Settings.System.LOCALE_PREFERENCES);
                            mAdapter.addLocale(mayAppendUnicodeTags(localeInfo, preferencesTags));
                        }
                    })
                    .setNegativeButton(android.R.string.cancel,
                            new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    mLocaleAdditionMode = true;
                                }
                            });
            mSuggestionDialog = dialogBuilder.create();
            mSuggestionDialog.setCanceledOnTouchOutside(false);
            mSuggestionDialog.show();
        } else {
            Log.d(TAG, "Invalid parameter, dialogType:" + dialogType);
        }
        final LocaleDialogFragment localeDialogFragment =
                LocaleDialogFragment.newInstance();
        Bundle args = new Bundle();
        args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE, DIALOG_ADD_SYSTEM_LOCALE);
        args.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE, localeInfo);
        localeDialogFragment.setArguments(args);
        localeDialogFragment.show(mFragmentManager, TAG_DIALOG_ADD_SYSTEM_LOCALE);
    }

    private void customizeLayout(AlertDialog.Builder dialogBuilder, String language) {
Loading