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

Commit cf8ac766 authored by Cintia Martins's avatar Cintia Martins
Browse files

Move SupervisionService's app binding to another thread.

Bug: 434022964
Test: atest SupervisionServiceTest
Test: atest CtsSupervisionTestCases
Flag: android.app.supervision.flags.enable_supervision_app_service
Change-Id: I8f9b03efa27e92224bf39fa0e8b6a228c94fed12
parent 5b241102
Loading
Loading
Loading
Loading
+39 −19
Original line number Diff line number Diff line
@@ -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;
@@ -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();

@@ -123,6 +128,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
    @VisibleForTesting
    SupervisionService(Injector injector) {
        mInjector = injector;
        mServiceThread = injector.getServiceThread();
        mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener());
    }

@@ -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);
                            }
@@ -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(
@@ -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 =
@@ -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));
@@ -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,
@@ -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);
                        }
                    });

        }
    }

+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"
    }
}
+25 −24
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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()
@@ -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)
    }
@@ -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)
@@ -548,7 +545,7 @@ class SupervisionServiceTest {
        service.setSupervisionEnabledForUser(userId, enabled)
        assertThat(service.isSupervisionEnabledForUser(userId)).isEqualTo(enabled)

        awaitBackgroundThreadIdle()
        injector.awaitServiceThreadIdle()

        verifySupervisionListeners(userId, enabled, listeners)
    }
@@ -565,7 +562,7 @@ class SupervisionServiceTest {
        service.mInternal.setSupervisionEnabledForUser(userId, enabled)
        assertThat(service.isSupervisionEnabledForUser(userId)).isEqualTo(enabled)

        awaitBackgroundThreadIdle()
        injector.awaitServiceThreadIdle()

        verifySupervisionListeners(userId, enabled, listeners)
    }
@@ -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
@@ -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>>()

@@ -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()
    }
}

/**