Loading services/supervision/java/com/android/server/supervision/SupervisionService.java +39 −19 Original line number Diff line number Diff line Loading @@ -67,11 +67,11 @@ 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; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.appbinding.AppBindingService; import com.android.server.appbinding.AppServiceConnection; Loading Loading @@ -113,6 +113,11 @@ public class SupervisionService extends ISupervisionManager.Stub { @GuardedBy("getLockObject()") final ArrayMap<IBinder, SupervisionListenerRecord> mSupervisionListeners = new ArrayMap<>(); // We need to create a new background thread here because the AppBindingService uses the // BackgroundThread for its connection callbacks. Using the same thread would block while // waiting for those callbacks, preventing the new connections from being perceived. final ServiceThread mServiceThread; @GuardedBy("getLockObject()") final SupervisionSettings mSupervisionSettings = SupervisionSettings.getInstance(); Loading @@ -123,6 +128,7 @@ public class SupervisionService extends ISupervisionManager.Stub { @VisibleForTesting SupervisionService(Injector injector) { mInjector = injector; mServiceThread = injector.getServiceThread(); mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener()); } Loading Loading @@ -388,10 +394,14 @@ public class SupervisionService extends ISupervisionManager.Stub { mSupervisionSettings.saveUserData(); } } BackgroundThread.getExecutor().execute(() -> { mServiceThread .getThreadExecutor() .execute( () -> { updateWebContentFilters(userId, enabled); dispatchSupervisionEvent( userId, listener -> listener.onSetSupervisionEnabled(userId, enabled)); userId, listener -> listener.onSetSupervisionEnabled(userId, enabled)); if (!enabled) { clearAllDevicePoliciesAndSuspendedPackages(userId); } Loading Loading @@ -436,8 +446,8 @@ public class SupervisionService extends ISupervisionManager.Stub { @NonNull RemoteExceptionIgnoringConsumer<ISupervisionListener> action) { // Add SupervisionAppServices listeners before the platform listeners. ArrayList<ISupervisionListener> listeners = new ArrayList<>( getSupervisionAppServiceListeners(userId, action)); ArrayList<ISupervisionListener> listeners = new ArrayList<>(getSupervisionAppServiceListeners(userId, action)); synchronized (getLockObject()) { mSupervisionListeners.forEach( Loading @@ -457,8 +467,8 @@ public class SupervisionService extends ISupervisionManager.Stub { } enforcePermission(MANAGE_ROLE_HOLDERS); List<String> roles = Arrays.asList(RoleManager.ROLE_SYSTEM_SUPERVISION, RoleManager.ROLE_SUPERVISION); List<String> roles = Arrays.asList(RoleManager.ROLE_SYSTEM_SUPERVISION, RoleManager.ROLE_SUPERVISION); List<String> supervisionPackages = new ArrayList<>(); for (String role : roles) { List<String> supervisionPackagesPerRole = Loading Loading @@ -570,9 +580,9 @@ public class SupervisionService extends ISupervisionManager.Stub { /** * Enforces that the caller can set supervision enabled state. * * This is restricted to the callers with the root, shell, or system uid or callers with the * BYPASS_ROLE_QUALIFICATION permission. This permission is only granted to the * SYSTEM_SHELL role holder. * <p>This is restricted to the callers with the root, shell, or system uid or callers with the * BYPASS_ROLE_QUALIFICATION permission. This permission is only granted to the SYSTEM_SHELL * role holder. */ private void enforceCallerCanSetSupervisionEnabled() { checkCallAuthorization(isCallerSystem() || hasCallingPermission(BYPASS_ROLE_QUALIFICATION)); Loading Loading @@ -659,6 +669,14 @@ public class SupervisionService extends ISupervisionManager.Stub { return mRoleManager.getRoleHoldersAsUser(roleName, user); } @NonNull ServiceThread getServiceThread() { ServiceThread thread = new ServiceThread(SupervisionLog.TAG, Process.THREAD_PRIORITY_BACKGROUND, true); thread.start(); return thread; } void removeRoleHoldersAsUser(String roleName, String packageName, UserHandle user) { mRoleManager.removeRoleHolderAsUser( roleName, Loading @@ -668,11 +686,13 @@ public class SupervisionService extends ISupervisionManager.Stub { context.getMainExecutor(), success -> { if (!success) { Slogf.e(SupervisionLog.TAG, "Failed to remove role %s fro %s", packageName, roleName); Slogf.e( SupervisionLog.TAG, "Failed to remove role %s fro %s", packageName, roleName); } }); } } Loading services/tests/servicestests/src/com/android/server/supervision/ServiceThreadRule.kt 0 → 100644 +44 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.supervision import android.os.Process import com.android.server.ServiceThread import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement class ServiceThreadRule : TestRule { lateinit var serviceThread: ServiceThread override fun apply(base: Statement, description: Description): Statement = object : Statement() { override fun evaluate() { serviceThread = ServiceThread(THREAD_NAME, Process.THREAD_PRIORITY_DEFAULT, true) serviceThread.start() try { base.evaluate() } finally { serviceThread.threadHandler.runWithScissors(serviceThread::quit, 0) } } } companion object { const val THREAD_NAME = "SupervisionTestService-Thread" } } services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +25 −24 Original line number Diff line number Diff line Loading @@ -58,8 +58,8 @@ import android.provider.Settings.Secure.SEARCH_CONTENT_FILTERS_ENABLED import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.R import com.android.internal.os.BackgroundThread import com.android.server.LocalServices import com.android.server.ServiceThread import com.android.server.SystemService.TargetUser import com.android.server.pm.UserManagerInternal import com.android.server.supervision.SupervisionService.ACTION_CONFIRM_SUPERVISION_CREDENTIALS Loading Loading @@ -97,6 +97,7 @@ class SupervisionServiceTest { @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() @get:Rule val mocks: MockitoRule = MockitoJUnit.rule() @get:Rule val setFlagsRule = SetFlagsRule() @get:Rule val serviceThreadRule = ServiceThreadRule() @Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal @Mock private lateinit var mockKeyguardManager: KeyguardManager Loading Loading @@ -136,7 +137,7 @@ class SupervisionServiceTest { // supervision CTS tests use to enable supervision. context.permissions[BYPASS_ROLE_QUALIFICATION] = PERMISSION_GRANTED injector = TestInjector(context) injector = TestInjector(context, serviceThreadRule.serviceThread) service = SupervisionService(injector) lifecycle = SupervisionService.Lifecycle(context, service) lifecycle.registerProfileOwnerListener() Loading Loading @@ -286,12 +287,10 @@ class SupervisionServiceTest { putSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED, 1) setSupervisionEnabledForUser(USER_ID, true) awaitBackgroundThreadIdle() assertThat(getSecureSetting(BROWSER_CONTENT_FILTERS_ENABLED)).isEqualTo(1) assertThat(getSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED)).isEqualTo(1) setSupervisionEnabledForUser(USER_ID, false) awaitBackgroundThreadIdle() assertThat(getSecureSetting(BROWSER_CONTENT_FILTERS_ENABLED)).isEqualTo(-1) assertThat(getSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED)).isEqualTo(-1) } Loading Loading @@ -339,13 +338,11 @@ class SupervisionServiceTest { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() setSupervisionEnabledForUserInternal(USER_ID, true) awaitBackgroundThreadIdle() assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() assertThat(getSecureSetting(BROWSER_CONTENT_FILTERS_ENABLED)).isEqualTo(1) assertThat(getSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED)).isEqualTo(0) setSupervisionEnabledForUserInternal(USER_ID, false) awaitBackgroundThreadIdle() assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() assertThat(getSecureSetting(BROWSER_CONTENT_FILTERS_ENABLED)).isEqualTo(-1) assertThat(getSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED)).isEqualTo(0) Loading Loading @@ -548,7 +545,7 @@ class SupervisionServiceTest { service.setSupervisionEnabledForUser(userId, enabled) assertThat(service.isSupervisionEnabledForUser(userId)).isEqualTo(enabled) awaitBackgroundThreadIdle() injector.awaitServiceThreadIdle() verifySupervisionListeners(userId, enabled, listeners) } Loading @@ -565,7 +562,7 @@ class SupervisionServiceTest { service.mInternal.setSupervisionEnabledForUser(userId, enabled) assertThat(service.isSupervisionEnabledForUser(userId)).isEqualTo(enabled) awaitBackgroundThreadIdle() injector.awaitServiceThreadIdle() verifySupervisionListeners(userId, enabled, listeners) } Loading Loading @@ -660,21 +657,6 @@ class SupervisionServiceTest { return Settings.Secure.getIntForUser(context.contentResolver, name, USER_ID) } /** * Awaits for the background thread to become idle, ensuring all pending tasks are completed. * * This method uses `runWithScissors` with a timeout to wait for the background thread's handler * to process all queued tasks. It asserts that the operation completes successfully within the * timeout. * * @see android.os.Handler.runWithScissors */ private fun awaitBackgroundThreadIdle() { val timeout = 1.seconds.inWholeMilliseconds val success = BackgroundThread.getHandler().runWithScissors({}, timeout) assertWithMessage("Waiting on the background thread timed out").that(success).isTrue() } private companion object { const val USER_ID = 0 const val APP_UID = USER_ID * UserHandle.PER_USER_RANGE Loading @@ -697,7 +679,7 @@ class SupervisionServiceTest { typealias SupervisionListenerMap = Map<Int, Pair<ISupervisionListener, IBinder>> private class TestInjector(val context: Context) : private class TestInjector(val context: Context, private val serviceThread: ServiceThread) : SupervisionService.Injector(context) { private val roleHolders = mutableMapOf<Pair<String, UserHandle>, List<String>>() Loading @@ -714,6 +696,25 @@ private class TestInjector(val context: Context) : fun setRoleHoldersAsUser(roleName: String, user: UserHandle, packages: List<String>) { roleHolders[Pair(roleName, user)] = packages } override fun getServiceThread(): ServiceThread { return serviceThread } /** * Awaits for the service thread to become idle, ensuring all pending tasks are completed. * * This method uses `runWithScissors` with a timeout to wait for the service thread's handler to * process all queued tasks. It asserts that the operation completes successfully within the * timeout. * * @see android.os.Handler.runWithScissors */ fun awaitServiceThreadIdle() { val timeout = 1.seconds.inWholeMilliseconds val success = serviceThread.threadHandler.runWithScissors({}, timeout) assertWithMessage("Waiting on the service thread timed out").that(success).isTrue() } } /** Loading Loading
services/supervision/java/com/android/server/supervision/SupervisionService.java +39 −19 Original line number Diff line number Diff line Loading @@ -67,11 +67,11 @@ 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; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.appbinding.AppBindingService; import com.android.server.appbinding.AppServiceConnection; Loading Loading @@ -113,6 +113,11 @@ public class SupervisionService extends ISupervisionManager.Stub { @GuardedBy("getLockObject()") final ArrayMap<IBinder, SupervisionListenerRecord> mSupervisionListeners = new ArrayMap<>(); // We need to create a new background thread here because the AppBindingService uses the // BackgroundThread for its connection callbacks. Using the same thread would block while // waiting for those callbacks, preventing the new connections from being perceived. final ServiceThread mServiceThread; @GuardedBy("getLockObject()") final SupervisionSettings mSupervisionSettings = SupervisionSettings.getInstance(); Loading @@ -123,6 +128,7 @@ public class SupervisionService extends ISupervisionManager.Stub { @VisibleForTesting SupervisionService(Injector injector) { mInjector = injector; mServiceThread = injector.getServiceThread(); mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener()); } Loading Loading @@ -388,10 +394,14 @@ public class SupervisionService extends ISupervisionManager.Stub { mSupervisionSettings.saveUserData(); } } BackgroundThread.getExecutor().execute(() -> { mServiceThread .getThreadExecutor() .execute( () -> { updateWebContentFilters(userId, enabled); dispatchSupervisionEvent( userId, listener -> listener.onSetSupervisionEnabled(userId, enabled)); userId, listener -> listener.onSetSupervisionEnabled(userId, enabled)); if (!enabled) { clearAllDevicePoliciesAndSuspendedPackages(userId); } Loading Loading @@ -436,8 +446,8 @@ public class SupervisionService extends ISupervisionManager.Stub { @NonNull RemoteExceptionIgnoringConsumer<ISupervisionListener> action) { // Add SupervisionAppServices listeners before the platform listeners. ArrayList<ISupervisionListener> listeners = new ArrayList<>( getSupervisionAppServiceListeners(userId, action)); ArrayList<ISupervisionListener> listeners = new ArrayList<>(getSupervisionAppServiceListeners(userId, action)); synchronized (getLockObject()) { mSupervisionListeners.forEach( Loading @@ -457,8 +467,8 @@ public class SupervisionService extends ISupervisionManager.Stub { } enforcePermission(MANAGE_ROLE_HOLDERS); List<String> roles = Arrays.asList(RoleManager.ROLE_SYSTEM_SUPERVISION, RoleManager.ROLE_SUPERVISION); List<String> roles = Arrays.asList(RoleManager.ROLE_SYSTEM_SUPERVISION, RoleManager.ROLE_SUPERVISION); List<String> supervisionPackages = new ArrayList<>(); for (String role : roles) { List<String> supervisionPackagesPerRole = Loading Loading @@ -570,9 +580,9 @@ public class SupervisionService extends ISupervisionManager.Stub { /** * Enforces that the caller can set supervision enabled state. * * This is restricted to the callers with the root, shell, or system uid or callers with the * BYPASS_ROLE_QUALIFICATION permission. This permission is only granted to the * SYSTEM_SHELL role holder. * <p>This is restricted to the callers with the root, shell, or system uid or callers with the * BYPASS_ROLE_QUALIFICATION permission. This permission is only granted to the SYSTEM_SHELL * role holder. */ private void enforceCallerCanSetSupervisionEnabled() { checkCallAuthorization(isCallerSystem() || hasCallingPermission(BYPASS_ROLE_QUALIFICATION)); Loading Loading @@ -659,6 +669,14 @@ public class SupervisionService extends ISupervisionManager.Stub { return mRoleManager.getRoleHoldersAsUser(roleName, user); } @NonNull ServiceThread getServiceThread() { ServiceThread thread = new ServiceThread(SupervisionLog.TAG, Process.THREAD_PRIORITY_BACKGROUND, true); thread.start(); return thread; } void removeRoleHoldersAsUser(String roleName, String packageName, UserHandle user) { mRoleManager.removeRoleHolderAsUser( roleName, Loading @@ -668,11 +686,13 @@ public class SupervisionService extends ISupervisionManager.Stub { context.getMainExecutor(), success -> { if (!success) { Slogf.e(SupervisionLog.TAG, "Failed to remove role %s fro %s", packageName, roleName); Slogf.e( SupervisionLog.TAG, "Failed to remove role %s fro %s", packageName, roleName); } }); } } Loading
services/tests/servicestests/src/com/android/server/supervision/ServiceThreadRule.kt 0 → 100644 +44 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.supervision import android.os.Process import com.android.server.ServiceThread import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement class ServiceThreadRule : TestRule { lateinit var serviceThread: ServiceThread override fun apply(base: Statement, description: Description): Statement = object : Statement() { override fun evaluate() { serviceThread = ServiceThread(THREAD_NAME, Process.THREAD_PRIORITY_DEFAULT, true) serviceThread.start() try { base.evaluate() } finally { serviceThread.threadHandler.runWithScissors(serviceThread::quit, 0) } } } companion object { const val THREAD_NAME = "SupervisionTestService-Thread" } }
services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +25 −24 Original line number Diff line number Diff line Loading @@ -58,8 +58,8 @@ import android.provider.Settings.Secure.SEARCH_CONTENT_FILTERS_ENABLED import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.R import com.android.internal.os.BackgroundThread import com.android.server.LocalServices import com.android.server.ServiceThread import com.android.server.SystemService.TargetUser import com.android.server.pm.UserManagerInternal import com.android.server.supervision.SupervisionService.ACTION_CONFIRM_SUPERVISION_CREDENTIALS Loading Loading @@ -97,6 +97,7 @@ class SupervisionServiceTest { @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() @get:Rule val mocks: MockitoRule = MockitoJUnit.rule() @get:Rule val setFlagsRule = SetFlagsRule() @get:Rule val serviceThreadRule = ServiceThreadRule() @Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal @Mock private lateinit var mockKeyguardManager: KeyguardManager Loading Loading @@ -136,7 +137,7 @@ class SupervisionServiceTest { // supervision CTS tests use to enable supervision. context.permissions[BYPASS_ROLE_QUALIFICATION] = PERMISSION_GRANTED injector = TestInjector(context) injector = TestInjector(context, serviceThreadRule.serviceThread) service = SupervisionService(injector) lifecycle = SupervisionService.Lifecycle(context, service) lifecycle.registerProfileOwnerListener() Loading Loading @@ -286,12 +287,10 @@ class SupervisionServiceTest { putSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED, 1) setSupervisionEnabledForUser(USER_ID, true) awaitBackgroundThreadIdle() assertThat(getSecureSetting(BROWSER_CONTENT_FILTERS_ENABLED)).isEqualTo(1) assertThat(getSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED)).isEqualTo(1) setSupervisionEnabledForUser(USER_ID, false) awaitBackgroundThreadIdle() assertThat(getSecureSetting(BROWSER_CONTENT_FILTERS_ENABLED)).isEqualTo(-1) assertThat(getSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED)).isEqualTo(-1) } Loading Loading @@ -339,13 +338,11 @@ class SupervisionServiceTest { assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() setSupervisionEnabledForUserInternal(USER_ID, true) awaitBackgroundThreadIdle() assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() assertThat(getSecureSetting(BROWSER_CONTENT_FILTERS_ENABLED)).isEqualTo(1) assertThat(getSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED)).isEqualTo(0) setSupervisionEnabledForUserInternal(USER_ID, false) awaitBackgroundThreadIdle() assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() assertThat(getSecureSetting(BROWSER_CONTENT_FILTERS_ENABLED)).isEqualTo(-1) assertThat(getSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED)).isEqualTo(0) Loading Loading @@ -548,7 +545,7 @@ class SupervisionServiceTest { service.setSupervisionEnabledForUser(userId, enabled) assertThat(service.isSupervisionEnabledForUser(userId)).isEqualTo(enabled) awaitBackgroundThreadIdle() injector.awaitServiceThreadIdle() verifySupervisionListeners(userId, enabled, listeners) } Loading @@ -565,7 +562,7 @@ class SupervisionServiceTest { service.mInternal.setSupervisionEnabledForUser(userId, enabled) assertThat(service.isSupervisionEnabledForUser(userId)).isEqualTo(enabled) awaitBackgroundThreadIdle() injector.awaitServiceThreadIdle() verifySupervisionListeners(userId, enabled, listeners) } Loading Loading @@ -660,21 +657,6 @@ class SupervisionServiceTest { return Settings.Secure.getIntForUser(context.contentResolver, name, USER_ID) } /** * Awaits for the background thread to become idle, ensuring all pending tasks are completed. * * This method uses `runWithScissors` with a timeout to wait for the background thread's handler * to process all queued tasks. It asserts that the operation completes successfully within the * timeout. * * @see android.os.Handler.runWithScissors */ private fun awaitBackgroundThreadIdle() { val timeout = 1.seconds.inWholeMilliseconds val success = BackgroundThread.getHandler().runWithScissors({}, timeout) assertWithMessage("Waiting on the background thread timed out").that(success).isTrue() } private companion object { const val USER_ID = 0 const val APP_UID = USER_ID * UserHandle.PER_USER_RANGE Loading @@ -697,7 +679,7 @@ class SupervisionServiceTest { typealias SupervisionListenerMap = Map<Int, Pair<ISupervisionListener, IBinder>> private class TestInjector(val context: Context) : private class TestInjector(val context: Context, private val serviceThread: ServiceThread) : SupervisionService.Injector(context) { private val roleHolders = mutableMapOf<Pair<String, UserHandle>, List<String>>() Loading @@ -714,6 +696,25 @@ private class TestInjector(val context: Context) : fun setRoleHoldersAsUser(roleName: String, user: UserHandle, packages: List<String>) { roleHolders[Pair(roleName, user)] = packages } override fun getServiceThread(): ServiceThread { return serviceThread } /** * Awaits for the service thread to become idle, ensuring all pending tasks are completed. * * This method uses `runWithScissors` with a timeout to wait for the service thread's handler to * process all queued tasks. It asserts that the operation completes successfully within the * timeout. * * @see android.os.Handler.runWithScissors */ fun awaitServiceThreadIdle() { val timeout = 1.seconds.inWholeMilliseconds val success = serviceThread.threadHandler.runWithScissors({}, timeout) assertWithMessage("Waiting on the service thread timed out").that(success).isTrue() } } /** Loading