Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 85d2efba authored by Yvonne Jiang's avatar Yvonne Jiang
Browse files

Restrict factory reset when supervision is enabled with a recovery method set.

Bug: 383624414
Flag: android.app.supervision.flags.supervision_manager_apis
Test: atest SupervisionServiceTest
Change-Id: Iae9129e739bf1906fd3da7eea44bf2fa2fdc72e5
parent d9314af4
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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) {
+83 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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();

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

    /**
@@ -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. */
@@ -404,6 +419,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
                                    listener -> listener.onSetSupervisionEnabled(userId, enabled));
                            if (!enabled) {
                                clearAllDevicePoliciesAndSuspendedPackages(userId);
                            } else {
                                maybeApplyUserRestrictionsFor(UserHandle.of(userId));
                            }
                        });
    }
@@ -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);
        }
    }

@@ -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.
     *
@@ -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);
                        }
@@ -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
+132 −0
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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

@@ -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))
@@ -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
@@ -438,6 +503,7 @@ class SupervisionServiceTest {
    @Test
    @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS)
    fun setSupervisionRecoveryInfo() {
        addDefaultAndTestUsers()
        assertThat(service.supervisionRecoveryInfo).isNull()

        val recoveryInfo =
@@ -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)
@@ -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,