From 927893a700704af415d2294e7526310aa5a9e9c9 Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Fri, 5 Nov 2021 15:58:03 -0700 Subject: [PATCH] Add association ID parameter to createVirtualDevice Bug: 194949534 Test: Manual, CTS to be added in b/204606917 Change-Id: I10327e650e87e0e1fa75c61082a8e4f0364031c1 --- .../companion/virtual/IVirtualDevice.aidl | 11 ++ .../virtual/IVirtualDeviceManager.aidl | 11 +- .../virtual/VirtualDeviceManager.java | 12 +- .../companion/virtual/PermissionUtils.java | 54 +++++++ .../virtual/VirtualDeviceManagerService.java | 144 ++++++++++++++++-- 5 files changed, 213 insertions(+), 19 deletions(-) create mode 100644 services/companion/java/com/android/server/companion/virtual/PermissionUtils.java diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 0aa442bfc5035..dabc603bc47fc 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -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(); } diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index 91e717d719a99..2dfa2021fdfe0 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -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); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 6187de57a2169..590b10887c7fa 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -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(); diff --git a/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java b/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java new file mode 100644 index 0000000000000..c397ea2997a4f --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java @@ -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; + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 18cf6f874ab14..58d08013eeffa 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -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 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 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> 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 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 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); } } }