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();
}
}