From 250edeead7625827110b6b944934fa470f7c0b47 Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Mon, 23 Jan 2023 15:00:42 +0100 Subject: [PATCH 01/12] [DO NOT MERGE] Enforce INTERACT_ACROSS_USERS_FULL permission for NotificationAccessDetails When using EXTRA_USER_HANDLE, check for INTERACT_ACROSS_USERS_FULL permission on calling package. Bug: 259385017 Test: 1. Build a test app that creates and starts an intent to NOTIFICATION_LISTENER_DETAIL_SETTINGS while setting the intent extra "android.intent.extra.user_handle" to UserHandle(secondaryUserId). 2. Create and switch to a secondary user Settings > System > Multiple users > Allow multiple users > Add user > Switch to New user 3. Open Settings > Notifications > Device & app notifications and choose an app from the list (uses android.permission.BIND_NOTIFICATION_LISTENER_SERVICE). Enable Device & app notifications for selected app and disable all attributed permissions. 4. Switch back to the Owner user. 5. Get the userId of the secondary user: adb shell pm list users. 6. Open the test app and enter the userId for the secondary user and the component name that uses android.permission.BIND_NOTIFICATION_LISTENER_SERVICE. 8. In the settings window that open, enable all 4 sub-options. 9. Switch to the secondary user and note that the all sub-options for the app are disabled. Change-Id: I875b9f2fc32c252acdcf8374a14067836e0f1ac6 (cherry picked from commit 99b8b4cd602affa6a8151c37f6a666ea0b7e0631) --- .../NotificationAccessDetails.java | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java index da25f17c138..f0fd85af345 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java @@ -16,14 +16,14 @@ package com.android.settings.applications.specialaccess.notificationaccess; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import static com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME; -import android.app.Activity; +import android.Manifest; import android.app.NotificationManager; import android.app.settings.SettingsEnums; import android.companion.ICompanionDeviceManager; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -37,8 +37,8 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; +import android.text.TextUtils; import android.util.Log; import android.util.Slog; @@ -48,7 +48,6 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.applications.AppInfoBase; -import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.bluetooth.Utils; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; @@ -208,8 +207,12 @@ public class NotificationAccessDetails extends DashboardFragment { } } if (intent != null && intent.hasExtra(Intent.EXTRA_USER_HANDLE)) { - mUserId = ((UserHandle) intent.getParcelableExtra( - Intent.EXTRA_USER_HANDLE)).getIdentifier(); + if (hasInteractAcrossUsersPermission()) { + mUserId = ((UserHandle) intent.getParcelableExtra( + Intent.EXTRA_USER_HANDLE)).getIdentifier(); + } else { + finish(); + } } else { mUserId = UserHandle.myUserId(); } @@ -224,6 +227,26 @@ public class NotificationAccessDetails extends DashboardFragment { } } + private boolean hasInteractAcrossUsersPermission() { + final String callingPackageName = + ((SettingsActivity) getActivity()).getInitialCallingPackage(); + + if (TextUtils.isEmpty(callingPackageName)) { + Log.w(TAG, "Not able to get calling package name for permission check"); + return false; + } + + if (getContext().getPackageManager().checkPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingPackageName) + != PERMISSION_GRANTED) { + Log.w(TAG, "Package " + callingPackageName + " does not have required permission " + + Manifest.permission.INTERACT_ACROSS_USERS_FULL); + return false; + } + + return true; + } + // Dialogs only have access to the parent fragment, not the controller, so pass the information // along to keep business logic out of this file public void disable(final ComponentName cn) { From c55cddc42272672063fc5199f5562845d94851c8 Mon Sep 17 00:00:00 2001 From: Patty Huang Date: Fri, 13 Jan 2023 18:58:27 +0800 Subject: [PATCH 02/12] Add LE Audio allow list feature switcher in the developer option menu Add a switcher to enable/disable LE audio allow list feature. The switcher could be enabled by setprop ro.bluetooth.leaudio_allow_list.supported=true Bug: 239768625 Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothLeAudioAllowListPreferenceControllerTest Change-Id: I290da870d952abd20bc25db7924fcc53ac39f880 --- res/values/strings.xml | 5 + res/xml/development_settings.xml | 5 + ...hLeAudioAllowListPreferenceController.java | 129 ++++++++++++++++++ .../DevelopmentSettingsDashboardFragment.java | 11 ++ ...udioAllowListPreferenceControllerTest.java | 106 ++++++++++++++ 5 files changed, 256 insertions(+) create mode 100644 src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 249c2de990d..4b55f5809df 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -365,6 +365,11 @@ Enables Bluetooth LE audio feature if the device supports LE audio hardware capabilities. + + Enable Bluetooth LE audio Allow List + + Enable Bluetooth LE audio allow list feature. + Media devices diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index b1b90171905..973d5b34def 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -324,6 +324,11 @@ android:title="@string/bluetooth_enable_leaudio" android:summary="@string/bluetooth_enable_leaudio_summary" /> + + diff --git a/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java new file mode 100644 index 00000000000..23506b3ccc3 --- /dev/null +++ b/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java @@ -0,0 +1,129 @@ +/* + * Copyright 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.development; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothStatusCodes; +import android.content.Context; +import android.os.SystemProperties; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.SwitchPreference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + +/** + * Preference controller to control Bluetooth LE audio feature + */ +public class BluetoothLeAudioAllowListPreferenceController + extends DeveloperOptionsPreferenceController + implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin { + + private static final String PREFERENCE_KEY = "bluetooth_enable_leaudio_allow_list"; + + private static final String LE_AUDIO_ALLOW_LIST_SWITCH_SUPPORT_PROPERTY = + "ro.bluetooth.leaudio_allow_list.supported"; + @VisibleForTesting + static final String LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY = + "persist.bluetooth.leaudio.enable_allow_list"; + + private static final String LE_AUDIO_DYNAMIC_SWITCH_PROPERTY = + "ro.bluetooth.leaudio_switcher.supported"; + @VisibleForTesting + static final String LE_AUDIO_DYNAMIC_ENABLED_PROPERTY = + "persist.bluetooth.leaudio_switcher.enabled"; + + @VisibleForTesting + BluetoothAdapter mBluetoothAdapter; + + private final DevelopmentSettingsDashboardFragment mFragment; + + @VisibleForTesting + boolean mChanged = false; + + public BluetoothLeAudioAllowListPreferenceController(Context context, + DevelopmentSettingsDashboardFragment fragment) { + super(context); + mFragment = fragment; + mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter(); + } + + @Override + public String getPreferenceKey() { + return PREFERENCE_KEY; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + BluetoothRebootDialog.show(mFragment); + mChanged = true; + return false; + } + + @Override + public void updateState(Preference preference) { + if (mBluetoothAdapter == null) { + return; + } + + int leAudioSupportedState = mBluetoothAdapter.isLeAudioSupported(); + boolean leAudioEnabled = false; + + if ((leAudioSupportedState == BluetoothStatusCodes.FEATURE_SUPPORTED) + || (leAudioSupportedState == BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED + && SystemProperties.getBoolean(LE_AUDIO_DYNAMIC_SWITCH_PROPERTY, false) + && SystemProperties.getBoolean(LE_AUDIO_DYNAMIC_ENABLED_PROPERTY, false))) { + leAudioEnabled = true; + } + + final boolean leAudioAllowListSupport = + SystemProperties.getBoolean(LE_AUDIO_ALLOW_LIST_SWITCH_SUPPORT_PROPERTY, false); + + if (leAudioEnabled && leAudioAllowListSupport) { + final boolean leAudioAllowListEnabled = + SystemProperties.getBoolean(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, false); + ((SwitchPreference) mPreference).setChecked(leAudioAllowListEnabled); + } else { + mPreference.setEnabled(false); + ((SwitchPreference) mPreference).setChecked(false); + } + } + + /** + * Called when the RebootDialog confirm is clicked. + */ + public void onRebootDialogConfirmed() { + if (!mChanged) { + return; + } + + final boolean leAudioAllowListEnabled = + SystemProperties.getBoolean(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, false); + SystemProperties.set(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, + Boolean.toString(!leAudioAllowListEnabled)); + } + + /** + * Called when the RebootDialog cancel is clicked. + */ + public void onRebootDialogCanceled() { + mChanged = false; + } +} diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index d92fb7fd99b..22f831e99ec 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -376,6 +376,11 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra getDevelopmentOptionsController( BluetoothLeAudioPreferenceController.class); leAudioFeatureController.onRebootDialogConfirmed(); + + final BluetoothLeAudioAllowListPreferenceController leAudioAllowListController = + getDevelopmentOptionsController( + BluetoothLeAudioAllowListPreferenceController.class); + leAudioAllowListController.onRebootDialogConfirmed(); } @Override @@ -393,6 +398,11 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra getDevelopmentOptionsController( BluetoothLeAudioPreferenceController.class); leAudioFeatureController.onRebootDialogCanceled(); + + final BluetoothLeAudioAllowListPreferenceController leAudioAllowListController = + getDevelopmentOptionsController( + BluetoothLeAudioAllowListPreferenceController.class); + leAudioAllowListController.onRebootDialogCanceled(); } @Override @@ -551,6 +561,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new BluetoothAvrcpVersionPreferenceController(context)); controllers.add(new BluetoothMapVersionPreferenceController(context)); controllers.add(new BluetoothLeAudioPreferenceController(context, fragment)); + controllers.add(new BluetoothLeAudioAllowListPreferenceController(context, fragment)); controllers.add(new BluetoothA2dpHwOffloadPreferenceController(context, fragment)); controllers.add(new BluetoothLeAudioHwOffloadPreferenceController(context, fragment)); controllers.add(new BluetoothMaxConnectedAudioDevicesPreferenceController(context)); diff --git a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceControllerTest.java new file mode 100644 index 00000000000..f4e52bada6a --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceControllerTest.java @@ -0,0 +1,106 @@ +/* + * 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.development; + +import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED; + +import static com.android.settings.development.BluetoothLeAudioAllowListPreferenceController + .LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.os.SystemProperties; + +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class BluetoothLeAudioAllowListPreferenceControllerTest { + + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private DevelopmentSettingsDashboardFragment mFragment; + + @Mock + private BluetoothAdapter mBluetoothAdapter; + + private Context mContext; + private SwitchPreference mPreference; + private BluetoothLeAudioPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mPreference = new SwitchPreference(mContext); + mController = spy(new BluetoothLeAudioPreferenceController(mContext, mFragment)); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())) + .thenReturn(mPreference); + mController.mBluetoothAdapter = mBluetoothAdapter; + mController.displayPreference(mPreferenceScreen); + when(mBluetoothAdapter.isLeAudioSupported()) + .thenReturn(FEATURE_SUPPORTED); + } + + @Test + public void onRebootDialogConfirmedAsLeAudioAllowListDisabled_shouldSwitchStatus() { + SystemProperties.set(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, Boolean.toString(false)); + mController.mChanged = true; + + mController.onRebootDialogConfirmed(); + final boolean mode = SystemProperties.getBoolean( + LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, false); + assertThat(mode).isFalse(); + } + + + @Test + public void onRebootDialogConfirmedAsLeAudioAllowListEnabled_shouldSwitchStatus() { + SystemProperties.set(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, Boolean.toString(true)); + mController.mChanged = true; + + mController.onRebootDialogConfirmed(); + final boolean status = SystemProperties.getBoolean( + LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, false); + assertThat(status).isTrue(); + } + + @Test + public void onRebootDialogCanceled_shouldNotSwitchStatus() { + SystemProperties.set(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, Boolean.toString(false)); + mController.mChanged = true; + + mController.onRebootDialogCanceled(); + final boolean status = SystemProperties.getBoolean( + LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, false); + assertThat(status).isFalse(); + } +} From 5fb0705664449e2a62c6219a8a417749620bb937 Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Mon, 6 Feb 2023 12:44:03 +0100 Subject: [PATCH 03/12] [DO NO MERGE] Enforce INTERACT_ACROSS_USERS_FULL permission for NotificationAccessDetails When using EXTRA_USER_HANDLE, check for INTERACT_ACROSS_USERS_FULL permission on calling package. Bug: 259385017 Test: 1. Build a test app that creates and starts an intent to NOTIFICATION_LISTENER_DETAIL_SETTINGS while setting the intent extra android.intent.extra.user_handle to UserHandle(secondaryUserId). 2. Create and switch to a secondary user Settings > System > Multiple users > Allow multiple users > Add user > Switch to New user 3. Open Settings > Notifications > Device & app notifications and choose an app from the list (uses android.permission.BIND_NOTIFICATION_LISTENER_SERVICE). Enable Device & app notifications for selected app and disable all attributed permissions. 4. Switch back to the Owner user. 5. Get the userId of the secondary user: adb shell pm list users. 6. Open the test app and enter the userId for the secondary user and the component name that uses android.permission.BIND_NOTIFICATION_LISTENER_SERVICE. 8. In the settings window that open, enable all 4 sub-options. 9. Switch to the secondary user and note that the all sub-options for the app are disabled. Change-Id: I875b9f2fc32c252acdcf8374a14067836e0f1ac6 Merged-In: I875b9f2fc32c252acdcf8374a14067836e0f1ac6 --- .../NotificationAccessDetails.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java index 58a6d7f5956..6a4c22efc7a 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java @@ -16,6 +16,9 @@ package com.android.settings.applications.specialaccess.notificationaccess; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.Manifest; import android.app.Activity; import android.app.NotificationManager; import android.app.settings.SettingsEnums; @@ -30,6 +33,7 @@ import android.os.Bundle; import android.os.UserManager; import android.provider.Settings; import android.service.notification.NotificationListenerService; +import android.text.TextUtils; import android.util.IconDrawableFactory; import android.util.Log; import android.util.Slog; @@ -42,6 +46,7 @@ import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.applications.AppInfoBase; import com.android.settings.overlay.FeatureFactory; +import com.android.settings.password.PasswordUtils; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.applications.AppUtils; @@ -139,6 +144,41 @@ public class NotificationAccessDetails extends AppInfoBase { return null; } + @Override + protected String retrieveAppEntry() { + final Bundle args = getArguments(); + final Intent intent = (args == null) ? + getIntent() : (Intent) args.getParcelable("intent"); + + if (intent != null && intent.hasExtra(Intent.EXTRA_USER_HANDLE)) { + if (!hasInteractAcrossUsersPermission()) { + finish(); + } + } + + return super.retrieveAppEntry(); + } + + private boolean hasInteractAcrossUsersPermission() { + final String callingPackageName = PasswordUtils.getCallingAppPackageName( + getActivity().getActivityToken()); + + if (TextUtils.isEmpty(callingPackageName)) { + Log.w(TAG, "Not able to get calling package name for permission check"); + return false; + } + + if (getContext().getPackageManager().checkPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingPackageName) + != PERMISSION_GRANTED) { + Log.w(TAG, "Package " + callingPackageName + " does not have required permission " + + Manifest.permission.INTERACT_ACROSS_USERS_FULL); + return false; + } + + return true; + } + public void updatePreference(SwitchPreference preference) { final CharSequence label = mPackageInfo.applicationInfo.loadLabel(mPm); preference.setChecked(isServiceEnabled(mComponentName)); From aa54f1b32be04c76487417a83af7418e5adde4f3 Mon Sep 17 00:00:00 2001 From: Angela Wang Date: Wed, 15 Feb 2023 03:00:24 +0000 Subject: [PATCH 04/12] Updates to use flash notification settings keys in settings provider Previously we hard-coded the settings key in FlashNotificationUtils. Change to use internal system settings key for retrieving flash notification settings from the database. Bug: 266775683 Test: make RunSettingsRoboTests ROBOTEST_FILTER=CameraFlashNotificationPreferenceControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=ScreenFlashNotificationPreferenceControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=FlashNotificationsPreviewPreferenceControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=FlashNotificationsUtilTest Change-Id: I1e7a0c4ed58505cfb3c06e43af866744fa1b2d41 --- ...FlashNotificationPreferenceController.java | 5 ++-- ...ificationsPreviewPreferenceController.java | 6 ++-- .../accessibility/FlashNotificationsUtil.java | 17 ++--------- ...FlashNotificationPreferenceController.java | 17 ++++++----- ...hNotificationPreferenceControllerTest.java | 21 ++++++++------ ...ationsPreviewPreferenceControllerTest.java | 12 +++----- .../FlashNotificationsUtilTest.java | 24 ++++++++-------- ...hNotificationPreferenceControllerTest.java | 28 ++++++++++--------- 8 files changed, 58 insertions(+), 72 deletions(-) diff --git a/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java b/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java index c630267e10c..f19795fa8de 100644 --- a/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java +++ b/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java @@ -18,7 +18,6 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.ON; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; import android.content.Context; import android.provider.Settings; @@ -45,13 +44,13 @@ public class CameraFlashNotificationPreferenceController extends TogglePreferenc @Override public boolean isChecked() { return Settings.System.getInt(mContext.getContentResolver(), - SETTING_KEY_CAMERA_FLASH_NOTIFICATION, OFF) != OFF; + Settings.System.CAMERA_FLASH_NOTIFICATION, OFF) != OFF; } @Override public boolean setChecked(boolean isChecked) { return Settings.System.putInt(mContext.getContentResolver(), - SETTING_KEY_CAMERA_FLASH_NOTIFICATION, (isChecked ? ON : OFF)); + Settings.System.CAMERA_FLASH_NOTIFICATION, (isChecked ? ON : OFF)); } @Override diff --git a/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java b/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java index 8774043c8d3..5a16a30bc9e 100644 --- a/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java +++ b/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java @@ -19,8 +19,6 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_START_PREVIEW; import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_SHORT_PREVIEW; import android.content.ContentResolver; @@ -95,10 +93,10 @@ public class FlashNotificationsPreviewPreferenceController extends @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_RESUME) { mContentResolver.registerContentObserver( - Settings.System.getUriFor(SETTING_KEY_CAMERA_FLASH_NOTIFICATION), + Settings.System.getUriFor(Settings.System.CAMERA_FLASH_NOTIFICATION), /* notifyForDescendants= */ false, mContentObserver); mContentResolver.registerContentObserver( - Settings.System.getUriFor(SETTING_KEY_SCREEN_FLASH_NOTIFICATION), + Settings.System.getUriFor(Settings.System.SCREEN_FLASH_NOTIFICATION), /* notifyForDescendants= */ false, mContentObserver); } else if (event == Lifecycle.Event.ON_PAUSE) { mContentResolver.unregisterContentObserver(mContentObserver); diff --git a/src/com/android/settings/accessibility/FlashNotificationsUtil.java b/src/com/android/settings/accessibility/FlashNotificationsUtil.java index 429936e41ba..544f835d8b5 100644 --- a/src/com/android/settings/accessibility/FlashNotificationsUtil.java +++ b/src/com/android/settings/accessibility/FlashNotificationsUtil.java @@ -16,8 +16,6 @@ package com.android.settings.accessibility; -import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; - import android.content.Context; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; @@ -43,19 +41,10 @@ class FlashNotificationsUtil { static final String EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE = "com.android.internal.intent.extra.FLASH_NOTIFICATION_PREVIEW_TYPE"; - static final String SETTING_KEY_CAMERA_FLASH_NOTIFICATION = - "camera_flash_notification"; - static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION = - "screen_flash_notification"; - static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR = - "screen_flash_notification_color_global"; - static final int TYPE_SHORT_PREVIEW = 0; static final int TYPE_LONG_PREVIEW = 1; - static final int DEFAULT_SCREEN_FLASH_COLOR = - ScreenFlashNotificationColor.YELLOW.mColorInt; - + static final int DEFAULT_SCREEN_FLASH_COLOR = ScreenFlashNotificationColor.YELLOW.mColorInt; @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -128,9 +117,9 @@ class FlashNotificationsUtil { final boolean isTorchAvailable = FlashNotificationsUtil.isTorchAvailable(context); final boolean isCameraFlashEnabled = Settings.System.getInt(context.getContentResolver(), - SETTING_KEY_CAMERA_FLASH_NOTIFICATION, State.OFF) != State.OFF; + Settings.System.CAMERA_FLASH_NOTIFICATION, State.OFF) != State.OFF; final boolean isScreenFlashEnabled = Settings.System.getInt(context.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION, State.OFF) != State.OFF; + Settings.System.SCREEN_FLASH_NOTIFICATION, State.OFF) != State.OFF; return ((isTorchAvailable && isCameraFlashEnabled) ? State.CAMERA : State.OFF) | (isScreenFlashEnabled ? State.SCREEN : State.OFF); diff --git a/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java b/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java index 546ae19eb88..2b96dcffb86 100644 --- a/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java +++ b/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java @@ -19,8 +19,6 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import static com.android.settings.accessibility.FlashNotificationsUtil.DEFAULT_SCREEN_FLASH_COLOR; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR; import android.content.Context; import android.graphics.Color; @@ -59,7 +57,7 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc @Override public boolean isChecked() { return Settings.System.getInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION, OFF) != OFF; + Settings.System.SCREEN_FLASH_NOTIFICATION, OFF) != OFF; } @Override @@ -67,7 +65,7 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc if (isChecked) checkAndSetInitialColor(); return Settings.System.putInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION, (isChecked ? ON : OFF)); + Settings.System.SCREEN_FLASH_NOTIFICATION, (isChecked ? ON : OFF)); } @Override @@ -79,7 +77,8 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc public CharSequence getSummary() { return FlashNotificationsUtil.getColorDescriptionText(mContext, Settings.System.getInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, DEFAULT_SCREEN_FLASH_COLOR)); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, + DEFAULT_SCREEN_FLASH_COLOR)); } @Override @@ -94,12 +93,12 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc if (getPreferenceKey().equals(preference.getKey()) && mParentFragment != null) { final int initialColor = Settings.System.getInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, DEFAULT_SCREEN_FLASH_COLOR); final Consumer consumer = color -> { Settings.System.putInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, color); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, color); refreshColorSummary(); }; @@ -115,10 +114,10 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc private void checkAndSetInitialColor() { if (Settings.System.getInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, Color.TRANSPARENT) + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, Color.TRANSPARENT) == Color.TRANSPARENT) { Settings.System.putInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, DEFAULT_SCREEN_FLASH_COLOR); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, DEFAULT_SCREEN_FLASH_COLOR); } } diff --git a/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java index db4e5210315..02ab32d6576 100644 --- a/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java @@ -16,7 +16,8 @@ package com.android.settings.accessibility; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; @@ -81,28 +82,30 @@ public class CameraFlashNotificationPreferenceControllerTest { @Test public void isChecked_setOff_assertFalse() { - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, OFF); assertThat(mController.isChecked()).isFalse(); } @Test public void isChecked_setOn_assertTrue() { - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, ON); assertThat(mController.isChecked()).isTrue(); } @Test - public void setChecked_setTrue_assertNotZero() { + public void setChecked_setTrue_assertNotOff() { mController.setChecked(true); - assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, - 0)).isNotEqualTo(0); + assertThat( + Settings.System.getInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, + OFF)).isNotEqualTo(OFF); } @Test - public void setChecked_setFalse_assertNotOne() { + public void setChecked_setFalse_assertNotOn() { mController.setChecked(false); - assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, - 1)).isNotEqualTo(1); + assertThat( + Settings.System.getInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, + OFF)).isNotEqualTo(ON); } @Test diff --git a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java index 5f1dfbb1e63..98da926b3f2 100644 --- a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java @@ -18,8 +18,6 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_START_PREVIEW; import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_LONG_PREVIEW; import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_SHORT_PREVIEW; import static com.android.settings.accessibility.ShadowFlashNotificationsUtils.setFlashNotificationsState; @@ -48,7 +46,6 @@ import androidx.test.core.app.ApplicationProvider; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -131,7 +128,6 @@ public class FlashNotificationsPreviewPreferenceControllerTest { verify(mPreference).setEnabled(eq(true)); } - @Ignore @Test public void testHandlePreferenceTreeClick_invalidPreference() { mController.handlePreferenceTreeClick(mock(Preference.class)); @@ -165,16 +161,16 @@ public class FlashNotificationsPreviewPreferenceControllerTest { public void onStateChanged_onResume_cameraUri_verifyRegister() { mController.onStateChanged(mock(LifecycleOwner.class), Lifecycle.Event.ON_RESUME); verify(mContentResolver).registerContentObserver( - eq(Settings.System.getUriFor(SETTING_KEY_CAMERA_FLASH_NOTIFICATION)), anyBoolean(), - eq(mController.mContentObserver)); + eq(Settings.System.getUriFor(Settings.System.CAMERA_FLASH_NOTIFICATION)), + anyBoolean(), eq(mController.mContentObserver)); } @Test public void onStateChanged_onResume_screenUri_verifyRegister() { mController.onStateChanged(mock(LifecycleOwner.class), Lifecycle.Event.ON_RESUME); verify(mContentResolver).registerContentObserver( - eq(Settings.System.getUriFor(SETTING_KEY_SCREEN_FLASH_NOTIFICATION)), anyBoolean(), - eq(mController.mContentObserver)); + eq(Settings.System.getUriFor(Settings.System.SCREEN_FLASH_NOTIFICATION)), + anyBoolean(), eq(mController.mContentObserver)); } @Test diff --git a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java index c5fe3a7c792..f943e3a3657 100644 --- a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java @@ -21,8 +21,8 @@ import static android.hardware.camera2.CameraCharacteristics.LENS_FACING; import static android.hardware.camera2.CameraCharacteristics.LENS_FACING_BACK; import static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import static com.android.settings.accessibility.FlashNotificationsUtil.getColorDescriptionText; import static com.android.settings.accessibility.FlashNotificationsUtil.getFlashNotificationsState; import static com.android.settings.accessibility.FlashNotificationsUtil.getScreenColor; @@ -156,8 +156,8 @@ public class FlashNotificationsUtilTest { @Test public void getFlashNotificationsState_torchPresent_cameraOff_screenOff_assertOff() { setTorchPresent(); - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0); - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, OFF); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, OFF); assertThat(getFlashNotificationsState(mContext)) .isEqualTo(FlashNotificationsUtil.State.OFF); @@ -166,8 +166,8 @@ public class FlashNotificationsUtilTest { @Test public void getFlashNotificationsState_torchNotPresent_cameraOn_screenOff_assertOff() { setTorchNotPresent(); - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, ON); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, OFF); assertThat(getFlashNotificationsState(mContext)) .isEqualTo(FlashNotificationsUtil.State.OFF); @@ -176,8 +176,8 @@ public class FlashNotificationsUtilTest { @Test public void getFlashNotificationsState_torchPresent_cameraOn_screenOff_assertCamera() { setTorchPresent(); - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, ON); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, OFF); assertThat(getFlashNotificationsState(mContext)) .isEqualTo(FlashNotificationsUtil.State.CAMERA); @@ -186,8 +186,8 @@ public class FlashNotificationsUtilTest { @Test public void getFlashNotificationsState_torchPresent_cameraOff_screenOn_assertScreen() { setTorchPresent(); - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0); - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, OFF); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, ON); assertThat(getFlashNotificationsState(mContext)) .isEqualTo(FlashNotificationsUtil.State.SCREEN); @@ -196,8 +196,8 @@ public class FlashNotificationsUtilTest { @Test public void testGetFlashNotificationsState_torchPresent_cameraOn_screenOn_assertCameraScreen() { setTorchPresent(); - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, ON); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, ON); assertThat(getFlashNotificationsState(mContext)) .isEqualTo(FlashNotificationsUtil.State.CAMERA_SCREEN); diff --git a/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java index 0662daa6fbb..5b8afe65ab5 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java @@ -16,9 +16,9 @@ package com.android.settings.accessibility; +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import static com.android.settings.accessibility.FlashNotificationsUtil.DEFAULT_SCREEN_FLASH_COLOR; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR; import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.google.common.truth.Truth.assertThat; @@ -114,48 +114,50 @@ public class ScreenFlashNotificationPreferenceControllerTest { @Test public void isChecked_setOff_assertFalse() { - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, OFF); assertThat(mController.isChecked()).isFalse(); } @Test public void isChecked_setOn_assertTrue() { - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, ON); assertThat(mController.isChecked()).isTrue(); } @Test public void setChecked_whenTransparentColor_setTrue_assertNotTransparentColor() { Settings.System.putInt(mContentResolver, - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, Color.TRANSPARENT); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, Color.TRANSPARENT); mController.setChecked(true); assertThat(Settings.System.getInt(mContentResolver, - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, 0)) - .isEqualTo(DEFAULT_SCREEN_FLASH_COLOR); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, 0)).isEqualTo( + DEFAULT_SCREEN_FLASH_COLOR); } @Test public void setChecked_whenNotTransparent_setTrue_assertSameColor() { Settings.System.putInt(mContentResolver, - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, 0x4D0000FF); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, 0x4D0000FF); mController.setChecked(true); assertThat(Settings.System.getInt(mContentResolver, - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, 0)) + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, 0)) .isEqualTo(0x4D0000FF); } @Test public void setChecked_setTrue_assertOn() { mController.setChecked(true); - assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, - 0)).isEqualTo(1); + assertThat( + Settings.System.getInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, + OFF)).isEqualTo(ON); } @Test public void setChecked_setFalse_assertOff() { mController.setChecked(false); - assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, - 1)).isEqualTo(0); + assertThat( + Settings.System.getInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, + OFF)).isEqualTo(OFF); } @Test From bc71ee5ecebfdaad91c234e3f9cd14cb6898a9ad Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Sat, 19 Nov 2022 22:28:47 -0800 Subject: [PATCH 05/12] Add getSubscriptionId API The old getSubId or getSubscriptionIds return multiple sub ids per slot index, which is not possible in today's implementation because the slot index here refers to the logical slot index, which is also known as phone id, or the logical modem index. In today's telephony, one logical phone can only have one subscription at one time, so does the modem. Bug: 239607619 Test: Manual Merged-In: I0a43d33daf523e6f0bea054c8965281bafbbcaa4 Change-Id: I0a43d33daf523e6f0bea054c8965281bafbbcaa4 --- .../sim/smartForwarding/DisableSmartForwardingTask.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/com/android/settings/sim/smartForwarding/DisableSmartForwardingTask.java b/src/com/android/settings/sim/smartForwarding/DisableSmartForwardingTask.java index d483dd12ed2..a1035dc8477 100644 --- a/src/com/android/settings/sim/smartForwarding/DisableSmartForwardingTask.java +++ b/src/com/android/settings/sim/smartForwarding/DisableSmartForwardingTask.java @@ -57,10 +57,6 @@ public class DisableSmartForwardingTask implements Runnable { } private int getSubId(int slotIndex) { - int[] subId = SubscriptionManager.getSubId(slotIndex); - if (subId != null && subId.length > 0) { - return subId[0]; - } - return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + return SubscriptionManager.getSubscriptionId(slotIndex); } } From 04eae5cc608cccb0b55f396be89d785fc82896ba Mon Sep 17 00:00:00 2001 From: tom hsu Date: Fri, 3 Mar 2023 20:00:27 +0800 Subject: [PATCH 06/12] Add confirmation dialog for system locale change. Bug: b/243511340 Test: atest passed. Change-Id: I7ef4ed4557a1064d078e49a372f11a573b81058a --- res/layout/locale_dialog.xml | 65 ++++++ res/values/strings.xml | 15 ++ .../localepicker/LocaleDialogFragment.java | 198 ++++++++++++++++++ .../LocaleDragAndDropAdapter.java | 69 +++++- .../localepicker/LocaleListEditor.java | 2 +- .../localepicker/LocaleRecyclerView.java | 2 +- .../LocaleDialogFragmentTest.java | 146 +++++++++++++ 7 files changed, 484 insertions(+), 13 deletions(-) create mode 100644 res/layout/locale_dialog.xml create mode 100644 src/com/android/settings/localepicker/LocaleDialogFragment.java create mode 100644 tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java diff --git a/res/layout/locale_dialog.xml b/res/layout/locale_dialog.xml new file mode 100644 index 00000000000..cbdb37eb23e --- /dev/null +++ b/res/layout/locale_dialog.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index bd0218608e6..d82498451cf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -372,6 +372,21 @@ https://support.google.com/android?p=per_language_app_settings + + Change system language to %s ? + + + Your device settings and regional preferences will change. + + + Change + + + %s not available + + + This language can’t be used as a system language, but you’ve let apps and websites know you prefer this language. + Regional preferences diff --git a/src/com/android/settings/localepicker/LocaleDialogFragment.java b/src/com/android/settings/localepicker/LocaleDialogFragment.java new file mode 100644 index 00000000000..63fc1792a46 --- /dev/null +++ b/src/com/android/settings/localepicker/LocaleDialogFragment.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2023 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.app.Activity; +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentManager; + +import com.android.internal.app.LocaleStore; +import com.android.settings.R; +import com.android.settings.RestrictedSettingsFragment; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +/** + * Create a dialog for system locale events. + */ +public class LocaleDialogFragment extends InstrumentedDialogFragment { + private static final String TAG = LocaleDialogFragment.class.getSimpleName(); + + static final int DIALOG_CONFIRM_SYSTEM_DEFAULT = 0; + static final int DIALOG_NOT_AVAILABLE_LOCALE = 1; + + static final String ARG_DIALOG_TYPE = "arg_dialog_type"; + static final String ARG_TARGET_LOCALE = "arg_target_locale"; + static final String ARG_RESULT_RECEIVER = "arg_result_receiver"; + + /** + * Show dialog + */ + public static void show( + @NonNull RestrictedSettingsFragment fragment, + int dialogType, + LocaleStore.LocaleInfo localeInfo) { + show(fragment, dialogType, localeInfo, null); + } + + /** + * Show dialog + */ + public static void show( + @NonNull RestrictedSettingsFragment fragment, + int dialogType, + LocaleStore.LocaleInfo localeInfo, + ResultReceiver resultReceiver) { + FragmentManager manager = fragment.getChildFragmentManager(); + Bundle args = new Bundle(); + args.putInt(ARG_DIALOG_TYPE, dialogType); + args.putSerializable(ARG_TARGET_LOCALE, localeInfo); + args.putParcelable(ARG_RESULT_RECEIVER, resultReceiver); + + LocaleDialogFragment localeDialogFragment = new LocaleDialogFragment(); + localeDialogFragment.setArguments(args); + localeDialogFragment.show(manager, TAG); + } + + @Override + public int getMetricsCategory() { + int dialogType = getArguments().getInt(ARG_DIALOG_TYPE); + switch (dialogType) { + case DIALOG_CONFIRM_SYSTEM_DEFAULT: + return SettingsEnums.DIALOG_SYSTEM_LOCALE_CHANGE; + case DIALOG_NOT_AVAILABLE_LOCALE: + return SettingsEnums.DIALOG_SYSTEM_LOCALE_UNAVAILABLE; + default: + return SettingsEnums.DIALOG_SYSTEM_LOCALE_CHANGE; + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + LocaleDialogController controller = new LocaleDialogController(this); + LocaleDialogController.DialogContent dialogContent = controller.getDialogContent(); + ViewGroup viewGroup = (ViewGroup) LayoutInflater.from(getContext()).inflate( + R.layout.locale_dialog, null); + setDialogTitle(viewGroup, dialogContent.mTitle); + setDialogMessage(viewGroup, dialogContent.mMessage); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()) + .setView(viewGroup); + if (!dialogContent.mPositiveButton.isEmpty()) { + builder.setPositiveButton(dialogContent.mPositiveButton, controller); + } + if (!dialogContent.mNegativeButton.isEmpty()) { + builder.setNegativeButton(dialogContent.mNegativeButton, controller); + } + return builder.create(); + } + + private static void setDialogTitle(View root, String content) { + TextView titleView = root.findViewById(R.id.dialog_title); + if (titleView == null) { + return; + } + titleView.setText(content); + } + + private static void setDialogMessage(View root, String content) { + TextView textView = root.findViewById(R.id.dialog_msg); + if (textView == null) { + return; + } + textView.setText(content); + } + + static class LocaleDialogController implements DialogInterface.OnClickListener { + private final Context mContext; + private final int mDialogType; + private final LocaleStore.LocaleInfo mLocaleInfo; + private final ResultReceiver mResultReceiver; + + LocaleDialogController( + @NonNull Context context, @NonNull LocaleDialogFragment dialogFragment) { + mContext = context; + Bundle arguments = dialogFragment.getArguments(); + mDialogType = arguments.getInt(ARG_DIALOG_TYPE); + mLocaleInfo = (LocaleStore.LocaleInfo) arguments.getSerializable( + ARG_TARGET_LOCALE); + mResultReceiver = (ResultReceiver) arguments.getParcelable(ARG_RESULT_RECEIVER); + } + + LocaleDialogController(@NonNull LocaleDialogFragment dialogFragment) { + this(dialogFragment.getContext(), dialogFragment); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (mResultReceiver != null && mDialogType == DIALOG_CONFIRM_SYSTEM_DEFAULT) { + Bundle bundle = new Bundle(); + bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT); + if (which == DialogInterface.BUTTON_POSITIVE) { + mResultReceiver.send(Activity.RESULT_OK, bundle); + } else if (which == DialogInterface.BUTTON_NEGATIVE) { + mResultReceiver.send(Activity.RESULT_CANCELED, bundle); + } + } + } + + @VisibleForTesting + DialogContent getDialogContent() { + DialogContent + dialogContent = new DialogContent(); + switch (mDialogType) { + case DIALOG_CONFIRM_SYSTEM_DEFAULT: + dialogContent.mTitle = String.format(mContext.getString( + R.string.title_change_system_locale), mLocaleInfo.getFullNameNative()); + dialogContent.mMessage = mContext.getString( + R.string.desc_notice_device_locale_settings_change); + dialogContent.mPositiveButton = mContext.getString( + R.string.button_label_confirmation_of_system_locale_change); + dialogContent.mNegativeButton = mContext.getString(R.string.cancel); + break; + case DIALOG_NOT_AVAILABLE_LOCALE: + dialogContent.mTitle = String.format(mContext.getString( + R.string.title_unavailable_locale), mLocaleInfo.getFullNameNative()); + dialogContent.mMessage = mContext.getString(R.string.desc_unavailable_locale); + dialogContent.mPositiveButton = mContext.getString(R.string.okay); + break; + default: + break; + } + return dialogContent; + } + + @VisibleForTesting + static class DialogContent { + String mTitle = ""; + String mMessage = ""; + String mPositiveButton = ""; + String mNegativeButton = ""; + } + } +} diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java index b3c2e3071af..bece4140153 100644 --- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java +++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java @@ -16,10 +16,14 @@ package com.android.settings.localepicker; +import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.os.Bundle; +import android.os.Handler; import android.os.LocaleList; +import android.os.Looper; +import android.os.ResultReceiver; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; @@ -49,9 +53,11 @@ class LocaleDragAndDropAdapter private static final String TAG = "LocaleDragAndDropAdapter"; private static final String CFGKEY_SELECTED_LOCALES = "selectedLocales"; private final Context mContext; - private final List mFeedItemList; + private List mFeedItemList; + private List mCacheItemList; private final ItemTouchHelper mItemTouchHelper; private RecyclerView mParentView = null; + private LocaleListEditor mParent; private boolean mRemoveMode = false; private boolean mDragEnabled = true; private NumberFormat mNumberFormatter = NumberFormat.getNumberInstance(); @@ -81,12 +87,15 @@ class LocaleDragAndDropAdapter } } - public LocaleDragAndDropAdapter(Context context, List feedItemList) { + LocaleDragAndDropAdapter(LocaleListEditor parent, + List feedItemList) { mFeedItemList = feedItemList; - mContext = context; + mParent = parent; + mCacheItemList = new ArrayList<>(feedItemList); + mContext = parent.getContext(); final float dragElevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, - context.getResources().getDisplayMetrics()); + mContext.getResources().getDisplayMetrics()); mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback( ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0 /* no swipe */) { @@ -168,13 +177,13 @@ class LocaleDragAndDropAdapter checkbox.setOnCheckedChangeListener(null); checkbox.setChecked(mRemoveMode ? feedItem.getChecked() : false); checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - LocaleStore.LocaleInfo feedItem = - (LocaleStore.LocaleInfo) dragCell.getTag(); - feedItem.setChecked(isChecked); - } - }); + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + LocaleStore.LocaleInfo feedItem = + (LocaleStore.LocaleInfo) dragCell.getTag(); + feedItem.setChecked(isChecked); + } + }); } @Override @@ -308,6 +317,42 @@ class LocaleDragAndDropAdapter }); } + public void doTheUpdateWithMovingLocaleItem() { + LocaleStore.LocaleInfo localeInfo = mFeedItemList.get(0); + if (!localeInfo.getLocale().equals(LocalePicker.getLocales().get(0))) { + LocaleDialogFragment.show(mParent, + LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, + localeInfo, + new ResultReceiver(new Handler(Looper.getMainLooper())) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + super.onReceiveResult(resultCode, resultData); + int type = resultData.getInt(LocaleDialogFragment.ARG_DIALOG_TYPE); + if (type == LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT) { + if (resultCode == Activity.RESULT_OK) { + doTheUpdate(); + if (!localeInfo.isTranslated()) { + LocaleDialogFragment.show(mParent, + LocaleDialogFragment + .DIALOG_NOT_AVAILABLE_LOCALE, + localeInfo); + } + } else { + if (!localeInfo.getLocale() + .equals(mCacheItemList.get(0).getLocale())) { + mFeedItemList = new ArrayList<>(mCacheItemList); + notifyDataSetChanged(); + } + } + mCacheItemList = new ArrayList<>(mFeedItemList); + } + } + }); + } else { + doTheUpdate(); + } + } + private void setDragEnabled(boolean enabled) { mDragEnabled = enabled; } @@ -315,6 +360,7 @@ class LocaleDragAndDropAdapter /** * Saves the list of checked locales to preserve status when the list is destroyed. * (for instance when the device is rotated) + * * @param outInstanceState Bundle in which to place the saved state */ public void saveState(Bundle outInstanceState) { @@ -332,6 +378,7 @@ class LocaleDragAndDropAdapter /** * Restores the list of checked locales to preserve status when the list is recreated. * (for instance when the device is rotated) + * * @param savedInstanceState Bundle with the data saved by {@link #saveState(Bundle)} */ public void restoreState(Bundle savedInstanceState) { diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java index 89efe53b4ff..bdb9295807e 100644 --- a/src/com/android/settings/localepicker/LocaleListEditor.java +++ b/src/com/android/settings/localepicker/LocaleListEditor.java @@ -105,7 +105,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment { LocaleStore.fillCache(this.getContext()); final List feedsList = getUserLocaleList(); - mAdapter = new LocaleDragAndDropAdapter(this.getContext(), feedsList); + mAdapter = new LocaleDragAndDropAdapter(this, feedsList); } @Override diff --git a/src/com/android/settings/localepicker/LocaleRecyclerView.java b/src/com/android/settings/localepicker/LocaleRecyclerView.java index d32a735d48c..5d469bf7f10 100644 --- a/src/com/android/settings/localepicker/LocaleRecyclerView.java +++ b/src/com/android/settings/localepicker/LocaleRecyclerView.java @@ -40,7 +40,7 @@ class LocaleRecyclerView extends RecyclerView { if (e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_CANCEL) { LocaleDragAndDropAdapter adapter = (LocaleDragAndDropAdapter) this.getAdapter(); if (adapter != null) { - adapter.doTheUpdate(); + adapter.doTheUpdateWithMovingLocaleItem(); } } return super.onTouchEvent(e); diff --git a/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java b/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java new file mode 100644 index 00000000000..5b10adf0ff6 --- /dev/null +++ b/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2023 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 com.android.settings.localepicker.LocaleDialogFragment.ARG_DIALOG_TYPE; +import static com.android.settings.localepicker.LocaleDialogFragment.ARG_RESULT_RECEIVER; +import static com.android.settings.localepicker.LocaleDialogFragment.ARG_TARGET_LOCALE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.Activity; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.ResultReceiver; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ApplicationProvider; + +import com.android.internal.app.LocaleStore; +import com.android.settings.testutils.ResourcesUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Locale; + +@UiThreadTest +public class LocaleDialogFragmentTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private Context mContext; + private LocaleDialogFragment mDialogFragment; + + @Before + public void setUp() throws Exception { + mContext = ApplicationProvider.getApplicationContext(); + mDialogFragment = new LocaleDialogFragment(); + } + + private void setArgument( + int type, ResultReceiver receiver) { + LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(Locale.ENGLISH); + Bundle args = new Bundle(); + args.putInt(ARG_DIALOG_TYPE, type); + args.putSerializable(ARG_TARGET_LOCALE, localeInfo); + args.putParcelable(ARG_RESULT_RECEIVER, receiver); + mDialogFragment.setArguments(args); + } + + @Test + public void getDialogContent_confirmSystemDefault_has2ButtonText() { + setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, null); + LocaleDialogFragment.LocaleDialogController controller = + new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); + + LocaleDialogFragment.LocaleDialogController.DialogContent dialogContent = + controller.getDialogContent(); + + assertEquals(ResourcesUtils.getResourcesString( + mContext, "button_label_confirmation_of_system_locale_change"), + dialogContent.mPositiveButton); + assertEquals(ResourcesUtils.getResourcesString(mContext, "cancel"), + dialogContent.mNegativeButton); + } + + @Test + public void getDialogContent_unavailableLocale_has1ButtonText() { + setArgument(LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE, null); + LocaleDialogFragment.LocaleDialogController controller = + new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); + + LocaleDialogFragment.LocaleDialogController.DialogContent dialogContent = + controller.getDialogContent(); + + assertEquals(ResourcesUtils.getResourcesString(mContext, "okay"), + dialogContent.mPositiveButton); + assertTrue(dialogContent.mNegativeButton.isEmpty()); + } + + @Test + public void onClick_clickPositiveButton_sendOK() { + ResultReceiver resultReceiver = spy(new ResultReceiver(null)); + setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, resultReceiver); + LocaleDialogFragment.LocaleDialogController controller = + new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); + + controller.onClick(null, DialogInterface.BUTTON_POSITIVE); + + verify(resultReceiver).send(eq(Activity.RESULT_OK), any()); + } + + @Test + public void onClick_clickNegativeButton_sendCancel() { + ResultReceiver resultReceiver = spy(new ResultReceiver(null)); + setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, resultReceiver); + LocaleDialogFragment.LocaleDialogController controller = + new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); + + controller.onClick(null, DialogInterface.BUTTON_NEGATIVE); + + verify(resultReceiver).send(eq(Activity.RESULT_CANCELED), any()); + } + + @Test + public void getMetricsCategory_systemLocaleChange() { + setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, null); + + int result = mDialogFragment.getMetricsCategory(); + + assertEquals(SettingsEnums.DIALOG_SYSTEM_LOCALE_CHANGE, result); + } + + @Test + public void getMetricsCategory_unavailableLocale() { + setArgument(LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE, null); + + int result = mDialogFragment.getMetricsCategory(); + + assertEquals(SettingsEnums.DIALOG_SYSTEM_LOCALE_UNAVAILABLE, result); + } +} From de797e1198a057d340a73466c9696bf39472b477 Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Mon, 6 Mar 2023 16:28:36 +0800 Subject: [PATCH 07/12] Add the SPA page enter/leave logging metrcis. - Add the SpaLogProvider and SpaLogData for logging writing. - Write the SPA page enter/leave logging. Fixes: 271793388 Bug: 253979024 Test: atest SpaActivityTest SpaLogDataTest MetricsDataModelTest Change-Id: I0ad5af39ba207ac00d58f6392496effa3adc42f4 --- .../settings/spa/SettingsSpaEnvironment.kt | 2 + src/com/android/settings/spa/SpaActivity.kt | 21 +++ .../core/instrumentation/MetricsDataModel.kt | 41 ++++++ .../core/instrumentation/SpaLogProvider.kt | 132 ++++++++++++++++++ .../android/settings/spa/SpaActivityTest.kt | 10 +- .../instrumentation/MetricsDataModelTest.kt | 95 +++++++++++++ .../core/instrumentation/SpaLogDataTest.kt | 83 +++++++++++ 7 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/spa/core/instrumentation/MetricsDataModel.kt create mode 100644 src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt create mode 100644 tests/spa_unit/src/com/android/settings/spa/core/instrumentation/MetricsDataModelTest.kt create mode 100644 tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt index 87a916a7e43..c2716b6e5ca 100644 --- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt +++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt @@ -32,6 +32,7 @@ import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider +import com.android.settings.spa.core.instrumentation.SpaLogProvider import com.android.settings.spa.development.UsageStatsPageProvider import com.android.settings.spa.home.HomePageProvider import com.android.settings.spa.network.NetworkAndInternetPageProvider @@ -87,4 +88,5 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { ), ) } + override val logger = SpaLogProvider } diff --git a/src/com/android/settings/spa/SpaActivity.kt b/src/com/android/settings/spa/SpaActivity.kt index 55883c12264..27f7241b257 100644 --- a/src/com/android/settings/spa/SpaActivity.kt +++ b/src/com/android/settings/spa/SpaActivity.kt @@ -16,18 +16,29 @@ package com.android.settings.spa +import android.app.ActivityManager import android.content.Context import android.content.Intent +import android.os.RemoteException import android.os.UserHandle +import android.util.Log import com.android.settingslib.spa.framework.BrowseActivity +import com.android.settingslib.spa.framework.util.SESSION_BROWSE +import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL import com.android.settingslib.spa.framework.util.appendSpaParams class SpaActivity : BrowseActivity() { companion object { + private const val TAG = "SpaActivity" @JvmStatic fun Context.startSpaActivity(destination: String) { val intent = Intent(this, SpaActivity::class.java) .appendSpaParams(destination = destination) + if (isLaunchedFromInternal()) { + intent.appendSpaParams(sessionName = SESSION_BROWSE) + } else { + intent.appendSpaParams(sessionName = SESSION_EXTERNAL) + } startActivity(intent) } @@ -37,5 +48,15 @@ class SpaActivity : BrowseActivity() { startSpaActivity("$destinationPrefix/$packageName/${UserHandle.myUserId()}") return true } + + fun Context.isLaunchedFromInternal(): Boolean { + var pkg: String? = null + try { + pkg = ActivityManager.getService().getLaunchedFromPackage(getActivityToken()) + } catch (e: RemoteException) { + Log.v(TAG, "Could not talk to activity manager.", e) + } + return applicationContext.packageName == pkg + } } } diff --git a/src/com/android/settings/spa/core/instrumentation/MetricsDataModel.kt b/src/com/android/settings/spa/core/instrumentation/MetricsDataModel.kt new file mode 100644 index 00000000000..62aa8df871a --- /dev/null +++ b/src/com/android/settings/spa/core/instrumentation/MetricsDataModel.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 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.spa.core.instrumentation + +import androidx.annotation.VisibleForTesting + +/** + * This class stores some metrics temporary data. Such as the timestamp of the page enter for + * calculating the duration time on page. + */ +class MetricsDataModel { + @VisibleForTesting + val pageTimeStampList = mutableListOf() + + fun addTimeStamp(dataItem: PageTimeStamp){ + pageTimeStampList.add(dataItem) + } + + fun getPageDuration(pageId: String, removed: Boolean = true): String { + val lastItem = pageTimeStampList.findLast { it.pageId == pageId } + if (removed && lastItem != null) { + pageTimeStampList.remove(lastItem) + } + return if (lastItem == null) "0" + else (System.currentTimeMillis() - lastItem.timeStamp).toString() + } +} diff --git a/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt b/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt new file mode 100644 index 00000000000..9b3e2d68ac2 --- /dev/null +++ b/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2023 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.spa.core.instrumentation + +import android.app.settings.SettingsEnums +import android.os.Bundle +import androidx.annotation.VisibleForTesting +import com.android.settings.core.instrumentation.ElapsedTimeUtils +import com.android.settings.core.instrumentation.SettingsStatsLog +import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME +import com.android.settingslib.spa.framework.common.LogCategory +import com.android.settingslib.spa.framework.common.LogEvent +import com.android.settingslib.spa.framework.common.SpaLogger +import com.android.settingslib.spa.framework.util.SESSION_BROWSE +import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL +import com.android.settingslib.spa.framework.util.SESSION_SEARCH +import com.android.settingslib.spa.framework.util.SESSION_SLICE +import com.android.settingslib.spa.framework.util.SESSION_UNKNOWN + +/** + * To receive the events from spa framework and logging the these events. + */ +object SpaLogProvider : SpaLogger { + private val dataModel = MetricsDataModel() + + override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) { + when(event) { + LogEvent.PAGE_ENTER, LogEvent.PAGE_LEAVE -> + write(SpaLogData(id, event, extraData, dataModel)) + else -> return //TODO(b/253979024): Will be implemented in subsequent CLs. + } + } + + private fun write(data: SpaLogData) { + with(data) { + SettingsStatsLog.write( + SettingsStatsLog.SETTINGS_SPA_REPORTED /* atomName */, + getSessionType(), + getPageId(), + getTarget(), + getAction(), + getKey(), + getValue(), + getPreValue(), + getElapsedTime() + ) + } + } +} + +@VisibleForTesting +class SpaLogData(val id: String, val event: LogEvent, + val extraData: Bundle, val dataModel: MetricsDataModel) { + + fun getSessionType(): Int { + if (!extraData.containsKey(LOG_DATA_SESSION_NAME)) { + return SettingsEnums.SESSION_UNKNOWN + } + val sessionSource = extraData.getString(LOG_DATA_SESSION_NAME) + return when(sessionSource) { + SESSION_BROWSE -> SettingsEnums.BROWSE + SESSION_SEARCH -> SettingsEnums.SEARCH + SESSION_SLICE -> SettingsEnums.SLICE_TYPE + SESSION_EXTERNAL -> SettingsEnums.EXTERNAL + else -> SettingsEnums.SESSION_UNKNOWN + } + } + + fun getPageId(): String { + return when(event) { + LogEvent.PAGE_ENTER, LogEvent.PAGE_LEAVE -> id + else -> getPageIdByEntryId(id) + } + } + + //TODO(b/253979024): Will be implemented in subsequent CLs. + fun getTarget(): String? { + return null + } + + fun getAction(): Int { + return event.action + } + + //TODO(b/253979024): Will be implemented in subsequent CLs. + fun getKey(): String? { + return null + } + + fun getValue(): String? { + when(event) { + LogEvent.PAGE_ENTER -> dataModel.addTimeStamp( + PageTimeStamp(id, System.currentTimeMillis())) + LogEvent.PAGE_LEAVE -> return dataModel.getPageDuration(id) + else -> {} //TODO(b/253979024): Will be implemented in subsequent CLs. + } + return null + } + + //TODO(b/253979024): Will be implemented in subsequent CLs. + fun getPreValue(): String? { + return null + } + + fun getElapsedTime(): Long { + return ElapsedTimeUtils.getElapsedTime(System.currentTimeMillis()) + } + + //TODO(b/253979024): Will be implemented in subsequent CLs. + private fun getPageIdByEntryId(id: String): String { + return "" + } +} + +/** + * The buffer is keeping the time stamp while spa page entering. + */ +data class PageTimeStamp(val pageId: String, val timeStamp: Long) diff --git a/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt b/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt index 8a894d5b4a8..46b956e6ccc 100644 --- a/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt @@ -25,12 +25,15 @@ import com.android.settings.spa.SpaActivity.Companion.startSpaActivity import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp import com.android.settingslib.spa.framework.util.KEY_DESTINATION import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule @@ -39,9 +42,14 @@ class SpaActivityTest { @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() - @Mock + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var context: Context + @Before + fun setUp() { + `when`(context.applicationContext.packageName).thenReturn("com.android.settings") + } + @Test fun startSpaActivity() { context.startSpaActivity(DESTINATION) diff --git a/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/MetricsDataModelTest.kt b/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/MetricsDataModelTest.kt new file mode 100644 index 00000000000..2ba03029b5d --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/MetricsDataModelTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 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.spa.core.instrumentation + +import android.os.SystemClock +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests for {@link MetricsDataModel}. */ +@RunWith(AndroidJUnit4::class) +class MetricsDataModelTest { + private val TEST_PID = "pseudo_page_id" + + private lateinit var metricsDataModel: MetricsDataModel + + @Before + fun setUp() { + metricsDataModel = MetricsDataModel() + } + + @Test + fun initMetricsDataModel() { + assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(0) + } + + @Test + fun addTimeStamp_addOnePageTimeStamp_sizeShouldBeOne() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + + assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(1) + } + + @Test + fun addTimeStamp_addTwoSamePageTimeStamp_sizeShouldBeTwo() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + + assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(2) + } + + @Test + fun getPageDuration_getExistPageId_mustFoundValue() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + SystemClock.sleep(5) + + assertThat(metricsDataModel.getPageDuration(TEST_PID).toInt()).isGreaterThan(0) + assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(0) + } + + @Test + fun getPageDuration_getNonExistPageId_valueShouldBeZero() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + + assertThat(metricsDataModel.getPageDuration("WRONG_ID").toLong()).isEqualTo(0L) + } + + @Test + fun getPageDuration_getExistPageIdAndDonotRemoved_sizeShouldBeOne() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + SystemClock.sleep(5) + + assertThat(metricsDataModel.getPageDuration(TEST_PID, false).toLong()).isGreaterThan(0L) + assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(1) + } + + @Test + fun getPageDuration_getTwoExistPageId_theOrderIsLIFO() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, 10000L)) + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, 20000L)) + + // The formula is d1 = t1 - 20000, d2 = t2 - 10000 + // d2 - d1 = t2 - t1 + 10000, because t2 > t1 the result of d2 - d1 is greater 10000 + val duration1 = metricsDataModel.getPageDuration(TEST_PID).toLong() + SystemClock.sleep(5) + val duration2 = metricsDataModel.getPageDuration(TEST_PID).toLong() + + assertThat(duration2 - duration1).isGreaterThan(10000L) + } +} \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt b/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt new file mode 100644 index 00000000000..19be10ec892 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 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.spa.core.instrumentation + +import android.app.settings.SettingsEnums +import android.os.Bundle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME +import com.android.settingslib.spa.framework.common.LogEvent +import com.android.settingslib.spa.framework.util.SESSION_BROWSE +import com.android.settingslib.spa.framework.util.SESSION_SEARCH +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests for {@link SpaLogData}. */ +@RunWith(AndroidJUnit4::class) +class SpaLogDataTest { + private val TEST_PID = "pseudo_page_id" + + private lateinit var bundle: Bundle + private lateinit var dataModel: MetricsDataModel + + @Before + fun setUp() { + bundle = Bundle() + dataModel = MetricsDataModel() + } + + @Test + fun getSessionType_withoutSessionExtraData_returnSessionUnknow() { + val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel) + + assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SESSION_UNKNOWN) + } + + @Test + fun getSessionType_hasSessionBrowseExtraData_returnSessionBrowse() { + bundle.putString(LOG_DATA_SESSION_NAME, SESSION_BROWSE) + val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel) + + assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.BROWSE) + } + + @Test + fun getSessionType_hasSessionSearchExtraData_returnSessionSearch() { + bundle.putString(LOG_DATA_SESSION_NAME, SESSION_SEARCH) + val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel) + + assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SEARCH) + } + + @Test + fun getSessionType_hasSessionUnknownExtraData_returnSessionUnknow() { + bundle.putString(LOG_DATA_SESSION_NAME, "SESSION_OTHER") + val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel) + + assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SESSION_UNKNOWN) + } + + @Test + fun getPageId_withPageEvent_returnInputId() { + val spaLogData1 = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel) + assertThat(spaLogData1.getPageId()).isEqualTo(TEST_PID) + + val spaLogData2 = SpaLogData(TEST_PID, LogEvent.PAGE_LEAVE, bundle, dataModel) + assertThat(spaLogData2.getPageId()).isEqualTo(TEST_PID) + } +} From d1d1ecc9d359808f9f4590daf1e63d54c124e2cd Mon Sep 17 00:00:00 2001 From: SongFerngWang Date: Tue, 7 Mar 2023 15:43:00 +0800 Subject: [PATCH 08/12] Using upper case country code According to the ISO 3166-1, the two letters country code is upper case. Bug: 271753491 Test: build pass and local test Change-Id: Ia71cb6de45682407e4d988522c50d83659fe0017 --- src/com/android/settings/network/SubscriptionUtil.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index a5629901966..13bb51928b6 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -50,6 +50,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -560,7 +561,8 @@ public class SubscriptionUtil { if (TextUtils.isEmpty(rawPhoneNumber)) { return null; } - String countryIso = MccTable.countryCodeForMcc(subscriptionInfo.getMccString()); + String countryIso = MccTable.countryCodeForMcc(subscriptionInfo.getMccString()) + .toUpperCase(Locale.ROOT); return PhoneNumberUtils.formatNumber(rawPhoneNumber, countryIso); } From cf4be05b64d8819564c1134a3f8c307a66de8132 Mon Sep 17 00:00:00 2001 From: danielwbhuang Date: Tue, 7 Mar 2023 16:31:15 +0800 Subject: [PATCH 09/12] Update KeyboardLayoutPickerControllerTest The logic is changed, so we need to update the test. (https://screenshot.googleplex.com/AnrJgmGfK2fPxmQ.png) Bug: 270109384 Test: atest KeyboardLayoutPickerControllerTest [1/8] com.android.settings.inputmethod.KeyboardLayoutPickerControllerTest#isAlwaysAvailable: PASSED (5.876s) [2/8] com.android.settings.inputmethod.KeyboardLayoutPickerControllerTest#test_createPreferenceHierarchy_shouldAddTwoPreference: PASSED (192ms) [3/8] com.android.settings.inputmethod.KeyboardLayoutPickerControllerTest#testOnDeviceRemove_getSameDevice_shouldFinish: PASSED (5.797s) [4/8] com.android.settings.inputmethod.KeyboardLayoutPickerControllerTest#testLifecycle_onStart_shouldRegisterInputManager: PASSED (1.495s) [5/8] com.android.settings.inputmethod.KeyboardLayoutPickerControllerTest#test_createPreferenceHierarchy_shouldAddOnePreference: PASSED (75ms) [6/8] com.android.settings.inputmethod.KeyboardLayoutPickerControllerTest#testOnDeviceRemove_getDifferentDevice_shouldNotFinish: PASSED (366ms) [7/8] com.android.settings.inputmethod.KeyboardLayoutPickerControllerTest#testLifecycle_onStop_shouldCancelRegisterInputManager: PASSED (65ms) [8/8] com.android.settings.inputmethod.KeyboardLayoutPickerControllerTest#testLifecycle_onStart_NoInputDevice_shouldReturn: PASSED (309ms) Change-Id: I018873cba9e73c6d684d828858cc1b08d3f2f7f9 --- .../inputmethod/KeyboardLayoutPickerControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java index 52d10833ef8..734f610b264 100644 --- a/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java @@ -93,13 +93,13 @@ public class KeyboardLayoutPickerControllerTest { } @Test - public void testLifecycle_onStart_NoInputDevice_shouldFinish() { + public void testLifecycle_onStart_NoInputDevice_shouldReturn() { final FragmentActivity activity = Robolectric.setupActivity(FragmentActivity.class); when(mInputManager.getInputDeviceByDescriptor(anyString())).thenReturn(null); when(mFragment.getActivity()).thenReturn(activity); mController.onStart(); - assertThat(activity.isFinishing()).isTrue(); + verify(mInputManager, never()).getEnabledKeyboardLayoutsForInputDevice(any()); } @Test From 5d18287b3ba2574b80f5a8d9d03c7c607e34f9c9 Mon Sep 17 00:00:00 2001 From: Wesley Wang Date: Tue, 7 Mar 2023 17:47:13 +0800 Subject: [PATCH 10/12] Fix battery saver switch test case - Fix the test fail which cause by ag/21784766, and also mentioned at ag/21644522 Bug: 260380584 Test: make SettingsRoboTests Change-Id: I72a7a24f57a7eeeacefeb40eef3025398329379b --- ...atterySaverButtonPreferenceController.java | 2 +- ...rySaverButtonPreferenceControllerTest.java | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java index ee3f54f70e0..274817e3819 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java @@ -118,7 +118,7 @@ public class BatterySaverButtonPreferenceController extends @Override public boolean setChecked(boolean stateOn) { return BatterySaverUtils.setPowerSaveMode(mContext, stateOn, - true /* needFirstTimeWarning */); + false /* needFirstTimeWarning */); } @Override diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java index 4f26754b212..9a7ef409f95 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java @@ -18,17 +18,13 @@ package com.android.settings.fuelgauge.batterysaver; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.Intent; import android.os.PowerManager; import android.provider.Settings; import android.provider.SettingsSlicesContract; @@ -85,6 +81,15 @@ public class BatterySaverButtonPreferenceControllerTest { assertThat(mPreference.isChecked()).isFalse(); } + @Test + public void onSwitchChanged_isCheckedAndAcked_setPowerSaveMode() { + setLowPowerWarningAcked(/* acked= */ 1); + + mController.onSwitchChanged(/* switchView= */ null, /* isChecked= */ true); + + verify(mPowerManager).setPowerSaveModeEnabled(true); + } + @Test public void updateState_lowPowerOn_preferenceIsChecked() { when(mPowerManager.isPowerSaveMode()).thenReturn(true); @@ -104,11 +109,10 @@ public class BatterySaverButtonPreferenceControllerTest { } @Test - public void setChecked_on_showWarningMessage() { + public void setChecked_on_setPowerSaveMode() { mController.setChecked(true); - verify(mContext).sendBroadcast(any(Intent.class)); - verify(mPowerManager, never()).setPowerSaveModeEnabled(anyBoolean()); + verify(mPowerManager).setPowerSaveModeEnabled(true); } @Test @@ -143,5 +147,9 @@ public class BatterySaverButtonPreferenceControllerTest { mContext.getContentResolver(), Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED, acked); + Settings.Secure.putInt( + mContext.getContentResolver(), + Settings.Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, + acked); } } From bf1b95e0feb70fbec677b9d4dce2563200a0cb7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Tue, 7 Mar 2023 16:39:01 +0100 Subject: [PATCH 11/12] Don't allow setting channels to bypass DND if the app cannot show notifications The fix makes the flow more consistent with preexisting behavior: * Apps appear in the "Apps that can interrupt" section only if some channels are set to bypass DND *and* the app is able to show notifications. * Channels cannot be set to bypass if those individual channels are blocked. Fixes: 265064188 Test: atest AppChannelsBypassingDndPreferenceControllerTest Change-Id: If10f086fd60f0f70f53adb8f5cd67f90936bc35f --- ...nnelsBypassingDndPreferenceController.java | 12 +- ...sBypassingDndPreferenceControllerTest.java | 151 ++++++++++++++++++ 2 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java index 92cd911843a..40bae5d7918 100644 --- a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java +++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; +import androidx.annotation.VisibleForTesting; import androidx.core.text.BidiFormatter; import androidx.lifecycle.LifecycleObserver; import androidx.preference.Preference; @@ -54,7 +55,7 @@ import java.util.List; public class AppChannelsBypassingDndPreferenceController extends NotificationPreferenceController implements PreferenceControllerMixin, LifecycleObserver { - private static final String KEY = "zen_mode_bypassing_app_channels_list"; + @VisibleForTesting static final String KEY = "zen_mode_bypassing_app_channels_list"; private static final String ARG_FROM_SETTINGS = "fromSettings"; private RestrictedSwitchPreference mAllNotificationsToggle; @@ -74,8 +75,8 @@ public class AppChannelsBypassingDndPreferenceController extends NotificationPre mAllNotificationsToggle = new RestrictedSwitchPreference(mPreferenceCategory.getContext()); mAllNotificationsToggle.setTitle(R.string.zen_mode_bypassing_app_channels_toggle_all); mAllNotificationsToggle.setDisabledByAdmin(mAdmin); - mAllNotificationsToggle.setEnabled( - (mAdmin == null || !mAllNotificationsToggle.isDisabledByAdmin())); + mAllNotificationsToggle.setEnabled(!mAppRow.banned + && (mAdmin == null || !mAllNotificationsToggle.isDisabledByAdmin())); mAllNotificationsToggle.setOnPreferenceClickListener( new Preference.OnPreferenceClickListener() { @Override @@ -206,6 +207,9 @@ public class AppChannelsBypassingDndPreferenceController extends NotificationPre } private boolean areAllChannelsBypassing() { + if (mAppRow.banned) { + return false; + } boolean allChannelsBypassing = true; for (NotificationChannel channel : mChannels) { if (showNotification(channel)) { @@ -226,7 +230,7 @@ public class AppChannelsBypassingDndPreferenceController extends NotificationPre * Whether notifications from this channel would show if DND weren't on. */ private boolean showNotification(NotificationChannel channel) { - return channel.getImportance() != IMPORTANCE_NONE; + return !mAppRow.banned && channel.getImportance() != IMPORTANCE_NONE; } /** diff --git a/tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java new file mode 100644 index 00000000000..bcb641d9c23 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 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.notification.app; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.ParceledListSlice; + +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.PrimarySwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowApplication; + +import java.util.ArrayList; +import java.util.Collections; + +@RunWith(RobolectricTestRunner.class) +public class AppChannelsBypassingDndPreferenceControllerTest { + + @Mock + private NotificationBackend mBackend; + + private NotificationBackend.AppRow mAppRow; + private AppChannelsBypassingDndPreferenceController mController; + + private PreferenceScreen mPreferenceScreen; + private PreferenceCategory mCategory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Context context = ApplicationProvider.getApplicationContext(); + + mAppRow = new NotificationBackend.AppRow(); + mAppRow.uid = 42; + mAppRow.pkg = "com.example.exampling"; + + mController = new AppChannelsBypassingDndPreferenceController(context, mBackend); + mController.onResume(mAppRow, null, null, null, null, null, new ArrayList<>()); + + PreferenceManager preferenceManager = new PreferenceManager(context); + mPreferenceScreen = preferenceManager.createPreferenceScreen(context); + mCategory = new PreferenceCategory(context); + mCategory.setKey(AppChannelsBypassingDndPreferenceController.KEY); + mPreferenceScreen.addPreference(mCategory); + } + + @Test + public void displayPreference_showsAllAndChannels() { + when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn( + buildGroupList(true, true, false)); + + mController.displayPreference(mPreferenceScreen); + ShadowApplication.runBackgroundTasks(); + + assertThat(mCategory.getPreferenceCount()).isEqualTo(4); // "All" + 3 channels + assertThat(mCategory.getPreference(0).getTitle().toString()).isEqualTo( + "Allow all notifications"); + assertThat(mCategory.getPreference(1).getTitle().toString()).isEqualTo("Channel 1"); + assertThat(mCategory.getPreference(2).getTitle().toString()).isEqualTo("Channel 2"); + assertThat(mCategory.getPreference(3).getTitle().toString()).isEqualTo("Channel 3"); + } + + @Test + public void displayPreference_canToggleAllInterrupt() { + when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn( + buildGroupList(true, true, false)); + + mController.displayPreference(mPreferenceScreen); + ShadowApplication.runBackgroundTasks(); + + assertThat(mCategory.getPreference(0).isEnabled()).isTrue(); + } + + @Test + public void displayPreference_canToggleInterruptIfChannelEnabled() { + when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn( + buildGroupList(true, false, true)); + + mController.displayPreference(mPreferenceScreen); + ShadowApplication.runBackgroundTasks(); + + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 1)).isSwitchEnabled()).isTrue(); + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 2)).isSwitchEnabled()).isFalse(); + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 3)).isSwitchEnabled()).isTrue(); + } + + @Test + public void displayPreference_appBlocked_cannotToggleAllOrChannelInterrupts() { + mAppRow.banned = true; + when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn( + buildGroupList(true, false, true)); + + mController.displayPreference(mPreferenceScreen); + ShadowApplication.runBackgroundTasks(); + + assertThat(mCategory.getPreference(0).isEnabled()).isFalse(); + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 1)).isSwitchEnabled()).isFalse(); + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 2)).isSwitchEnabled()).isFalse(); + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 3)).isSwitchEnabled()).isFalse(); + } + + private static ParceledListSlice buildGroupList( + boolean... enabledByChannel) { + NotificationChannelGroup group = new NotificationChannelGroup("group", "The Group"); + for (int i = 0; i < enabledByChannel.length; i++) { + group.addChannel(new NotificationChannel("channel-" + (i + 1), "Channel " + (i + 1), + enabledByChannel[i] ? NotificationManager.IMPORTANCE_DEFAULT + : NotificationManager.IMPORTANCE_NONE)); + } + return new ParceledListSlice<>(Collections.singletonList(group)); + } +} From e174f66a71d4efddfce53646a9daf908412a314c Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Mon, 6 Mar 2023 18:59:15 +0000 Subject: [PATCH 12/12] Move CredentialProviderInfo for test/settings (settings) This is a precusor CL to adding a subtitle for settings to use so we need to move CPI where it can be used by settings, atest and CTS. Test: ondevice & atest & cts Bug: 253157366 Change-Id: Ief25f562eb5c2ca4438701de8a8e26941a8370a3 (cherry picked from commit on googleplex-android-review.googlesource.com host: 573e844275e3bc9da2de83cb419562172518ebb6) Merged-In: Ief25f562eb5c2ca4438701de8a8e26941a8370a3 --- ...CredentialManagerPreferenceController.java | 77 +++++-------- ...entialManagerPreferenceControllerTest.java | 104 +++++++++++++++--- 2 files changed, 116 insertions(+), 65 deletions(-) diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java index 0bccfe2da86..f8c1f643a0c 100644 --- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java +++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java @@ -25,14 +25,11 @@ import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; import android.credentials.CredentialManager; -import android.credentials.ListEnabledProvidersException; -import android.credentials.ListEnabledProvidersResponse; +import android.credentials.CredentialProviderInfo; import android.credentials.SetEnabledProvidersException; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.os.CancellationSignal; import android.os.OutcomeReceiver; import android.os.UserHandle; import android.util.IconDrawableFactory; @@ -71,10 +68,9 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl private final PackageManager mPm; private final IconDrawableFactory mIconFactory; - private final List mServices; + private final List mServices; private final Set mEnabledPackageNames; private final @Nullable CredentialManager mCredentialManager; - private final CancellationSignal mCancellationSignal = new CancellationSignal(); private final Executor mExecutor; private final Map mPrefs = new HashMap<>(); // key is package name @@ -132,42 +128,20 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl lifecycleOwner, mCredentialManager.getCredentialProviderServices( getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY)); - - mCredentialManager.listEnabledProviders( - mCancellationSignal, - mExecutor, - new OutcomeReceiver() { - @Override - public void onResult(ListEnabledProvidersResponse result) { - Set enabledPackages = new HashSet<>(); - for (String flattenedComponentName : result.getProviderComponentNames()) { - ComponentName cn = - ComponentName.unflattenFromString(flattenedComponentName); - if (cn != null) { - enabledPackages.add(cn.getPackageName()); - } - } - - setEnabledPackageNames(enabledPackages); - } - - @Override - public void onError(ListEnabledProvidersException e) { - Log.e(TAG, "listEnabledProviders error: " + e.toString()); - } - }); } @VisibleForTesting - void setAvailableServices(LifecycleOwner lifecycleOwner, List availableServices) { + void setAvailableServices( + LifecycleOwner lifecycleOwner, List availableServices) { mServices.clear(); mServices.addAll(availableServices); - } - @VisibleForTesting - void setEnabledPackageNames(Set enabledPackages) { mEnabledPackageNames.clear(); - mEnabledPackageNames.addAll(enabledPackages); + for (CredentialProviderInfo cpi : availableServices) { + if (cpi.isEnabled()) { + mEnabledPackageNames.add(cpi.getServiceInfo().packageName); + } + } for (String packageName : mPrefs.keySet()) { mPrefs.get(packageName).setChecked(mEnabledPackageNames.contains(packageName)); @@ -189,22 +163,22 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl PreferenceGroup group = screen.findPreference(getPreferenceKey()); Context context = screen.getContext(); - for (ServiceInfo serviceInfo : mServices) { - CharSequence title = ""; - if (serviceInfo.nonLocalizedLabel != null) { - title = serviceInfo.loadLabel(mPm); - } - - group.addPreference( - addProviderPreference( - context, - title, - mIconFactory.getBadgedIcon( - serviceInfo, serviceInfo.applicationInfo, getUser()), - serviceInfo.packageName)); + for (CredentialProviderInfo service : mServices) { + group.addPreference(createPreference(context, service)); } } + /** Creates a preference object based on the provider info. */ + @VisibleForTesting + public SwitchPreference createPreference(Context context, CredentialProviderInfo service) { + CharSequence label = service.getLabel(context); + return addProviderPreference( + context, + label == null ? "" : label, + service.getServiceIcon(mContext), + service.getServiceInfo().packageName); + } + /** * Enables the package name as an enabled credential manager provider. * @@ -246,9 +220,10 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl public List getEnabledSettings() { // Get all the component names that match the enabled package names. List enabledServices = new ArrayList<>(); - for (ServiceInfo service : mServices) { - if (mEnabledPackageNames.contains(service.packageName)) { - enabledServices.add(service.getComponentName().flattenToString()); + for (CredentialProviderInfo service : mServices) { + ComponentName cn = service.getServiceInfo().getComponentName(); + if (mEnabledPackageNames.contains(service.getServiceInfo().packageName)) { + enabledServices.add(cn.flattenToString()); } } diff --git a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java index ff7e71a948f..2633ea7ad40 100644 --- a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java @@ -27,12 +27,14 @@ import static org.mockito.Mockito.spy; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; +import android.credentials.CredentialProviderInfo; import android.os.Looper; import androidx.lifecycle.Lifecycle; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -87,7 +89,7 @@ public class CredentialManagerPreferenceControllerTest { @Test public void getAvailabilityStatus_withServices_returnsAvailable() { CredentialManagerPreferenceController controller = - createControllerWithServices(Lists.newArrayList(createServiceInfo())); + createControllerWithServices(Lists.newArrayList(createCredentialProviderInfo())); assertThat(controller.isConnected()).isFalse(); assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); } @@ -103,24 +105,59 @@ public class CredentialManagerPreferenceControllerTest { @Test public void displayPreference_withServices_preferencesAdded() { CredentialManagerPreferenceController controller = - createControllerWithServices(Lists.newArrayList(createServiceInfo())); + createControllerWithServices(Lists.newArrayList(createCredentialProviderInfo())); controller.displayPreference(mScreen); assertThat(controller.isConnected()).isFalse(); assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(1); } + @Test + public void buildSwitchPreference() { + CredentialProviderInfo providerInfo1 = + createCredentialProviderInfo( + "com.android.provider1", "ClassA", "Service Title", false); + CredentialProviderInfo providerInfo2 = + createCredentialProviderInfo( + "com.android.provider2", "ClassA", "Service Title", false); + CredentialManagerPreferenceController controller = + createControllerWithServices(Lists.newArrayList(providerInfo1, providerInfo2)); + assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); + assertThat(controller.isConnected()).isFalse(); + + // Test the data is correct. + assertThat(providerInfo1.isEnabled()).isFalse(); + assertThat(providerInfo2.isEnabled()).isFalse(); + assertThat(controller.getEnabledProviders().size()).isEqualTo(0); + + // Toggle one provider and make sure it worked. + assertThat(controller.togglePackageNameEnabled("com.android.provider1")).isTrue(); + Set enabledProviders = controller.getEnabledProviders(); + assertThat(enabledProviders.size()).isEqualTo(1); + assertThat(enabledProviders.contains("com.android.provider1")).isTrue(); + + // Create the pref (checked). + SwitchPreference pref = controller.createPreference(mContext, providerInfo1); + assertThat(pref.getTitle().toString()).isEqualTo("Service Title"); + assertThat(pref.isChecked()).isTrue(); + + // Create the pref (not checked). + SwitchPreference pref2 = controller.createPreference(mContext, providerInfo2); + assertThat(pref2.getTitle().toString()).isEqualTo("Service Title"); + assertThat(pref2.isChecked()).isFalse(); + } + @Test public void getAvailabilityStatus_handlesToggleAndSave() { CredentialManagerPreferenceController controller = createControllerWithServices( Lists.newArrayList( - createServiceInfo("com.android.provider1", "ClassA"), - createServiceInfo("com.android.provider1", "ClassB"), - createServiceInfo("com.android.provider2", "ClassA"), - createServiceInfo("com.android.provider3", "ClassA"), - createServiceInfo("com.android.provider4", "ClassA"), - createServiceInfo("com.android.provider5", "ClassA"), - createServiceInfo("com.android.provider6", "ClassA"))); + createCredentialProviderInfo("com.android.provider1", "ClassA"), + createCredentialProviderInfo("com.android.provider1", "ClassB"), + createCredentialProviderInfo("com.android.provider2", "ClassA"), + createCredentialProviderInfo("com.android.provider3", "ClassA"), + createCredentialProviderInfo("com.android.provider4", "ClassA"), + createCredentialProviderInfo("com.android.provider5", "ClassA"), + createCredentialProviderInfo("com.android.provider6", "ClassA"))); assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); assertThat(controller.isConnected()).isFalse(); @@ -177,8 +214,38 @@ public class CredentialManagerPreferenceControllerTest { assertThat(currentlyEnabledServices.contains("com.android.provider6/ClassA")).isFalse(); } + @Test + public void handlesCredentialProviderInfoEnabledDisabled() { + CredentialProviderInfo providerInfo1 = + createCredentialProviderInfo( + "com.android.provider1", "ClassA", "Service Title", false); + CredentialProviderInfo providerInfo2 = + createCredentialProviderInfo( + "com.android.provider2", "ClassA", "Service Title", true); + CredentialManagerPreferenceController controller = + createControllerWithServices(Lists.newArrayList(providerInfo1, providerInfo2)); + assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); + assertThat(controller.isConnected()).isFalse(); + + // Test the data is correct. + assertThat(providerInfo1.isEnabled()).isFalse(); + assertThat(providerInfo2.isEnabled()).isTrue(); + + // Check that they are all actually registered. + Set enabledProviders = controller.getEnabledProviders(); + assertThat(enabledProviders.size()).isEqualTo(1); + assertThat(enabledProviders.contains("com.android.provider1")).isFalse(); + assertThat(enabledProviders.contains("com.android.provider2")).isTrue(); + + // Check that the settings string has the right component names. + List enabledServices = controller.getEnabledSettings(); + assertThat(enabledServices.size()).isEqualTo(1); + assertThat(enabledServices.contains("com.android.provider1/ClassA")).isFalse(); + assertThat(enabledServices.contains("com.android.provider2/ClassA")).isTrue(); + } + private CredentialManagerPreferenceController createControllerWithServices( - List availableServices) { + List availableServices) { CredentialManagerPreferenceController controller = new CredentialManagerPreferenceController( mContext, mCredentialsPreferenceCategory.getKey()); @@ -186,11 +253,17 @@ public class CredentialManagerPreferenceControllerTest { return controller; } - private ServiceInfo createServiceInfo() { - return createServiceInfo("com.android.provider", "CredManProvider"); + private CredentialProviderInfo createCredentialProviderInfo() { + return createCredentialProviderInfo("com.android.provider", "CredManProvider"); } - private ServiceInfo createServiceInfo(String packageName, String className) { + private CredentialProviderInfo createCredentialProviderInfo( + String packageName, String className) { + return createCredentialProviderInfo(packageName, className, null, false); + } + + private CredentialProviderInfo createCredentialProviderInfo( + String packageName, String className, CharSequence label, boolean isEnabled) { ServiceInfo si = new ServiceInfo(); si.packageName = packageName; si.name = className; @@ -200,6 +273,9 @@ public class CredentialManagerPreferenceControllerTest { si.applicationInfo.packageName = packageName; si.applicationInfo.nonLocalizedLabel = "test"; - return si; + return new CredentialProviderInfo.Builder(si) + .setOverrideLabel(label) + .setEnabled(isEnabled) + .build(); } }