Small changes in VDMS w.r.t. caching and camera controller.

Only get the CDM associations on demand when creating a virtual device,
instead of caching them whenever a user is starting. The asscociations
are only used during VD creation, so it's unnecessary to cache them.

Remove the camera access controller maps from VDMS, and keep the
controllers in VirtualDeviceImpl instead, to be a bit more consistent
with input and sensor handling. Since the camera access controllers
are per user, provide a mechanism to reuse them from existing VDs and
only create a new one if no VD exists for the current user. They keep
track of the current observers, and automatically close when it is 0.

A couple of other very minor clean-ups as well.

Bug: 263231559
Test: atest CameraAccessControllerTest
Test: atest VirtualDeviceManagerServiceTest
Test: atest android.virtualdevice.cts

Change-Id: I3b007fc75ea8e36d826b9034f7574bc4e3207293
This commit is contained in:
Vladimir Komsiyski
2022-12-14 15:40:34 +01:00
parent 0288fd630a
commit 3934796dce
5 changed files with 140 additions and 141 deletions

View File

@@ -19,6 +19,7 @@ package com.android.server.companion.virtual;
import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -94,6 +95,23 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen
mUserManager = mContext.getSystemService(UserManager.class);
}
/**
* Returns the userId for which the camera access should be blocked.
*/
@UserIdInt
public int getUserId() {
return mContext.getUserId();
}
/**
* Returns the number of observers currently relying on this controller.
*/
public int getObserverCount() {
synchronized (mLock) {
return mObserverCount;
}
}
/**
* Starts watching for camera access by uids running on a virtual device, if we were not
* already doing so.

View File

@@ -110,6 +110,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private final int mDeviceId;
private final InputController mInputController;
private final SensorController mSensorController;
private final CameraAccessController mCameraAccessController;
private VirtualAudioController mVirtualAudioController;
@VisibleForTesting
final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
@@ -165,6 +166,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
IBinder token,
int ownerUid,
int deviceId,
CameraAccessController cameraAccessController,
OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
@@ -178,6 +180,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
deviceId,
/* inputController= */ null,
/* sensorController= */ null,
cameraAccessController,
onDeviceCloseListener,
pendingTrampolineCallback,
activityListener,
@@ -194,6 +197,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
int deviceId,
InputController inputController,
SensorController sensorController,
CameraAccessController cameraAccessController,
OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
@@ -223,6 +227,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
} else {
mSensorController = sensorController;
}
mCameraAccessController = cameraAccessController;
mCameraAccessController.startObservingIfNeeded();
mOnDeviceCloseListener = onDeviceCloseListener;
try {
token.linkToDeath(this, 0);
@@ -243,6 +249,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return flags;
}
/** Returns the camera access controller of this device. */
CameraAccessController getCameraAccessController() {
return mCameraAccessController;
}
/** Returns the device display name. */
CharSequence getDisplayName() {
return mAssociationInfo.getDisplayName();
@@ -359,6 +370,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
mOnDeviceCloseListener.onClose(mDeviceId);
mAppToken.unlinkToDeath(this, 0);
mCameraAccessController.stopObservingIfNeeded();
final long ident = Binder.clearCallingIdentity();
try {
@@ -376,6 +388,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Override
public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
mCameraAccessController.blockCameraAccessIfNeeded(runningUids);
mRunningAppsChangedCallback.accept(runningUids);
}

View File

@@ -27,7 +27,6 @@ import android.annotation.SuppressLint;
import android.app.ActivityOptions;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceManager;
import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceManager;
@@ -70,6 +69,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@SuppressLint("LongLogTag")
@@ -86,20 +86,6 @@ public class VirtualDeviceManagerService extends SystemService {
private static AtomicInteger sNextUniqueIndex = new AtomicInteger(
VirtualDeviceManager.DEVICE_ID_DEFAULT + 1);
/**
* Mapping from user IDs to CameraAccessControllers.
*/
@GuardedBy("mVirtualDeviceManagerLock")
private final SparseArray<CameraAccessController> mCameraAccessControllers =
new SparseArray<>();
/**
* Mapping from device IDs to CameraAccessControllers.
*/
@GuardedBy("mVirtualDeviceManagerLock")
private final SparseArray<CameraAccessController> mCameraAccessControllersByDeviceId =
new SparseArray<>();
/**
* Mapping from device IDs to virtual devices.
*/
@@ -112,21 +98,6 @@ public class VirtualDeviceManagerService extends SystemService {
@GuardedBy("mVirtualDeviceManagerLock")
private final SparseArray<ArraySet<Integer>> mAppsOnVirtualDevices = 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);
mImpl = new VirtualDeviceManagerImpl();
@@ -177,54 +148,9 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
@Override
public void onUserStarting(@NonNull TargetUser user) {
super.onUserStarting(user);
Context userContext = getContext().createContextAsUser(user.getUserHandle(), 0);
synchronized (mVirtualDeviceManagerLock) {
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);
}
}
@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);
}
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);
}
}
}
void onCameraAccessBlocked(int appUid) {
synchronized (mVirtualDeviceManagerLock) {
int size = mVirtualDevices.size();
for (int i = 0; i < size; i++) {
for (int i = 0; i < mVirtualDevices.size(); i++) {
CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName();
mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
getContext().getString(
@@ -235,6 +161,21 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
CameraAccessController getCameraAccessController(UserHandle userHandle) {
int userId = userHandle.getIdentifier();
synchronized (mVirtualDeviceManagerLock) {
for (int i = 0; i < mVirtualDevices.size(); i++) {
final CameraAccessController cameraAccessController =
mVirtualDevices.valueAt(i).getCameraAccessController();
if (cameraAccessController.getUserId() == userId) {
return cameraAccessController;
}
}
}
Context userContext = getContext().createContextAsUser(userHandle, 0);
return new CameraAccessController(userContext, mLocalService, this::onCameraAccessBlocked);
}
@VisibleForTesting
VirtualDeviceManagerInternal getLocalServiceInstance() {
return mLocalService;
@@ -255,8 +196,34 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
VirtualDeviceImpl.PendingTrampolineCallback {
@VisibleForTesting
void removeVirtualDevice(int deviceId) {
synchronized (mVirtualDeviceManagerLock) {
mAppsOnVirtualDevices.remove(deviceId);
mVirtualDevices.remove(deviceId);
}
}
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback =
new VirtualDeviceImpl.PendingTrampolineCallback() {
@Override
public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
PendingTrampoline existing = mPendingTrampolines.put(
pendingTrampoline.mPendingIntent.getCreatorPackage(),
pendingTrampoline);
if (existing != null) {
existing.mResultReceiver.send(
VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
}
}
@Override
public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
}
};
@Override // Binder call
public IVirtualDevice createVirtualDevice(
@@ -279,25 +246,16 @@ public class VirtualDeviceManagerService extends SystemService {
throw new IllegalArgumentException("No association with ID " + associationId);
}
synchronized (mVirtualDeviceManagerLock) {
final int userId = UserHandle.getUserId(callingUid);
final UserHandle userHandle = getCallingUserHandle();
final CameraAccessController cameraAccessController =
mCameraAccessControllers.get(userId);
getCameraAccessController(userHandle);
final int deviceId = sNextUniqueIndex.getAndIncrement();
final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
associationInfo, token, callingUid, deviceId,
/* onDeviceCloseListener= */ this::onDeviceClosed,
this, activityListener,
runningUids -> {
cameraAccessController.blockCameraAccessIfNeeded(runningUids);
notifyRunningAppsChanged(deviceId, runningUids);
},
params);
if (cameraAccessController != null) {
cameraAccessController.startObservingIfNeeded();
mCameraAccessControllersByDeviceId.put(deviceId, cameraAccessController);
} else {
Slog.w(TAG, "cameraAccessController not found for user " + userId);
}
associationInfo, token, callingUid, deviceId, cameraAccessController,
this::onDeviceClosed, mPendingTrampolineCallback, activityListener,
runningAppsChangedCallback, params);
mVirtualDevices.put(deviceId, virtualDevice);
return virtualDevice;
}
@@ -409,8 +367,18 @@ public class VirtualDeviceManagerService extends SystemService {
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
final int callingUserId = getCallingUserHandle().getIdentifier();
final List<AssociationInfo> associations = mAllAssociations.get(callingUserId);
final UserHandle userHandle = getCallingUserHandle();
final CompanionDeviceManager cdm =
getContext().createContextAsUser(userHandle, 0)
.getSystemService(CompanionDeviceManager.class);
List<AssociationInfo> associations;
final long identity = Binder.clearCallingIdentity();
try {
associations = cdm.getAllAssociations();
} finally {
Binder.restoreCallingIdentity(identity);
}
final int callingUserId = userHandle.getIdentifier();
if (associations != null) {
final int associationSize = associations.size();
for (int i = 0; i < associationSize; i++) {
@@ -427,25 +395,15 @@ public class VirtualDeviceManagerService extends SystemService {
}
private void onDeviceClosed(int deviceId) {
synchronized (mVirtualDeviceManagerLock) {
mVirtualDevices.remove(deviceId);
Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
final long identity = Binder.clearCallingIdentity();
try {
getContext().sendBroadcastAsUser(i, UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(identity);
}
mAppsOnVirtualDevices.remove(deviceId);
final CameraAccessController cameraAccessController =
mCameraAccessControllersByDeviceId.removeReturnOld(deviceId);
if (cameraAccessController != null) {
cameraAccessController.stopObservingIfNeeded();
} else {
Slog.w(TAG, "cameraAccessController not found for device Id " + deviceId);
}
removeVirtualDevice(deviceId);
Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
final long identity = Binder.clearCallingIdentity();
try {
getContext().sendBroadcastAsUser(i, UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@@ -474,22 +432,6 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
}
@Override
public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
PendingTrampoline existing = mPendingTrampolines.put(
pendingTrampoline.mPendingIntent.getCreatorPackage(),
pendingTrampoline);
if (existing != null) {
existing.mResultReceiver.send(
VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
}
}
@Override
public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
}
}
private final class LocalService extends VirtualDeviceManagerInternal {

View File

@@ -18,6 +18,8 @@ package com.android.server.companion.virtual;
import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -115,6 +117,11 @@ public class CameraAccessControllerTest {
mController.startObservingIfNeeded();
}
@Test
public void getUserId_returnsCorrectId() {
assertThat(mController.getUserId()).isEqualTo(mContext.getUserId());
}
@Test
public void onCameraOpened_uidNotRunning_noCameraBlocking() throws CameraAccessException {
when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(

View File

@@ -203,6 +203,7 @@ public class VirtualDeviceManagerServiceTest {
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
private SensorController mSensorController;
private CameraAccessController mCameraAccessController;
private AssociationInfo mAssociationInfo;
private VirtualDeviceManagerService mVdms;
private VirtualDeviceManagerInternal mLocalService;
@@ -237,6 +238,8 @@ public class VirtualDeviceManagerServiceTest {
@Mock
private IAudioConfigChangedCallback mConfigChangedCallback;
@Mock
private CameraAccessController.CameraAccessBlockedCallback mCameraAccessBlockedCallback;
@Mock
private ApplicationInfo mApplicationInfoMock;
@Mock
IInputManager mIInputManagerMock;
@@ -325,6 +328,8 @@ public class VirtualDeviceManagerServiceTest {
new Handler(TestableLooper.get(this).getLooper()),
mContext.getSystemService(WindowManager.class), threadVerifier);
mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID_1);
mCameraAccessController =
new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
@@ -394,12 +399,7 @@ public class VirtualDeviceManagerServiceTest {
.setBlockedActivities(getBlockedActivities())
.setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID_1,
mInputController, mSensorController,
/* onDeviceCloseListener= */ (int deviceId) -> {},
mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(mDeviceImpl);
mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
.isEqualTo(DEVICE_POLICY_CUSTOM);
@@ -557,6 +557,21 @@ public class VirtualDeviceManagerServiceTest {
LocaleList.forLanguageTags(firstKeyboardConfig.getLanguageTag()));
}
@Test
public void cameraAccessController_observerCountUpdated() {
assertThat(mCameraAccessController.getObserverCount()).isEqualTo(1);
VirtualDeviceImpl secondDevice =
createVirtualDevice(VIRTUAL_DEVICE_ID_2, DEVICE_OWNER_UID_2);
assertThat(mCameraAccessController.getObserverCount()).isEqualTo(2);
mDeviceImpl.close();
assertThat(mCameraAccessController.getObserverCount()).isEqualTo(1);
secondDevice.close();
assertThat(mCameraAccessController.getObserverCount()).isEqualTo(0);
}
@Test
public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
@@ -1543,14 +1558,18 @@ public class VirtualDeviceManagerServiceTest {
}
private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
VirtualDeviceParams params = new VirtualDeviceParams
.Builder()
VirtualDeviceParams params = new VirtualDeviceParams.Builder()
.setBlockedActivities(getBlockedActivities())
.build();
return createVirtualDevice(virtualDeviceId, ownerUid, params);
}
private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
VirtualDeviceParams params) {
VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
mInputController, mSensorController,
/* onDeviceCloseListener= */ (int deviceId) -> {},
mInputController, mSensorController, mCameraAccessController,
/* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId),
mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(virtualDeviceImpl);
return virtualDeviceImpl;