summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Vitor Carvalho <vtrmc@google.com> 2024-11-27 14:02:55 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-11-27 14:02:55 +0000
commit31b1e9e740ee9e5e7c051c438e2e648892d6c76f (patch)
treea9ddcd8090fc801bde522cdff5e6220094eef12a
parent9c0f89383dc6168b64c2e1a05fd974561feb8db2 (diff)
parent12167a25050c5a9b5c5d5813d5766235a034c0d8 (diff)
Merge "Make `SupervisionService` react immediately to profile owner changes." into main
-rw-r--r--core/java/android/app/supervision/flags.aconfig8
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java54
-rw-r--r--services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt124
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)
+ }
+ }
}
}