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

Commit 31b1e9e7 authored by Vitor Carvalho's avatar Vitor Carvalho Committed by Android (Google) Code Review
Browse files

Merge "Make `SupervisionService` react immediately to profile owner changes." into main

parents 9c0f8938 12167a25
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -16,3 +16,11 @@ flag {
  description: "Flag to enable the SupervisionService on Wear devices"
  bug: "373358935"
}

flag {
  name: "enable_sync_with_dpm"
  is_exported: true
  namespace: "supervision"
  description: "Flag that enables supervision when the supervision app is the profile owner"
  bug: "377261590"
}
+45 −9
Original line number Diff line number Diff line
@@ -19,11 +19,16 @@ package com.android.server.supervision;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.supervision.ISupervisionManager;
import android.app.supervision.SupervisionManagerInternal;
import android.app.supervision.flags.Flags;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.os.PersistableBundle;
import android.os.RemoteException;
@@ -33,6 +38,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.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
@@ -42,6 +48,7 @@ import com.android.server.pm.UserManagerInternal;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;

/** Service for handling system supervision. */
public class SupervisionService extends ISupervisionManager.Stub {
@@ -65,12 +72,10 @@ public class SupervisionService extends ISupervisionManager.Stub {
        mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
    }

    void syncStateWithDevicePolicyManager(TargetUser user) {
        if (user.isPreCreated()) return;

    private void syncStateWithDevicePolicyManager(@UserIdInt int userId) {
        // Ensure that supervision is enabled when supervision app is the profile owner.
        if (android.app.admin.flags.Flags.enableSupervisionServiceSync() && isProfileOwner(user)) {
            setSupervisionEnabledForUser(user.getUserIdentifier(), true);
        if (Flags.enableSyncWithDpm() && isProfileOwner(userId)) {
            setSupervisionEnabledForUser(userId, true);
        }
    }

@@ -103,7 +108,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
            pw.println("SupervisionService state:");
            pw.increaseIndent();

            var users = mUserManagerInternal.getUsers(false);
            List<UserInfo> users = mUserManagerInternal.getUsers(false);
            synchronized (getLockObject()) {
                for (var user : users) {
                    getUserDataLocked(user.id).dump(pw);
@@ -136,8 +141,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
    }

    /** Returns whether the supervision app has profile owner status. */
    private boolean isProfileOwner(TargetUser user) {
        ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(user.getUserIdentifier());
    private boolean isProfileOwner(@UserIdInt int userId) {
        ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(userId);
        if (profileOwner == null) {
            return false;
        }
@@ -154,15 +159,46 @@ public class SupervisionService extends ISupervisionManager.Stub {
            mSupervisionService = new SupervisionService(context);
        }

        @VisibleForTesting
        Lifecycle(Context context, SupervisionService supervisionService) {
            super(context);
            mSupervisionService = supervisionService;
        }

        @Override
        public void onStart() {
            publishLocalService(SupervisionManagerInternal.class, mSupervisionService.mInternal);
            publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService);
            if (Flags.enableSyncWithDpm()) {
                registerProfileOwnerListener();
            }
        }

        @VisibleForTesting
        void registerProfileOwnerListener() {
            IntentFilter poIntentFilter = new IntentFilter();
            poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
            poIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
            getContext()
                    .registerReceiverForAllUsers(
                            new ProfileOwnerBroadcastReceiver(),
                            poIntentFilter,
                            /* brodcastPermission= */ null,
                            /* scheduler= */ null);
        }

        @Override
        public void onUserStarting(@NonNull TargetUser user) {
            mSupervisionService.syncStateWithDevicePolicyManager(user);
            if (!user.isPreCreated()) {
                mSupervisionService.syncStateWithDevicePolicyManager(user.getUserIdentifier());
            }
        }

        private final class ProfileOwnerBroadcastReceiver extends BroadcastReceiver {
            @Override
            public void onReceive(Context context, Intent intent) {
                mSupervisionService.syncStateWithDevicePolicyManager(getSendingUserId());
            }
        }
    }

+101 −23
Original line number Diff line number Diff line
@@ -16,11 +16,20 @@

package com.android.server.supervision

import android.app.Activity
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManagerInternal
import android.app.supervision.flags.Flags
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.UserInfo
import android.os.Handler
import android.os.PersistableBundle
import android.os.UserHandle
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -46,22 +55,20 @@ import org.mockito.kotlin.whenever
 */
@RunWith(AndroidJUnit4::class)
class SupervisionServiceTest {
    companion object {
        const val USER_ID = 100
    }

    @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
    @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
    @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()

    @Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal
    @Mock private lateinit var mockUserManagerInternal: UserManagerInternal

    private lateinit var context: Context
    private lateinit var lifecycle: SupervisionService.Lifecycle
    private lateinit var service: SupervisionService

    @Before
    fun setUp() {
        context = InstrumentationRegistry.getInstrumentation().context
        context = SupervisionContextWrapper(context)

        LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
        LocalServices.addService(DevicePolicyManagerInternal::class.java, mockDpmInternal)
@@ -70,43 +77,61 @@ class SupervisionServiceTest {
        LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal)

        service = SupervisionService(context)
        lifecycle = SupervisionService.Lifecycle(context, service)
        lifecycle.registerProfileOwnerListener()
    }

    @Test
    @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
    fun syncStateWithDevicePolicyManager_supervisionAppIsProfileOwner_enablesSupervision() {
        val supervisionPackageName =
            context.getResources().getString(R.string.config_systemSupervision)

    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
    fun onUserStarting_supervisionAppIsProfileOwner_enablesSupervision() {
        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
            .thenReturn(ComponentName(supervisionPackageName, "MainActivity"))
            .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))

        service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID))
        simulateUserStarting(USER_ID)

        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
    }

    @Test
    @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
    fun syncStateWithDevicePolicyManager_userPreCreated_doesNotEnableSupervision() {
        val supervisionPackageName =
            context.getResources().getString(R.string.config_systemSupervision)
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
    fun onUserStarting_userPreCreated_doesNotEnableSupervision() {
        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
            .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))

        simulateUserStarting(USER_ID, preCreated = true)

        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
    fun onUserStarting_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
            .thenReturn(ComponentName(supervisionPackageName, "MainActivity"))
            .thenReturn(ComponentName("other.package", "MainActivity"))

        service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID, preCreated = true))
        simulateUserStarting(USER_ID)

        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
    }

    @Test
    @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
    fun syncStateWithDevicePolicyManager_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
    fun profileOwnerChanged_supervisionAppIsProfileOwner_enablesSupervision() {
        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
            .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))

        broadcastProfileOwnerChanged(USER_ID)

        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
    fun profileOwnerChanged_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
            .thenReturn(ComponentName("other.package", "MainActivity"))

        service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID))
        broadcastProfileOwnerChanged(USER_ID)

        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
    }
@@ -150,9 +175,62 @@ class SupervisionServiceTest {
        assertThat(userData.supervisionLockScreenOptions).isNull()
    }

    private fun newTargetUser(userId: Int, preCreated: Boolean = false): TargetUser {
    private val systemSupervisionPackage: String
        get() = context.getResources().getString(R.string.config_systemSupervision)

    private fun simulateUserStarting(userId: Int, preCreated: Boolean = false) {
        val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0)
        userInfo.preCreated = preCreated
        return TargetUser(userInfo)
        lifecycle.onUserStarting(TargetUser(userInfo))
    }

    private fun broadcastProfileOwnerChanged(userId: Int) {
        val intent = Intent(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED)
        context.sendBroadcastAsUser(intent, UserHandle.of(userId))
    }

    private companion object {
        const val USER_ID = 100
    }
}

/**
 * A context wrapper that allows broadcast intents to immediately invoke the receivers without
 * performing checks on the sending user.
 */
private class SupervisionContextWrapper(val context: Context) : ContextWrapper(context) {
    val interceptors = mutableListOf<Pair<BroadcastReceiver, IntentFilter>>()

    override fun registerReceiverForAllUsers(
        receiver: BroadcastReceiver?,
        filter: IntentFilter,
        broadcastPermission: String?,
        scheduler: Handler?,
    ): Intent? {
        if (receiver != null) {
            interceptors.add(Pair(receiver, filter))
        }
        return null
    }

    override fun sendBroadcastAsUser(intent: Intent, user: UserHandle) {
        val pendingResult =
            BroadcastReceiver.PendingResult(
                Activity.RESULT_OK,
                /* resultData= */ "",
                /* resultExtras= */ null,
                /* type= */ 0,
                /* ordered= */ true,
                /* sticky= */ false,
                /* token= */ null,
                user.identifier,
                /* flags= */ 0,
            )
        for ((receiver, filter) in interceptors) {
            if (filter.match(contentResolver, intent, false, "") > 0) {
                receiver.setPendingResult(pendingResult)
                receiver.onReceive(context, intent)
            }
        }
    }
}