diff options
| author | 2024-11-27 14:02:55 +0000 | |
|---|---|---|
| committer | 2024-11-27 14:02:55 +0000 | |
| commit | 31b1e9e740ee9e5e7c051c438e2e648892d6c76f (patch) | |
| tree | a9ddcd8090fc801bde522cdff5e6220094eef12a | |
| parent | 9c0f89383dc6168b64c2e1a05fd974561feb8db2 (diff) | |
| parent | 12167a25050c5a9b5c5d5813d5766235a034c0d8 (diff) | |
Merge "Make `SupervisionService` react immediately to profile owner changes." into main
3 files changed, 154 insertions, 32 deletions
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig index d5e696d49ff4..d4f82f665fd4 100644 --- a/core/java/android/app/supervision/flags.aconfig +++ b/core/java/android/app/supervision/flags.aconfig @@ -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" +} diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java index 53a25dd454ef..55bd8df3c82e 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionService.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java @@ -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()); + } } } diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt index 8290e1cfb9db..a8544f60fb74 100644 --- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt @@ -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) + } + } } } |