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

Commit 4bbab50b authored by Cintia Martins's avatar Cintia Martins Committed by Android (Google) Code Review
Browse files

Merge "Move SupervisionService's app binding to another thread." into main

parents cdcd07af cf8ac766
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()
    }
}

/**