Loading core/java/android/companion/virtual/IVirtualDevice.aidl +11 −0 Original line number Diff line number Diff line Loading @@ -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(); } core/java/android/companion/virtual/IVirtualDeviceManager.aidl +10 −1 Original line number Diff line number Diff line Loading @@ -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); } core/java/android/companion/virtual/VirtualDeviceManager.java +9 −3 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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(); Loading services/companion/java/com/android/server/companion/virtual/PermissionUtils.java 0 → 100644 +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; } } services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +129 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 */ Loading @@ -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); Loading @@ -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 Loading @@ -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); } } } Loading Loading
core/java/android/companion/virtual/IVirtualDevice.aidl +11 −0 Original line number Diff line number Diff line Loading @@ -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(); }
core/java/android/companion/virtual/IVirtualDeviceManager.aidl +10 −1 Original line number Diff line number Diff line Loading @@ -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); }
core/java/android/companion/virtual/VirtualDeviceManager.java +9 −3 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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(); Loading
services/companion/java/com/android/server/companion/virtual/PermissionUtils.java 0 → 100644 +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; } }
services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +129 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 */ Loading @@ -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); Loading @@ -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 Loading @@ -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); } } } Loading