Add association ID parameter to createVirtualDevice

Bug: 194949534
Test: Manual, CTS to be added in b/204606917
Change-Id: I10327e650e87e0e1fa75c61082a8e4f0364031c1
This commit is contained in:
Maurice Lam
2021-11-05 15:58:03 -07:00
parent 2b60cd04ee
commit 927893a700
5 changed files with 213 additions and 19 deletions

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -0,0 +1,54 @@
/*
* 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;
}
}

View File

@@ -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());
}
return virtualDevice;
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 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);
}
}
}