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

Commit 20454ca2 authored by Cintia Martins's avatar Cintia Martins
Browse files

Unsuspend apps when disabling supervision

Bug: 417955784
Test: atest SupervisionServiceTest
Flag: android.app.supervision.flags.enable_remove_policies_on_supervision_disable
Change-Id: Icb4a569b0915f0bc13c116ab6e4eb2b709f40b32
parent 594c319f
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SpecialUsers;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
@@ -300,6 +301,18 @@ public abstract class PackageManagerInternal {
    public abstract String[] setPackagesSuspendedByAdmin(
            @UserIdInt int userId, @NonNull String[] packageNames, boolean suspended);



    /**
     * Unsuspends all packages that were previously suspended by a specific suspending package.
     *
     * @param suspendingPackage The package that originally requested suspension.
     * @param suspendingUserId The user ID that requested suspension.
     * @param affectedUserId The user ID for which to unsuspend the packages.
     */
    public abstract void unsuspendForSuspendingPackage(String suspendingPackage,
            @UserIdInt int suspendingUserId, @SpecialUsers.CanBeALL @UserIdInt int affectedUserId);

    /**
     * Get the information describing the dialog to be shown to the user when they try to launch a
     * suspended application.
+12 −1
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SpecialUsers;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
@@ -700,6 +701,15 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal {
        return snapshot().isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, suspendingUserId, userId);
    }


    @Override
    @Deprecated
    public void unsuspendForSuspendingPackage(String suspendingPackage,
            @UserIdInt int suspendingUserId, @SpecialUsers.CanBeALL @UserIdInt int affectedUserId) {
        mService.unsuspendForSuspendingPackage(
                snapshot(), suspendingPackage, suspendingUserId, affectedUserId);
    }

    @Override
    @Deprecated
    public final void requestChecksums(@NonNull String packageName, boolean includeSplits,
@@ -716,7 +726,8 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal {
    @Deprecated
    public final boolean isPackageFrozen(@NonNull String packageName,
            int callingUid, int userId) {
        return snapshot().getPackageStartability(mService.getSafeMode(), packageName, callingUid, userId)
        return snapshot().getPackageStartability(mService.getSafeMode(),
                packageName, callingUid, userId)
                == PackageManagerService.PACKAGE_STARTABILITY_FROZEN;
    }

+112 −52
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.annotation.UserIdInt;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.role.RoleManager;
import android.app.supervision.ISupervisionListener;
import android.app.supervision.ISupervisionManager;
import android.app.supervision.SupervisionManagerInternal;
@@ -45,6 +46,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Bundle;
@@ -165,9 +167,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
    }

    /**
     * Creates an {@link Intent} that can be used with
     * {@link Context#startActivityForResult(String, Intent, int, Bundle)} to launch the activity to
     * verify supervision credentials.
     * Creates an {@link Intent} that can be used with {@link Context#startActivityForResult(String,
     * Intent, int, Bundle)} to launch the activity to verify supervision credentials.
     *
     * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this
     * method is called, the launched activity still need to perform validity checks as the
@@ -271,8 +272,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
            SupervisionListenerRecord record = mSupervisionListeners.get(listener.asBinder());
            if (record == null) {
                try {
                    mSupervisionListeners.put(listener.asBinder(),
                            new SupervisionListenerRecord(listener, userId));
                    mSupervisionListeners.put(
                            listener.asBinder(), new SupervisionListenerRecord(listener, userId));
                } catch (RemoteException e) {
                    // Binder died, ignore
                }
@@ -380,18 +381,20 @@ public class SupervisionService extends ISupervisionManager.Stub {
                mSupervisionSettings.saveUserData();
            }
        }
        Binder.withCleanCallingIdentity(() -> {
        Binder.withCleanCallingIdentity(
                () -> {
                    updateWebContentFilters(userId, enabled);
            dispatchSupervisionEvent(userId,
                    listener -> listener.onSetSupervisionEnabled(userId, enabled));
                    dispatchSupervisionEvent(
                            userId, listener -> listener.onSetSupervisionEnabled(userId, enabled));
                    if (!enabled) {
                clearDevicePolicies(userId, supervisionAppPackage);
                        clearAllDevicePoliciesAndSuspendedPackages(userId);
                    }
                });
    }

    @NonNull
    private List<ISupervisionListener> getSupervisionAppServiceListeners(@UserIdInt int userId,
    private List<ISupervisionListener> getSupervisionAppServiceListeners(
            @UserIdInt int userId,
            @NonNull RemoteExceptionIgnoringConsumer<ISupervisionListener> action) {
        ArrayList<ISupervisionListener> listeners = new ArrayList<>();
        if (!Flags.enableSupervisionAppService()) {
@@ -409,8 +412,10 @@ public class SupervisionService extends ISupervisionManager.Stub {
            }

            if (binder == null) {
                Slogf.d(SupervisionLog.TAG,
                        "Failed to bind to SupervisionAppService for %s now", targetPackage);
                Slogf.d(
                        SupervisionLog.TAG,
                        "Failed to bind to SupervisionAppService for %s now",
                        targetPackage);

                dispatchSupervisionAppServiceWhenConnected(conn, action);
                continue;
@@ -422,7 +427,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
        return listeners;
    }

    private void dispatchSupervisionEvent(@UserIdInt int userId,
    private void dispatchSupervisionEvent(
            @UserIdInt int userId,
            @NonNull RemoteExceptionIgnoringConsumer<ISupervisionListener> action) {
        ArrayList<ISupervisionListener> listeners = new ArrayList<>();

@@ -430,7 +436,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
        listeners.addAll(getSupervisionAppServiceListeners(userId, action));

        synchronized (getLockObject()) {
            mSupervisionListeners.forEach((binder, record) -> {
            mSupervisionListeners.forEach(
                    (binder, record) -> {
                        if (record.userId == userId || record.userId == UserHandle.USER_ALL) {
                            listeners.add(record.listener);
                        }
@@ -440,12 +447,37 @@ public class SupervisionService extends ISupervisionManager.Stub {
        listeners.forEach(listener -> action.accept(listener));
    }

    private void clearDevicePolicies(
            @UserIdInt int userId, @Nullable String supervisionAppPackage) {
    private void clearAllDevicePoliciesAndSuspendedPackages(@UserIdInt int userId) {
        if (!Flags.enableRemovePoliciesOnSupervisionDisable()) {
            return;
        }

        enforcePermission(MANAGE_ROLE_HOLDERS);
        List<String> supervisionPackages = new ArrayList<>();
        RoleManagerWrapper roleManager = mInjector.getRoleManagerWrapper();
        if (roleManager != null) {
            supervisionPackages.addAll(
                    roleManager.getRoleHoldersAsUser(
                            RoleManager.ROLE_SUPERVISION, UserHandle.of(userId)));
            supervisionPackages.addAll(
                    roleManager.getRoleHoldersAsUser(
                            RoleManager.ROLE_SYSTEM_SUPERVISION, UserHandle.of(userId)));
        }

        for (String supervisionPackage : supervisionPackages) {
            clearDevicePoliciesAndSuspendedPackagesFor(userId, supervisionPackage);
        }
    }

    private void clearDevicePoliciesAndSuspendedPackagesFor(int userId, String supervisionPackage) {
        DevicePolicyManagerInternal dpmi = mInjector.getDpmInternal();
        if (Flags.enableRemovePoliciesOnSupervisionDisable()
                && dpmi != null && supervisionAppPackage != null) {
            dpmi.removePoliciesForAdmins(supervisionAppPackage, userId);
        if (dpmi != null) {
            dpmi.removePoliciesForAdmins(supervisionPackage, userId);
        }

        PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
        if (pmi != null) {
            pmi.unsuspendForSuspendingPackage(supervisionPackage, userId, userId);
        }
    }

@@ -456,8 +488,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
        AppServiceConnection.ConnectionStatusListener connectionListener =
                new AppServiceConnection.ConnectionStatusListener() {
                    @Override
            public void onConnected(@NonNull AppServiceConnection connection,
                    @NonNull IInterface service) {
                    public void onConnected(
                            @NonNull AppServiceConnection connection, @NonNull IInterface service) {
                        try {
                            ISupervisionListener binder = (ISupervisionListener) service;
                            Binder.withCleanCallingIdentity(() -> action.accept(binder));
@@ -496,11 +528,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
            final ContentResolver contentResolver = mInjector.context.getContentResolver();
            final int value = Settings.Secure.getIntForUser(contentResolver, key, userId);
            if (!enabled || value != 1) {
                Settings.Secure.putIntForUser(
                        contentResolver,
                        key,
                        value * -1,
                        userId);
                Settings.Secure.putIntForUser(contentResolver, key, value * -1, userId);
            }
        } catch (Settings.SettingNotFoundException ignored) {
            // Ignore the exception and do not change the value as no value has been set.
@@ -536,7 +564,9 @@ public class SupervisionService extends ISupervisionManager.Stub {
     */
    private ComponentName getSupervisionProfileOwnerComponent() {
        return ComponentName.unflattenFromString(
                mInjector.context.getResources()
                mInjector
                        .context
                        .getResources()
                        .getString(R.string.config_defaultSupervisionProfileOwnerComponent));
    }

@@ -570,7 +600,9 @@ public class SupervisionService extends ISupervisionManager.Stub {
        private AppBindingService mAppBindingService;
        private KeyguardManager mKeyguardManager;
        private PackageManager mPackageManager;
        private PackageManagerInternal mPackageManagerInternal;
        private UserManagerInternal mUserManagerInternal;
        private RoleManagerWrapper mRoleManagerWrapper;

        Injector(Context context) {
            this.context = context;
@@ -612,6 +644,34 @@ public class SupervisionService extends ISupervisionManager.Stub {
            }
            return mUserManagerInternal;
        }

        PackageManagerInternal getPackageManagerInternal() {
            if (mPackageManagerInternal == null) {
                mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
            }
            return mPackageManagerInternal;
        }

        RoleManagerWrapper getRoleManagerWrapper() {
            if (mRoleManagerWrapper == null) {
                final Object roleManager = context.getSystemService(RoleManager.class);
                if (roleManager instanceof RoleManager) {
                    mRoleManagerWrapper = new RoleManagerWrapper() {
                        @Override
                        public List<String> getRoleHoldersAsUser(String roleName, UserHandle user) {
                            return ((RoleManager) roleManager).getRoleHoldersAsUser(roleName, user);
                        }
                    };
                } else {
                    mRoleManagerWrapper = (RoleManagerWrapper) roleManager;
                }
            }
            return mRoleManagerWrapper;
        }
    }

    public interface RoleManagerWrapper {
        List<String> getRoleHoldersAsUser(String roleName, UserHandle user);
    }

    /** Publishes local and binder services and allows the service to act during initialization. */
+67 −10
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.Activity
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManagerInternal
import android.app.role.RoleManager
import android.app.supervision.ISupervisionListener
import android.app.supervision.SupervisionRecoveryInfo
import android.app.supervision.SupervisionRecoveryInfo.STATE_PENDING
@@ -30,14 +31,15 @@ import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.IBinder
import android.content.pm.PackageManager
import android.content.pm.PackageManagerInternal
import android.content.pm.UserInfo
import android.content.pm.UserInfo.FLAG_FOR_TESTING
import android.content.pm.UserInfo.FLAG_FULL
import android.content.pm.UserInfo.FLAG_MAIN
import android.content.pm.UserInfo.FLAG_SYSTEM
import android.os.Handler
import android.os.IBinder
import android.os.PersistableBundle
import android.os.UserHandle
import android.os.UserHandle.MIN_SECONDARY_USER_ID
@@ -56,6 +58,7 @@ import com.android.server.LocalServices
import com.android.server.SystemService.TargetUser
import com.android.server.pm.UserManagerInternal
import com.android.server.supervision.SupervisionService.ACTION_CONFIRM_SUPERVISION_CREDENTIALS
import com.android.server.supervision.SupervisionService.RoleManagerWrapper
import com.google.common.truth.Truth.assertThat
import java.nio.file.Files
import org.junit.Before
@@ -88,8 +91,9 @@ class SupervisionServiceTest {
    @Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal
    @Mock private lateinit var mockKeyguardManager: KeyguardManager
    @Mock private lateinit var mockPackageManager: PackageManager
    @Mock private lateinit var mockPackageManagerInternal: PackageManagerInternal
    @Mock private lateinit var mockUserManagerInternal: UserManagerInternal
    @Mock private lateinit var mockSupervisionListener: ISupervisionListener
    @Mock private lateinit var mockRoleManager: SupervisionService.RoleManagerWrapper

    private lateinit var context: Context
    private lateinit var lifecycle: SupervisionService.Lifecycle
@@ -98,7 +102,8 @@ class SupervisionServiceTest {
    @Before
    fun setUp() {
        context = InstrumentationRegistry.getInstrumentation().context
        context = SupervisionContextWrapper(context, mockKeyguardManager, mockPackageManager)
        context = SupervisionContextWrapper(context, mockKeyguardManager, mockPackageManager,
            mockRoleManager)

        LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
        LocalServices.addService(DevicePolicyManagerInternal::class.java, mockDpmInternal)
@@ -106,6 +111,9 @@ class SupervisionServiceTest {
        LocalServices.removeServiceForTest(UserManagerInternal::class.java)
        LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal)

        LocalServices.removeServiceForTest(PackageManagerInternal::class.java)
        LocalServices.addService(PackageManagerInternal::class.java, mockPackageManagerInternal)

        // Creating a temporary folder to enable access to SupervisionSettings.
        SupervisionSettings.getInstance()
            .changeDirForTesting(Files.createTempDirectory("tempSupervisionFolder").toFile())
@@ -114,6 +122,7 @@ class SupervisionServiceTest {
        lifecycle = SupervisionService.Lifecycle(context, service)
        lifecycle.registerProfileOwnerListener()

        //TODO: b/427453821 Remove after converting SupervisionSettings from being a singleton.
        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
    }

@@ -273,10 +282,18 @@ class SupervisionServiceTest {
    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_REMOVE_POLICIES_ON_SUPERVISION_DISABLE)
    fun setSupervisionEnabledForUser_removesPoliciesWhenDisabling() {
        for ((role, packageName) in supervisionRoleHolders) {
            whenever(mockRoleManager.getRoleHoldersAsUser(eq(role), any()))
                .thenReturn(listOf(packageName));
        }

        service.setSupervisionEnabledForUser(USER_ID, false)

        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
        verify(mockDpmInternal).removePoliciesForAdmins(eq(systemSupervisionPackage), eq(USER_ID))
        for (packageName in supervisionRoleHolders.values) {
            verify(mockPackageManagerInternal)
                .unsuspendForSuspendingPackage(eq(packageName), eq(USER_ID), eq(USER_ID))
        }
    }

    @Test
@@ -491,12 +508,16 @@ class SupervisionServiceTest {
        assertThat(service.mSupervisionListeners).doesNotContainKey(binder)
    }

    private fun setSupervisionEnabledForUser(expectedUserId: Int, enabled: Boolean,
            listeners: Map<Int, Pair<ISupervisionListener, IBinder>>) {
    private fun setSupervisionEnabledForUser(
        expectedUserId: Int,
        enabled: Boolean,
        listeners: Map<Int, Pair<ISupervisionListener, IBinder>>,
    ) {
        service.setSupervisionEnabledForUser(expectedUserId, enabled)
        listeners.forEach { userId, (listener, binder) ->
            when (userId) {
                expectedUserId, UserHandle.USER_ALL -> {
                expectedUserId,
                UserHandle.USER_ALL -> {
                    verify(listener).onSetSupervisionEnabled(eq(expectedUserId), eq(enabled))
                }
                else -> {
@@ -506,6 +527,34 @@ class SupervisionServiceTest {
        }
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_REMOVE_POLICIES_ON_SUPERVISION_DISABLE)
    fun clearPackageSuspensions_unsuspendsSupervisionPackages() {
        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
        for ((role, packageName) in supervisionRoleHolders) {
            whenever(mockRoleManager.getRoleHoldersAsUser(eq(role), any()))
                .thenReturn(listOf(packageName));
        }

        service.setSupervisionEnabledForUser(USER_ID, false)

        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
        for (packageName in supervisionRoleHolders.values) {
            verify(mockPackageManagerInternal)
                .unsuspendForSuspendingPackage(eq(packageName), eq(USER_ID), eq(USER_ID))
        }
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_REMOVE_POLICIES_ON_SUPERVISION_DISABLE)
    fun clearPackageSuspensions_noSupervisionPackages_doesNothing() {
        whenever(mockRoleManager.getRoleHoldersAsUser(
            any(), any())).thenReturn(listOf())
        service.setSupervisionEnabledForUser(USER_ID, false)

        verify(mockPackageManagerInternal, never()).unsuspendForSuspendingPackage(any(), any(), any())
    }

    private val systemSupervisionPackage: String
        get() = context.getResources().getString(R.string.config_systemSupervision)

@@ -557,6 +606,10 @@ class SupervisionServiceTest {
        const val SUPERVISING_USER_ID = 10
        const val USER_ICON = "user_icon"
        const val USER_TYPE = "fake_user_type"
        val supervisionRoleHolders = mapOf(
            RoleManager.ROLE_SYSTEM_SUPERVISION to "com.example.supervisionapp1",
            RoleManager.ROLE_SUPERVISION to "com.example.supervisionapp2"
        )
        val userData: Map<Int, Int> =
            mapOf(
                USER_SYSTEM to FLAG_SYSTEM,
@@ -574,14 +627,18 @@ private class SupervisionContextWrapper(
    val context: Context,
    val keyguardManager: KeyguardManager,
    val pkgManager: PackageManager,
    val roleManagerWrapper: RoleManagerWrapper,
) : ContextWrapper(context) {
    val interceptors = mutableListOf<Pair<BroadcastReceiver, IntentFilter>>()

    override fun getSystemService(name: String): Any =
        when (name) {
            KEYGUARD_SERVICE -> keyguardManager
    override fun getSystemService(name: String): Any? {
        var ret =  when (name) {
            Context.KEYGUARD_SERVICE -> keyguardManager
            Context.ROLE_SERVICE -> roleManagerWrapper
            else -> super.getSystemService(name)
        }
        return ret;
    }

    override fun getPackageManager() = pkgManager