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

Commit 3dcbccd8 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Restrict factory reset when supervision is enabled with a recovery method set." into main

parents 906d3fcf 85d2efba
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,