From 109084137ad2ce0025c67fd326501e205737d176 Mon Sep 17 00:00:00 2001 From: Nikhil Kumar Date: Thu, 8 Dec 2022 18:39:46 +0000 Subject: [PATCH 01/18] Refactor power menu bug reporting flow to work for multiple admins Existing implementation of power menu bugreporting flow works only for the primary user. In HSUM(Headless system user mode) there is going to be more than one admin users who are allowed to trigger bug report from the power menu, below refactorings has been done do handle this case. Configurations for custom_bugreport_handler_app and custom_bugreport_handler_user are moved from Global to Secure settings so that each user can save them. The current context user will be the fallback default user in the absence of a correctly configured handlerApp or handlerUser. Retrieval of the current bug report handler apps are refactored to add shell as user's default preferences for any user(current user). Test: manually tested the bugreport is working for multiple admin with different combination of handlerApp and handlerUser Bug: 261184202 Change-Id: I3110c1f9619ede2edeb744daf37521318f0ff6e7 --- .../BugReportHandlerUtil.java | 32 ++++++++++--------- .../BugReportInPowerPreferenceController.java | 6 ++-- ...ReportInPowerPreferenceControllerTest.java | 10 +++--- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/com/android/settings/bugreporthandler/BugReportHandlerUtil.java b/src/com/android/settings/bugreporthandler/BugReportHandlerUtil.java index 266a44929ea..aefa3b7ecae 100644 --- a/src/com/android/settings/bugreporthandler/BugReportHandlerUtil.java +++ b/src/com/android/settings/bugreporthandler/BugReportHandlerUtil.java @@ -78,7 +78,7 @@ public class BugReportHandlerUtil { boolean needToResetOutdatedSettings = false; if (!isBugreportAllowlistedApp(handlerApp)) { handlerApp = getDefaultBugReportHandlerApp(context); - handlerUser = UserHandle.USER_SYSTEM; + handlerUser = context.getUserId(); } else if (getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) { // It looks like the settings are outdated, need to reset outdated settings. // @@ -89,7 +89,7 @@ public class BugReportHandlerUtil { // The chosen bugreport handler app is outdated because the profile is removed, // so need to reset outdated settings handlerApp = getDefaultBugReportHandlerApp(context); - handlerUser = UserHandle.USER_SYSTEM; + handlerUser = context.getUserId(); needToResetOutdatedSettings = true; } @@ -99,7 +99,7 @@ public class BugReportHandlerUtil { // bugreport, so change to let shell to handle a bugreport and need to reset // settings. handlerApp = SHELL_APP_PACKAGE; - handlerUser = UserHandle.USER_SYSTEM; + handlerUser = context.getUserId(); needToResetOutdatedSettings = true; } @@ -112,13 +112,13 @@ public class BugReportHandlerUtil { private String getCustomBugReportHandlerApp(Context context) { // Get the package of custom bugreport handler app - return Settings.Global.getString(context.getContentResolver(), - Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP); + return Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP); } private int getCustomBugReportHandlerUser(Context context) { - return Settings.Global.getInt(context.getContentResolver(), - Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL); + return Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL); } private String getDefaultBugReportHandlerApp(Context context) { @@ -164,22 +164,24 @@ public class BugReportHandlerUtil { return validBugReportHandlerApplicationInfos; } - // Add "Shell with system user" as System default preference on top of screen + int currentUser = UserHandle.getCallingUserId(); + + // Add "Shell" as user's default preference on top of screen if (bugreportAllowlistedPackages.contains(SHELL_APP_PACKAGE) && !getBugReportHandlerAppReceivers(context, SHELL_APP_PACKAGE, - UserHandle.USER_SYSTEM).isEmpty()) { + currentUser).isEmpty()) { try { validBugReportHandlerApplicationInfos.add( Pair.create( context.getPackageManager().getApplicationInfo(SHELL_APP_PACKAGE, - PackageManager.MATCH_ANY_USER), UserHandle.USER_SYSTEM) + PackageManager.MATCH_ANY_USER), currentUser) ); } catch (PackageManager.NameNotFoundException e) { } } final UserManager userManager = context.getSystemService(UserManager.class); - final List profileList = userManager.getProfiles(UserHandle.getCallingUserId()); + final List profileList = userManager.getProfiles(currentUser); // Only add non-Shell app as normal preference final List nonShellPackageList = bugreportAllowlistedPackages.stream() .filter(pkg -> !SHELL_APP_PACKAGE.equals(pkg)).collect(Collectors.toList()); @@ -228,11 +230,11 @@ public class BugReportHandlerUtil { private void setBugreportHandlerAppAndUser(Context context, String handlerApp, int handlerUser) { - Settings.Global.putString(context.getContentResolver(), - Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP, + Settings.Secure.putString(context.getContentResolver(), + Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP, handlerApp); - Settings.Global.putInt(context.getContentResolver(), - Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, handlerUser); + Settings.Secure.putInt(context.getContentResolver(), + Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, handlerUser); } /** diff --git a/src/com/android/settings/development/BugReportInPowerPreferenceController.java b/src/com/android/settings/development/BugReportInPowerPreferenceController.java index 99ced773255..5ad63e0ac96 100644 --- a/src/com/android/settings/development/BugReportInPowerPreferenceController.java +++ b/src/com/android/settings/development/BugReportInPowerPreferenceController.java @@ -59,7 +59,7 @@ public class BugReportInPowerPreferenceController extends public boolean onPreferenceChange(Preference preference, Object newValue) { final boolean isEnabled = (Boolean) newValue; Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Global.BUGREPORT_IN_POWER_MENU, + Settings.Secure.BUGREPORT_IN_POWER_MENU, isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF); return true; } @@ -67,7 +67,7 @@ public class BugReportInPowerPreferenceController extends @Override public void updateState(Preference preference) { final int mode = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Global.BUGREPORT_IN_POWER_MENU, SETTING_VALUE_OFF); + Settings.Secure.BUGREPORT_IN_POWER_MENU, SETTING_VALUE_OFF); ((SwitchPreference) mPreference).setChecked(mode != SETTING_VALUE_OFF); } @@ -75,7 +75,7 @@ public class BugReportInPowerPreferenceController extends protected void onDeveloperOptionsSwitchDisabled() { super.onDeveloperOptionsSwitchDisabled(); Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Global.BUGREPORT_IN_POWER_MENU, SETTING_VALUE_OFF); + Settings.Secure.BUGREPORT_IN_POWER_MENU, SETTING_VALUE_OFF); ((SwitchPreference) mPreference).setChecked(false); } } diff --git a/tests/robotests/src/com/android/settings/development/BugReportInPowerPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BugReportInPowerPreferenceControllerTest.java index 604dcb7a783..462ed16074b 100644 --- a/tests/robotests/src/com/android/settings/development/BugReportInPowerPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/BugReportInPowerPreferenceControllerTest.java @@ -99,7 +99,7 @@ public class BugReportInPowerPreferenceControllerTest { mController.onPreferenceChange(mPreference, false /* new value */); int mode = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Global.BUGREPORT_IN_POWER_MENU, -1 /* default */); + Settings.Secure.BUGREPORT_IN_POWER_MENU, -1 /* default */); assertThat(mode).isEqualTo(SETTING_VALUE_OFF); } @@ -111,7 +111,7 @@ public class BugReportInPowerPreferenceControllerTest { mController.onPreferenceChange(mPreference, true /* new value */); int mode = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Global.BUGREPORT_IN_POWER_MENU, -1 /* default */); + Settings.Secure.BUGREPORT_IN_POWER_MENU, -1 /* default */); assertThat(mode).isEqualTo(SETTING_VALUE_ON); } @@ -121,7 +121,7 @@ public class BugReportInPowerPreferenceControllerTest { public void updateState_settingsOn_preferenceShouldBeChecked() { when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Global.BUGREPORT_IN_POWER_MENU, SETTING_VALUE_ON); + Settings.Secure.BUGREPORT_IN_POWER_MENU, SETTING_VALUE_ON); mController.displayPreference(mScreen); mController.updateState(mPreference); @@ -133,7 +133,7 @@ public class BugReportInPowerPreferenceControllerTest { public void updateState_settingsOff_preferenceShouldNotBeChecked() { when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Global.BUGREPORT_IN_POWER_MENU, SETTING_VALUE_OFF); + Settings.Secure.BUGREPORT_IN_POWER_MENU, SETTING_VALUE_OFF); mController.displayPreference(mScreen); mController.updateState(mPreference); @@ -148,7 +148,7 @@ public class BugReportInPowerPreferenceControllerTest { mController.onDeveloperOptionsSwitchDisabled(); int mode = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Global.BUGREPORT_IN_POWER_MENU, -1 /* default */); + Settings.Secure.BUGREPORT_IN_POWER_MENU, -1 /* default */); assertThat(mode).isEqualTo(SETTING_VALUE_OFF); verify(mPreference).setChecked(false); From 8327dbaeed92b5ee2a9cf3bc70d9f89e570c2061 Mon Sep 17 00:00:00 2001 From: Azhara Assanova Date: Wed, 11 Jan 2023 20:44:32 +0000 Subject: [PATCH 02/18] Make mutable implicit PendingIntents immutable Starting from target SDK U, we will block creation of mutable PendingIntents with implicit Intents because attackers can mutate the Intent object within and launch altered behavior on behalf of victim apps. For more details on the vulnerability, see go/pendingintent-rca. From a quick analysis, we concluded that the PendingIntents here do not get mutated, so we made them immutable. Reviewers, please call out if this is not the case. Bug: 236704164 Bug: 229362273 Test: atest MediaVolumePreferenceControllerTest Change-Id: Ic5f701b504c0d8d0d0a44b002117ee5ef1c188f7 --- .../notification/MediaVolumePreferenceControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java index 251b1d75c59..ed934733ea5 100644 --- a/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java @@ -216,12 +216,12 @@ public class MediaVolumePreferenceControllerTest { final Intent intent = new Intent(action); intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME); return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } private PendingIntent getActivityIntent(String action) { final Intent intent = new Intent(action); return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } } From b21a88559257e3bd47fe939cad1f5ecee03c7b34 Mon Sep 17 00:00:00 2001 From: Taran Singh Date: Thu, 12 Jan 2023 22:41:56 +0000 Subject: [PATCH 03/18] Toggle Stylus handwriting default ON Enable Stylus handwriting to ON in developer options. This reduces one step to dogfood the feature. Bug: 210039666 Bug: 219777744 Test: atest StylusHandwritingTest Change-Id: If35528b2faf3a22b58b23584809bded3ce3bc7e8 --- res/xml/development_settings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 2bc1b87a50c..82d7e43e5d1 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -748,6 +748,7 @@ From d6b117fcdf1750d28869bb2dccd559fa9060c258 Mon Sep 17 00:00:00 2001 From: Ling Ma Date: Fri, 16 Dec 2022 17:11:25 -0800 Subject: [PATCH 04/18] Add dialog for enable auto data switch When user activate multi-SIM and suitable to enable auto data switch, prompt the user when a dialog following choosing default data sub. UX: https://docs.google.com/presentation/d/1G6bcPoJUW-YcBzpQXAQLecY3nu69rLlCLQBfpAvVD7c/edit?resourcekey=0-qPkfUNLQ2Wfz2pq3V_6MZA#slide=id.g1c25e24318e_0_0 Test: manual verified the dialog buttons following simListDialog + specific dialog Bug: 260659244 Change-Id: Ib35f2d08093c2dd79036034bbfe0efaaa0be3162 --- res/values/strings.xml | 7 + .../EnableAutoDataSwitchDialogFragment.java | 209 ++++++++++++++++++ .../SelectSpecificDataSimDialogFragment.java | 29 ++- .../settings/sim/SimDialogActivity.java | 40 +++- .../settings/sim/SimListDialogFragment.java | 6 +- ...nableAutoDataSwitchDialogFragmentTest.java | 147 ++++++++++++ 6 files changed, 424 insertions(+), 14 deletions(-) create mode 100644 src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java create mode 100644 tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 4edc3b505ea..9ff8753cc0e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2396,6 +2396,13 @@ %1$s is the only SIM in your device. Do you want to use this SIM for mobile data, calls, and SMS messages? + + Switch SIMs automatically? + + Allow your phone to automatically switch to %1$s for mobile data when it has better availability. + + \n\nCalls, messages, and network traffic may be visible to your organization. + Incorrect SIM PIN code you must now contact your carrier to unlock your device. diff --git a/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java b/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java new file mode 100644 index 00000000000..b1b5f8e69ed --- /dev/null +++ b/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java @@ -0,0 +1,209 @@ +/* + * 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. + */ + +package com.android.settings.sim; + +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; +import com.android.settings.network.SubscriptionUtil; + +import java.util.List; + +/** + * Show a dialog prompting the user to enable auto data switch following the dialog where user chose + * default data SIM. + */ +public class EnableAutoDataSwitchDialogFragment extends SimDialogFragment implements + DialogInterface.OnClickListener { + private static final String TAG = "EnableAutoDataSwitchDialogFragment"; + /** Sub Id of the non-default data SIM */ + private int mBackupDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + + /** @return a new instance of this fragment */ + public static EnableAutoDataSwitchDialogFragment newInstance() { + final EnableAutoDataSwitchDialogFragment fragment = + new EnableAutoDataSwitchDialogFragment(); + final Bundle args = initArguments(SimDialogActivity.ENABLE_AUTO_DATA_SWITCH, + R.string.enable_auto_data_switch_dialog_title); + fragment.setArguments(args); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + final AlertDialog dialog = new AlertDialog.Builder(getContext()) + .setPositiveButton(R.string.yes, this) + .setNegativeButton(R.string.sim_action_no_thanks, null) + .create(); + updateDialog(dialog); + return dialog; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.DIALOG_AUTO_DATA_SWITCH; + } + + /** update dialog */ + public void updateDialog(AlertDialog dialog) { + Log.d(TAG, "Dialog updated, dismiss status: " + mWasDismissed); + + if (mWasDismissed) { + return; + } + + if (dialog == null) { + Log.d(TAG, "Dialog is null."); + dismiss(); + return; + } + + // Set message + View content = LayoutInflater.from(getContext()).inflate( + R.layout.sim_confirm_dialog_multiple_enabled_profiles_supported, null); + TextView dialogMessage = content != null ? content.findViewById(R.id.msg) : null; + final String message = getMessage(); + if (TextUtils.isEmpty(message) || dialogMessage == null) { + onDismiss(dialog); + return; + } + dialogMessage.setText(message); + dialogMessage.setVisibility(View.VISIBLE); + dialog.setView(content); + + // Set title + View titleView = LayoutInflater.from(getContext()).inflate( + R.layout.sim_confirm_dialog_title_multiple_enabled_profiles_supported, null); + TextView titleTextView = titleView.findViewById(R.id.title); + titleTextView.setText(getContext().getString(getTitleResId())); + dialog.setCustomTitle(titleTextView); + } + + /** + * @return The message of the dialog. {@code null} if the dialog shouldn't be displayed. + */ + @VisibleForTesting + protected String getMessage() { + int ddsSubId = getDefaultDataSubId(); + if (ddsSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return null; + Log.d(TAG, "DDS SubId: " + ddsSubId); + + SubscriptionManager subscriptionManager = getSubscriptionManager(); + List activeSubscriptions = subscriptionManager + .getActiveSubscriptionInfoList(); + if (activeSubscriptions == null) return null; + + // Find if a backup data sub exists. + SubscriptionInfo backupSubInfo = activeSubscriptions.stream() + .filter(subInfo -> subInfo.getSubscriptionId() != ddsSubId) + .findFirst() + .orElse(null); + if (backupSubInfo == null) return null; + mBackupDataSubId = backupSubInfo.getSubscriptionId(); + + // Check if auto data switch is already enabled + final TelephonyManager telephonyManager = getTelephonyManagerForSub(mBackupDataSubId); + if (telephonyManager == null) { + Log.d(TAG, "telephonyManager for " + mBackupDataSubId + " is null"); + return null; + } + if (telephonyManager.isMobileDataPolicyEnabled( + TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)) { + Log.d(TAG, "AUTO_DATA_SWITCH already enabled"); + return null; + } + + Log.d(TAG, "Backup data sub Id: " + mBackupDataSubId); + // The description of the feature + String message = + getContext().getString( + R.string.enable_auto_data_switch_dialog_message, + SubscriptionUtil.getUniqueSubscriptionDisplayName( + backupSubInfo, getContext())); + UserManager userManager = getUserManager(); + if (userManager == null) return message; + + // If one of the sub is dedicated to work profile(enterprise-managed), which means we might + // switching between personal & work profile, append a warning to the message. + UserHandle ddsUserHandle = subscriptionManager.getSubscriptionUserHandle(ddsSubId); + UserHandle nDdsUserHandle = subscriptionManager.getSubscriptionUserHandle(mBackupDataSubId); + boolean isDdsManaged = ddsUserHandle != null && userManager.isManagedProfile( + ddsUserHandle.getIdentifier()); + boolean isNDdsManaged = nDdsUserHandle != null && userManager.isManagedProfile( + nDdsUserHandle.getIdentifier()); + Log.d(TAG, "isDdsManaged= " + isDdsManaged + " isNDdsManaged=" + isNDdsManaged); + if (isDdsManaged ^ isNDdsManaged) { + message += getContext().getString( + R.string.auto_data_switch_dialog_managed_profile_warning); + } + + return message; + } + + @Override + public void updateDialog() { + updateDialog((AlertDialog) getDialog()); + } + + @Override + public void onClick(DialogInterface dialog, int buttonClicked) { + if (buttonClicked != DialogInterface.BUTTON_POSITIVE) { + return; + } + final SimDialogActivity activity = (SimDialogActivity) getActivity(); + if (mBackupDataSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + activity.onSubscriptionSelected(getDialogType(), mBackupDataSubId); + } + } + + private TelephonyManager getTelephonyManagerForSub(int subId) { + return getContext().getSystemService(TelephonyManager.class) + .createForSubscriptionId(subId); + } + + private SubscriptionManager getSubscriptionManager() { + return getContext().getSystemService(SubscriptionManager.class); + } + + @VisibleForTesting + protected int getDefaultDataSubId() { + return SubscriptionManager.getDefaultDataSubscriptionId(); + } + + private UserManager getUserManager() { + return getContext().getSystemService(UserManager.class); + } +} diff --git a/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java b/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java index b2ca6210a86..6ac0067d382 100644 --- a/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java +++ b/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java @@ -72,14 +72,31 @@ public class SelectSpecificDataSimDialogFragment extends SimDialogFragment imple } @Override - public void onClick(DialogInterface dialog, int buttonClicked) { - if (buttonClicked != DialogInterface.BUTTON_POSITIVE) { - return; + public void onDismiss(@NonNull DialogInterface dialog) { + Log.d(TAG, "Dialog onDismiss, dismiss status: " + mWasDismissed); + if (!mWasDismissed) { + // This dialog might be called onDismiss twice due to first time called by onDismiss() + // as a consequence of user action. We need this fragment alive so the activity + // doesn't end, which allows the following dialog to attach. Upon the second dialog + // dismiss, this fragment is removed from SimDialogActivity.onFragmentDismissed to + // end the activity. + mWasDismissed = true; + final SimDialogActivity activity = (SimDialogActivity) getActivity(); + activity.showEnableAutoDataSwitchDialog(); + // Not using super.onDismiss because it will result in an immediate end of the activity, + // before the second auto data switch dialog can attach. + if (getDialog() != null) getDialog().dismiss(); } + } + + @Override + public void onClick(DialogInterface dialog, int buttonClicked) { final SimDialogActivity activity = (SimDialogActivity) getActivity(); - final SubscriptionInfo info = getTargetSubscriptionInfo(); - if (info != null) { - activity.onSubscriptionSelected(getDialogType(), info.getSubscriptionId()); + if (buttonClicked == DialogInterface.BUTTON_POSITIVE) { + final SubscriptionInfo info = getTargetSubscriptionInfo(); + if (info != null) { + activity.onSubscriptionSelected(getDialogType(), info.getSubscriptionId()); + } } } diff --git a/src/com/android/settings/sim/SimDialogActivity.java b/src/com/android/settings/sim/SimDialogActivity.java index 464ba9b4a51..a1258be64b0 100644 --- a/src/com/android/settings/sim/SimDialogActivity.java +++ b/src/com/android/settings/sim/SimDialogActivity.java @@ -16,8 +16,6 @@ package com.android.settings.sim; -import static android.content.Context.MODE_PRIVATE; - import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; @@ -61,6 +59,8 @@ public class SimDialogActivity extends FragmentActivity { public static final int SMS_PICK_FOR_MESSAGE = 4; // Dismiss the current dialog and finish the activity. public static final int PICK_DISMISS = 5; + // Show auto data switch dialog(when user enables multi-SIM) + public static final int ENABLE_AUTO_DATA_SWITCH = 6; @Override protected void onCreate(Bundle savedInstanceState) { @@ -122,7 +122,7 @@ public class SimDialogActivity extends FragmentActivity { private SimDialogFragment createFragment(int dialogType) { switch (dialogType) { case DATA_PICK: - return getDataPickDialogFramgent(); + return getDataPickDialogFragment(); case CALLS_PICK: return CallsSimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_calls, @@ -141,12 +141,14 @@ public class SimDialogActivity extends FragmentActivity { return SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_sms, false /* includeAskEveryTime */, false /* isCancelItemShowed */); + case ENABLE_AUTO_DATA_SWITCH: + return EnableAutoDataSwitchDialogFragment.newInstance(); default: throw new IllegalArgumentException("Invalid dialog type " + dialogType + " sent."); } } - private SimDialogFragment getDataPickDialogFramgent() { + private SimDialogFragment getDataPickDialogFragment() { if (SubscriptionManager.getDefaultDataSubscriptionId() == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return SimListDialogFragment.newInstance(DATA_PICK, R.string.select_sim_for_data, @@ -181,15 +183,40 @@ public class SimDialogActivity extends FragmentActivity { intent.putExtra(RESULT_SUB_ID, subId); setResult(Activity.RESULT_OK, intent); break; + case ENABLE_AUTO_DATA_SWITCH: + onEnableAutoDataSwitch(subId); + break; default: throw new IllegalArgumentException( "Invalid dialog type " + dialogType + " sent."); } } + /** + * Show dialog prompting the user to enable auto data switch + */ + public void showEnableAutoDataSwitchDialog() { + final FragmentManager fragmentManager = getSupportFragmentManager(); + SimDialogFragment fragment = createFragment(ENABLE_AUTO_DATA_SWITCH); + fragment.show(fragmentManager, Integer.toString(ENABLE_AUTO_DATA_SWITCH)); + } + + /** + * @param subId The sub Id to enable auto data switch + */ + public void onEnableAutoDataSwitch(int subId) { + Log.d(TAG, "onEnableAutoDataSwitch subId:" + subId); + final TelephonyManager telephonyManager = getSystemService( + TelephonyManager.class).createForSubscriptionId(subId); + telephonyManager.setMobileDataPolicyEnabled( + TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true); + } + public void onFragmentDismissed(SimDialogFragment simDialogFragment) { final List fragments = getSupportFragmentManager().getFragments(); - if (fragments.size() == 1 && fragments.get(0) == simDialogFragment) { + if (fragments.size() == 1 && fragments.get(0) == simDialogFragment + || simDialogFragment.getDialogType() == ENABLE_AUTO_DATA_SWITCH) { + Log.d(TAG, "onFragmentDismissed dialogType:" + simDialogFragment.getDialogType()); finishAndRemoveTask(); } } @@ -200,7 +227,8 @@ public class SimDialogActivity extends FragmentActivity { TelephonyManager.class).createForSubscriptionId(subId); subscriptionManager.setDefaultDataSubId(subId); if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - telephonyManager.setDataEnabled(true); + telephonyManager.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER, + true); Toast.makeText(this, R.string.data_switch_started, Toast.LENGTH_LONG).show(); } } diff --git a/src/com/android/settings/sim/SimListDialogFragment.java b/src/com/android/settings/sim/SimListDialogFragment.java index 245d31e8980..5b84d7b67c0 100644 --- a/src/com/android/settings/sim/SimListDialogFragment.java +++ b/src/com/android/settings/sim/SimListDialogFragment.java @@ -109,16 +109,18 @@ public class SimListDialogFragment extends SimDialogFragment { * @param selectionIndex the index of item in the list. */ public void onClick(int selectionIndex) { + final SimDialogActivity activity = (SimDialogActivity) getActivity(); if (selectionIndex >= 0 && selectionIndex < mSubscriptions.size()) { int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; final SubscriptionInfo subscription = mSubscriptions.get(selectionIndex); if (subscription != null) { subId = subscription.getSubscriptionId(); } - final SimDialogActivity activity = (SimDialogActivity) getActivity(); activity.onSubscriptionSelected(getDialogType(), subId); } - dismiss(); + Log.d(TAG, "Start showing auto data switch dialog"); + activity.showEnableAutoDataSwitchDialog(); + if (getDialog() != null) getDialog().dismiss(); } protected List getCurrentSubscriptions() { diff --git a/tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java b/tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java new file mode 100644 index 00000000000..ad60d06dc04 --- /dev/null +++ b/tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java @@ -0,0 +1,147 @@ +/* + * 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. + */ + +package com.android.settings.sim; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.os.UserHandle; +import android.os.UserManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import com.android.settings.R; +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Arrays; +import java.util.Collections; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowAlertDialogCompat.class) +public class EnableAutoDataSwitchDialogFragmentTest + extends SimDialogFragmentTestBase { + private static final String SUMMARY = "fake summary"; + private static final String WARNING = "fake warning"; + + // Mock + @Mock + private Context mContext; + @Mock + private SubscriptionManager mSubscriptionManager; + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private UserManager mUserManager; + + @Before + public void setUp() { + super.setUp(); + mFragment = spy(EnableAutoDataSwitchDialogFragment.newInstance()); + doReturn(mContext).when(mFragment).getContext(); + + doReturn(mSubscriptionManager).when(mContext).getSystemService(SubscriptionManager.class); + doReturn(mTelephonyManager).when(mContext).getSystemService(TelephonyManager.class); + doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt()); + doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); + + doReturn(SIM1_ID).when(mFragment).getDefaultDataSubId(); + doReturn(Arrays.asList(mSim1, mSim2)).when(mSubscriptionManager) + .getActiveSubscriptionInfoList(); + doReturn(true).when(mUserManager) + .isManagedProfile(UserHandle.MIN_SECONDARY_USER_ID); + + doReturn(SUMMARY).when(mContext).getString( + eq(R.string.enable_auto_data_switch_dialog_message), any()); + doReturn(WARNING).when(mContext).getString( + R.string.auto_data_switch_dialog_managed_profile_warning); + } + + @After + public void tearDown() { + mFragment = null; + } + + @Test + public void updateDialog_getMessage_noDdsExists() { + doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mFragment).getDefaultDataSubId(); + String msg = mFragment.getMessage(); + assertThat(msg).isEqualTo(null); + } + + @Test + public void updateDialog_getMessage_noBackupSubExists() { + doReturn(Collections.singletonList(mSim1)).when(mSubscriptionManager) + .getActiveSubscriptionInfoList(); + String msg = mFragment.getMessage(); + assertThat(msg).isEqualTo(null); + } + + @Test + public void updateDialog_getMessage_autoSwitchAlreadyEnabled() { + doReturn(true).when(mTelephonyManager).isMobileDataPolicyEnabled( + TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH); + String msg = mFragment.getMessage(); + assertThat(msg).isEqualTo(null); + } + + @Test + public void updateDialog_getMessage_noManagedProfile() { + UserHandle userHandle = UserHandle.of(UserHandle.USER_NULL); + UserHandle userHandle2 = UserHandle.of(UserHandle.USER_SYSTEM); + doReturn(userHandle).when(mSubscriptionManager).getSubscriptionUserHandle(SIM1_ID); + doReturn(userHandle2).when(mSubscriptionManager).getSubscriptionUserHandle(SIM2_ID); + String msg = mFragment.getMessage(); + assertThat(msg).contains(SUMMARY); + assertThat(msg).doesNotContain(WARNING); + } + + @Test + public void updateDialog_getMessage_hasManagedProfile() { + UserHandle userHandle = UserHandle.of(UserHandle.USER_NULL); + UserHandle userHandle2 = UserHandle.of(UserHandle.MIN_SECONDARY_USER_ID); + doReturn(userHandle).when(mSubscriptionManager).getSubscriptionUserHandle(SIM1_ID); + doReturn(userHandle2).when(mSubscriptionManager).getSubscriptionUserHandle(SIM2_ID); + String msg = mFragment.getMessage(); + assertThat(msg).contains(SUMMARY); + assertThat(msg).contains(WARNING); + } + + @Test + public void updateDialog_getMessage_BothManagedProfile() { + UserHandle userHandle = UserHandle.of(UserHandle.MIN_SECONDARY_USER_ID); + UserHandle userHandle2 = UserHandle.of(UserHandle.MIN_SECONDARY_USER_ID); + doReturn(userHandle).when(mSubscriptionManager).getSubscriptionUserHandle(SIM1_ID); + doReturn(userHandle2).when(mSubscriptionManager).getSubscriptionUserHandle(SIM2_ID); + String msg = mFragment.getMessage(); + assertThat(msg).contains(SUMMARY); + assertThat(msg).doesNotContain(WARNING); + } +} From c54771a165108f221fe8baa011a2ecd0aad41e18 Mon Sep 17 00:00:00 2001 From: Joshua McCloskey Date: Sat, 14 Jan 2023 19:18:29 +0000 Subject: [PATCH 05/18] Set default value for performant auth. Test: Manually verified its default to enabled. Bug: 265264294 Change-Id: Ibbad0165f4ef396be70d3a566ca77597c2bcbb8e --- ...rprintSettingsRequireScreenOnToAuthPreferenceController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java index 65070833e09..52e6d135abc 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java @@ -58,7 +58,7 @@ public class FingerprintSettingsRequireScreenOnToAuthPreferenceController getUserHandle()); if (toReturn == -1) { toReturn = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0; + com.android.internal.R.bool.config_performantAuthDefault) ? 1 : 0; Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED, toReturn, getUserHandle()); } From ef1959a4e735eb343ba11dd4cfd8895086d4f5fc Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Sun, 15 Jan 2023 19:15:49 -0800 Subject: [PATCH 06/18] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I6d5a9e79cae81b0121a7aca947c96aeb9caa8234 --- res/values-ko/arrays.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values-ko/arrays.xml b/res/values-ko/arrays.xml index 41f81614012..89fc7ad6d1f 100644 --- a/res/values-ko/arrays.xml +++ b/res/values-ko/arrays.xml @@ -88,7 +88,7 @@ "나쁨" "매우 약함" - "양호" + "약함" "좋음" "우수함" From 90794305967e1a14ef8b0d4c0bbab02df406274a Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Sun, 15 Jan 2023 19:17:23 -0800 Subject: [PATCH 07/18] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I53a234d7805064773be4dc133dbb7d6c81ae7075 --- res/values-ko/arrays.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values-ko/arrays.xml b/res/values-ko/arrays.xml index 13371c3a5f3..7f1e36da436 100644 --- a/res/values-ko/arrays.xml +++ b/res/values-ko/arrays.xml @@ -138,7 +138,7 @@ "나쁨" "매우 약함" - "양호" + "약함" "좋음" "우수함" From c28daecbc58603c66c6f0e7dc6f2d9faf81c2222 Mon Sep 17 00:00:00 2001 From: Kuan Wang Date: Mon, 16 Jan 2023 18:40:47 +0800 Subject: [PATCH 08/18] Use battery unplugging event to compute the full charge start time on Pixel devices. Test: make RunSettingsRoboTests + manual Bug: 256124406 Change-Id: I80b33db6e25ac0c693c50ddf93b6343c0fea942f --- AndroidManifest.xml | 3 +- .../fuelgauge/PowerUsageFeatureProvider.java | 7 +- .../PowerUsageFeatureProviderImpl.java | 5 + .../BatteryUsageBroadcastReceiver.java | 26 +++++- .../BatteryUsageBroadcastReceiverTest.java | 93 ++++++++++++++++++- 5 files changed, 128 insertions(+), 6 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index eed87ed49ef..0fb6dc81334 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3051,11 +3051,12 @@ android:exported="false" android:authorities="${applicationId}.battery.usage.bugreport"/> - + diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java index c151517d6b4..66e09a9cc3b 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java @@ -133,10 +133,15 @@ public interface PowerUsageFeatureProvider { boolean delayHourlyJobWhenBooting(); /** - * Gets a intent for one time bypass charge limited to resume charging. + * Gets an intent for one time bypass charge limited to resume charging. */ Intent getResumeChargeIntent(boolean isDockDefender); + /** + * Returns the intent action used to mark as the full charge start event. + */ + String getFullChargeIntentAction(); + /** * Returns {@link Set} for the system component ids which are combined into others */ diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java index 657e5c6018b..e0af88a694e 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java @@ -147,6 +147,11 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider return null; } + @Override + public String getFullChargeIntentAction() { + return Intent.ACTION_BATTERY_LEVEL_CHANGED; + } + @Override public boolean isExtraDefend() { return false; diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java index 817d367ca11..9c7ec35fb4a 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java @@ -26,6 +26,7 @@ import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.fuelgauge.BatteryStatus; import java.time.Duration; @@ -36,6 +37,9 @@ public final class BatteryUsageBroadcastReceiver extends BroadcastReceiver { /** An intent action to request Settings to clear cache data. */ public static final String ACTION_CLEAR_BATTERY_CACHE_DATA = "com.android.settings.battery.action.CLEAR_BATTERY_CACHE_DATA"; + /** An intent action to request Settings to clear cache data. */ + public static final String ACTION_BATTERY_UNPLUGGING = + "com.android.settings.battery.action.ACTION_BATTERY_UNPLUGGING"; @VisibleForTesting static long sBroadcastDelayFromBoot = Duration.ofMinutes(40).toMillis(); @@ -51,9 +55,27 @@ public final class BatteryUsageBroadcastReceiver extends BroadcastReceiver { return; } Log.d(TAG, "onReceive:" + intent.getAction()); + final String fullChargeIntentAction = FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context) + .getFullChargeIntentAction(); switch (intent.getAction()) { case Intent.ACTION_BATTERY_LEVEL_CHANGED: - tryToFetchUsageData(context); + // Only when fullChargeIntentAction is ACTION_BATTERY_LEVEL_CHANGED, + // ACTION_BATTERY_LEVEL_CHANGED will be considered as the full charge event and then + // start usage events fetching. + if (Intent.ACTION_BATTERY_LEVEL_CHANGED.equals(fullChargeIntentAction)) { + Log.d(TAG, "fetch data because of event: ACTION_BATTERY_LEVEL_CHANGED"); + tryToFetchUsageData(context); + } + break; + case ACTION_BATTERY_UNPLUGGING: + // Only when fullChargeIntentAction is ACTION_POWER_DISCONNECTED, + // ACTION_BATTERY_UNPLUGGING will be considered as the full charge event and then + // start usage events fetching. + if (Intent.ACTION_POWER_DISCONNECTED.equals(fullChargeIntentAction)) { + Log.d(TAG, "fetch data because of event: ACTION_POWER_DISCONNECTED"); + tryToFetchUsageData(context); + } break; case ACTION_CLEAR_BATTERY_CACHE_DATA: if (sIsDebugMode) { @@ -74,7 +96,7 @@ public final class BatteryUsageBroadcastReceiver extends BroadcastReceiver { final long broadcastDelay = sBroadcastDelayFromBoot - SystemClock.elapsedRealtime(); // If current boot time is smaller than expected delay, cancel sending the broadcast. if (broadcastDelay > 0) { - Log.d(TAG, "cancel sendBroadcastToFetchUsageData when broadcastDelay is" + Log.d(TAG, "cancel sendBroadcastToFetchUsageData when broadcastDelay is " + broadcastDelay + "ms."); return; } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java index addfd9bfdb9..60481975429 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; @@ -29,6 +30,8 @@ import android.os.BatteryManager; import android.os.SystemClock; import android.text.format.DateUtils; +import com.android.settings.testutils.FakeFeatureFactory; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,6 +45,8 @@ public final class BatteryUsageBroadcastReceiverTest { private Context mContext; private BatteryUsageBroadcastReceiver mBatteryUsageBroadcastReceiver; + private FakeFeatureFactory mFakeFeatureFactory; + @Mock private PackageManager mPackageManager; @@ -49,6 +54,7 @@ public final class BatteryUsageBroadcastReceiverTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); mBatteryUsageBroadcastReceiver = new BatteryUsageBroadcastReceiver(); doReturn(mPackageManager).when(mContext).getPackageManager(); } @@ -62,6 +68,8 @@ public final class BatteryUsageBroadcastReceiverTest { @Test public void onReceive_actionBatteryLevelChanged_notFetchUsageData_notFullCharged() { + when(mFakeFeatureFactory.powerUsageFeatureProvider.getFullChargeIntentAction()) + .thenReturn(Intent.ACTION_BATTERY_LEVEL_CHANGED); doReturn(getBatteryIntent(/*level=*/ 20, BatteryManager.BATTERY_STATUS_UNKNOWN)) .when(mContext).registerReceiver(any(), any()); @@ -72,7 +80,9 @@ public final class BatteryUsageBroadcastReceiverTest { } @Test - public void onReceive_actionBatteryLevelChanged_cancelFetchUsageData() { + public void onReceive_actionBatteryLevelChanged_notFetchUsageData_nearBooting() { + when(mFakeFeatureFactory.powerUsageFeatureProvider.getFullChargeIntentAction()) + .thenReturn(Intent.ACTION_BATTERY_LEVEL_CHANGED); // Make sure isCharged returns true. doReturn(getBatteryIntent(/*level=*/ 100, BatteryManager.BATTERY_STATUS_FULL)) .when(mContext).registerReceiver(any(), any()); @@ -87,7 +97,25 @@ public final class BatteryUsageBroadcastReceiverTest { } @Test - public void onReceive_actionBatteryLevelChanged_notFetchUsageData() { + public void onReceive_actionBatteryLevelChanged_notFetchUsageData_wrongAction() { + when(mFakeFeatureFactory.powerUsageFeatureProvider.getFullChargeIntentAction()) + .thenReturn(Intent.ACTION_POWER_DISCONNECTED); + // Make sure isCharged returns true. + doReturn(getBatteryIntent(/*level=*/ 100, BatteryManager.BATTERY_STATUS_UNKNOWN)) + .when(mContext).registerReceiver(any(), any()); + BatteryUsageBroadcastReceiver.sBroadcastDelayFromBoot = + SystemClock.elapsedRealtime() - 5 * DateUtils.MINUTE_IN_MILLIS; + + mBatteryUsageBroadcastReceiver.onReceive(mContext, + new Intent(Intent.ACTION_BATTERY_LEVEL_CHANGED)); + + assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isFalse(); + } + + @Test + public void onReceive_actionBatteryLevelChanged_fetchUsageData() { + when(mFakeFeatureFactory.powerUsageFeatureProvider.getFullChargeIntentAction()) + .thenReturn(Intent.ACTION_BATTERY_LEVEL_CHANGED); // Make sure isCharged returns true. doReturn(getBatteryIntent(/*level=*/ 100, BatteryManager.BATTERY_STATUS_UNKNOWN)) .when(mContext).registerReceiver(any(), any()); @@ -100,6 +128,67 @@ public final class BatteryUsageBroadcastReceiverTest { assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isTrue(); } + @Test + public void onReceive_actionBatteryUnplugging_notFetchUsageData_notFullCharged() { + when(mFakeFeatureFactory.powerUsageFeatureProvider.getFullChargeIntentAction()) + .thenReturn(Intent.ACTION_POWER_DISCONNECTED); + doReturn(getBatteryIntent(/*level=*/ 20, BatteryManager.BATTERY_STATUS_UNKNOWN)) + .when(mContext).registerReceiver(any(), any()); + + mBatteryUsageBroadcastReceiver.onReceive(mContext, + new Intent(BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING)); + + assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isFalse(); + } + + @Test + public void onReceive_actionBatteryUnplugging_notFetchUsageData_nearBooting() { + when(mFakeFeatureFactory.powerUsageFeatureProvider.getFullChargeIntentAction()) + .thenReturn(Intent.ACTION_POWER_DISCONNECTED); + // Make sure isCharged returns true. + doReturn(getBatteryIntent(/*level=*/ 100, BatteryManager.BATTERY_STATUS_FULL)) + .when(mContext).registerReceiver(any(), any()); + // Make sure broadcast will be sent with delay. + BatteryUsageBroadcastReceiver.sBroadcastDelayFromBoot = + SystemClock.elapsedRealtime() + 5 * DateUtils.MINUTE_IN_MILLIS; + + mBatteryUsageBroadcastReceiver.onReceive(mContext, + new Intent(BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING)); + + assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isFalse(); + } + + @Test + public void onReceive_actionBatteryUnplugging_notFetchUsageData_wrongAction() { + when(mFakeFeatureFactory.powerUsageFeatureProvider.getFullChargeIntentAction()) + .thenReturn(Intent.ACTION_BATTERY_LEVEL_CHANGED); + // Make sure isCharged returns true. + doReturn(getBatteryIntent(/*level=*/ 100, BatteryManager.BATTERY_STATUS_UNKNOWN)) + .when(mContext).registerReceiver(any(), any()); + BatteryUsageBroadcastReceiver.sBroadcastDelayFromBoot = + SystemClock.elapsedRealtime() - 5 * DateUtils.MINUTE_IN_MILLIS; + + mBatteryUsageBroadcastReceiver.onReceive(mContext, + new Intent(BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING)); + + assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isFalse(); + } + + @Test + public void onReceive_actionBatteryUnplugging_fetchUsageData() { + when(mFakeFeatureFactory.powerUsageFeatureProvider.getFullChargeIntentAction()) + .thenReturn(Intent.ACTION_POWER_DISCONNECTED); + // Make sure isCharged returns true. + doReturn(getBatteryIntent(/*level=*/ 100, BatteryManager.BATTERY_STATUS_UNKNOWN)) + .when(mContext).registerReceiver(any(), any()); + BatteryUsageBroadcastReceiver.sBroadcastDelayFromBoot = + SystemClock.elapsedRealtime() - 5 * DateUtils.MINUTE_IN_MILLIS; + + mBatteryUsageBroadcastReceiver.onReceive(mContext, + new Intent(BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING)); + + assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isTrue(); + } @Test public void onReceive_clearCacheIntentInDebugMode_clearBatteryCacheData() { From 5a0d63c5cd7f9a1d1a2c4916dc87ef91d80b7b11 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Tue, 17 Jan 2023 17:16:56 +0800 Subject: [PATCH 09/18] Update the About device icon for tablet Fix: 252728840 Test: visual Change-Id: I5e772ef30be82da276bc0d7bf97c586992723365 --- .../ic_settings_about_device.xml | 25 +++++++++++++++++++ res/drawable/ic_homepage_about.xml | 2 +- ..._info.xml => ic_settings_about_device.xml} | 0 res/xml/top_level_settings.xml | 2 +- 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 res/drawable-sw600dp/ic_settings_about_device.xml rename res/drawable/{ic_phone_info.xml => ic_settings_about_device.xml} (100%) diff --git a/res/drawable-sw600dp/ic_settings_about_device.xml b/res/drawable-sw600dp/ic_settings_about_device.xml new file mode 100644 index 00000000000..a7164823762 --- /dev/null +++ b/res/drawable-sw600dp/ic_settings_about_device.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_homepage_about.xml b/res/drawable/ic_homepage_about.xml index ef2136eed8f..459ec934265 100644 --- a/res/drawable/ic_homepage_about.xml +++ b/res/drawable/ic_homepage_about.xml @@ -29,5 +29,5 @@ android:height="@dimen/dashboard_tile_foreground_image_size" android:start="@dimen/dashboard_tile_foreground_image_inset" android:top="@dimen/dashboard_tile_foreground_image_inset" - android:drawable="@drawable/ic_phone_info" /> + android:drawable="@drawable/ic_settings_about_device" /> diff --git a/res/drawable/ic_phone_info.xml b/res/drawable/ic_settings_about_device.xml similarity index 100% rename from res/drawable/ic_phone_info.xml rename to res/drawable/ic_settings_about_device.xml diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml index 8c82b671688..d050a1f2747 100644 --- a/res/xml/top_level_settings.xml +++ b/res/xml/top_level_settings.xml @@ -196,7 +196,7 @@ Date: Sat, 14 Jan 2023 19:18:29 +0000 Subject: [PATCH 10/18] Set default value for performant auth. Test: Manually verified its default to enabled. Bug: 265264294 Change-Id: Ibbad0165f4ef396be70d3a566ca77597c2bcbb8e Merged-In: Ibbad0165f4ef396be70d3a566ca77597c2bcbb8e --- ...rprintSettingsRequireScreenOnToAuthPreferenceController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java index 65070833e09..52e6d135abc 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java @@ -58,7 +58,7 @@ public class FingerprintSettingsRequireScreenOnToAuthPreferenceController getUserHandle()); if (toReturn == -1) { toReturn = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0; + com.android.internal.R.bool.config_performantAuthDefault) ? 1 : 0; Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED, toReturn, getUserHandle()); } From bda3c06469836f7021bf716dcbc993124d63f5fc Mon Sep 17 00:00:00 2001 From: Diya Bera Date: Tue, 17 Jan 2023 20:45:16 +0000 Subject: [PATCH 11/18] No scrolling required to see find sensor animation Changed the string security_settings_sfps_enroll_find_sensor_message. Changed the layout of the animation. Test: Manually check the position of the animation Fixes: 265023019 Change-Id: Iae3100009e4430fbff7abc4bc3c1d4b9ffca6638 --- res/layout/sfps_enroll_find_sensor_layout.xml | 3 ++- res/values/dimens.xml | 3 +++ res/values/strings.xml | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/res/layout/sfps_enroll_find_sensor_layout.xml b/res/layout/sfps_enroll_find_sensor_layout.xml index f08986e113a..b8a656e5c3e 100644 --- a/res/layout/sfps_enroll_find_sensor_layout.xml +++ b/res/layout/sfps_enroll_find_sensor_layout.xml @@ -29,7 +29,8 @@ android:gravity="center_vertical" android:orientation="vertical" android:clipToPadding="false" - android:clipChildren="false"> + android:clipChildren="false" + android:layout_marginTop="@dimen/sfps_enroll_find_sensor_lottie_margin"> 0dp 0dp 0dp + + + 0dp diff --git a/res/values/strings.xml b/res/values/strings.xml index c861ed9c3c8..722b2e2e845 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -808,6 +808,12 @@ How to set up your fingerprint It\u2019s on the back of your phone. Use your index finger. + + The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the tablet. + + The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the device. + + The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the phone. The fingerprint sensor is on your screen. You\u2019ll capture your fingerprint on the next screen. From 1f824d5c18217bde2f659c9e206f893ff79066a2 Mon Sep 17 00:00:00 2001 From: Tetiana Meronyk Date: Tue, 17 Jan 2023 23:41:50 +0000 Subject: [PATCH 12/18] Add restriction to prevent user from being made an admin Bug: 261700461 Test: croot && make RunSettingsRoboTests -j40 ROBOTEST_FILTER="com.android.settings.users.UserDetailsSettingsTest" Change-Id: Ie518a17ae330e9b0864e5179dff7eb1d03f3937b --- .../android/settings/users/UserDetailsSettings.java | 5 +++-- .../settings/users/UserDetailsSettingsTest.java | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index f9b42209755..b84fc323edd 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -307,8 +307,9 @@ public class UserDetailsSettings extends SettingsPreferenceFragment mSwitchUserPref.setSelectable(true); mSwitchUserPref.setOnPreferenceClickListener(this); } - //TODO(b/261700461): remove preference for supervised user - if (mUserInfo.isMain() || mUserInfo.isGuest() || !UserManager.isMultipleAdminEnabled()) { + if (mUserInfo.isMain() || mUserInfo.isGuest() || !UserManager.isMultipleAdminEnabled() + || mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_GRANT_ADMIN, + mUserInfo.getUserHandle())) { removePreference(KEY_GRANT_ADMIN); } if (!mUserManager.isAdminUser()) { // non admin users can't remove users and allow calls diff --git a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java index 62846c1c87d..42c1e9f98b4 100644 --- a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java +++ b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java @@ -699,6 +699,16 @@ public class UserDetailsSettingsTest { verify(mFragment).removePreference(KEY_GRANT_ADMIN); } + @Test + public void initialize_restrictUserSelected_shouldNotShowGrantAdminPref_MultipleAdminEnabled() { + setupSelectedUser(); + ShadowUserManager.setIsMultipleAdminEnabled(true); + mUserManager.setUserRestriction(mUserInfo.getUserHandle(), + UserManager.DISALLOW_GRANT_ADMIN, true); + mFragment.initialize(mActivity, mArguments); + verify(mFragment).removePreference(KEY_GRANT_ADMIN); + } + @Test public void initialize_mainUserSelected_shouldShowGrantAdminPref_MultipleAdminEnabled() { setupSelectedMainUser(); From 546d49c3dfa655720c92ef36889e868b4f4135de Mon Sep 17 00:00:00 2001 From: Steven Lee Date: Wed, 21 Dec 2022 14:46:22 +0800 Subject: [PATCH 13/18] Make toggle permission provider list in SettingsSpaEnvironment extendable. Legal tracker bug: 262937731 Privacy tracker bug: 262936913 UX tracker bug: 262938050 Bug: 259436697 Bug: 263163538 Test: Settings builds. Change-Id: Ida26f11b101d49375bad85380458b018060d9fec --- .../settings/spa/SettingsSpaEnvironment.kt | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt index b4b4d6e09b5..55ea0c95861 100644 --- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt +++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt @@ -41,24 +41,27 @@ import com.android.settings.spa.system.SystemMainPageProvider import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository import com.android.settingslib.spa.framework.common.SpaEnvironment +import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListTemplate open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { + open fun getTogglePermissionAppListProviders(): List { + return listOf( + AllFilesAccessAppListProvider, + DisplayOverOtherAppsAppListProvider, + MediaManagementAppsAppListProvider, + ModifySystemSettingsAppListProvider, + PictureInPictureListProvider, + InstallUnknownAppsListProvider, + AlarmsAndRemindersAppListProvider, + WifiControlAppListProvider, + ) + } + override val pageProviderRepository = lazy { - val togglePermissionAppListTemplate = - TogglePermissionAppListTemplate( - allProviders = - listOf( - AllFilesAccessAppListProvider, - DisplayOverOtherAppsAppListProvider, - MediaManagementAppsAppListProvider, - ModifySystemSettingsAppListProvider, - PictureInPictureListProvider, - InstallUnknownAppsListProvider, - AlarmsAndRemindersAppListProvider, - WifiControlAppListProvider, - ), - ) + val togglePermissionAppListTemplate = TogglePermissionAppListTemplate( + allProviders = getTogglePermissionAppListProviders() + ) SettingsPageProviderRepository( allPageProviders = listOf( HomePageProvider, From bc561ec2dc7f5e0f1615122929dcc8e7606d3515 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Wed, 18 Jan 2023 07:06:28 +0000 Subject: [PATCH 14/18] [Settings] Do not show SIM if not supported Bug: 264510592 Test: manual Change-Id: Iaf250175361d86837a62330391c4b529639c3d9d --- res/xml/network_provider_internet.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml index 981afb3b3e6..f114dc6577f 100644 --- a/res/xml/network_provider_internet.xml +++ b/res/xml/network_provider_internet.xml @@ -52,7 +52,8 @@ settings:userRestriction="no_config_mobile_networks" settings:isPreferenceVisible="@bool/config_show_sim_info" settings:allowDividerAbove="true" - settings:useAdminDisabledSummary="true" /> + settings:useAdminDisabledSummary="true" + settings:searchable="@bool/config_show_sim_info"/> Date: Thu, 11 Aug 2022 23:51:24 +0800 Subject: [PATCH 15/18] [Languages] Add new information to system language page. - Add top Intro preference - Add footer preference - Add sub string to language picker - Modify title of app locale pages Bug: 242182598 Add information to system language page. Bug: 241600418 [Panlingual] Change strings on App languages Test: Maunal test Change-Id: I9bca8911e8e70c97c273294b3c9d1d73d4ea0cb2 --- res/layout/locale_drag_cell.xml | 11 +++ res/values/strings.xml | 24 +++++- res/xml/languages.xml | 42 +++++++++++ .../LocaleDragAndDropAdapter.java | 3 +- .../settings/localepicker/LocaleDragCell.java | 10 +++ .../LocaleHelperPreferenceController.java | 74 +++++++++++++++++++ .../localepicker/LocaleListEditor.java | 27 +++++-- .../LocaleHelperPreferenceControllerTest.java | 59 +++++++++++++++ 8 files changed, 238 insertions(+), 12 deletions(-) create mode 100644 res/xml/languages.xml create mode 100644 src/com/android/settings/localepicker/LocaleHelperPreferenceController.java create mode 100644 tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java diff --git a/res/layout/locale_drag_cell.xml b/res/layout/locale_drag_cell.xml index 7b932f39a02..47bf70a473e 100644 --- a/res/layout/locale_drag_cell.xml +++ b/res/layout/locale_drag_cell.xml @@ -56,6 +56,17 @@ android:layout_toStartOf="@+id/dragHandle" android:layout_below="@id/label"/> + + Languages + + Preferred language order + + + System language + Remove @@ -325,13 +331,13 @@ Preferred Language - App Languages + App languages Set the language for each app - App Language + App language Suggested languages @@ -354,6 +360,18 @@ Only apps that support language selection are shown here. + + Your system, apps, and websites use the first supported language from your preferred languages. + + + To select a language for each app, go to app language settings. + + + Learn more about languages + + + https://support.google.com/android?p=per_language_app_settings + Regional preferences @@ -407,7 +425,7 @@ Keep at least one preferred language - May not be available in some apps + Not available as system language Move up diff --git a/res/xml/languages.xml b/res/xml/languages.xml new file mode 100644 index 00000000000..0f455407645 --- /dev/null +++ b/res/xml/languages.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java index ab9110d01ab..b3c2e3071af 100644 --- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java +++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java @@ -43,7 +43,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; - class LocaleDragAndDropAdapter extends RecyclerView.Adapter { @@ -154,8 +153,10 @@ class LocaleDragAndDropAdapter final LocaleDragCell dragCell = holder.getLocaleDragCell(); final String label = feedItem.getFullNameNative(); final String description = feedItem.getFullNameInUiLanguage(); + dragCell.setLabelAndDescription(label, description); dragCell.setLocalized(feedItem.isTranslated()); + dragCell.setCurrentDefault(feedItem.getLocale().equals(Locale.getDefault())); dragCell.setMiniLabel(mNumberFormatter.format(i + 1)); dragCell.setShowCheckbox(mRemoveMode); dragCell.setShowMiniLabel(!mRemoveMode); diff --git a/src/com/android/settings/localepicker/LocaleDragCell.java b/src/com/android/settings/localepicker/LocaleDragCell.java index ea86189b018..2f4cfefefb1 100644 --- a/src/com/android/settings/localepicker/LocaleDragCell.java +++ b/src/com/android/settings/localepicker/LocaleDragCell.java @@ -33,6 +33,7 @@ class LocaleDragCell extends RelativeLayout { private CheckBox mCheckbox; private TextView mMiniLabel; private TextView mLocalized; + private TextView mCurrentDefault; private ImageView mDragHandle; public LocaleDragCell(Context context, AttributeSet attrs) { @@ -44,6 +45,7 @@ class LocaleDragCell extends RelativeLayout { super.onFinishInflate(); mLabel = (TextView) findViewById(R.id.label); mLocalized = (TextView) findViewById(R.id.l10nWarn); + mCurrentDefault = (TextView) findViewById(R.id.default_locale); mMiniLabel = (TextView) findViewById(R.id.miniLabel); mCheckbox = (CheckBox) findViewById(R.id.checkbox); mDragHandle = (ImageView) findViewById(R.id.dragHandle); @@ -100,6 +102,14 @@ class LocaleDragCell extends RelativeLayout { invalidate(); } + /** + * Indicate current locale is system default. + */ + public void setCurrentDefault(boolean isCurrentDefault) { + mCurrentDefault.setVisibility(isCurrentDefault ? VISIBLE : GONE); + invalidate(); + } + public ImageView getDragHandle() { return mDragHandle; } diff --git a/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java b/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java new file mode 100644 index 00000000000..05c740139cc --- /dev/null +++ b/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java @@ -0,0 +1,74 @@ +/* + * 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. + */ + +package com.android.settings.localepicker; + +import android.content.Context; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settingslib.HelpUtils; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.widget.FooterPreference; + +/** + * A controller to update current locale information of application. + */ +public class LocaleHelperPreferenceController extends AbstractPreferenceController { + private static final String TAG = LocaleHelperPreferenceController.class.getSimpleName(); + + private static final String KEY_FOOTER_LANGUAGE_PICKER = "footer_languages_picker"; + + public LocaleHelperPreferenceController(Context context) { + super(context); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_FOOTER_LANGUAGE_PICKER; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + FooterPreference footerPreference = screen.findPreference(getPreferenceKey()); + updateFooterPreference(footerPreference); + } + + @VisibleForTesting + void updateFooterPreference(FooterPreference footerPreference) { + if (footerPreference != null) { + footerPreference.setLearnMoreAction(v -> openLocaleLearnMoreLink()); + footerPreference.setLearnMoreText(mContext.getString( + R.string.desc_locale_helper_footer_general)); + } + } + + private void openLocaleLearnMoreLink() { + mContext.startActivity( + HelpUtils.getHelpIntent( + mContext, + mContext.getString(R.string.link_locale_picker_footer_learn_more), + /*backupContext=*/"")); + } +} diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java index 1d6fb47d76a..75ea768e232 100644 --- a/src/com/android/settings/localepicker/LocaleListEditor.java +++ b/src/com/android/settings/localepicker/LocaleListEditor.java @@ -36,6 +36,7 @@ import android.widget.TextView; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; +import androidx.preference.PreferenceScreen; import androidx.recyclerview.widget.RecyclerView; import com.android.internal.app.LocalePicker; @@ -47,6 +48,7 @@ import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexableRaw; import com.android.settingslib.utils.StringUtil; +import com.android.settingslib.widget.LayoutPreference; import java.util.ArrayList; import java.util.List; @@ -65,6 +67,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment { 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 LocaleDragAndDropAdapter mAdapter; private Menu mMenu; @@ -73,6 +76,9 @@ public class LocaleListEditor extends RestrictedSettingsFragment { private boolean mShowingRemoveDialog; private boolean mIsUiRestricted; + private LayoutPreference mLocalePickerPreference; + private LocaleHelperPreferenceController mLocaleHelperPreferenceController; + public LocaleListEditor() { super(DISALLOW_CONFIG_LOCALE); } @@ -87,6 +93,14 @@ public class LocaleListEditor extends RestrictedSettingsFragment { super.onCreate(savedInstanceState); setHasOptionsMenu(true); + addPreferencesFromResource(R.xml.languages); + final Activity activity = getActivity(); + activity.setTitle(R.string.language_picker_title); + mLocaleHelperPreferenceController = new LocaleHelperPreferenceController(activity); + final PreferenceScreen screen = getPreferenceScreen(); + mLocalePickerPreference = screen.findPreference(KEY_LANGUAGES_PICKER); + mLocaleHelperPreferenceController.displayPreference(screen); + LocaleStore.fillCache(this.getContext()); final List feedsList = getUserLocaleList(); mAdapter = new LocaleDragAndDropAdapter(this.getContext(), feedsList); @@ -94,11 +108,8 @@ public class LocaleListEditor extends RestrictedSettingsFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstState) { - final View result = super.onCreateView(inflater, container, savedInstState); - final View myLayout = inflater.inflate(R.layout.locale_order_list, (ViewGroup) result); - - configureDragAndDrop(myLayout); - return result; + configureDragAndDrop(mLocalePickerPreference); + return super.onCreateView(inflater, container, savedInstState); } @Override @@ -288,8 +299,8 @@ public class LocaleListEditor extends RestrictedSettingsFragment { return result; } - private void configureDragAndDrop(View view) { - final RecyclerView list = view.findViewById(R.id.dragList); + private void configureDragAndDrop(LayoutPreference layout) { + final RecyclerView list = layout.findViewById(R.id.dragList); final LocaleLinearLayoutManager llm = new LocaleLinearLayoutManager(getContext(), mAdapter); llm.setAutoMeasureEnabled(true); list.setLayoutManager(llm); @@ -298,7 +309,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment { mAdapter.setRecyclerView(list); list.setAdapter(mAdapter); - mAddLanguage = view.findViewById(R.id.add_language); + mAddLanguage = layout.findViewById(R.id.add_language); mAddLanguage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java b/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java new file mode 100644 index 00000000000..31b8e794480 --- /dev/null +++ b/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java @@ -0,0 +1,59 @@ +/* + * 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. + */ + +package com.android.settings.localepicker; + +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.os.Looper; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settingslib.widget.FooterPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class LocaleHelperPreferenceControllerTest { + private Context mContext; + private LocaleHelperPreferenceController mLocaleHelperPreferenceController; + + @Mock + private FooterPreference mMockFooterPreference; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mContext = ApplicationProvider.getApplicationContext(); + mLocaleHelperPreferenceController = new LocaleHelperPreferenceController(mContext); + } + + @Test + public void updateFooterPreference_setFooterPreference_hasClickAction() { + mLocaleHelperPreferenceController.updateFooterPreference(mMockFooterPreference); + verify(mMockFooterPreference).setLearnMoreText(anyString()); + } +} From 610c6928a1bd406ee387abb558a667151a6f5e48 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Wed, 18 Jan 2023 15:24:28 +0800 Subject: [PATCH 16/18] Fix b/265386376: Failed Test: BatteryOptimizeUtilsTest Bug: 265386376 Fix: 265386376 Test: presubmit Change-Id: Iac6129fd54f55055dc7dfc7bc0fe1d5f8ee381c1 --- .../fuelgauge/BatteryOptimizeUtilsTest.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java index 104a6773f09..5f70a5e84ae 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -150,8 +151,9 @@ public class BatteryOptimizeUtilsTest { @Test public void testSetAppUsageState_Restricted_verifyAction() throws Exception { // Sets the current mode as MODE_UNRESTRICTED. - mBatteryOptimizeUtils.mAllowListed = false; - mBatteryOptimizeUtils.mMode = AppOpsManager.MODE_ALLOWED; + when(mMockBackend.isAllowlisted(anyString())).thenReturn(true); + when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) + .thenReturn(AppOpsManager.MODE_ALLOWED); mBatteryOptimizeUtils.setAppUsageState(MODE_RESTRICTED, Action.UNKNOWN); TimeUnit.SECONDS.sleep(1); @@ -161,6 +163,12 @@ public class BatteryOptimizeUtilsTest { @Test public void testSetAppUsageState_Unrestricted_verifyAction() throws Exception { + // Sets the current mode as MODE_RESTRICTED. + when(mMockBackend.isAllowlisted(anyString())).thenReturn(false); + when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) + .thenReturn(AppOpsManager.MODE_IGNORED); + + mBatteryOptimizeUtils.setAppUsageState(MODE_UNRESTRICTED, Action.UNKNOWN); TimeUnit.SECONDS.sleep(1); @@ -169,6 +177,11 @@ public class BatteryOptimizeUtilsTest { @Test public void testSetAppUsageState_Optimized_verifyAction() throws Exception { + // Sets the current mode as MODE_UNRESTRICTED. + when(mMockBackend.isAllowlisted(anyString())).thenReturn(true); + when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) + .thenReturn(AppOpsManager.MODE_ALLOWED); + mBatteryOptimizeUtils.setAppUsageState(MODE_OPTIMIZED, Action.UNKNOWN); TimeUnit.SECONDS.sleep(1); @@ -178,14 +191,16 @@ public class BatteryOptimizeUtilsTest { @Test public void testSetAppUsageState_sameUnrestrictedMode_verifyNoAction() throws Exception { // Sets the current mode as MODE_UNRESTRICTED. - mBatteryOptimizeUtils.mAllowListed = true; - mBatteryOptimizeUtils.mMode = AppOpsManager.MODE_ALLOWED; + when(mMockBackend.isAllowlisted(anyString())).thenReturn(true); + when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) + .thenReturn(AppOpsManager.MODE_ALLOWED); mBatteryOptimizeUtils.setAppUsageState(MODE_UNRESTRICTED, Action.UNKNOWN); TimeUnit.SECONDS.sleep(1); - verifyNoInteractions(mMockBackend); - verifyNoInteractions(mMockBatteryUtils); + verify(mMockBatteryUtils, never()).setForceAppStandby(anyInt(), anyString(), anyInt()); + verify(mMockBackend, never()).addApp(anyString()); + verify(mMockBackend, never()).removeApp(anyString()); } @Test From 478c4e9e4a5aabb7f0b47f3801b034d820ef18d0 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Wed, 18 Jan 2023 16:07:52 +0800 Subject: [PATCH 17/18] Fix b/265617603: The 'breakdown by systems' list will change to 'breakdown by apps' after the user scrolls up and down. The root cause is that the spinner was initialized many times for every onBindViewHolder(). When the spinner is scrolled out of the screen and scrolled back, onBindViewHolder() is triggered. The spinner selection position was set to the mSavedSpinnerPosition. This is unexpected. Only the first initialization should set the saved position. This fix changes the spinner initialization to be only once. Bug: 265617603 Fix: 265617603 Test: manual Change-Id: I7ebd2b074cf6f560919ceec338eb8fcfaadf4d3a --- .../settings/fuelgauge/batteryusage/SpinnerPreference.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreference.java b/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreference.java index eaac00a2588..800dc1426e6 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreference.java +++ b/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreference.java @@ -56,6 +56,10 @@ public class SpinnerPreference extends Preference { @Override public void onBindViewHolder(PreferenceViewHolder view) { + if (mSpinner != null) { + return; + } + mSpinner = (Spinner) view.findViewById(R.id.spinner); mSpinner.setAdapter(new SpinnerAdapter(getContext(), mItems)); mSpinner.setSelection(mSavedSpinnerPosition); From c2a429f4f8960a2ecc22788ecafef1de61d9c5d3 Mon Sep 17 00:00:00 2001 From: Diya Bera Date: Tue, 17 Jan 2023 20:45:16 +0000 Subject: [PATCH 18/18] No scrolling required to see find sensor animation Changed the string security_settings_sfps_enroll_find_sensor_message. Changed the layout of the animation. Test: Manually check the position of the animation Fixes: 265023019 Change-Id: Iae3100009e4430fbff7abc4bc3c1d4b9ffca6638 Merged-In: Iae3100009e4430fbff7abc4bc3c1d4b9ffca6638 --- res/layout/sfps_enroll_find_sensor_layout.xml | 3 ++- res/values/dimens.xml | 3 +++ res/values/strings.xml | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/res/layout/sfps_enroll_find_sensor_layout.xml b/res/layout/sfps_enroll_find_sensor_layout.xml index f08986e113a..b8a656e5c3e 100644 --- a/res/layout/sfps_enroll_find_sensor_layout.xml +++ b/res/layout/sfps_enroll_find_sensor_layout.xml @@ -29,7 +29,8 @@ android:gravity="center_vertical" android:orientation="vertical" android:clipToPadding="false" - android:clipChildren="false"> + android:clipChildren="false" + android:layout_marginTop="@dimen/sfps_enroll_find_sensor_lottie_margin"> 0dp 0dp 0dp + + + 0dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 1dc49a48019..063e7c1f200 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1213,11 +1213,11 @@ It\u2019s on the back of your phone. Use your index finger. - The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the tablet.\n\nPressing the power button turns off the screen. + The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the tablet. - The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the device.\n\nPressing the power button turns off the screen. + The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the device. - The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the phone.\n\nPressing the power button turns off the screen. + The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the phone. The fingerprint sensor is on your screen. You\u2019ll capture your fingerprint on the next screen.