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

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

Merge "Add association ID parameter to createVirtualDevice"

parents 5141dbec 927893a7
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);
                }
            }
        }