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

Commit 927893a7 authored by Maurice Lam's avatar Maurice Lam
Browse files

Add association ID parameter to createVirtualDevice

Bug: 194949534
Test: Manual, CTS to be added in b/204606917
Change-Id: I10327e650e87e0e1fa75c61082a8e4f0364031c1
parent 2b60cd04
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -22,5 +22,16 @@ package android.companion.virtual;
 * @hide
 */
interface IVirtualDevice {

    /**
     * Returns the association ID for this virtual device.
     *
     * @see AssociationInfo#getId()
     */
    int getAssociationId();

    /**
     * Closes the virtual device and frees all associated resources.
     */
    void close();
}
+10 −1
Original line number Diff line number Diff line
@@ -25,5 +25,14 @@ import android.companion.virtual.IVirtualDevice;
 */
interface IVirtualDeviceManager {

    IVirtualDevice createVirtualDevice();
    /**
     * Creates a virtual device that can be used to create virtual displays and stream contents.
     *
     * @param token The binder token created by the caller of this API.
     * @param packageName The package name of the caller. Implementation of this method must verify
     *   that this belongs to the calling UID.
     * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from
     *   CDM. Virtual devices must have a corresponding association with CDM in order to be created.
     */
    IVirtualDevice createVirtualDevice(in IBinder token, String packageName, int associationId);
}
+9 −3
Original line number Diff line number Diff line
@@ -21,7 +21,9 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.companion.AssociationInfo;
import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;

/**
@@ -49,14 +51,18 @@ public final class VirtualDeviceManager {
    /**
     * Creates a virtual device.
     *
     * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from
     *   Companion Device Manager. Virtual devices must have a corresponding association with CDM in
     *   order to be created.
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
    @Nullable
    public VirtualDevice createVirtualDevice() {
        // TODO(b/194949534): Add CDM association ID here and unhide this API
    public VirtualDevice createVirtualDevice(int associationId) {
        // TODO(b/194949534): Unhide this API
        try {
            IVirtualDevice virtualDevice = mService.createVirtualDevice();
            IVirtualDevice virtualDevice = mService.createVirtualDevice(
                    new Binder(), mContext.getPackageName(), associationId);
            return new VirtualDevice(mContext, virtualDevice);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
+54 −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.virtual;

import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Slog;

/**
 * Utility methods for checking permissions required for VirtualDeviceManager operations.
 */
class PermissionUtils {

    private static final String LOG_TAG = "VDM.PermissionUtils";

    /**
     * Verifies whether the calling package name matches the calling app uid.
     *
     * @param context the context
     * @param callingPackage the calling application package name
     * @param callingUid the calling application uid
     * @return {@code true} if the package name matches the calling app uid, {@code false} otherwise
     */
    public static boolean validatePackageName(Context context, String callingPackage,
            int callingUid) {
        try {
            int packageUid = context.getPackageManager().getPackageUid(callingPackage, 0);
            if (packageUid != callingUid) {
                Slog.e(LOG_TAG, "validatePackageName: App with package name " + callingPackage
                        + " is UID " + packageUid + " but caller is " + callingUid);
                return false;
            }
        } catch (PackageManager.NameNotFoundException e) {
            Slog.e(LOG_TAG, "validatePackageName: App with package name " + callingPackage
                    + " does not exist");
            return false;
        }
        return true;
    }
}
+129 −15
Original line number Diff line number Diff line
@@ -19,13 +19,18 @@ package com.android.server.companion.virtual;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceManager;
import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceManager;
import android.content.Context;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.ExceptionUtils;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
@@ -33,7 +38,8 @@ import com.android.server.SystemService;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;


/** @hide */
@@ -42,9 +48,30 @@ public class VirtualDeviceManagerService extends SystemService {

    private static final boolean DEBUG = false;
    private static final String LOG_TAG = "VirtualDeviceManagerService";
    private final Object mVirtualDeviceManagerLock = new Object();
    private final VirtualDeviceManagerImpl mImpl;
    @GuardedBy("mVirtualDevices")
    private final ArrayList<VirtualDeviceImpl> mVirtualDevices = new ArrayList<>();

    /**
     * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
     * each CDM associated device.
     */
    @GuardedBy("mVirtualDeviceManagerLock")
    private final SparseArray<VirtualDeviceImpl> mVirtualDevices = new SparseArray<>();

    /**
     * Mapping from user ID to CDM associations. The associations come from
     * {@link CompanionDeviceManager#getAllAssociations()}, which contains associations across all
     * packages.
     */
    private final ConcurrentHashMap<Integer, List<AssociationInfo>> mAllAssociations =
            new ConcurrentHashMap<>();

    /**
     * Mapping from user ID to its change listener. The listeners are added when the user is
     * started and removed when the user stops.
     */
    private final SparseArray<OnAssociationsChangedListener> mOnAssociationsChangedListeners =
            new SparseArray<>();

    public VirtualDeviceManagerService(Context context) {
        super(context);
@@ -56,30 +83,116 @@ public class VirtualDeviceManagerService extends SystemService {
        publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
    }

    private class VirtualDeviceImpl extends IVirtualDevice.Stub {
    @Override
    public void onUserStarting(@NonNull TargetUser user) {
        super.onUserStarting(user);
        synchronized (mVirtualDeviceManagerLock) {
            final CompanionDeviceManager cdm = getContext()
                    .createContextAsUser(user.getUserHandle(), 0)
                    .getSystemService(CompanionDeviceManager.class);
            final int userId = user.getUserIdentifier();
            mAllAssociations.put(userId, cdm.getAllAssociations());
            OnAssociationsChangedListener listener =
                    associations -> mAllAssociations.put(userId, associations);
            mOnAssociationsChangedListeners.put(userId, listener);
            cdm.addOnAssociationsChangedListener(Runnable::run, listener);
        }
    }

        private VirtualDeviceImpl() {}
    @Override
    public void onUserStopping(@NonNull TargetUser user) {
        super.onUserStopping(user);
        synchronized (mVirtualDeviceManagerLock) {
            int userId = user.getUserIdentifier();
            mAllAssociations.remove(userId);
            final CompanionDeviceManager cdm = getContext().createContextAsUser(
                    user.getUserHandle(), 0)
                    .getSystemService(CompanionDeviceManager.class);
            OnAssociationsChangedListener listener = mOnAssociationsChangedListeners.get(userId);
            if (listener != null) {
                cdm.removeOnAssociationsChangedListener(listener);
                mOnAssociationsChangedListeners.remove(userId);
            }
        }
    }

    private class VirtualDeviceImpl extends IVirtualDevice.Stub implements IBinder.DeathRecipient {

        private final AssociationInfo mAssociationInfo;

        private VirtualDeviceImpl(IBinder token, AssociationInfo associationInfo) {
            mAssociationInfo = associationInfo;
            try {
                token.linkToDeath(this, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            mVirtualDevices.put(associationInfo.getId(), this);
        }

        @Override
        public int getAssociationId() {
            return mAssociationInfo.getId();
        }

        @Override
        public void close() {
            synchronized (mVirtualDevices) {
                mVirtualDevices.remove(this);
            synchronized (mVirtualDeviceManagerLock) {
                mVirtualDevices.remove(mAssociationInfo.getId());
            }
        }

        @Override
        public void binderDied() {
            close();
        }
    }

    class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {

        @Override
        public IVirtualDevice createVirtualDevice() {
        public IVirtualDevice createVirtualDevice(
                IBinder token, String packageName, int associationId) {
            getContext().enforceCallingOrSelfPermission(
                    android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                    "createVirtualDevice");
            VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl();
            synchronized (mVirtualDevices) {
                mVirtualDevices.add(virtualDevice);
            if (!PermissionUtils.validatePackageName(getContext(), packageName, getCallingUid())) {
                throw new SecurityException(
                        "Package name " + packageName + " does not belong to calling uid "
                                + getCallingUid());
            }
            AssociationInfo associationInfo = getAssociationInfo(packageName, associationId);
            if (associationInfo == null) {
                throw new IllegalArgumentException("No association with ID " + associationId);
            }
            synchronized (mVirtualDeviceManagerLock) {
                if (mVirtualDevices.contains(associationId)) {
                    throw new IllegalStateException(
                            "Virtual device for association ID " + associationId
                                    + " already exists");
                }
                return new VirtualDeviceImpl(token, associationInfo);
            }
        }

        @Nullable
        private AssociationInfo getAssociationInfo(String packageName, int associationId) {
            final int callingUserId = getCallingUserHandle().getIdentifier();
            final List<AssociationInfo> associations =
                    mAllAssociations.get(callingUserId);
            if (associations != null) {
                final int associationSize = associations.size();
                for (int i = 0; i < associationSize; i++) {
                    AssociationInfo associationInfo = associations.get(i);
                    if (associationInfo.belongsToPackage(callingUserId, packageName)
                            && associationId == associationInfo.getId()) {
                        return associationInfo;
                    }
                }
            } else {
                Slog.w(LOG_TAG, "No associations for user " + callingUserId);
            }
            return virtualDevice;
            return null;
        }

        @Override
@@ -101,9 +214,10 @@ public class VirtualDeviceManagerService extends SystemService {
                return;
            }
            fout.println("Created virtual devices: ");
            synchronized (mVirtualDevices) {
                for (VirtualDeviceImpl virtualDevice : mVirtualDevices) {
                    fout.println(virtualDevice.toString());
            synchronized (mVirtualDeviceManagerLock) {
                for (int i = 0; i < mVirtualDevices.size(); i++) {
                    VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
                    fout.printf("%d: %s\n", mVirtualDevices.keyAt(i), virtualDevice);
                }
            }
        }