Loading Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -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", Loading AndroidManifest.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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" Loading src/com/android/settings/localepicker/AppLocalePickerActivity.java +80 −46 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading @@ -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, Loading Loading @@ -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() { Loading src/com/android/settings/localepicker/LocaleDialogFragment.java +19 −4 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading @@ -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(); } } Loading Loading @@ -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) { Loading @@ -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; } Loading @@ -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; } Loading src/com/android/settings/localepicker/LocaleListEditor.java +58 −78 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; } } Loading Loading @@ -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 Loading Loading @@ -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)) { Loading @@ -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 Loading
Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -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", Loading
AndroidManifest.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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" Loading
src/com/android/settings/localepicker/AppLocalePickerActivity.java +80 −46 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading @@ -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, Loading Loading @@ -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() { Loading
src/com/android/settings/localepicker/LocaleDialogFragment.java +19 −4 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading @@ -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(); } } Loading Loading @@ -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) { Loading @@ -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; } Loading @@ -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; } Loading
src/com/android/settings/localepicker/LocaleListEditor.java +58 −78 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; } } Loading Loading @@ -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 Loading Loading @@ -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)) { Loading @@ -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