Loading core/java/android/app/supervision/SupervisionManager.java +8 −0 Original line number Diff line number Diff line Loading @@ -119,6 +119,14 @@ public class SupervisionManager { public static final String ACTION_DISABLE_SUPERVISION = "android.app.supervision.action.DISABLE_SUPERVISION"; /** * SupervisionService's identifier for setting policies or restrictions in * {@link DevicePolicyManager}. * * @hide */ public static final String SUPERVISION_SYSTEM_ENTITY = SupervisionManager.class.getName(); /** @hide */ @UnsupportedAppUsage public SupervisionManager(Context context, @Nullable ISupervisionManager service) { Loading services/supervision/java/com/android/server/supervision/SupervisionService.java +83 −4 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.app.supervision.ISupervisionListener; import android.app.supervision.ISupervisionManager; import android.app.supervision.SupervisionManager; import android.app.supervision.SupervisionManagerInternal; import android.app.supervision.SupervisionRecoveryInfo; import android.app.supervision.flags.Flags; Loading @@ -60,6 +61,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.util.ArrayMap; import android.util.SparseArray; Loading @@ -67,6 +69,7 @@ import android.util.SparseArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FunctionalUtils.RemoteExceptionIgnoringConsumer; import com.android.internal.util.IndentingPrintWriter; Loading Loading @@ -101,6 +104,10 @@ public class SupervisionService extends ISupervisionManager.Stub { static final String ACTION_CONFIRM_SUPERVISION_CREDENTIALS = "android.app.supervision.action.CONFIRM_SUPERVISION_CREDENTIALS"; @VisibleForTesting static final List<String> SYSTEM_ENTITIES = List.of(SupervisionManager.SUPERVISION_SYSTEM_ENTITY); // TODO(b/362756788): Does this need to be a LockGuard lock? private final Object mLockDoNoUseDirectly = new Object(); Loading @@ -108,6 +115,7 @@ public class SupervisionService extends ISupervisionManager.Stub { private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>(); private final Injector mInjector; private final RoleObserver mRoleObserver; final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); @GuardedBy("getLockObject()") Loading @@ -130,6 +138,8 @@ public class SupervisionService extends ISupervisionManager.Stub { mInjector = injector; mServiceThread = injector.getServiceThread(); mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener()); mRoleObserver = new RoleObserver(); mRoleObserver.register(); } /** Loading Loading @@ -211,12 +221,17 @@ public class SupervisionService extends ISupervisionManager.Stub { /** Set the Supervision Recovery Info. */ @Override public void setSupervisionRecoveryInfo(SupervisionRecoveryInfo recoveryInfo) { if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.saveRecoveryInfo(recoveryInfo); } else { if (!Flags.persistentSupervisionSettings()) { SupervisionRecoveryInfoStorage.getInstance(mInjector.context) .saveRecoveryInfo(recoveryInfo); return; } synchronized (getLockObject()) { mSupervisionSettings.saveRecoveryInfo(recoveryInfo); } maybeApplyUserRestrictions(); } /** Returns the Supervision Recovery Info or null if recovery is not set. */ Loading Loading @@ -404,6 +419,8 @@ public class SupervisionService extends ISupervisionManager.Stub { listener -> listener.onSetSupervisionEnabled(userId, enabled)); if (!enabled) { clearAllDevicePoliciesAndSuspendedPackages(userId); } else { maybeApplyUserRestrictionsFor(UserHandle.of(userId)); } }); } Loading Loading @@ -483,7 +500,15 @@ public class SupervisionService extends ISupervisionManager.Stub { DevicePolicyManagerInternal dpmi = mInjector.getDpmInternal(); if (dpmi != null) { // Ideally all policy removals would be done atomically in a single call, but there // isn't a good way to handle that right now so they will be done separately. // It is currently safe to separate them because no restrictions are set by the // system entity when supervision role holders are present anyway. dpmi.removePoliciesForAdmins(userId, supervisionPackages); // We're only setting local policies for now, but if we ever were to add a global policy // we should also clear that here, if there are no longer any users with supervision // enabled. dpmi.removeLocalPoliciesForSystemEntities(userId, SYSTEM_ENTITIES); } } Loading @@ -496,6 +521,44 @@ public class SupervisionService extends ISupervisionManager.Stub { mInjector.removeRoleHoldersAsUser(roleName, supervisionPackage, UserHandle.of(userId)); } private void maybeApplyUserRestrictions() { List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(/* excludeDying= */ false); for (var user : users) { maybeApplyUserRestrictionsFor(user.getUserHandle()); } } private void maybeApplyUserRestrictionsFor(@NonNull UserHandle user) { DevicePolicyManagerInternal dpmi = mInjector.getDpmInternal(); if (dpmi != null) { boolean enabled = shouldApplyFactoryResetRestriction(user); dpmi.setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, enabled, user.getIdentifier()); } } private boolean shouldApplyFactoryResetRestriction(@NonNull UserHandle user) { List<String> supervisionRoleHolders = mInjector.getRoleHoldersAsUser(RoleManager.ROLE_SUPERVISION, user); @UserIdInt int userId = user.getIdentifier(); synchronized (getLockObject()) { // If there are no Supervision role holders to otherwise enforce restrictions, set a // factory reset restriction by default when supervision is enabled and recovery info is // set. SupervisionRecoveryInfo recoveryInfo = mSupervisionSettings.getRecoveryInfo(); return supervisionRoleHolders.isEmpty() && getUserDataLocked(userId).supervisionEnabled && recoveryInfo != null && recoveryInfo.getState() == SupervisionRecoveryInfo.STATE_VERIFIED; } } /** * Updates Web Content Filters when supervision status is updated. * Loading Loading @@ -688,7 +751,7 @@ public class SupervisionService extends ISupervisionManager.Stub { if (!success) { Slogf.e( SupervisionLog.TAG, "Failed to remove role %s fro %s", "Failed to remove role %s for %s", packageName, roleName); } Loading Loading @@ -801,6 +864,22 @@ public class SupervisionService extends ISupervisionManager.Stub { } } private final class RoleObserver implements OnRoleHoldersChangedListener { RoleObserver() {} void register() { mInjector.addOnRoleHoldersChangedListenerAsUser( BackgroundThread.getExecutor(), this, UserHandle.ALL); } @Override public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { if (RoleManager.ROLE_SUPERVISION.equals(roleName)) { maybeApplyUserRestrictionsFor(user); } } } /** Deletes user data when the user gets removed. */ private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener { @Override Loading services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +132 −0 Original line number Diff line number Diff line Loading @@ -24,8 +24,10 @@ import android.app.admin.DevicePolicyManagerInternal import android.app.role.OnRoleHoldersChangedListener import android.app.role.RoleManager import android.app.supervision.ISupervisionListener import android.app.supervision.SupervisionManager import android.app.supervision.SupervisionRecoveryInfo import android.app.supervision.SupervisionRecoveryInfo.STATE_PENDING import android.app.supervision.SupervisionRecoveryInfo.STATE_VERIFIED import android.app.supervision.flags.Flags import android.content.BroadcastReceiver import android.content.ComponentName Loading @@ -48,6 +50,7 @@ import android.os.PersistableBundle import android.os.UserHandle import android.os.UserHandle.MIN_SECONDARY_USER_ID import android.os.UserHandle.USER_SYSTEM import android.os.UserManager import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.DeviceFlagsValueProvider Loading Loading @@ -84,6 +87,7 @@ import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever Loading Loading @@ -316,6 +320,11 @@ class SupervisionServiceTest { eq(USER_ID), argThat { toSet() == supervisionRoleHolders.values.toSet() }, ) verify(mockDpmInternal) .removeLocalPoliciesForSystemEntities( eq(USER_ID), eq(SupervisionService.SYSTEM_ENTITIES), ) for (packageName in supervisionRoleHolders.values) { verify(mockPackageManagerInternal) .unsuspendForSuspendingPackage(eq(packageName), eq(USER_ID), eq(USER_ID)) Loading @@ -329,6 +338,62 @@ class SupervisionServiceTest { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() verify(mockDpmInternal, never()).removePoliciesForAdmins(any(), any()) verify(mockDpmInternal, never()).removeLocalPoliciesForSystemEntities(any(), any()) } @Test fun setSupervisionEnabledForUser_hasVerifiedRecoveryInfo_restrictsFactoryResetWhenEnabling() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() setSupervisionRecoveryInfo(state = STATE_VERIFIED) setSupervisionEnabledForUser(USER_ID, true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() verify(mockDpmInternal) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ true, USER_ID, ) } @Test fun setSupervisionEnabledForUser_hasPendingRecoveryInfo_doesNotRestrictFactoryReset() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() setSupervisionRecoveryInfo(state = STATE_PENDING) setSupervisionEnabledForUser(USER_ID, true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() verify(mockDpmInternal) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ false, USER_ID, ) } @Test fun setSupervisionEnabledForUser_hasSupervisionRoleHolders_doesNotRestrictFactoryReset() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() injector.setRoleHoldersAsUser( RoleManager.ROLE_SUPERVISION, UserHandle.of(USER_ID), listOf("com.example.supervisionapp1"), ) setSupervisionRecoveryInfo(state = STATE_VERIFIED) setSupervisionEnabledForUser(USER_ID, true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() verify(mockDpmInternal) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ false, USER_ID, ) } @Test Loading Loading @@ -438,6 +503,7 @@ class SupervisionServiceTest { @Test @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS) fun setSupervisionRecoveryInfo() { addDefaultAndTestUsers() assertThat(service.supervisionRecoveryInfo).isNull() val recoveryInfo = Loading @@ -457,6 +523,64 @@ class SupervisionServiceTest { assertThat(service.supervisionRecoveryInfo.state).isEqualTo(recoveryInfo.state) } @Test @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS) fun setSupervisionRecoveryInfo_supervisionEnabled_restrictsFactoryReset() { addDefaultAndTestUsers() setSupervisionEnabledForUser(USER_ID, true) setSupervisionRecoveryInfo(state = STATE_VERIFIED) verify(mockDpmInternal) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ true, USER_ID, ) } @Test @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS) fun setSupervisionRecoveryInfo_toNull_supervisionEnabled_unrestrictsFactoryReset() { addDefaultAndTestUsers() setSupervisionEnabledForUser(USER_ID, true) service.setSupervisionRecoveryInfo(null) // Once for the initial setSupervisionEnabledForUser, once for the // setSupervisionRecoveryInfo(null). verify(mockDpmInternal, times(2)) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ false, USER_ID, ) } @Test @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS) fun setSupervisionRecoveryInfo_supervisionEnabled_hasSupervisionRoleHolders_doesNotRestrictFactoryReset() { addDefaultAndTestUsers() setSupervisionEnabledForUser(USER_ID, true) injector.setRoleHoldersAsUser( RoleManager.ROLE_SUPERVISION, UserHandle.of(USER_ID), listOf("com.example.supervisionapp1"), ) setSupervisionRecoveryInfo(state = STATE_VERIFIED) // Once for the initial setSupervisionEnabledForUser, and again for the // setSupervisionRecoveryInfo. verify(mockDpmInternal, times(2)) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ false, USER_ID, ) } @Test fun hasSupervisionCredentials() { whenever(mockUserManagerInternal.getSupervisingProfileId()).thenReturn(SUPERVISING_USER_ID) Loading Loading @@ -567,6 +691,14 @@ class SupervisionServiceTest { verifySupervisionListeners(userId, enabled, listeners) } private fun setSupervisionRecoveryInfo( @SupervisionRecoveryInfo.State state: Int, accountData: PersistableBundle? = null, ) { val recoveryInfo = SupervisionRecoveryInfo("email", "default", state, accountData) service.setSupervisionRecoveryInfo(recoveryInfo) } private fun verifySupervisionListeners( expectedUserId: Int, enabled: Boolean, Loading Loading
core/java/android/app/supervision/SupervisionManager.java +8 −0 Original line number Diff line number Diff line Loading @@ -119,6 +119,14 @@ public class SupervisionManager { public static final String ACTION_DISABLE_SUPERVISION = "android.app.supervision.action.DISABLE_SUPERVISION"; /** * SupervisionService's identifier for setting policies or restrictions in * {@link DevicePolicyManager}. * * @hide */ public static final String SUPERVISION_SYSTEM_ENTITY = SupervisionManager.class.getName(); /** @hide */ @UnsupportedAppUsage public SupervisionManager(Context context, @Nullable ISupervisionManager service) { Loading
services/supervision/java/com/android/server/supervision/SupervisionService.java +83 −4 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.app.supervision.ISupervisionListener; import android.app.supervision.ISupervisionManager; import android.app.supervision.SupervisionManager; import android.app.supervision.SupervisionManagerInternal; import android.app.supervision.SupervisionRecoveryInfo; import android.app.supervision.flags.Flags; Loading @@ -60,6 +61,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.util.ArrayMap; import android.util.SparseArray; Loading @@ -67,6 +69,7 @@ import android.util.SparseArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FunctionalUtils.RemoteExceptionIgnoringConsumer; import com.android.internal.util.IndentingPrintWriter; Loading Loading @@ -101,6 +104,10 @@ public class SupervisionService extends ISupervisionManager.Stub { static final String ACTION_CONFIRM_SUPERVISION_CREDENTIALS = "android.app.supervision.action.CONFIRM_SUPERVISION_CREDENTIALS"; @VisibleForTesting static final List<String> SYSTEM_ENTITIES = List.of(SupervisionManager.SUPERVISION_SYSTEM_ENTITY); // TODO(b/362756788): Does this need to be a LockGuard lock? private final Object mLockDoNoUseDirectly = new Object(); Loading @@ -108,6 +115,7 @@ public class SupervisionService extends ISupervisionManager.Stub { private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>(); private final Injector mInjector; private final RoleObserver mRoleObserver; final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); @GuardedBy("getLockObject()") Loading @@ -130,6 +138,8 @@ public class SupervisionService extends ISupervisionManager.Stub { mInjector = injector; mServiceThread = injector.getServiceThread(); mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener()); mRoleObserver = new RoleObserver(); mRoleObserver.register(); } /** Loading Loading @@ -211,12 +221,17 @@ public class SupervisionService extends ISupervisionManager.Stub { /** Set the Supervision Recovery Info. */ @Override public void setSupervisionRecoveryInfo(SupervisionRecoveryInfo recoveryInfo) { if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.saveRecoveryInfo(recoveryInfo); } else { if (!Flags.persistentSupervisionSettings()) { SupervisionRecoveryInfoStorage.getInstance(mInjector.context) .saveRecoveryInfo(recoveryInfo); return; } synchronized (getLockObject()) { mSupervisionSettings.saveRecoveryInfo(recoveryInfo); } maybeApplyUserRestrictions(); } /** Returns the Supervision Recovery Info or null if recovery is not set. */ Loading Loading @@ -404,6 +419,8 @@ public class SupervisionService extends ISupervisionManager.Stub { listener -> listener.onSetSupervisionEnabled(userId, enabled)); if (!enabled) { clearAllDevicePoliciesAndSuspendedPackages(userId); } else { maybeApplyUserRestrictionsFor(UserHandle.of(userId)); } }); } Loading Loading @@ -483,7 +500,15 @@ public class SupervisionService extends ISupervisionManager.Stub { DevicePolicyManagerInternal dpmi = mInjector.getDpmInternal(); if (dpmi != null) { // Ideally all policy removals would be done atomically in a single call, but there // isn't a good way to handle that right now so they will be done separately. // It is currently safe to separate them because no restrictions are set by the // system entity when supervision role holders are present anyway. dpmi.removePoliciesForAdmins(userId, supervisionPackages); // We're only setting local policies for now, but if we ever were to add a global policy // we should also clear that here, if there are no longer any users with supervision // enabled. dpmi.removeLocalPoliciesForSystemEntities(userId, SYSTEM_ENTITIES); } } Loading @@ -496,6 +521,44 @@ public class SupervisionService extends ISupervisionManager.Stub { mInjector.removeRoleHoldersAsUser(roleName, supervisionPackage, UserHandle.of(userId)); } private void maybeApplyUserRestrictions() { List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(/* excludeDying= */ false); for (var user : users) { maybeApplyUserRestrictionsFor(user.getUserHandle()); } } private void maybeApplyUserRestrictionsFor(@NonNull UserHandle user) { DevicePolicyManagerInternal dpmi = mInjector.getDpmInternal(); if (dpmi != null) { boolean enabled = shouldApplyFactoryResetRestriction(user); dpmi.setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, enabled, user.getIdentifier()); } } private boolean shouldApplyFactoryResetRestriction(@NonNull UserHandle user) { List<String> supervisionRoleHolders = mInjector.getRoleHoldersAsUser(RoleManager.ROLE_SUPERVISION, user); @UserIdInt int userId = user.getIdentifier(); synchronized (getLockObject()) { // If there are no Supervision role holders to otherwise enforce restrictions, set a // factory reset restriction by default when supervision is enabled and recovery info is // set. SupervisionRecoveryInfo recoveryInfo = mSupervisionSettings.getRecoveryInfo(); return supervisionRoleHolders.isEmpty() && getUserDataLocked(userId).supervisionEnabled && recoveryInfo != null && recoveryInfo.getState() == SupervisionRecoveryInfo.STATE_VERIFIED; } } /** * Updates Web Content Filters when supervision status is updated. * Loading Loading @@ -688,7 +751,7 @@ public class SupervisionService extends ISupervisionManager.Stub { if (!success) { Slogf.e( SupervisionLog.TAG, "Failed to remove role %s fro %s", "Failed to remove role %s for %s", packageName, roleName); } Loading Loading @@ -801,6 +864,22 @@ public class SupervisionService extends ISupervisionManager.Stub { } } private final class RoleObserver implements OnRoleHoldersChangedListener { RoleObserver() {} void register() { mInjector.addOnRoleHoldersChangedListenerAsUser( BackgroundThread.getExecutor(), this, UserHandle.ALL); } @Override public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { if (RoleManager.ROLE_SUPERVISION.equals(roleName)) { maybeApplyUserRestrictionsFor(user); } } } /** Deletes user data when the user gets removed. */ private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener { @Override Loading
services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +132 −0 Original line number Diff line number Diff line Loading @@ -24,8 +24,10 @@ import android.app.admin.DevicePolicyManagerInternal import android.app.role.OnRoleHoldersChangedListener import android.app.role.RoleManager import android.app.supervision.ISupervisionListener import android.app.supervision.SupervisionManager import android.app.supervision.SupervisionRecoveryInfo import android.app.supervision.SupervisionRecoveryInfo.STATE_PENDING import android.app.supervision.SupervisionRecoveryInfo.STATE_VERIFIED import android.app.supervision.flags.Flags import android.content.BroadcastReceiver import android.content.ComponentName Loading @@ -48,6 +50,7 @@ import android.os.PersistableBundle import android.os.UserHandle import android.os.UserHandle.MIN_SECONDARY_USER_ID import android.os.UserHandle.USER_SYSTEM import android.os.UserManager import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.DeviceFlagsValueProvider Loading Loading @@ -84,6 +87,7 @@ import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever Loading Loading @@ -316,6 +320,11 @@ class SupervisionServiceTest { eq(USER_ID), argThat { toSet() == supervisionRoleHolders.values.toSet() }, ) verify(mockDpmInternal) .removeLocalPoliciesForSystemEntities( eq(USER_ID), eq(SupervisionService.SYSTEM_ENTITIES), ) for (packageName in supervisionRoleHolders.values) { verify(mockPackageManagerInternal) .unsuspendForSuspendingPackage(eq(packageName), eq(USER_ID), eq(USER_ID)) Loading @@ -329,6 +338,62 @@ class SupervisionServiceTest { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() verify(mockDpmInternal, never()).removePoliciesForAdmins(any(), any()) verify(mockDpmInternal, never()).removeLocalPoliciesForSystemEntities(any(), any()) } @Test fun setSupervisionEnabledForUser_hasVerifiedRecoveryInfo_restrictsFactoryResetWhenEnabling() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() setSupervisionRecoveryInfo(state = STATE_VERIFIED) setSupervisionEnabledForUser(USER_ID, true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() verify(mockDpmInternal) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ true, USER_ID, ) } @Test fun setSupervisionEnabledForUser_hasPendingRecoveryInfo_doesNotRestrictFactoryReset() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() setSupervisionRecoveryInfo(state = STATE_PENDING) setSupervisionEnabledForUser(USER_ID, true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() verify(mockDpmInternal) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ false, USER_ID, ) } @Test fun setSupervisionEnabledForUser_hasSupervisionRoleHolders_doesNotRestrictFactoryReset() { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() injector.setRoleHoldersAsUser( RoleManager.ROLE_SUPERVISION, UserHandle.of(USER_ID), listOf("com.example.supervisionapp1"), ) setSupervisionRecoveryInfo(state = STATE_VERIFIED) setSupervisionEnabledForUser(USER_ID, true) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() verify(mockDpmInternal) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ false, USER_ID, ) } @Test Loading Loading @@ -438,6 +503,7 @@ class SupervisionServiceTest { @Test @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS) fun setSupervisionRecoveryInfo() { addDefaultAndTestUsers() assertThat(service.supervisionRecoveryInfo).isNull() val recoveryInfo = Loading @@ -457,6 +523,64 @@ class SupervisionServiceTest { assertThat(service.supervisionRecoveryInfo.state).isEqualTo(recoveryInfo.state) } @Test @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS) fun setSupervisionRecoveryInfo_supervisionEnabled_restrictsFactoryReset() { addDefaultAndTestUsers() setSupervisionEnabledForUser(USER_ID, true) setSupervisionRecoveryInfo(state = STATE_VERIFIED) verify(mockDpmInternal) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ true, USER_ID, ) } @Test @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS) fun setSupervisionRecoveryInfo_toNull_supervisionEnabled_unrestrictsFactoryReset() { addDefaultAndTestUsers() setSupervisionEnabledForUser(USER_ID, true) service.setSupervisionRecoveryInfo(null) // Once for the initial setSupervisionEnabledForUser, once for the // setSupervisionRecoveryInfo(null). verify(mockDpmInternal, times(2)) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ false, USER_ID, ) } @Test @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS) fun setSupervisionRecoveryInfo_supervisionEnabled_hasSupervisionRoleHolders_doesNotRestrictFactoryReset() { addDefaultAndTestUsers() setSupervisionEnabledForUser(USER_ID, true) injector.setRoleHoldersAsUser( RoleManager.ROLE_SUPERVISION, UserHandle.of(USER_ID), listOf("com.example.supervisionapp1"), ) setSupervisionRecoveryInfo(state = STATE_VERIFIED) // Once for the initial setSupervisionEnabledForUser, and again for the // setSupervisionRecoveryInfo. verify(mockDpmInternal, times(2)) .setUserRestrictionForUser( SupervisionManager.SUPERVISION_SYSTEM_ENTITY, UserManager.DISALLOW_FACTORY_RESET, /* enabled= */ false, USER_ID, ) } @Test fun hasSupervisionCredentials() { whenever(mockUserManagerInternal.getSupervisingProfileId()).thenReturn(SUPERVISING_USER_ID) Loading Loading @@ -567,6 +691,14 @@ class SupervisionServiceTest { verifySupervisionListeners(userId, enabled, listeners) } private fun setSupervisionRecoveryInfo( @SupervisionRecoveryInfo.State state: Int, accountData: PersistableBundle? = null, ) { val recoveryInfo = SupervisionRecoveryInfo("email", "default", state, accountData) service.setSupervisionRecoveryInfo(recoveryInfo) } private fun verifySupervisionListeners( expectedUserId: Int, enabled: Boolean, Loading