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

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

Refactor appBindingService to Allow Binding to Multiple Supervision Apps

Bug: 420613420
Test: atest CtsAppBindingHostTestCases
Flag: android.app.supervision.flags.enable_supervision_app_service
Change-Id: I6197b91330ef052693733acde9366ad8475e7d61
parent fe0568b4
Loading
Loading
Loading
Loading
+109 −29
Original line number Diff line number Diff line
@@ -32,7 +32,6 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IInterface;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
@@ -48,11 +47,13 @@ import com.android.server.SystemService;
import com.android.server.appbinding.finders.AppServiceFinder;
import com.android.server.appbinding.finders.CarrierMessagingClientServiceFinder;
import com.android.server.appbinding.finders.SupervisionAppServiceFinder;
import com.android.server.utils.Slogf;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;

/**
@@ -143,8 +144,8 @@ public class AppBindingService extends Binder {
    }

    /** Get the list of services bound to a specific finder class. */
    public <T extends IInterface>  List<AppServiceConnection> getAppServiceConnections(
            Class<? extends AppServiceFinder<?, T>> appServiceFinderClass, int userId) {
    public List<AppServiceConnection> getAppServiceConnections(
            Class<? extends AppServiceFinder<?, ?>> appServiceFinderClass, int userId) {
        List<AppServiceConnection> serviceConnections = new ArrayList<>();
        synchronized (mLock) {
            for (int i = 0; i < mApps.size(); i++) {
@@ -152,10 +153,7 @@ public class AppBindingService extends Binder {
                if (app.getClass() != appServiceFinderClass) {
                    continue;
                }
                AppServiceConnection conn = getBoundConnectionLocked(userId, app);
                if (conn != null) {
                    serviceConnections.add(conn);
                }
                serviceConnections.addAll(getBoundConnectionsLocked(userId, app));
            }
        }
        return serviceConnections;
@@ -163,23 +161,33 @@ public class AppBindingService extends Binder {

    /** Get the connection bound to a specific finder. If the connection does not
     * already exist, create one.  */
    private AppServiceConnection getBoundConnectionLocked(int userId, AppServiceFinder app) {
        AppServiceConnection conn = findConnectionLock(userId, app);
    private List<AppServiceConnection> getBoundConnectionsLocked(int userId, AppServiceFinder app) {
        Set<String> targetPackages = app.getTargetPackages(userId);
        List<AppServiceConnection> connections = new ArrayList<>();
        for (String targetPackage : targetPackages) {
            AppServiceConnection conn = findConnectionLock(userId, app, targetPackage);
            if (conn == null) {
            final ServiceInfo service = app.findService(userId, mIPackageManager,
                    mConstants);
            if (service==null) {
                Slog.d(TAG, "Can't create connection with app " +
                        app.getTargetPackage(userId) + ". Service is null.");
                return null;
            }
            conn = new AppServiceConnection(
                    mContext, userId, mConstants, mHandler,
                    app, service.getComponentName());
        }
                final ServiceInfo service =
                        app.findService(userId, mIPackageManager, mConstants, targetPackage);
                if (service != null) {
                    conn =
                            new AppServiceConnection(
                                    mContext,
                                    userId,
                                    mConstants,
                                    mHandler,
                                    app,
                                    targetPackage,
                                    service.getComponentName());
                    mConnections.add(conn);
                    conn.bind();
        return conn;
                }
            }
            if (conn != null) {
                connections.add(conn);
            }
        }
        return connections;
    }

    private AppBindingService(Injector injector, Context context) {
@@ -249,6 +257,8 @@ public class AppBindingService extends Binder {
        final IntentFilter packageFilter = new IntentFilter();
        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
        packageFilter.addDataScheme("package");

        mContext.registerReceiverAsUser(mPackageUserMonitor, UserHandle.ALL,
@@ -329,6 +339,12 @@ public class AppBindingService extends Binder {
                case Intent.ACTION_PACKAGE_CHANGED:
                    handlePackageAddedReplacing(packageName, userId);
                    break;
                case Intent.ACTION_PACKAGE_REMOVED:
                case Intent.ACTION_PACKAGE_FULLY_REMOVED:
                    if (!replacing) {
                        onAppRemoved(userId, packageName);
                    }
                    break;
            }
        }
    };
@@ -386,6 +402,22 @@ public class AppBindingService extends Binder {
        }
    }

    private void onAppRemoved(int userId, String packageName) {
        if (!Flags.enableSupervisionAppService()) {
            return;
        }

        if (DEBUG) {
            Slogf.d(TAG, "onAppRemoved: u%s %s", userId, packageName);
        }
        synchronized (mLock) {
            final AppServiceFinder finder = findFinderLocked(userId, packageName);
            if (finder != null) {
                bindServicesLocked(userId, finder, "package removed");
            }
        }
    }

    /**
     * Called when a target package changes; e.g. when the user changes the default SMS app.
     */
@@ -405,16 +437,21 @@ public class AppBindingService extends Binder {
    private AppServiceFinder findFinderLocked(int userId, @NonNull String packageName) {
        for (int i = 0; i < mApps.size(); i++) {
            final AppServiceFinder app = mApps.get(i);
            if (Flags.enableSupervisionAppService()) {
                if (app.getTargetPackages(userId).contains(packageName)) {
                    return app;
                }
            } else {
                if (packageName.equals(app.getTargetPackage(userId))) {
                    return app;
                }
            }
        }
        return null;
    }

    @Nullable
    private AppServiceConnection findConnectionLock(
            int userId, @NonNull AppServiceFinder target) {
    private AppServiceConnection findConnectionLock(int userId, @NonNull AppServiceFinder target) {
        for (int i = 0; i < mConnections.size(); i++) {
            final AppServiceConnection conn = mConnections.get(i);
            if ((conn.getUserId() == userId) && (conn.getFinder() == target)) {
@@ -424,6 +461,20 @@ public class AppBindingService extends Binder {
        return null;
    }

    @Nullable
    private AppServiceConnection findConnectionLock(
            int userId, @NonNull AppServiceFinder target, String targetPackage) {
        for (int i = 0; i < mConnections.size(); i++) {
            final AppServiceConnection conn = mConnections.get(i);
            if ((conn.getUserId() == userId)
                    && (conn.getFinder() == target)
                    && conn.getPackageName().equals(targetPackage)) {
                return conn;
            }
        }
        return null;
    }

    private void handlePackageAddedReplacing(String packageName, int userId) {
        if (DEBUG) {
            Slog.d(TAG, "handlePackageAddedReplacing: u" + userId + " " + packageName);
@@ -456,7 +507,37 @@ public class AppBindingService extends Binder {
            if (target != null && target != app) {
                continue;
            }
            if (!Flags.enableSupervisionAppService()) {
                bindServicesForFinderLocked(userId, target, reasonForLog, app); // old code
                continue;
            }
            // Disconnect from existing binding.
            unbindServicesLocked(userId, app, reasonForLog);

            final List<ServiceInfo> services =
                    app.findServices(userId, mIPackageManager, mConstants);
            if (services==null || services.isEmpty()) {
                continue;
            }
            for (ServiceInfo service : services) {
                if (DEBUG) {
                    Slog.d(TAG, "bindServicesLocked: u" + userId + " " + app.getAppDescription()
                            + " binding " + service.getComponentName() + " for " + reasonForLog);
                }
                if (service == null) {
                    continue;
                }
                final AppServiceConnection conn =
                        new AppServiceConnection(mContext, userId, mConstants, mHandler,
                                app, service.packageName, service.getComponentName());
                mConnections.add(conn);
                conn.bind();
            }
        }
    }

    private void bindServicesForFinderLocked(int userId, @Nullable AppServiceFinder target,
            @NonNull String reasonForLog, AppServiceFinder app) {
            // Disconnect from existing binding.
            final AppServiceConnection existingConn = findConnectionLock(userId, app);
            if (existingConn != null) {
@@ -465,7 +546,7 @@ public class AppBindingService extends Binder {

            final ServiceInfo service = app.findService(userId, mIPackageManager, mConstants);
            if (service == null) {
                continue;
                return;
            }
            if (DEBUG) {
                Slog.d(TAG, "bindServicesLocked: u" + userId + " " + app.getAppDescription()
@@ -473,11 +554,10 @@ public class AppBindingService extends Binder {
            }
            final AppServiceConnection conn =
                    new AppServiceConnection(mContext, userId, mConstants, mHandler,
                            app, service.getComponentName());
                            app, service.packageName, service.getComponentName());
            mConnections.add(conn);
            conn.bind();
    }
    }

    private void unbindServicesLocked(int userId, @Nullable AppServiceFinder target,
            @NonNull String reasonForLog) {
+9 −3
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ public class AppServiceConnection extends PersistentConnection<IInterface> {
    public static final String TAG = "AppServiceConnection";
    private final AppBindingConstants mConstants;
    private final AppServiceFinder mFinder;
    private final String mPackageName;

    /**
     * Listener for connection status updates
@@ -54,7 +55,7 @@ public class AppServiceConnection extends PersistentConnection<IInterface> {
            new CopyOnWriteArrayList<>();

    AppServiceConnection(Context context, int userId, AppBindingConstants constants,
            Handler handler, AppServiceFinder finder,
            Handler handler, AppServiceFinder finder, String packageName,
            @NonNull ComponentName componentName) {
        super(TAG, context, handler, userId, componentName,
                constants.SERVICE_RECONNECT_BACKOFF_SEC,
@@ -63,6 +64,7 @@ public class AppServiceConnection extends PersistentConnection<IInterface> {
                constants.SERVICE_STABLE_CONNECTION_THRESHOLD_SEC);
        mFinder = finder;
        mConstants = constants;
        mPackageName = packageName;
    }

    @Override
@@ -76,8 +78,8 @@ public class AppServiceConnection extends PersistentConnection<IInterface> {

        if (service != null) {
            // Notify all listeners.
            Slogf.d(TAG, "Service for %s is connected. Notifying listeners.",
                    getComponentName());
            Slogf.d(TAG, "Service for %s is connected. Notifying %s listeners.",
                    getComponentName(), mConnectionListeners.size());
            for (ConnectionStatusListener listener : mConnectionListeners) {
                listener.onConnected(this, service);
            }
@@ -91,6 +93,10 @@ public class AppServiceConnection extends PersistentConnection<IInterface> {
        return mFinder;
    }

    public String getPackageName() {
        return mPackageName;
    }

    /**
     * Adds a listener to be notified of connection changes.
     * If service is already connected, notify immediately.
+156 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.appbinding.finders;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.supervision.flags.Flags;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.ServiceInfo;
@@ -34,6 +35,11 @@ import com.android.server.appbinding.AppBindingService;
import com.android.server.appbinding.AppBindingUtils;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;

/**
@@ -53,14 +59,37 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten
    private final Object mLock = new Object();

    @GuardedBy("mLock")
    @Deprecated
    private final SparseArray<String> mTargetPackages = new SparseArray(4);

    @GuardedBy("mLock")
    @Deprecated
    private final SparseArray<ServiceInfo> mTargetServices = new SparseArray(4);

    @GuardedBy("mLock")
    @Deprecated
    private final SparseArray<String> mLastMessages = new SparseArray(4);

    @GuardedBy("mLock")
    private final SparseArray<HashMap<String, TargetServiceInfo>> mServiceInfos =
            new SparseArray(4);

    /**
     * Helper class to store the ServiceInfo and the last message related to it.
     */
    private static class TargetServiceInfo {
        @Nullable
        public final ServiceInfo serviceInfo;
        @Nullable
        public final String lastMessage;

        TargetServiceInfo(@Nullable ServiceInfo serviceInfo, @Nullable String lastMessage) {
            this.serviceInfo = serviceInfo;
            this.lastMessage = lastMessage;
        }
    }


    public AppServiceFinder(Context context,
            BiConsumer<AppServiceFinder, Integer> listener,
            Handler callbackHandler) {
@@ -88,6 +117,7 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten
            mTargetPackages.delete(userId);
            mTargetServices.delete(userId);
            mLastMessages.delete(userId);
            mServiceInfos.delete(userId);
        }
    }

@@ -95,6 +125,7 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten
     * Find the target service from the target app on a given user.
     */
    @Nullable
    @Deprecated
    public final ServiceInfo findService(int userId, IPackageManager ipm,
            AppBindingConstants constants) {
        synchronized (mLock) {
@@ -155,6 +186,90 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten
        }
    }

    /**
     * Find the target service from the target app on a given user.
     */
    @Nullable
    public final ServiceInfo findService(int userId, IPackageManager ipm,
            AppBindingConstants constants, String targetPackage) {
        synchronized (mLock) {
            HashMap<String, TargetServiceInfo> currServiceInfo = mServiceInfos.get(userId);
            if (DEBUG) {
                Slog.d(TAG, getAppDescription() + " package=" + targetPackage);
            }

            if (!isEnabled(constants, userId)) {
                final String message = "feature disabled";
                currServiceInfo.put(targetPackage, new TargetServiceInfo(null, message));
                Slog.i(TAG, getAppDescription() + " " + message);
                return null;
            }

            final StringBuilder errorMessage = new StringBuilder();
            final ServiceInfo service = AppBindingUtils.findService(
                    targetPackage,
                    userId,
                    getServiceAction(),
                    getServicePermission(),
                    getServiceClass(),
                    ipm,
                    errorMessage);

            if (service == null) {
                final String message = errorMessage.toString();
                currServiceInfo.put(targetPackage, new TargetServiceInfo(null, message));
                if (DEBUG) {
                    Slog.w(TAG, getAppDescription() + " package " + targetPackage + " u" + userId
                            + " " + message);
                }
                return null;
            }
            final String error = validateService(service);
            if (error != null) {
                currServiceInfo.put(targetPackage, new TargetServiceInfo(null, error));
                Log.e(TAG, error);
                return null;
            }

            currServiceInfo.put(targetPackage, new TargetServiceInfo(service,
                    "Valid service found"));
            return service;
        }
    }

    /**
     * Find the list of target service from the target apps on a given user.
     */
    @Nullable
    public final List<ServiceInfo> findServices(int userId, IPackageManager ipm,
            AppBindingConstants constants) {
        final Set<String> targetPackages = getTargetPackages(userId);
        synchronized (mLock) {
            mServiceInfos.put(userId, new HashMap<String, TargetServiceInfo>());

            if (DEBUG) {
                Slog.d(TAG, getAppDescription() + " packages=" + targetPackages);
            }

            if (!isEnabled(constants, userId) || targetPackages.isEmpty()) {
                final String message = (!isEnabled(constants, userId)) ? "feature disabled"
                        : "Target packages not found";
                mServiceInfos.get(userId).put(null, new TargetServiceInfo(null, message));
                Slog.w(TAG, getAppDescription() + " u" + userId + " " + message);
                return new ArrayList<>();
            }
        }

        List<ServiceInfo> services = new ArrayList<>();
        for (String targetPackage: targetPackages) {
            ServiceInfo service = findService(userId, ipm, constants, targetPackage);
            if (service != null) {
                services.add(service);
            }
        }
        return services;
    }

    protected abstract Class<TServiceType> getServiceClass();

    /**
@@ -166,8 +281,14 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten
     * @return the target package on a given user.
     */
    @Nullable
    @Deprecated
    public abstract String getTargetPackage(int userId);

    /**
     * @return the target packages on a given user.
     */
    public abstract Set<String> getTargetPackages(int userId);

    /**
     * @return the intent action that identifies the target service in the target app.
     */
@@ -199,6 +320,31 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten
        pw.println();

        synchronized (mLock) {
            for (int i = 0; i < mServiceInfos.size(); i++) {
                final int userId = mServiceInfos.keyAt(i);
                pw.print(prefix);
                pw.print("  User: ");
                pw.print(userId);
                pw.println();
                for (String targetPackage : mServiceInfos.get(userId).keySet()) {
                    pw.print(prefix);
                    pw.print("    Package: ");
                    pw.print(targetPackage);
                    pw.println();

                    pw.print(prefix);
                    pw.print("    Service: ");
                    pw.print(mServiceInfos.get(userId).get(targetPackage).serviceInfo);
                    pw.println();

                    pw.print(prefix);
                    pw.print("    Message: ");
                    pw.print(mServiceInfos.get(userId).get(targetPackage).lastMessage);
                    pw.println();
                }
                pw.println();
            }

            for (int i = 0; i < mTargetPackages.size(); i++) {
                final int userId = mTargetPackages.keyAt(i);
                pw.print(prefix);
@@ -227,6 +373,16 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten
    /** Dumpys support */
    public void dumpSimple(PrintWriter pw) {
        synchronized (mLock) {
            for (int i = 0; i < mServiceInfos.size(); i++) {
                final int userId = mServiceInfos.keyAt(i);
                pw.println(String.format("finder,%s,%s", getAppDescription(), userId));
                for (String targetPackage : mServiceInfos.get(userId).keySet()) {
                    pw.println(String.format("finder,%s,%s,%s,%s,%s",
                            getAppDescription(), userId, targetPackage,
                            mServiceInfos.get(userId).get(targetPackage).serviceInfo,
                            mServiceInfos.get(userId).get(targetPackage).lastMessage));
                }
            }
            for (int i = 0; i < mTargetPackages.size(); i++) {
                final int userId = mTargetPackages.keyAt(i);
                pw.print("finder,");
+19 −0
Original line number Diff line number Diff line
@@ -35,6 +35,9 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.util.CollectionUtils;
import com.android.server.appbinding.AppBindingConstants;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;

/**
@@ -85,6 +88,7 @@ public class CarrierMessagingClientServiceFinder
    }

    @Override
    @Deprecated
    public String getTargetPackage(int userId) {
        final String ret = CollectionUtils.firstOrNull(mRoleManager.getRoleHoldersAsUser(
                RoleManager.ROLE_SMS, UserHandle.of(userId)));
@@ -96,6 +100,21 @@ public class CarrierMessagingClientServiceFinder
        return ret;
    }

    @Override
    public Set<String> getTargetPackages(int userId) {
        String targetPackage = CollectionUtils.firstOrNull(mRoleManager.getRoleHoldersAsUser(
                RoleManager.ROLE_SMS, UserHandle.of(userId)));

        if (DEBUG) {
            Slog.d(TAG, "getTargetPackages()=" + targetPackage);
        }

        if (targetPackage == null) {
            return Collections.emptySet();
        }
        return new HashSet<>(Collections.singletonList(targetPackage));
    }

    @Override
    public void startMonitoring() {
        mRoleManager.addOnRoleHoldersChangedListenerAsUser(
+13 −0
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ import com.android.internal.util.CollectionUtils;
import com.android.server.LocalServices;
import com.android.server.appbinding.AppBindingConstants;

import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;

/** Finds the @{link SupervisionAppService} implementation within the supervision app. */
@@ -77,6 +79,7 @@ public class SupervisionAppServiceFinder

    @Nullable
    @Override
    @Deprecated
    public String getTargetPackage(int userId) {
        final String ret =
                CollectionUtils.firstOrNull(
@@ -85,6 +88,16 @@ public class SupervisionAppServiceFinder
        return ret;
    }

    @Override
    public Set<String> getTargetPackages(int userId) {
        final Set<String> ret = new HashSet<>();
        ret.addAll(mRoleManager.getRoleHoldersAsUser(
                RoleManager.ROLE_SYSTEM_SUPERVISION, UserHandle.of(userId)));
        ret.addAll(mRoleManager.getRoleHoldersAsUser(
                RoleManager.ROLE_SUPERVISION, UserHandle.of(userId)));
        return ret;
    }

    @NonNull
    @Override
    protected String getServiceAction() {
Loading