From 27c3987860ca7aa7820bce3af81c2dfb61711bb3 Mon Sep 17 00:00:00 2001 From: Manish Singh Date: Mon, 31 Jul 2023 14:41:00 +0100 Subject: [PATCH] Add Private Space settings page This involves: 1. Adding a new page for PS settings under Security & Privacy 2. Integrating the PS safety source with SafetyCenter. Also, add the capability to create and delete PS from the page. Creation is temporary to help with prototyping, and will be removed finally. Screenshots: Private Space Entry point in Security & Privacy Settings: https://screenshot.googleplex.com/4VHxNekjhxZHJwF Private Space Settings page: https://screenshot.googleplex.com/3Raw4wuymTHTgtM Creating Private Space: https://screenshot.googleplex.com/3dvzAH6V4kQmuYf Private Space created: https://screenshot.googleplex.com/Aj7nnF9uuUCa9Q5 Design doc: https://docs.google.com/document/d/1CdjUjAE84-5ZEPRIfw5KYFjLVHtEZxc_sF0w95su8DA/edit?usp=sharing&resourcekey=0-dAACT9HRexY1IyoxMmkVlw Bug: 286539356 Bug: 293569406 Test: manual Test: atest DeletePrivateSpaceControllerTest Test: atest UseOneLockControllerTest Test: atest HidePrivateSpaceControllerTest Change-Id: I9caf8e04e7fb2df36e60f607225e2931988ee692 --- res/values/strings.xml | 27 +++- res/xml/private_space_settings.xml | 58 +++++++ .../CreatePrivateSpaceController.java | 74 +++++++++ .../DeletePrivateSpaceController.java | 90 +++++++++++ .../HidePrivateSpaceController.java | 50 ++++++ .../PrivateSpaceDashboardFragment.java | 78 ++++++++++ .../privatespace/PrivateSpaceMaintainer.java | 145 ++++++++++++++++++ .../PrivateSpaceSafetySource.java | 106 +++++++++++++ .../privatespace/UseOneLockController.java | 50 ++++++ .../SafetySourceBroadcastReceiver.java | 6 + .../DeletePrivateSpaceControllerTest.java | 108 +++++++++++++ .../HidePrivateSpaceControllerTest.java | 51 ++++++ .../PrivateSpaceSafetySourceTest.java | 143 +++++++++++++++++ .../UseOneLockControllerTest.java | 51 ++++++ .../SafetySourceBroadcastReceiverTest.java | 71 ++++++++- 15 files changed, 1101 insertions(+), 7 deletions(-) create mode 100644 res/xml/private_space_settings.xml create mode 100644 src/com/android/settings/privatespace/CreatePrivateSpaceController.java create mode 100644 src/com/android/settings/privatespace/DeletePrivateSpaceController.java create mode 100644 src/com/android/settings/privatespace/HidePrivateSpaceController.java create mode 100644 src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java create mode 100644 src/com/android/settings/privatespace/PrivateSpaceMaintainer.java create mode 100644 src/com/android/settings/privatespace/PrivateSpaceSafetySource.java create mode 100644 src/com/android/settings/privatespace/UseOneLockController.java create mode 100644 tests/unit/src/com/android/settings/privatespace/DeletePrivateSpaceControllerTest.java create mode 100644 tests/unit/src/com/android/settings/privatespace/HidePrivateSpaceControllerTest.java create mode 100644 tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java create mode 100644 tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 9d106f4b0a5..9942534edfc 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1175,13 +1175,38 @@ More security & privacy - + Security Privacy Work profile + + Private Space + + Hide apps in a private folder + + Unlock using screen lock + + Show Private Space + + System + + Create Private Space + + Delete Private Space + + Private Space successfully created + + Private Space already exists + + Private Space could not be created + + Private Space successfully deleted + + Private Space could not be deleted + You can add up to %d fingerprints diff --git a/res/xml/private_space_settings.xml b/res/xml/private_space_settings.xml new file mode 100644 index 00000000000..08053e0f5b6 --- /dev/null +++ b/res/xml/private_space_settings.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/privatespace/CreatePrivateSpaceController.java b/src/com/android/settings/privatespace/CreatePrivateSpaceController.java new file mode 100644 index 00000000000..32149885745 --- /dev/null +++ b/src/com/android/settings/privatespace/CreatePrivateSpaceController.java @@ -0,0 +1,74 @@ +/* + * 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.privatespace; + +import android.content.Context; +import android.text.TextUtils; +import android.widget.Toast; + +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +// TODO(b/293569406): Remove this when we have the setup flow in place to create PS +/** + * Temp Controller to create the private space from the PS Settings page. This is to allow PM, UX, + * and other folks to play around with PS before the PS setup flow is ready. + */ +public final class CreatePrivateSpaceController extends BasePreferenceController { + + public CreatePrivateSpaceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; + } + + if (PrivateSpaceMaintainer.getInstance(mContext).doesPrivateSpaceExist()) { + showPrivateSpaceAlreadyExistsToast(); + return super.handlePreferenceTreeClick(preference); + } + + if (PrivateSpaceMaintainer.getInstance(mContext).createPrivateSpace()) { + showPrivateSpaceCreatedToast(); + } else { + showPrivateSpaceCreationFailedToast(); + } + return super.handlePreferenceTreeClick(preference); + } + + private void showPrivateSpaceCreatedToast() { + Toast.makeText(mContext, R.string.private_space_created, Toast.LENGTH_SHORT).show(); + } + + private void showPrivateSpaceCreationFailedToast() { + Toast.makeText(mContext, R.string.private_space_create_failed, Toast.LENGTH_SHORT).show(); + } + + private void showPrivateSpaceAlreadyExistsToast() { + Toast.makeText(mContext, R.string.private_space_already_exists, Toast.LENGTH_SHORT).show(); + } +} diff --git a/src/com/android/settings/privatespace/DeletePrivateSpaceController.java b/src/com/android/settings/privatespace/DeletePrivateSpaceController.java new file mode 100644 index 00000000000..c94f63a2629 --- /dev/null +++ b/src/com/android/settings/privatespace/DeletePrivateSpaceController.java @@ -0,0 +1,90 @@ +/* + * 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.privatespace; + +import static com.android.settings.privatespace.PrivateSpaceMaintainer.ErrorDeletingPrivateSpace.DELETE_PS_ERROR_INTERNAL; +import static com.android.settings.privatespace.PrivateSpaceMaintainer.ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NONE; +import static com.android.settings.privatespace.PrivateSpaceMaintainer.ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NO_PRIVATE_SPACE; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Toast; + +import androidx.preference.Preference; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +/** Controller to delete the private space from the PS Settings page */ +public class DeletePrivateSpaceController extends BasePreferenceController { + private static final String TAG = "DeletePrivateSpaceController"; + private final PrivateSpaceMaintainer mPrivateSpaceMaintainer; + + static class Injector { + PrivateSpaceMaintainer injectPrivateSpaceMaintainer(Context context) { + return PrivateSpaceMaintainer.getInstance(context); + } + } + + public DeletePrivateSpaceController(Context context, String preferenceKey) { + this(context, preferenceKey, new Injector()); + } + + DeletePrivateSpaceController(Context context, String preferenceKey, Injector injector) { + super(context, preferenceKey); + mPrivateSpaceMaintainer = injector.injectPrivateSpaceMaintainer(context); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; + } + + PrivateSpaceMaintainer.ErrorDeletingPrivateSpace error = + mPrivateSpaceMaintainer.deletePrivateSpace(); + if (error == DELETE_PS_ERROR_NONE) { + showSuccessfulDeletionToast(); + } else if (error == DELETE_PS_ERROR_INTERNAL) { + showDeletionInternalErrorToast(); + } else if (error == DELETE_PS_ERROR_NO_PRIVATE_SPACE) { + // Ideally this should never happen as PS Settings is not available when there's no + // Private Profile. + Log.e(TAG, "Unexpected attempt to delete non-existent PS"); + } + return super.handlePreferenceTreeClick(preference); + } + + /** Shows a toast saying that the private space was deleted */ + @VisibleForTesting + public void showSuccessfulDeletionToast() { + Toast.makeText(mContext, R.string.private_space_deleted, Toast.LENGTH_SHORT).show(); + } + + /** Shows a toast saying that the private space could not be deleted */ + @VisibleForTesting + public void showDeletionInternalErrorToast() { + Toast.makeText(mContext, R.string.private_space_delete_failed, Toast.LENGTH_SHORT).show(); + } +} diff --git a/src/com/android/settings/privatespace/HidePrivateSpaceController.java b/src/com/android/settings/privatespace/HidePrivateSpaceController.java new file mode 100644 index 00000000000..f27acbd62b9 --- /dev/null +++ b/src/com/android/settings/privatespace/HidePrivateSpaceController.java @@ -0,0 +1,50 @@ +/* + * 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.privatespace; + +import android.content.Context; + +import com.android.settings.core.TogglePreferenceController; + +/** Represents the preference controller for (un)hiding the Private Space */ +public final class HidePrivateSpaceController extends TogglePreferenceController { + public HidePrivateSpaceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isChecked() { + // TODO(b/293569406) Need to check this from a persistent store, maybe like SettingsProvider + return false; + } + + @Override + public boolean setChecked(boolean isChecked) { + // TODO(b/293569406) Need to save this to a persistent store, maybe like SettingsProvider + return true; + } + + @Override + public int getSliceHighlightMenuRes() { + return 0; + } +} diff --git a/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java b/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java new file mode 100644 index 00000000000..9e1d0d51bd6 --- /dev/null +++ b/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java @@ -0,0 +1,78 @@ +/* + * 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.privatespace; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.util.FeatureFlagUtils; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.safetycenter.SafetyCenterManagerWrapper; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +import java.util.List; + +/** Fragment representing the Private Space dashboard in Settings. */ +@SearchIndexable +public class PrivateSpaceDashboardFragment extends DashboardFragment { + private static final String TAG = "PrivateSpaceDashboardFragment"; + private static final String KEY_CREATE_PROFILE_PREFERENCE = "private_space_create"; + private static final String KEY_DELETE_PROFILE_PREFERENCE = "private_space_delete"; + private static final String KEY_ONE_LOCK_PREFERENCE = "private_space_use_one_lock"; + private static final String KEY_PS_HIDDEN_PREFERENCE = "private_space_hidden"; + + @Override + protected int getPreferenceScreenResId() { + return R.xml.private_space_settings; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.PRIVATE_SPACE_SETTINGS; + } + + @Override + protected String getLogTag() { + return TAG; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.private_space_settings) { + @Override + protected boolean isPageSearchEnabled(Context context) { + // Temporary workaround for hiding PS Settings until the trunk stable feature + // flag is available. + // TODO(b/295516544): Remove this workaround when trunk stable feature flag is + // available. + return SafetyCenterManagerWrapper.get().isEnabled(context) + && FeatureFlagUtils.isEnabled(context, + FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS); + } + + @Override + public List getNonIndexableKeys(Context context) { + List keys = super.getNonIndexableKeys(context); + keys.add(KEY_CREATE_PROFILE_PREFERENCE); + keys.add(KEY_DELETE_PROFILE_PREFERENCE); + keys.add(KEY_ONE_LOCK_PREFERENCE); + keys.add(KEY_PS_HIDDEN_PREFERENCE); + return keys; + } + }; +} diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java new file mode 100644 index 00000000000..709814d745f --- /dev/null +++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java @@ -0,0 +1,145 @@ +/* + * 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.privatespace; + +import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; + +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.ArraySet; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.List; + +// TODO(b/293569406): Update the javadoc when we have the setup flow in place to create PS +/** A class to help with the creation / deletion of Private Space */ +public class PrivateSpaceMaintainer { + private static final String TAG = "PrivateSpaceMaintainer"; + @GuardedBy("this") + private static PrivateSpaceMaintainer sPrivateSpaceMaintainer; + + private final Context mContext; + private final UserManager mUserManager; + @GuardedBy("this") + private UserHandle mUserHandle; + + public enum ErrorDeletingPrivateSpace { + DELETE_PS_ERROR_NONE, + DELETE_PS_ERROR_NO_PRIVATE_SPACE, + DELETE_PS_ERROR_INTERNAL + } + + /** + * Returns true if the private space was successfully created. + * + *

This method should be used by the Private Space Setup Flow ONLY. + */ + final synchronized boolean createPrivateSpace() { + // Check if Private space already exists + if (doesPrivateSpaceExist()) { + return true; + } + // a name indicating that the profile was created from the PS Settings page + final String userName = "psSettingsUser"; + + if (mUserHandle == null) { + try { + mUserHandle = mUserManager.createProfile( + userName, USER_TYPE_PROFILE_PRIVATE, new ArraySet<>()); + } catch (Exception e) { + Log.e(TAG, "Error creating private space", e); + return false; + } + + if (mUserHandle == null) { + Log.e(TAG, "Failed to create private space"); + return false; + } + + IActivityManager am = ActivityManager.getService(); + try { + am.startProfile(mUserHandle.getIdentifier()); + } catch (RemoteException e) { + Log.e(TAG, "Failed to start private profile"); + return false; + } + + Log.i(TAG, "Private space created with id: " + mUserHandle.getIdentifier()); + } + return true; + } + + /** Returns the {@link ErrorDeletingPrivateSpace} enum representing the result of operation. + * + *

This method should be used ONLY by the delete-PS controller in the PS Settings page. + */ + public synchronized ErrorDeletingPrivateSpace deletePrivateSpace() { + if (!doesPrivateSpaceExist()) { + return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NO_PRIVATE_SPACE; + } + + try { + Log.i(TAG, "Deleting Private space with id: " + mUserHandle.getIdentifier()); + if (mUserManager.removeUser(mUserHandle)) { + Log.i(TAG, "Private space deleted"); + mUserHandle = null; + + return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NONE; + } else { + Log.e(TAG, "Failed to delete private space"); + } + } catch (Exception e) { + Log.e(TAG, "Error deleting private space", e); + } + return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_INTERNAL; + } + + /** Returns true if the Private space exists. */ + public synchronized boolean doesPrivateSpaceExist() { + if (mUserHandle != null) { + return true; + } + + List users = mUserManager.getProfiles(0); + for (UserInfo user : users) { + if (user.isPrivateProfile()) { + mUserHandle = user.getUserHandle(); + return true; + } + } + return false; + } + + static synchronized PrivateSpaceMaintainer getInstance(Context context) { + if (sPrivateSpaceMaintainer == null) { + sPrivateSpaceMaintainer = new PrivateSpaceMaintainer(context); + } + return sPrivateSpaceMaintainer; + } + + private PrivateSpaceMaintainer(Context context) { + mContext = context.getApplicationContext(); + mUserManager = mContext.getSystemService(UserManager.class); + } +} diff --git a/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java b/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java new file mode 100644 index 00000000000..b07c6233cfb --- /dev/null +++ b/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java @@ -0,0 +1,106 @@ +/* + * 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.privatespace; + +import android.app.PendingIntent; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.os.UserManager; +import android.safetycenter.SafetyEvent; +import android.safetycenter.SafetySourceData; +import android.safetycenter.SafetySourceStatus; +import android.util.FeatureFlagUtils; +import android.util.Log; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.safetycenter.SafetyCenterManagerWrapper; +import com.android.settingslib.transition.SettingsTransitionHelper; + +/** Private Space safety source for the Safety Center */ +public final class PrivateSpaceSafetySource { + public static final String SAFETY_SOURCE_ID = "AndroidPrivateSpace"; + private static final String TAG = "PrivateSpaceSafetySource"; + + private PrivateSpaceSafetySource() {} + + /** Sets lock screen safety data for Safety Center. */ + public static void setSafetySourceData(Context context, + SafetyEvent safetyEvent) { + if (!SafetyCenterManagerWrapper.get().isEnabled(context)) { + Log.i(TAG, "Safety Center disabled"); + return; + } + + // Check the profile type - we don't want to show this for anything other than primary user. + UserManager userManager = context.getSystemService(UserManager.class); + if (userManager != null && !userManager.isMainUser()) { + Log.i(TAG, "setSafetySourceData not main user"); + return; + } + + // Temporary workaround to help prevent the PS Settings showing up in droidfood builds. + // TODO(b/295516544): remove this when the trunk stable feature flag for PS is available. + if (!FeatureFlagUtils.isEnabled(context, + FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS)) { + // Setting null safetySourceData so that an old entry gets cleared out and this way + // provide a response since SC always expects one on rescan. + SafetyCenterManagerWrapper.get().setSafetySourceData( + context, + SAFETY_SOURCE_ID, + /* safetySourceData */ null, + safetyEvent + ); + return; + } + + PendingIntent pendingIntent = getPendingIntentForPsDashboard(context); + + SafetySourceStatus status = new SafetySourceStatus.Builder( + context.getString(R.string.private_space_title), + context.getString(R.string.private_space_summary), + SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED) + .setPendingIntent(pendingIntent).build(); + SafetySourceData safetySourceData = + new SafetySourceData.Builder().setStatus(status).build(); + + Log.d(TAG, "Setting safety source data"); + SafetyCenterManagerWrapper.get().setSafetySourceData( + context, + SAFETY_SOURCE_ID, + safetySourceData, + safetyEvent + ); + } + + private static PendingIntent getPendingIntentForPsDashboard(Context context) { + Intent privateSpaceDashboardIntent = new SubSettingLauncher(context) + .setDestination(PrivateSpaceDashboardFragment.class.getName()) + .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE) + .setSourceMetricsCategory(SettingsEnums.PRIVATE_SPACE_SETTINGS) + .toIntent() + .setIdentifier(SAFETY_SOURCE_ID); + + return PendingIntent + .getActivity( + context, + /* requestCode */ 0, + privateSpaceDashboardIntent, + PendingIntent.FLAG_IMMUTABLE); + } +} diff --git a/src/com/android/settings/privatespace/UseOneLockController.java b/src/com/android/settings/privatespace/UseOneLockController.java new file mode 100644 index 00000000000..a94db57a814 --- /dev/null +++ b/src/com/android/settings/privatespace/UseOneLockController.java @@ -0,0 +1,50 @@ +/* + * 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.privatespace; + +import android.content.Context; + +import com.android.settings.core.TogglePreferenceController; + +/** Represents the preference controller for using the same lock as the screen lock */ +public class UseOneLockController extends TogglePreferenceController { + public UseOneLockController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isChecked() { + // TODO(b/293569406) Need to save this to a persistent store, maybe like SettingsProvider + return false; + } + + @Override + public boolean setChecked(boolean isChecked) { + // TODO(b/293569406) Need to save this to a persistent store, maybe like SettingsProvider + return true; + } + + @Override + public int getSliceHighlightMenuRes() { + return 0; + } +} diff --git a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java index 0b556e74a37..cc0f892e873 100644 --- a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java +++ b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java @@ -28,6 +28,7 @@ import android.content.Intent; import android.safetycenter.SafetyCenterManager; import android.safetycenter.SafetyEvent; +import com.android.settings.privatespace.PrivateSpaceSafetySource; import com.android.settings.security.ScreenLockPreferenceDetailsUtils; import com.google.common.collect.ImmutableList; @@ -79,11 +80,16 @@ public class SafetySourceBroadcastReceiver extends BroadcastReceiver { if (sourceIds.contains(BiometricsSafetySource.SAFETY_SOURCE_ID)) { BiometricsSafetySource.setSafetySourceData(context, safetyEvent); } + + if (sourceIds.contains(PrivateSpaceSafetySource.SAFETY_SOURCE_ID)) { + PrivateSpaceSafetySource.setSafetySourceData(context, safetyEvent); + } } private static void refreshAllSafetySources(Context context, SafetyEvent safetyEvent) { LockScreenSafetySource.setSafetySourceData(context, new ScreenLockPreferenceDetailsUtils(context), safetyEvent); BiometricsSafetySource.setSafetySourceData(context, safetyEvent); + PrivateSpaceSafetySource.setSafetySourceData(context, safetyEvent); } } diff --git a/tests/unit/src/com/android/settings/privatespace/DeletePrivateSpaceControllerTest.java b/tests/unit/src/com/android/settings/privatespace/DeletePrivateSpaceControllerTest.java new file mode 100644 index 00000000000..8fb3eae9dab --- /dev/null +++ b/tests/unit/src/com/android/settings/privatespace/DeletePrivateSpaceControllerTest.java @@ -0,0 +1,108 @@ +/* + * 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.privatespace; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.privatespace.PrivateSpaceMaintainer.ErrorDeletingPrivateSpace.DELETE_PS_ERROR_INTERNAL; +import static com.android.settings.privatespace.PrivateSpaceMaintainer.ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NONE; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.content.Context; + +import androidx.preference.Preference; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class DeletePrivateSpaceControllerTest { + @Mock private PrivateSpaceMaintainer mPrivateSpaceMaintainer; + @Mock private Context mContext; + + private Preference mPreference; + private DeletePrivateSpaceController mDeletePrivateSpaceController; + + /** Required setup before a test. */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + final String preferenceKey = "private_space_delete"; + + mPreference = new Preference(ApplicationProvider.getApplicationContext()); + mPreference.setKey(preferenceKey); + + mDeletePrivateSpaceController = + new DeletePrivateSpaceController( + mContext, + preferenceKey, + new DeletePrivateSpaceController.Injector() { + @Override + PrivateSpaceMaintainer injectPrivateSpaceMaintainer(Context context) { + return mPrivateSpaceMaintainer; + } + }); + } + + /** Tests that the controller is always available. */ + @Test + public void getAvailabilityStatus_returnsAvailable() { + assertThat(mDeletePrivateSpaceController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + /** Tests that on click it attempts to delete the PS. */ + @Test + public void handlePreferenceTreeClick_attemptsToDeletePrivateSpace() { + doReturn(DELETE_PS_ERROR_NONE).when(mPrivateSpaceMaintainer).deletePrivateSpace(); + DeletePrivateSpaceController spy = Mockito.spy(mDeletePrivateSpaceController); + doNothing().when(spy).showSuccessfulDeletionToast(); + spy.handlePreferenceTreeClick(mPreference); + + verify(mPrivateSpaceMaintainer).deletePrivateSpace(); + } + + /** Tests that on deletion of PS relevant toast is shown. */ + @Test + public void handlePreferenceTreeClick_onDeletion_showsDeletedToast() { + doReturn(DELETE_PS_ERROR_NONE).when(mPrivateSpaceMaintainer).deletePrivateSpace(); + DeletePrivateSpaceController spy = Mockito.spy(mDeletePrivateSpaceController); + doNothing().when(spy).showSuccessfulDeletionToast(); + spy.handlePreferenceTreeClick(mPreference); + + verify(spy).showSuccessfulDeletionToast(); + } + + /** Tests that on failing to delete the PS relevant toast is shown. */ + @Test + public void handlePreferenceTreeClick_onDeletionError_showsDeletionFailedToast() { + doReturn(DELETE_PS_ERROR_INTERNAL).when(mPrivateSpaceMaintainer).deletePrivateSpace(); + DeletePrivateSpaceController spy = Mockito.spy(mDeletePrivateSpaceController); + doNothing().when(spy).showDeletionInternalErrorToast(); + spy.handlePreferenceTreeClick(mPreference); + + verify(spy).showDeletionInternalErrorToast(); + } +} diff --git a/tests/unit/src/com/android/settings/privatespace/HidePrivateSpaceControllerTest.java b/tests/unit/src/com/android/settings/privatespace/HidePrivateSpaceControllerTest.java new file mode 100644 index 00000000000..1a1769edf57 --- /dev/null +++ b/tests/unit/src/com/android/settings/privatespace/HidePrivateSpaceControllerTest.java @@ -0,0 +1,51 @@ +/* + * 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.privatespace; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +@RunWith(AndroidJUnit4.class) +public class HidePrivateSpaceControllerTest { + @Mock private Context mContext; + private HidePrivateSpaceController mHidePrivateSpaceController; + + /** Required setup before a test. */ + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + final String preferenceKey = "private_space_hidden"; + + mHidePrivateSpaceController = new HidePrivateSpaceController(mContext, preferenceKey); + } + + /** Tests that the controller is always available. */ + @Test + public void getAvailabilityStatus_returnsAvailable() { + assertThat(mHidePrivateSpaceController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } +} diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java new file mode 100644 index 00000000000..2dc00e145f1 --- /dev/null +++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java @@ -0,0 +1,143 @@ +/* + * 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.privatespace; + + +import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED; +import static com.android.settings.privatespace.PrivateSpaceSafetySource.SAFETY_SOURCE_ID; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.safetycenter.SafetyEvent; +import android.safetycenter.SafetySourceData; +import android.safetycenter.SafetySourceStatus; +import android.util.FeatureFlagUtils; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.safetycenter.SafetyCenterManagerWrapper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class PrivateSpaceSafetySourceTest { + private static final SafetyEvent EVENT_TYPE_DEVICE_REBOOTED = + new SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_REBOOTED).build(); + private Context mContext = ApplicationProvider.getApplicationContext(); + @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; + + /** Required setup before a test. */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper; + + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS, true); + } + + /** Required setup after a test. */ + @After + public void tearDown() { + SafetyCenterManagerWrapper.sInstance = null; + } + + /** Tests that when SC is disabled we don't set any data. */ + @Test + public void onDeviceRebootedEvent_whenSafetyCenterDisabled_doesNotSetData() { + when(mSafetyCenterManagerWrapper.isEnabled(mContext)).thenReturn(false); + + PrivateSpaceSafetySource.setSafetySourceData(mContext, EVENT_TYPE_DEVICE_REBOOTED); + + verify(mSafetyCenterManagerWrapper, never()).setSafetySourceData( + any(), any(), any(), any()); + } + + /** Tests that when SC is enabled we set data. */ + @Test + public void onDeviceRebootedEvent_whenSafetyCenterEnabled_setsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mContext)).thenReturn(true); + + PrivateSpaceSafetySource.setSafetySourceData(mContext, EVENT_TYPE_DEVICE_REBOOTED); + + verify(mSafetyCenterManagerWrapper).setSafetySourceData( + any(), eq(SAFETY_SOURCE_ID), any(), eq(EVENT_TYPE_DEVICE_REBOOTED)); + } + + // TODO(b/295516544): Modify this test for the new trunk stable flag instead when available. + /** Tests that when the feature is disabled null data is set. */ + @Test + public void setSafetySourceData_whenFeatureDisabled_setsNullData() { + when(mSafetyCenterManagerWrapper.isEnabled(mContext)).thenReturn(true); + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS, false); + + PrivateSpaceSafetySource.setSafetySourceData(mContext, EVENT_TYPE_DEVICE_REBOOTED); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper).setSafetySourceData( + any(), eq(SAFETY_SOURCE_ID), captor.capture(), eq(EVENT_TYPE_DEVICE_REBOOTED)); + SafetySourceData safetySourceData = captor.getValue(); + assertThat(safetySourceData).isNull(); + + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS, true); + } + + /** Tests that setSafetySourceData sets the source status enabled. */ + @Test + public void setSafetySourceData_setsEnabled() { + when(mSafetyCenterManagerWrapper.isEnabled(mContext)).thenReturn(true); + + PrivateSpaceSafetySource.setSafetySourceData(mContext, EVENT_TYPE_DEVICE_REBOOTED); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper).setSafetySourceData( + any(), eq(SAFETY_SOURCE_ID), captor.capture(), eq(EVENT_TYPE_DEVICE_REBOOTED)); + SafetySourceData safetySourceData = captor.getValue(); + SafetySourceStatus safetySourceStatus = safetySourceData.getStatus(); + assertThat(safetySourceStatus.isEnabled()).isTrue(); + } + + /** Tests that setSafetySourceData sets the PS settings page intent. */ + @Test + public void setSafetySourceData_setsPsIntent() { + when(mSafetyCenterManagerWrapper.isEnabled(mContext)).thenReturn(true); + + PrivateSpaceSafetySource.setSafetySourceData(mContext, EVENT_TYPE_DEVICE_REBOOTED); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper).setSafetySourceData( + any(), eq(SAFETY_SOURCE_ID), captor.capture(), eq(EVENT_TYPE_DEVICE_REBOOTED)); + SafetySourceData safetySourceData = captor.getValue(); + SafetySourceStatus safetySourceStatus = safetySourceData.getStatus(); + assertThat(safetySourceStatus.getPendingIntent().getIntent().getIdentifier()) + .isEqualTo(SAFETY_SOURCE_ID); + } +} diff --git a/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java b/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java new file mode 100644 index 00000000000..e7ebb37faf6 --- /dev/null +++ b/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java @@ -0,0 +1,51 @@ +/* + * 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.privatespace; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +@RunWith(AndroidJUnit4.class) +public class UseOneLockControllerTest { + @Mock private Context mContext; + private UseOneLockController mUseOneLockController; + + /** Required setup before a test. */ + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + final String preferenceKey = "private_space_use_one_lock"; + + mUseOneLockController = new UseOneLockController(mContext, preferenceKey); + } + + /** Tests that the controller is always available. */ + @Test + public void getAvailabilityStatus_returnsAvailable() { + assertThat(mUseOneLockController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } +} diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java index 3ad1874995b..caae44a0546 100644 --- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java @@ -21,9 +21,7 @@ import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOUR import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS; import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED; import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -33,11 +31,14 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; import android.safetycenter.SafetyEvent; +import android.safetycenter.SafetySourceData; +import android.util.FeatureFlagUtils; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.privatespace.PrivateSpaceSafetySource; import com.android.settings.testutils.FakeFeatureFactory; import org.junit.After; @@ -216,6 +217,62 @@ public class SafetySourceBroadcastReceiverTest { assertThat(captor.getValue()).isEqualTo(BiometricsSafetySource.SAFETY_SOURCE_ID); } + /** + * Tests that on receiving the refresh broadcast request with the PS source id, the PS data + * is set. + */ + @Test + public void onReceive_onRefresh_withPrivateSpaceSourceId_setsPrivateSpaceData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + Intent intent = + new Intent() + .setAction(ACTION_REFRESH_SAFETY_SOURCES) + .putExtra( + EXTRA_REFRESH_SAFETY_SOURCE_IDS, + new String[] {PrivateSpaceSafetySource.SAFETY_SOURCE_ID}) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); + + new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(mSafetyCenterManagerWrapper, times(1)) + .setSafetySourceData(any(), captor.capture(), any(), any()); + + assertThat(captor.getValue()).isEqualTo(PrivateSpaceSafetySource.SAFETY_SOURCE_ID); + } + + /** Tests that the PS source sets null data when it's disabled. */ + // TODO(b/295516544): Modify this test for the new trunk stable flag instead when available. + @Test + public void onReceive_onRefresh_withPrivateSpaceFeatureDisabled_setsNullData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + FeatureFlagUtils + .setEnabled( + mApplicationContext, + FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS, + false); + + Intent intent = + new Intent() + .setAction(ACTION_REFRESH_SAFETY_SOURCES) + .putExtra( + EXTRA_REFRESH_SAFETY_SOURCE_IDS, + new String[] {PrivateSpaceSafetySource.SAFETY_SOURCE_ID}) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); + + new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper, times(1)) + .setSafetySourceData(any(), any(), captor.capture(), any()); + + assertThat(captor.getValue()).isEqualTo(null); + + FeatureFlagUtils + .setEnabled( + mApplicationContext, + FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS, + true); + } + @Test public void onReceive_onBootCompleted_setsBootCompleteEvent() { when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); @@ -223,22 +280,22 @@ public class SafetySourceBroadcastReceiverTest { new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); ArgumentCaptor captor = ArgumentCaptor.forClass(SafetyEvent.class); - verify(mSafetyCenterManagerWrapper, times(2)) + verify(mSafetyCenterManagerWrapper, times(3)) .setSafetySourceData(any(), any(), any(), captor.capture()); SafetyEvent bootEvent = new SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_REBOOTED).build(); assertThat(captor.getAllValues()) - .containsExactlyElementsIn(Arrays.asList(bootEvent, bootEvent)); + .containsExactlyElementsIn(Arrays.asList(bootEvent, bootEvent, bootEvent)); } @Test - public void onReceive_onBootCompleted_sendsBiometricAndLockscreenData() { + public void onReceive_onBootCompleted_sendsAllSafetySourcesData() { when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); Intent intent = new Intent().setAction(Intent.ACTION_BOOT_COMPLETED); new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(mSafetyCenterManagerWrapper, times(2)) + verify(mSafetyCenterManagerWrapper, times(3)) .setSafetySourceData(any(), captor.capture(), any(), any()); List safetySourceIdList = captor.getAllValues(); @@ -246,5 +303,7 @@ public class SafetySourceBroadcastReceiverTest { id -> id.equals(LockScreenSafetySource.SAFETY_SOURCE_ID))).isTrue(); assertThat(safetySourceIdList.stream().anyMatch( id -> id.equals(BiometricsSafetySource.SAFETY_SOURCE_ID))).isTrue(); + assertThat(safetySourceIdList.stream().anyMatch( + id -> id.equals(PrivateSpaceSafetySource.SAFETY_SOURCE_ID))).isTrue(); } }