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

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

Merge "Ensure all SupervisionAppServices are bound before dispatching" into main

parents 019c5ab9 fba9388b
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.