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

Commit f63c1051 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[8/8] Use PresenceController and AppController in CdmService"

parents 903315ba aa15e1cc
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -207,6 +207,18 @@ public final class AssociationInfo implements Parcelable {
        return macAddress.equals(mDeviceMacAddress);
    }

    /**
     * Utility method to be used by CdmService only.
     *
     * @return whether CdmService should bind the companion application that "owns" this association
     *         when the device is present.
     *
     * @hide
     */
    public boolean shouldBindWhenPresent() {
        return mNotifyOnDeviceNearby || mSelfManaged;
    }

    /** @hide */
    public @NonNull String toShortString() {
        final StringBuilder sb = new StringBuilder();
+9 −8
Original line number Diff line number Diff line
@@ -16,7 +16,9 @@

package com.android.server.companion;

import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
import static com.android.server.companion.CompanionDeviceManagerService.TAG;

import static java.util.concurrent.TimeUnit.DAYS;

import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -37,17 +39,16 @@ import com.android.server.LocalServices;
 */
public class AssociationCleanUpService extends JobService {
    private static final int JOB_ID = AssociationCleanUpService.class.hashCode();
    private static final long ONE_DAY_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 1 Day
    private CompanionDeviceManagerServiceInternal mCdmServiceInternal = LocalServices.getService(
            CompanionDeviceManagerServiceInternal.class);
    private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1);

    @Override
    public boolean onStartJob(final JobParameters params) {
        Slog.i(LOG_TAG, "Execute the Association CleanUp job");
        Slog.i(TAG, "Execute the Association CleanUp job");
        // Special policy for APP_STREAMING role that need to revoke associations if the device
        // does not connect for 3 months.
        AsyncTask.execute(() -> {
            mCdmServiceInternal.associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
            LocalServices.getService(CompanionDeviceManagerServiceInternal.class)
                    .associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
            jobFinished(params, false);
        });
        return true;
@@ -55,7 +56,7 @@ public class AssociationCleanUpService extends JobService {

    @Override
    public boolean onStopJob(final JobParameters params) {
        Slog.i(LOG_TAG, "Association cleanup job stopped; id=" + params.getJobId()
        Slog.i(TAG, "Association cleanup job stopped; id=" + params.getJobId()
                + ", reason="
                + JobParameters.getInternalReasonCodeDescription(
                params.getInternalStopReasonCode()));
@@ -63,7 +64,7 @@ public class AssociationCleanUpService extends JobService {
    }

    static void schedule(Context context) {
        Slog.i(LOG_TAG, "Scheduling the Association Cleanup job");
        Slog.i(TAG, "Scheduling the Association Cleanup job");
        final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
        final JobInfo job = new JobInfo.Builder(JOB_ID,
                new ComponentName(context, AssociationCleanUpService.class))
+285 −585

File changed.

Preview size limit exceeded, changes collapsed.

+0 −238
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.AssociationInfo;
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 = "android.companion.primary";
    private final CompanionDeviceManagerService mService;

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

    void onDeviceNotifyAppeared(AssociationInfo association, Context context, Handler handler) {
        for (BoundService boundService : getDeviceListenerServiceConnector(
                association, context,  handler)) {
            if (boundService.mIsPrimary) {
                Slog.i(LOG_TAG,
                        "Sending onDeviceAppeared to " + association.getPackageName() + ")");
                boundService.mServiceConnector.run(
                        service -> service.onDeviceAppeared(association));
            } else {
                Slog.i(LOG_TAG, "Connecting to " + boundService.mComponentName);
                boundService.mServiceConnector.connect();
            }
        }
    }

    void onDeviceNotifyDisappeared(AssociationInfo association, Context context, Handler handler) {
        for (BoundService boundService : getDeviceListenerServiceConnector(
                association, context,  handler)) {
            if (boundService.mIsPrimary) {
                Slog.i(LOG_TAG,
                        "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
                boundService.mServiceConnector.run(service ->
                        service.onDeviceDisappeared(association));
            }
        }
    }

    void onDeviceNotifyDisappearedAndUnbind(AssociationInfo association,
            Context context, Handler handler) {
        for (BoundService boundService : getDeviceListenerServiceConnector(
                association, context, handler)) {
            if (boundService.mIsPrimary) {
                Slog.i(LOG_TAG,
                        "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
                boundService.mServiceConnector.post(
                        service -> {
                            service.onDeviceDisappeared(association);
                        }).thenRun(() -> unbindDevicePresenceListener(
                                        association.getPackageName(), association.getUserId()));
            }
        }
    }

    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 List<BoundService> getDeviceListenerServiceConnector(AssociationInfo a, Context context,
            Handler handler) {
        return mBoundServices.forUser(a.getUserId()).computeIfAbsent(
                a.getPackageName(),
                pkg -> createDeviceListenerServiceConnector(a, context, handler));
    }

    private List<BoundService> createDeviceListenerServiceConnector(AssociationInfo 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 -1;
                        }

                        @Override
                        public void binderDied() {
                            super.binderDied();
                            if (a.isSelfManaged()) {
                                mBoundServices.forUser(a.getUserId()).remove(a.getPackageName());
                                mService.mPresentSelfManagedDevices.remove(a.getId());
                            } else {
                                // 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,
            AssociationInfo 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.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService "
                    + "to be bound when declare more than one CompanionDeviceService 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;
        }
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.GET_PERMISSIONS;

import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
import static com.android.server.companion.CompanionDeviceManagerService.TAG;

import android.Manifest;
import android.annotation.NonNull;
@@ -96,7 +96,7 @@ final class PackageUtils {
            final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE
                    .equals(resolveInfo.serviceInfo.permission);
            if (!requiresPermission) {
                Slog.w(LOG_TAG, "CompanionDeviceService "
                Slog.w(TAG, "CompanionDeviceService "
                        + service.getComponentName().flattenToShortString() + " must require "
                        + "android.permission.BIND_COMPANION_DEVICE_SERVICE");
                continue;
Loading