Fix secondary user test for VirtualDevice
- Clear binder identity before calling getPackageUid
- VirtualDeviceImpl uses the owner's user ID to create its Context
- For virtual camera, create a different CameraAccessController for each
user
Fixes: 218790107
Test: (On secondary user) atest CtsVirtualDevicesTestCases
Change-Id: I63027ab58854f11faa4e2b20da202886dd10d2f3
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index 2c42c91..adc8459 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -33,17 +33,19 @@
/**
* Handles blocking access to the camera for apps running on virtual devices.
*/
-class CameraAccessController extends CameraManager.AvailabilityCallback {
+class CameraAccessController extends CameraManager.AvailabilityCallback implements AutoCloseable {
private static final String TAG = "CameraAccessController";
private final Object mLock = new Object();
private final Context mContext;
- private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
- CameraAccessBlockedCallback mBlockedCallback;
- private CameraManager mCameraManager;
- private boolean mListeningForCameraEvents;
- private PackageManager mPackageManager;
+ private final VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
+ private final CameraAccessBlockedCallback mBlockedCallback;
+ private final CameraManager mCameraManager;
+ private final PackageManager mPackageManager;
+
+ @GuardedBy("mLock")
+ private int mObserverCount = 0;
@GuardedBy("mLock")
private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
@@ -77,23 +79,38 @@
*/
public void startObservingIfNeeded() {
synchronized (mLock) {
- if (!mListeningForCameraEvents) {
+ if (mObserverCount == 0) {
mCameraManager.registerAvailabilityCallback(mContext.getMainExecutor(), this);
- mListeningForCameraEvents = true;
}
+ mObserverCount++;
}
}
/**
* Stop watching for camera access.
*/
- public void stopObserving() {
+ public void stopObservingIfNeeded() {
synchronized (mLock) {
- mCameraManager.unregisterAvailabilityCallback(this);
- mListeningForCameraEvents = false;
+ mObserverCount--;
+ if (mObserverCount <= 0) {
+ close();
+ }
}
}
+
+ @Override
+ public void close() {
+ synchronized (mLock) {
+ if (mObserverCount < 0) {
+ Slog.wtf(TAG, "Unexpected negative mObserverCount: " + mObserverCount);
+ } else if (mObserverCount > 0) {
+ Slog.w(TAG, "Unexpected close with observers remaining: " + mObserverCount);
+ }
+ }
+ mCameraManager.unregisterAvailabilityCallback(this);
+ }
+
@Override
public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
synchronized (mLock) {
diff --git a/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java b/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java
index c397ea2..77b880f 100644
--- a/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java
+++ b/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java
@@ -18,6 +18,8 @@
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.UserHandle;
import android.util.Slog;
/**
@@ -32,13 +34,15 @@
*
* @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
+ * @return {@code true} if the package name matches {@link Binder#getCallingUid()}, or
+ * {@code false} otherwise
*/
- public static boolean validatePackageName(Context context, String callingPackage,
- int callingUid) {
+ public static boolean validateCallingPackageName(Context context, String callingPackage) {
+ final int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
try {
- int packageUid = context.getPackageManager().getPackageUid(callingPackage, 0);
+ int packageUid = context.getPackageManager()
+ .getPackageUidAsUser(callingPackage, UserHandle.getUserId(callingUid));
if (packageUid != callingUid) {
Slog.e(LOG_TAG, "validatePackageName: App with package name " + callingPackage
+ " is UID " + packageUid + " but caller is " + callingUid);
@@ -48,6 +52,8 @@
Slog.e(LOG_TAG, "validatePackageName: App with package name " + callingPackage
+ " does not exist");
return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
return true;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index c0a904f..e248f67 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -140,7 +140,8 @@
int ownerUid, InputController inputController, OnDeviceCloseListener listener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener, VirtualDeviceParams params) {
- mContext = context;
+ UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
+ mContext = context.createContextAsUser(ownerUserHandle, 0);
mAssociationInfo = associationInfo;
mPendingTrampolineCallback = pendingTrampolineCallback;
mActivityListener = activityListener;
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 c7d8daa..9f252d7 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -36,6 +36,7 @@
import android.os.Looper;
import android.os.Parcel;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.ExceptionUtils;
import android.util.Slog;
import android.util.SparseArray;
@@ -66,7 +67,12 @@
private VirtualDeviceManagerInternal mLocalService;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
- private final CameraAccessController mCameraAccessController;
+ /**
+ * Mapping from user IDs to CameraAccessControllers.
+ */
+ @GuardedBy("mVirtualDeviceManagerLock")
+ private final SparseArray<CameraAccessController> mCameraAccessControllers =
+ new SparseArray<>();
/**
* Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
@@ -94,8 +100,6 @@
super(context);
mImpl = new VirtualDeviceManagerImpl();
mLocalService = new LocalService();
- mCameraAccessController = new CameraAccessController(getContext(), mLocalService,
- this::onCameraAccessBlocked);
}
private final ActivityInterceptorCallback mActivityInterceptorCallback =
@@ -144,16 +148,19 @@
@Override
public void onUserStarting(@NonNull TargetUser user) {
super.onUserStarting(user);
+ Context userContext = getContext().createContextAsUser(user.getUserHandle(), 0);
synchronized (mVirtualDeviceManagerLock) {
- final CompanionDeviceManager cdm = getContext()
- .createContextAsUser(user.getUserHandle(), 0)
- .getSystemService(CompanionDeviceManager.class);
+ final CompanionDeviceManager cdm =
+ userContext.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);
+ CameraAccessController cameraAccessController = new CameraAccessController(
+ userContext, mLocalService, this::onCameraAccessBlocked);
+ mCameraAccessControllers.put(user.getUserIdentifier(), cameraAccessController);
}
}
@@ -171,6 +178,14 @@
cdm.removeOnAssociationsChangedListener(listener);
mOnAssociationsChangedListeners.remove(userId);
}
+ CameraAccessController cameraAccessController = mCameraAccessControllers.get(
+ user.getUserIdentifier());
+ if (cameraAccessController != null) {
+ cameraAccessController.close();
+ mCameraAccessControllers.remove(user.getUserIdentifier());
+ } else {
+ Slog.w(TAG, "Cannot unregister cameraAccessController for user " + user);
+ }
}
}
@@ -198,7 +213,7 @@
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"createVirtualDevice");
final int callingUid = getCallingUid();
- if (!PermissionUtils.validatePackageName(getContext(), packageName, callingUid)) {
+ if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
throw new SecurityException(
"Package name " + packageName + " does not belong to calling uid "
+ callingUid);
@@ -213,6 +228,9 @@
"Virtual device for association ID " + associationId
+ " already exists");
}
+ final int userId = UserHandle.getUserId(callingUid);
+ final CameraAccessController cameraAccessController =
+ mCameraAccessControllers.get(userId);
VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
associationInfo, token, callingUid,
new VirtualDeviceImpl.OnDeviceCloseListener() {
@@ -220,14 +238,21 @@
public void onClose(int associationId) {
synchronized (mVirtualDeviceManagerLock) {
mVirtualDevices.remove(associationId);
- if (mVirtualDevices.size() == 0) {
- mCameraAccessController.stopObserving();
+ if (cameraAccessController != null) {
+ cameraAccessController.stopObservingIfNeeded();
+ } else {
+ Slog.w(TAG, "cameraAccessController not found for user "
+ + userId);
}
}
}
},
this, activityListener, params);
- mCameraAccessController.startObservingIfNeeded();
+ if (cameraAccessController != null) {
+ cameraAccessController.startObservingIfNeeded();
+ } else {
+ Slog.w(TAG, "cameraAccessController not found for user " + userId);
+ }
mVirtualDevices.put(associationInfo.getId(), virtualDevice);
return virtualDevice;
}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 53cab9e..a6194df 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -98,6 +98,8 @@
<uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK"/>
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+ <uses-permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY" />
+ <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" />
<uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 9aac81c..7b921ab 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -58,6 +58,7 @@
import android.os.IPowerManager;
import android.os.IThermalService;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
@@ -136,6 +137,7 @@
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ doReturn(mContext).when(mContext).createContextAsUser(eq(Process.myUserHandle()), anyInt());
doNothing().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(