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

Commit faa026cd authored by Evan Chen's avatar Evan Chen
Browse files

Allow multiple CDM services

Allow to create multiple CompanionDeviceManagerService.
Need to create excatly one meta-tag in the service if service is more
than one.
No need to create meta-tag if only one CDM service
Not callback for the primary service
Bug: 198539218
Test: CTS Verifier test

Change-Id: I38eb7374f0e4f784a94b4e6dc14e3ae4c05f067c
parent f9a3f5cd
Loading
Loading
Loading
Loading
+18 −99
Original line number Diff line number Diff line
@@ -17,14 +17,11 @@

package com.android.server.companion;

import static android.Manifest.permission.BIND_COMPANION_DEVICE_SERVICE;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED;
import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.content.Context.BIND_IMPORTANT;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import static com.android.internal.util.CollectionUtils.any;
@@ -63,11 +60,9 @@ import android.bluetooth.le.ScanSettings;
import android.companion.Association;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.companion.CompanionDeviceService;
import android.companion.DeviceNotAssociatedException;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceManager;
import android.companion.ICompanionDeviceService;
import android.companion.IFindDeviceCallback;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -80,7 +75,6 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.net.NetworkPolicyManager;
@@ -176,7 +170,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
    private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000;
    private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000;

    private static final long DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS = 10 * 1000;
    static final long DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS = 10 * 1000;

    private static final boolean DEBUG = false;
    private static final String LOG_TAG = "CompanionDeviceManagerService";
@@ -207,9 +201,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
    private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
    private PowerWhitelistManager mPowerWhitelistManager;
    private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
    /** userId -> packageName -> serviceConnector */
    private PerUser<ArrayMap<String, ServiceConnector<ICompanionDeviceService>>>
            mDeviceListenerServiceConnectors;
    private IAppOpsService mAppOpsManager;
    private RoleManager mRoleManager;
    private BluetoothAdapter mBluetoothAdapter;
@@ -234,6 +225,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind

    private final Object mLock = new Object();
    private final Handler mMainHandler = Handler.getMain();
    private CompanionDevicePresenceController mCompanionDevicePresenceController;

    /** userId -> [association] */
    @GuardedBy("mLock")
@@ -256,6 +248,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        mPermissionControllerManager = requireNonNull(
                context.getSystemService(PermissionControllerManager.class));
        mUserManager = context.getSystemService(UserManager.class);
        mCompanionDevicePresenceController = new CompanionDevicePresenceController();

        Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
        mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
@@ -268,16 +261,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
            }
        };

        mDeviceListenerServiceConnectors = new PerUser<ArrayMap<String,
                ServiceConnector<ICompanionDeviceService>>>() {
            @NonNull
            @Override
            protected ArrayMap<String, ServiceConnector<ICompanionDeviceService>> create(
                    int userId) {
                return new ArrayMap<>();
            }
        };

        registerPackageMonitor();
    }

@@ -293,7 +276,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
                                a -> !Objects.equals(a.getPackageName(), packageName)),
                        userId);

                unbindDevicePresenceListener(packageName, userId);
                mCompanionDevicePresenceController.unbindDevicePresenceListener(
                        packageName, userId);
            }

            @Override
@@ -308,15 +292,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
    }

    private void unbindDevicePresenceListener(String packageName, int userId) {
        ServiceConnector<ICompanionDeviceService> deviceListener =
                mDeviceListenerServiceConnectors.forUser(userId)
                        .remove(packageName);
        if (deviceListener != null) {
            deviceListener.unbind();
        }
    }

    @Override
    public void onStart() {
        publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
@@ -784,11 +759,13 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
            }

            fout.append("Device Listener Services State:").append('\n');
            for (int i = 0, size = mDeviceListenerServiceConnectors.size(); i < size; i++) {
                int userId = mDeviceListenerServiceConnectors.keyAt(i);
            for (int i = 0, size =  mCompanionDevicePresenceController.mBoundServices.size();
                    i < size; i++) {
                int userId = mCompanionDevicePresenceController.mBoundServices.keyAt(i);
                fout.append("  ")
                        .append("u").append(Integer.toString(userId)).append(": ")
                        .append(Objects.toString(mDeviceListenerServiceConnectors.valueAt(i)))
                        .append(Objects.toString(
                                mCompanionDevicePresenceController.mBoundServices.valueAt(i)))
                        .append('\n');
            }
        }
@@ -822,12 +799,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind

    void onAssociationPreRemove(Association association) {
        if (association.isNotifyOnDeviceNearby()) {
            ServiceConnector<ICompanionDeviceService> serviceConnector =
                    mDeviceListenerServiceConnectors.forUser(association.getUserId())
                            .get(association.getPackageName());
            if (serviceConnector != null) {
                serviceConnector.unbind();
            }
            mCompanionDevicePresenceController.unbindDevicePresenceListener(
                    association.getPackageName(), association.getUserId());
        }

        String deviceProfile = association.getDeviceProfile();
@@ -1242,55 +1215,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
                >= DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS;
    }

    private ServiceConnector<ICompanionDeviceService> getDeviceListenerServiceConnector(
            Association a) {
        return mDeviceListenerServiceConnectors.forUser(a.getUserId()).computeIfAbsent(
                a.getPackageName(),
                pkg -> createDeviceListenerServiceConnector(a));
    }

    private ServiceConnector<ICompanionDeviceService> createDeviceListenerServiceConnector(
            Association a) {
        List<ResolveInfo> resolveInfos = getContext().getPackageManager().queryIntentServicesAsUser(
                new Intent(CompanionDeviceService.SERVICE_INTERFACE), MATCH_ALL, a.getUserId());
        List<ResolveInfo> packageResolveInfos = filter(resolveInfos,
                info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName()));
        if (packageResolveInfos.size() != 1) {
            Slog.w(LOG_TAG, "Device presence listener package must have exactly one "
                    + "CompanionDeviceService, but " + a.getPackageName()
                    + " has " + packageResolveInfos.size());
            return new ServiceConnector.NoOp<>();
        }
        String servicePermission = packageResolveInfos.get(0).serviceInfo.permission;
        if (!BIND_COMPANION_DEVICE_SERVICE.equals(servicePermission)) {
            Slog.w(LOG_TAG, "Binding CompanionDeviceService must have "
                    + BIND_COMPANION_DEVICE_SERVICE + " permission.");
            return new ServiceConnector.NoOp<>();
        }
        ComponentName componentName = packageResolveInfos.get(0).serviceInfo.getComponentName();
        Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName);
        return new ServiceConnector.Impl<ICompanionDeviceService>(getContext(),
                new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent(componentName),
                BIND_IMPORTANT,
                a.getUserId(),
                ICompanionDeviceService.Stub::asInterface) {

            @Override
            protected long getAutoDisconnectTimeoutMs() {
                // Service binding is managed manually based on corresponding device being nearby
                return Long.MAX_VALUE;
            }

            @Override
            public void binderDied() {
                super.binderDied();

                // Re-connect to the service if process gets killed
                mMainHandler.postDelayed(this::connect, DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS);
            }
        };
    }

    private class BleScanCallback extends ScanCallback {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
@@ -1354,8 +1278,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind

        @Override
        public void run() {
            Slog.i(LOG_TAG, "UnbindDeviceListenersRunnable.run(); devicesNearby = "
                    + mDevicesLastNearby);
            int size = mDevicesLastNearby.size();
            for (int i = 0; i < size; i++) {
                String address = mDevicesLastNearby.keyAt(i);
@@ -1364,7 +1286,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
                if (isDeviceDisappeared(lastNearby)) {
                    for (Association association : getAllAssociations(address)) {
                        if (association.isNotifyOnDeviceNearby()) {
                            getDeviceListenerServiceConnector(association).unbind();
                            mCompanionDevicePresenceController.unbindDevicePresenceListener(
                                    association.getPackageName(), association.getUserId());
                        }
                    }
                }
@@ -1434,10 +1357,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
            Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")");
            for (Association association : getAllAssociations(address)) {
                if (association.isNotifyOnDeviceNearby()) {
                    Slog.i(LOG_TAG,
                            "Sending onDeviceAppeared to " + association.getPackageName() + ")");
                    getDeviceListenerServiceConnector(association).run(
                            service -> service.onDeviceAppeared(association.getDeviceMacAddress()));
                    mCompanionDevicePresenceController.onDeviceNotifyAppeared(association,
                            getContext(), mMainHandler);
                }
            }
        }
@@ -1449,10 +1370,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        boolean hasDeviceListeners = false;
        for (Association association : getAllAssociations(address)) {
            if (association.isNotifyOnDeviceNearby()) {
                Slog.i(LOG_TAG,
                        "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
                getDeviceListenerServiceConnector(association).run(
                        service -> service.onDeviceDisappeared(address));
                mCompanionDevicePresenceController.onDeviceNotifyDisappeared(
                        association, getContext(), mMainHandler);
                hasDeviceListeners = true;
            }
        }
+215 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.companion;

import static android.Manifest.permission.BIND_COMPANION_DEVICE_SERVICE;
import static android.content.Context.BIND_IMPORTANT;

import static com.android.internal.util.CollectionUtils.filter;

import android.annotation.NonNull;
import android.companion.Association;
import android.companion.CompanionDeviceService;
import android.companion.ICompanionDeviceService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.util.ArrayMap;
import android.util.Slog;

import com.android.internal.infra.PerUser;
import com.android.internal.infra.ServiceConnector;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * This class creates/removes {@link ServiceConnector}s between {@link CompanionDeviceService} and
 * the companion apps. The controller also will notify the companion apps with device status.
 */
public class CompanionDevicePresenceController {
    private static final String LOG_TAG = "CompanionDevicePresenceController";
    PerUser<ArrayMap<String, List<BoundService>>> mBoundServices;
    private static final String META_DATA_KEY_PRIMARY = "primary";

    public CompanionDevicePresenceController() {
        mBoundServices = new PerUser<ArrayMap<String, List<BoundService>>>() {
            @NonNull
            @Override
            protected ArrayMap<String, List<BoundService>> create(int userId) {
                return new ArrayMap<>();
            }
        };
    }

    void onDeviceNotifyAppeared(Association association, Context context, Handler handler) {
        ServiceConnector<ICompanionDeviceService> primaryConnector =
                getPrimaryServiceConnector(association, context, handler);
        if (primaryConnector != null) {
            Slog.i(LOG_TAG,
                    "Sending onDeviceAppeared to " + association.getPackageName() + ")");
            primaryConnector.run(
                    service -> service.onDeviceAppeared(association.getDeviceMacAddress()));
        }
    }

    void onDeviceNotifyDisappeared(Association association, Context context, Handler handler) {
        ServiceConnector<ICompanionDeviceService> primaryConnector =
                getPrimaryServiceConnector(association, context, handler);
        if (primaryConnector != null) {
            Slog.i(LOG_TAG,
                    "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
            primaryConnector.run(
                    service -> service.onDeviceDisappeared(association.getDeviceMacAddress()));
        }
    }

    void unbindDevicePresenceListener(String packageName, int userId) {
        List<BoundService> boundServices = mBoundServices.forUser(userId)
                .remove(packageName);
        if (boundServices != null) {
            for (BoundService boundService: boundServices) {
                Slog.d(LOG_TAG, "Unbinding the serviceConnector: " + boundService.mComponentName);
                boundService.mServiceConnector.unbind();
            }
        }
    }

    private ServiceConnector<ICompanionDeviceService> getPrimaryServiceConnector(
            Association association, Context context, Handler handler) {
        for (BoundService boundService: getDeviceListenerServiceConnector(association, context,
                handler)) {
            if (boundService.mIsPrimary) {
                return boundService.mServiceConnector;
            }
        }
        return null;
    }

    private List<BoundService> getDeviceListenerServiceConnector(Association a, Context context,
            Handler handler) {
        return mBoundServices.forUser(a.getUserId()).computeIfAbsent(
                a.getPackageName(),
                pkg -> createDeviceListenerServiceConnector(a, context, handler));
    }

    private List<BoundService> createDeviceListenerServiceConnector(Association a, Context context,
            Handler handler) {
        List<ResolveInfo> resolveInfos = context
                .getPackageManager()
                .queryIntentServicesAsUser(new Intent(CompanionDeviceService.SERVICE_INTERFACE),
                        PackageManager.GET_META_DATA, a.getUserId());
        List<ResolveInfo> packageResolveInfos = filter(resolveInfos,
                info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName()));
        List<BoundService> serviceConnectors = new ArrayList<>();
        if (!validatePackageInfo(packageResolveInfos, a)) {
            return serviceConnectors;
        }
        for (ResolveInfo packageResolveInfo : packageResolveInfos) {
            boolean isPrimary = (packageResolveInfo.serviceInfo.metaData != null
                    && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY))
                    || packageResolveInfos.size() == 1;
            ComponentName componentName = packageResolveInfo.serviceInfo.getComponentName();

            Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName);

            ServiceConnector<ICompanionDeviceService> serviceConnector =
                    new ServiceConnector.Impl<ICompanionDeviceService>(context,
                            new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent(
                                    componentName), BIND_IMPORTANT, a.getUserId(),
                            ICompanionDeviceService.Stub::asInterface) {
                        @Override
                        protected long getAutoDisconnectTimeoutMs() {
                            // Service binding is managed manually based on corresponding device
                            // being nearby
                            return Long.MAX_VALUE;
                        }

                        @Override
                        public void binderDied() {
                            super.binderDied();

                            // Re-connect to the service if process gets killed
                            handler.postDelayed(
                                    this::connect,
                                    CompanionDeviceManagerService
                                            .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS);
                        }
                    };

            serviceConnectors.add(new BoundService(componentName, isPrimary, serviceConnector));
        }
        return serviceConnectors;
    }

    private boolean validatePackageInfo(List<ResolveInfo> packageResolveInfos,
            Association association) {
        if (packageResolveInfos.size() == 0 || packageResolveInfos.size() > 5) {
            Slog.e(LOG_TAG, "Device presence listener package must have at least one and not "
                    + "more than five CompanionDeviceService(s) declared. But "
                    + association.getPackageName()
                    + " has " + packageResolveInfos.size());
            return false;
        }

        int primaryCount = 0;
        for (ResolveInfo packageResolveInfo : packageResolveInfos) {
            String servicePermission = packageResolveInfo.serviceInfo.permission;
            if (!BIND_COMPANION_DEVICE_SERVICE.equals(servicePermission)) {
                Slog.e(LOG_TAG, "Binding CompanionDeviceService must have "
                        + BIND_COMPANION_DEVICE_SERVICE + " permission.");
                return false;
            }

            if (packageResolveInfo.serviceInfo.metaData != null
                    && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY)) {
                primaryCount++;
                if (primaryCount > 1) {
                    Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService "
                            + "to be bound but "
                            + association.getPackageName() + "has " + primaryCount);
                    return false;
                }
            }
        }

        if (packageResolveInfos.size() == 1 && primaryCount != 0) {
            Slog.w(LOG_TAG, "Do not need the primary metadata if there's only one"
                    + " CompanionDeviceService " + "but " + association.getPackageName()
                    + " has " + primaryCount);
        }

        return true;
    }

    private static class BoundService {
        private final ComponentName mComponentName;
        private final boolean mIsPrimary;
        private final ServiceConnector<ICompanionDeviceService> mServiceConnector;

        BoundService(ComponentName componentName,
                boolean isPrimary,  ServiceConnector<ICompanionDeviceService> serviceConnector) {
            this.mComponentName = componentName;
            this.mIsPrimary = isPrimary;
            this.mServiceConnector = serviceConnector;
        }
    }
}