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

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

Ensure all SupervisionAppServices are bound before dispatching

Bug: 427463811
Bug: 414747515
Test: atest SupervisionServiceTest
Test: atest CtsSupervisionTestCases
Test: atest CtsAppBindingHostTestCases
Flag: android.app.supervision.flags.enable_supervision_app_service
Change-Id: I90a30fcc269ee110cca46ec724a43bb940bd9587
parent 12a16615
Loading
Loading
Loading
Loading
+20 −3
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 * 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.
@@ -38,8 +38,6 @@ import java.io.PrintWriter;
 * - Call {@link #bind()} to create a connection.
 * - Call {@link #unbind()} to disconnect.  Make sure to disconnect when the user stops.
 *
 * Add onConnected/onDisconnected callbacks as needed.
 *
 * When the target process gets killed (by OOM-killer, etc), then the activity manager will
 * re-connect the connection automatically, in which case onServiceDisconnected() gets called
 * and then onServiceConnected().
@@ -138,6 +136,7 @@ public abstract class PersistentConnection<T> {

                scheduleStableCheckLocked();
            }
            onConnected(mService);
        }

        @Override
@@ -153,6 +152,7 @@ public abstract class PersistentConnection<T> {
                // Note we won't increase the rebind timeout here, because we don't explicitly
                // rebind in this case.
            }
            onDisconnected();
        }

        @Override
@@ -173,6 +173,7 @@ public abstract class PersistentConnection<T> {

                scheduleRebindLocked();
            }
            onDisconnected();
        }
    };

@@ -206,6 +207,22 @@ public abstract class PersistentConnection<T> {

    protected abstract int getBindFlags();

    /**
     * Called when the service is successfully connected.
     *
     * <p>Note: This method is called without holding {@link #mLock}.
     *
     * @param service The interface to the connected service.
     */
    protected void onConnected(@NonNull T service) {}

    /**
     * Called when the service is disconnected or the binding dies.
     *
     * <p>Note: This method is called without holding {@link #mLock}.
     */
    protected void onDisconnected() {}

    /**
     * @return whether {@link #bind()} has been called and {@link #unbind()} hasn't.
     *
+26 −7
Original line number Diff line number Diff line
@@ -143,7 +143,15 @@ public class AppBindingService extends Binder {
        }
    }

    /** Get the list of services bound to a specific finder class. */
    /**
     * Get the list of services bound to a specific finder class.
     *
     * This method will block until all connections are established or a timeout occurs.
     *
     * <p><b>NOTE: This method should be called from a background thread other than
     * {@link BackgroundThread}, otherwise the {@link PersistentConnection} callbacks will not be
     * delivered until after this method returns.</b></p>
     */
    public List<AppServiceConnection> getAppServiceConnections(
            Class<? extends AppServiceFinder<?, ?>> appServiceFinderClass, int userId) {
        List<AppServiceConnection> serviceConnections = new ArrayList<>();
@@ -153,15 +161,21 @@ public class AppBindingService extends Binder {
                if (app.getClass() != appServiceFinderClass) {
                    continue;
                }
                serviceConnections.addAll(getBoundConnectionsLocked(userId, app));
                serviceConnections.addAll(getBoundConnectionsBlockingLocked(userId, app));
            }
        }
        return serviceConnections;
    }

    /** Get the connection bound to a specific finder. If the connection does not
     * already exist, create one.  */
    private List<AppServiceConnection> getBoundConnectionsLocked(int userId, AppServiceFinder app) {
    /**
     * Get the connection bound to a specific finder, ensuring it's bound.
     *
     * This method will block until all connections are established or a timeout occurs.
     *
     * <p><b>NOTE: This method must be called from a background thread.</b></p>
     */
    private List<AppServiceConnection> getBoundConnectionsBlockingLocked(int userId,
            AppServiceFinder app) {
        Set<String> targetPackages = app.getTargetPackages(userId);
        List<AppServiceConnection> connections = new ArrayList<>();
        for (String targetPackage : targetPackages) {
@@ -180,11 +194,16 @@ public class AppBindingService extends Binder {
                                    targetPackage,
                                    service.getComponentName());
                    mConnections.add(conn);
                    conn.bind();
                }
            }
            if (conn != null) {
                conn.bind();
                if (conn.awaitConnection()) {
                    connections.add(conn);
                } else {
                    Slogf.w(TAG, "Failed to establish connection for %s, user %d, package %s",
                            app.getAppDescription(), userId, targetPackage);
                }
            }
        }
        return connections;
+20 −40
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.appbinding;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
@@ -27,9 +28,6 @@ import com.android.server.am.PersistentConnection;
import com.android.server.appbinding.finders.AppServiceFinder;
import com.android.server.utils.Slogf;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Establishes a persistent connection to a given service component for a given user
 * Each connection is associated with an AppServiceFinder to facilitate the service exchange if the
@@ -40,19 +38,7 @@ public class AppServiceConnection extends PersistentConnection<IInterface> {
    private final AppBindingConstants mConstants;
    private final AppServiceFinder mFinder;
    private final String mPackageName;

    /**
     * Listener for connection status updates
     * TODO: Refactor this (b/423644620)
     */
    public interface ConnectionStatusListener {
        void onConnected(@NonNull AppServiceConnection connection, @NonNull IInterface service);
        void onDisconnected(@NonNull AppServiceConnection connection);
        void onBinderDied(@NonNull AppServiceConnection connection);
    }

    private final List<ConnectionStatusListener> mConnectionListeners =
            new CopyOnWriteArrayList<>();
    private final ConditionVariable mConditionVariable = new ConditionVariable();

    AppServiceConnection(Context context, int userId, AppBindingConstants constants,
            Handler handler, AppServiceFinder finder, String packageName,
@@ -75,20 +61,22 @@ public class AppServiceConnection extends PersistentConnection<IInterface> {
    @Override
    protected IInterface asInterface(IBinder obj) {
        final IInterface service = mFinder.asInterface(obj);

        if (service != null) {
            // Notify all listeners.
            Slogf.d(TAG, "Service for %s is connected. Notifying %s listeners.",
                    getComponentName(), mConnectionListeners.size());
            for (ConnectionStatusListener listener : mConnectionListeners) {
                listener.onConnected(this, service);
            }
        } else {
        if (service == null) {
            Slogf.w(TAG, "Service for %s is null.", getComponentName());
        }
        return service;
    }

    @Override
    protected void onConnected(@NonNull IInterface service) {
        mConditionVariable.open();
    }

    @Override
    protected void onDisconnected() {
        mConditionVariable.close();
    }

    public AppServiceFinder getFinder() {
        return mFinder;
    }
@@ -98,21 +86,13 @@ public class AppServiceConnection extends PersistentConnection<IInterface> {
    }

    /**
     * Adds a listener to be notified of connection changes.
     * If service is already connected, notify immediately.
     */
    public void addConnectionStatusListener(@NonNull ConnectionStatusListener listener) {
        mConnectionListeners.add(listener);
        final IInterface service = getServiceBinder();
        if (isConnected() && service != null) {
            listener.onConnected(this, service);
        }
    }

    /**
     * Removes an existing listener.
     * Establishes the service connection and blocks until the service is connected
     * or a timeout occurs.
     *
     * @return true if the service connected successfully within the timeout, false otherwise.
     */
    public void removeConnectionStatusListener(@NonNull ConnectionStatusListener listener) {
        mConnectionListeners.remove(listener);
    public boolean awaitConnection() {
        long timeoutMs = mConstants.SERVICE_RECONNECT_MAX_BACKOFF_SEC * 10 * 1000L;
        return mConditionVariable.block(timeoutMs) && isConnected();
    }
}
+43 −58
Original line number Diff line number Diff line
@@ -54,7 +54,6 @@ import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
@@ -68,6 +67,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.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FunctionalUtils.RemoteExceptionIgnoringConsumer;
import com.android.internal.util.IndentingPrintWriter;
@@ -82,6 +82,7 @@ import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -387,8 +388,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
                mSupervisionSettings.saveUserData();
            }
        }
        Binder.withCleanCallingIdentity(
                () -> {
        BackgroundThread.getExecutor().execute(() -> {
            updateWebContentFilters(userId, enabled);
            dispatchSupervisionEvent(
                    userId, listener -> listener.onSetSupervisionEnabled(userId, enabled));
@@ -420,10 +420,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
            if (binder == null) {
                Slogf.d(
                        SupervisionLog.TAG,
                        "Failed to bind to SupervisionAppService for %s now",
                        "Failed to bind to SupervisionAppService for %s",
                        targetPackage);

                dispatchSupervisionAppServiceWhenConnected(conn, action);
                continue;
            }

@@ -436,10 +434,10 @@ public class SupervisionService extends ISupervisionManager.Stub {
    private void dispatchSupervisionEvent(
            @UserIdInt int userId,
            @NonNull RemoteExceptionIgnoringConsumer<ISupervisionListener> action) {
        ArrayList<ISupervisionListener> listeners = new ArrayList<>();

        // Add SupervisionAppServices listeners before the platform listeners.
        listeners.addAll(getSupervisionAppServiceListeners(userId, action));
        ArrayList<ISupervisionListener> listeners = new ArrayList<>(
                getSupervisionAppServiceListeners(userId, action));

        synchronized (getLockObject()) {
            mSupervisionListeners.forEach(
@@ -450,7 +448,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
                    });
        }

        listeners.forEach(listener -> action.accept(listener));
        listeners.forEach(action);
    }

    private void clearAllDevicePoliciesAndSuspendedPackages(@UserIdInt int userId) {
@@ -459,20 +457,19 @@ public class SupervisionService extends ISupervisionManager.Stub {
        }

        enforcePermission(MANAGE_ROLE_HOLDERS);
        List<String> supervisionPackages = new ArrayList<>();
        supervisionPackages.addAll(
                mInjector.getRoleHoldersAsUser(
                        RoleManager.ROLE_SUPERVISION, UserHandle.of(userId)));
        supervisionPackages.addAll(
                mInjector.getRoleHoldersAsUser(
                        RoleManager.ROLE_SYSTEM_SUPERVISION, UserHandle.of(userId)));

        List<String> roles =  Arrays.asList(RoleManager.ROLE_SYSTEM_SUPERVISION,
                RoleManager.ROLE_SUPERVISION);
        for (String role : roles) {
            List<String> supervisionPackages =
                    mInjector.getRoleHoldersAsUser(role, UserHandle.of(userId));
            for (String supervisionPackage : supervisionPackages) {
            clearDevicePoliciesAndSuspendedPackagesFor(userId, supervisionPackage);
                clearDevicePoliciesAndSuspendedPackagesFor(userId, supervisionPackage, role);
            }
        }
    }

    private void clearDevicePoliciesAndSuspendedPackagesFor(int userId, String supervisionPackage) {
    private void clearDevicePoliciesAndSuspendedPackagesFor(int userId, String supervisionPackage,
            String roleName) {
        DevicePolicyManagerInternal dpmi = mInjector.getDpmInternal();
        if (dpmi != null) {
            dpmi.removePoliciesForAdmins(supervisionPackage, userId);
@@ -482,36 +479,8 @@ public class SupervisionService extends ISupervisionManager.Stub {
        if (pmi != null) {
            pmi.unsuspendForSuspendingPackage(supervisionPackage, userId, userId);
        }
    }

    private void dispatchSupervisionAppServiceWhenConnected(
            AppServiceConnection conn,
            @NonNull RemoteExceptionIgnoringConsumer<ISupervisionListener> action) {
        // This listener will be notified when the connection changes.
        AppServiceConnection.ConnectionStatusListener connectionListener =
                new AppServiceConnection.ConnectionStatusListener() {
                    @Override
                    public void onConnected(
                            @NonNull AppServiceConnection connection, @NonNull IInterface service) {
                        try {
                            ISupervisionListener binder = (ISupervisionListener) service;
                            Binder.withCleanCallingIdentity(() -> action.accept(binder));
                        } finally {
                            connection.removeConnectionStatusListener(this);
                        }
                    }

                    @Override
                    public void onDisconnected(@NonNull AppServiceConnection connection) {
                        connection.removeConnectionStatusListener(this);
                    }

                    @Override
                    public void onBinderDied(@NonNull AppServiceConnection connection) {
                        connection.removeConnectionStatusListener(this);
                    }
                };
        conn.addConnectionStatusListener(connectionListener);
        mInjector.removeRoleHoldersAsUser(roleName, supervisionPackage, UserHandle.of(userId));
    }

    /**
@@ -618,13 +587,13 @@ public class SupervisionService extends ISupervisionManager.Stub {
    static class Injector {
        public Context context;

        private DevicePolicyManagerInternal mDpmInternal;
        private AppBindingService mAppBindingService;
        private DevicePolicyManagerInternal mDpmInternal;
        private KeyguardManager mKeyguardManager;
        private PackageManager mPackageManager;
        private PackageManagerInternal mPackageManagerInternal;
        private UserManagerInternal mUserManagerInternal;
        private RoleManager mRoleManager;
        private UserManagerInternal mUserManagerInternal;

        Injector(Context context) {
            this.context = context;
@@ -686,6 +655,22 @@ public class SupervisionService extends ISupervisionManager.Stub {
        List<String> getRoleHoldersAsUser(String roleName, UserHandle user) {
            return mRoleManager.getRoleHoldersAsUser(roleName, user);
        }

        void removeRoleHoldersAsUser(String roleName, String packageName, UserHandle user) {
            mRoleManager.removeRoleHolderAsUser(
                    roleName,
                    packageName,
                    0,
                    user,
                    context.getMainExecutor(),
                    success -> {
                        if (!success) {
                            Slogf.e(SupervisionLog.TAG, "Failed to remove role %s fro %s",
                                    packageName, roleName);
                        }
                    });

        }
    }

    /** Publishes local and binder services and allows the service to act during initialization. */
+117 −71

File changed.

Preview size limit exceeded, changes collapsed.