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

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

Merge changes Ie0400e06,Icac9c6b3

* changes:
  [2/X] Introduce CDM PackageUtils
  [1/X] Introduce CompanionDeviceServiceConnector
parents 47dfd08b adfc1e06
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVER
import static android.content.ComponentName.createRelative;

import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
import static com.android.server.companion.RolesUtils.isRoleHolder;

@@ -31,6 +31,7 @@ import static java.util.Objects.requireNonNull;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
@@ -102,8 +103,9 @@ import java.util.Set;
 * @see #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback,
 * ResultReceiver, MacAddress)
 */
@SuppressLint("LongLogTag")
class AssociationRequestsProcessor {
    private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor";
    private static final String TAG = "CompanionDevice_AssociationRequestsProcessor";

    private static final ComponentName ASSOCIATION_REQUEST_APPROVAL_ACTIVITY =
            createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceActivity");
@@ -161,7 +163,7 @@ class AssociationRequestsProcessor {

        // 1. Enforce permissions and other requirements.
        enforcePermissionsForAssociation(mContext, request, packageUid);
        mService.checkUsesFeature(packageName, userId);
        enforceUsesCompanionDeviceFeature(mContext, userId, packageName);

        // 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER
        // to perform discovery NOR to collect user consent).
+119 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.content.Context.BIND_IMPORTANT;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
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.os.IBinder;
import android.util.Log;

import com.android.internal.infra.ServiceConnector;

/**
 * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the
 * application process.
 */
@SuppressLint("LongLogTag")
class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
    private static final String TAG = "CompanionDevice_ServiceConnector";
    private static final boolean DEBUG = false;
    private static final int BINDING_FLAGS = BIND_IMPORTANT;

    /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector}  */
    interface Listener {
        void onBindingDied(@UserIdInt int userId, @NonNull String packageName);
    }

    private final @UserIdInt int mUserId;
    private final @NonNull ComponentName mComponentName;
    private @Nullable Listener mListener;

    CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId,
            @NonNull ComponentName componentName) {
        super(context, buildIntent(componentName), BINDING_FLAGS, userId, null);
        mUserId = userId;
        mComponentName = componentName;
    }

    void setListener(@Nullable Listener listener) {
        mListener = listener;
    }

    void postOnDeviceAppeared(@NonNull AssociationInfo associationInfo) {
        post(companionService -> companionService.onDeviceAppeared(associationInfo));
    }

    void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
        post(companionService -> companionService.onDeviceDisappeared(associationInfo));
    }

    /**
     * Post "unbind" job, which will run *after* all previously posted jobs complete.
     *
     * IMPORTANT: use this method instead of invoking {@link ServiceConnector#unbind()} directly,
     * because the latter may cause previously posted callback, such as
     * {@link ICompanionDeviceService#onDeviceDisappeared(AssociationInfo)} to be dropped.
     */
    void postUnbind() {
        post(it -> unbind());
    }

    @Override
    protected void onServiceConnectionStatusChanged(
            @NonNull ICompanionDeviceService service, boolean isConnected) {
        if (DEBUG) {
            Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString()
                    + " connected=" + isConnected);
        }
    }

    @Override
    public void onBindingDied(@NonNull ComponentName name) {
        // IMPORTANT: call super!
        super.onBindingDied(name);

        if (DEBUG) Log.d(TAG, "onBindingDied() " + mComponentName.toShortString());

        mListener.onBindingDied(mUserId, mComponentName.getPackageName());
    }

    @Override
    protected ICompanionDeviceService binderAsInterface(@NonNull IBinder service) {
        return ICompanionDeviceService.Stub.asInterface(service);
    }

    @Override
    protected long getAutoDisconnectTimeoutMs() {
        // Do NOT auto-disconnect.
        return -1;
    }

    private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) {
        return new Intent(CompanionDeviceService.SERVICE_INTERFACE)
                .setComponent(componentName);
    }
}
+126 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
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 android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.companion.CompanionDeviceService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.PackageManager.ResolveInfoFlags;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.util.Slog;

import com.android.internal.util.ArrayUtils;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Utility methods for working with {@link PackageInfo}-s.
 */
final class PackageUtils {
    private static final Intent COMPANION_SERVICE_INTENT =
            new Intent(CompanionDeviceService.SERVICE_INTERFACE);
    private static final String META_DATA_KEY_PRIMARY = "primary";

    static @Nullable PackageInfo getPackageInfo(@NonNull Context context,
            @UserIdInt int userId, @NonNull String packageName) {
        final PackageManager pm = context.getPackageManager();
        final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS);
        return Binder.withCleanCallingIdentity(() ->
                pm.getPackageInfoAsUser(packageName, flags , userId));
    }

    static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
            @UserIdInt int userId, @NonNull String packageName) {
        final boolean requested = ArrayUtils.contains(
                getPackageInfo(context, userId, packageName).reqFeatures,
                FEATURE_COMPANION_DEVICE_SETUP);

        if (requested) {
            throw new IllegalStateException("Must declare uses-feature "
                    + FEATURE_COMPANION_DEVICE_SETUP
                    + " in manifest to use this API");
        }
    }

    /**
     * @return list of {@link CompanionDeviceService}-s per package for a given user.
     *         Services marked as "primary" would always appear at the head of the lists, *before*
     *         all non-primary services.
     */
    static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser(
            @NonNull Context context, @UserIdInt int userId) {
        final PackageManager pm = context.getPackageManager();
        final ResolveInfoFlags flags = ResolveInfoFlags.of(GET_META_DATA);
        final List<ResolveInfo> companionServices =
                pm.queryIntentServicesAsUser(COMPANION_SERVICE_INTENT, flags, userId);

        final Map<String, List<ComponentName>> packageNameToServiceInfoList = new HashMap<>();

        for (ResolveInfo resolveInfo : companionServices) {
            final ServiceInfo service = resolveInfo.serviceInfo;

            final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE
                    .equals(resolveInfo.serviceInfo.permission);
            if (!requiresPermission) {
                Slog.w(LOG_TAG, "CompanionDeviceService "
                        + service.getComponentName().flattenToShortString() + " must require "
                        + "android.permission.BIND_COMPANION_DEVICE_SERVICE");
                continue;
            }

            // Use LinkedList, because we'll need to prepend "primary" services, while appending the
            // other (non-primary) services to the list.
            final LinkedList<ComponentName> services =
                    (LinkedList<ComponentName>) packageNameToServiceInfoList.computeIfAbsent(
                            service.packageName, it -> new LinkedList<>());

            final ComponentName componentName = service.getComponentName();
            if (isPrimaryCompanionDeviceService(service)) {
                // "Primary" service should be at the head of the list.
                services.addFirst(componentName);
            } else {
                services.addLast(componentName);
            }
        }

        return packageNameToServiceInfoList;
    }

    private static boolean isPrimaryCompanionDeviceService(ServiceInfo service) {
        return service.metaData != null && service.metaData.getBoolean(META_DATA_KEY_PRIMARY);
    }
}