From fa3d822f877310aa6f1b29a3dcab37632c9f6cb0 Mon Sep 17 00:00:00 2001 From: Zixuan Qu Date: Sat, 30 Jul 2022 11:26:57 +0000 Subject: [PATCH] Add listeners for the changes of App UIDs running on virtual displays. This will be used later by VibrationSettings to keep record of apps currently on virtual displays. Test: VirtualDeviceManagerServiceTest bug: 189474679 Change-Id: I120f8f8a1d3d0d8c72bf3ff42a6661147e474ccb (cherry picked from commit 0880191a03477eebf9cf78850bca00fe6def5514) --- .../virtual/VirtualDeviceManagerService.java | 73 ++++++++++++++++++- .../virtual/VirtualDeviceManagerInternal.java | 25 ++++++- .../VirtualDeviceManagerServiceTest.java | 73 +++++++++++++++++-- 3 files changed, 159 insertions(+), 12 deletions(-) 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 cdddc1dea82bd..2b644fe8f7ba6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -41,6 +41,7 @@ import android.os.Looper; import android.os.Parcel; import android.os.RemoteException; import android.os.UserHandle; +import android.util.ArraySet; import android.util.ExceptionUtils; import android.util.Slog; import android.util.SparseArray; @@ -86,6 +87,12 @@ public class VirtualDeviceManagerService extends SystemService { @GuardedBy("mVirtualDeviceManagerLock") private final SparseArray mVirtualDevices = new SparseArray<>(); + /** + * Mapping from CDM association IDs to app UIDs running on the corresponding virtual device. + */ + @GuardedBy("mVirtualDeviceManagerLock") + private final SparseArray> mAppsOnVirtualDevices = new SparseArray<>(); + /** * Mapping from user ID to CDM associations. The associations come from * {@link CompanionDeviceManager#getAllAssociations()}, which contains associations across all @@ -213,6 +220,14 @@ public class VirtualDeviceManagerService extends SystemService { return mLocalService; } + @VisibleForTesting + void notifyRunningAppsChanged(int associationId, ArraySet uids) { + synchronized (mVirtualDeviceManagerLock) { + mAppsOnVirtualDevices.put(associationId, uids); + } + mLocalService.onAppsOnVirtualDeviceChanged(); + } + class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements VirtualDeviceImpl.PendingTrampolineCallback { @@ -252,6 +267,7 @@ public class VirtualDeviceManagerService extends SystemService { public void onClose(int associationId) { synchronized (mVirtualDeviceManagerLock) { mVirtualDevices.remove(associationId); + mAppsOnVirtualDevices.remove(associationId); if (cameraAccessController != null) { cameraAccessController.stopObservingIfNeeded(); } else { @@ -262,8 +278,10 @@ public class VirtualDeviceManagerService extends SystemService { } }, this, activityListener, - runningUids -> cameraAccessController.blockCameraAccessIfNeeded( - runningUids), + runningUids -> { + cameraAccessController.blockCameraAccessIfNeeded(runningUids); + notifyRunningAppsChanged(associationInfo.getId(), runningUids); + }, params); if (cameraAccessController != null) { cameraAccessController.startObservingIfNeeded(); @@ -385,8 +403,13 @@ public class VirtualDeviceManagerService extends SystemService { private final class LocalService extends VirtualDeviceManagerInternal { @GuardedBy("mVirtualDeviceManagerLock") - private final ArrayList + private final ArrayList mVirtualDisplayListeners = new ArrayList<>(); + @GuardedBy("mVirtualDeviceManagerLock") + private final ArrayList + mAppsOnVirtualDeviceListeners = new ArrayList<>(); + @GuardedBy("mVirtualDeviceManagerLock") + private final ArraySet mAllUidsOnVirtualDevice = new ArraySet<>(); @Override public boolean isValidVirtualDevice(IVirtualDevice virtualDevice) { @@ -422,6 +445,34 @@ public class VirtualDeviceManagerService extends SystemService { }); } + @Override + public void onAppsOnVirtualDeviceChanged() { + ArraySet latestRunningUids = new ArraySet<>(); + final AppsOnVirtualDeviceListener[] listeners; + synchronized (mVirtualDeviceManagerLock) { + int size = mAppsOnVirtualDevices.size(); + for (int i = 0; i < size; i++) { + latestRunningUids.addAll(mAppsOnVirtualDevices.valueAt(i)); + } + if (!mAllUidsOnVirtualDevice.equals(latestRunningUids)) { + mAllUidsOnVirtualDevice.clear(); + mAllUidsOnVirtualDevice.addAll(latestRunningUids); + listeners = + mAppsOnVirtualDeviceListeners.toArray( + new AppsOnVirtualDeviceListener[0]); + } else { + listeners = null; + } + } + if (listeners != null) { + mHandler.post(() -> { + for (AppsOnVirtualDeviceListener listener : listeners) { + listener.onAppsOnAnyVirtualDeviceChanged(latestRunningUids); + } + }); + } + } + @Override public int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice) { return ((VirtualDeviceImpl) virtualDevice).getBaseVirtualDisplayFlags(); @@ -481,6 +532,22 @@ public class VirtualDeviceManagerService extends SystemService { mVirtualDisplayListeners.remove(listener); } } + + @Override + public void registerAppsOnVirtualDeviceListener( + @NonNull AppsOnVirtualDeviceListener listener) { + synchronized (mVirtualDeviceManagerLock) { + mAppsOnVirtualDeviceListeners.add(listener); + } + } + + @Override + public void unregisterAppsOnVirtualDeviceListener( + @NonNull AppsOnVirtualDeviceListener listener) { + synchronized (mVirtualDeviceManagerLock) { + mAppsOnVirtualDeviceListeners.remove(listener); + } + } } private static final class PendingTrampolineMap { diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 230bfc50d1864..81b56a3107380 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -19,6 +19,8 @@ package com.android.server.companion.virtual; import android.annotation.NonNull; import android.companion.virtual.IVirtualDevice; +import java.util.Set; + /** * Virtual device manager local service interface. * Only for use within system server. @@ -34,14 +36,35 @@ public abstract class VirtualDeviceManagerInternal { void onVirtualDisplayRemoved(int displayId); } + + /** Interface to listen to the changes on the list of app UIDs running on any virtual device. */ + public interface AppsOnVirtualDeviceListener { + /** Notifies that running apps on any virtual device has changed */ + void onAppsOnAnyVirtualDeviceChanged(Set allRunningUids); + } + /** Register a listener for the creation and destruction of virtual displays. */ public abstract void registerVirtualDisplayListener( @NonNull VirtualDisplayListener listener); - /** Unregister a listener for the creation and destruction of virtual displays. */ + /** Unregister a listener for the creation and destruction of virtual displays. */ public abstract void unregisterVirtualDisplayListener( @NonNull VirtualDisplayListener listener); + /** Register a listener for changes of running app UIDs on any virtual device. */ + public abstract void registerAppsOnVirtualDeviceListener( + @NonNull AppsOnVirtualDeviceListener listener); + + /** Unregister a listener for changes of running app UIDs on any virtual device. */ + public abstract void unregisterAppsOnVirtualDeviceListener( + @NonNull AppsOnVirtualDeviceListener listener); + + /** + * Notifies that the set of apps running on virtual devices has changed. + * This method only notifies the listeners when the union of running UIDs on all virtual devices + * has changed. + */ + public abstract void onAppsOnVirtualDeviceChanged(); /** * Validate the virtual device. 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 966df4f927261..cc2cdba69af6d 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 @@ -35,6 +35,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; @@ -110,6 +111,12 @@ public class VirtualDeviceManagerServiceTest { private static final String GOOGLE_MAPS_PACKAGE_NAME = "com.google.android.apps.maps"; private static final String DEVICE_NAME = "device name"; private static final int DISPLAY_ID = 2; + private static final int UID_1 = 0; + private static final int UID_2 = 10; + private static final int UID_3 = 10000; + private static final int UID_4 = 10001; + private static final int ASSOCIATION_ID_1 = 1; + private static final int ASSOCIATION_ID_2 = 2; private static final int PRODUCT_ID = 10; private static final int VENDOR_ID = 5; private static final String UNIQUE_ID = "uniqueid"; @@ -143,6 +150,8 @@ public class VirtualDeviceManagerServiceTest { @Mock private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener; @Mock + private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener mAppsOnVirtualDeviceListener; + @Mock IPowerManager mIPowerManagerMock; @Mock IThermalService mIThermalServiceMock; @@ -242,7 +251,7 @@ public class VirtualDeviceManagerServiceTest { } @Test - public void onVirtualDisplayCreatedLocked_listenersNotified() throws RemoteException { + public void onVirtualDisplayCreatedLocked_listenersNotified() { mLocalService.registerVirtualDisplayListener(mDisplayListener); mLocalService.onVirtualDisplayCreated(DISPLAY_ID); @@ -252,7 +261,7 @@ public class VirtualDeviceManagerServiceTest { } @Test - public void onVirtualDisplayRemovedLocked_listenersNotified() throws RemoteException { + public void onVirtualDisplayRemovedLocked_listenersNotified() { mLocalService.registerVirtualDisplayListener(mDisplayListener); mDeviceImpl.onVirtualDisplayCreatedLocked( mDeviceImpl.createWindowPolicyController(), DISPLAY_ID); @@ -263,6 +272,54 @@ public class VirtualDeviceManagerServiceTest { verify(mDisplayListener).onVirtualDisplayRemoved(DISPLAY_ID); } + @Test + public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() { + ArraySet uids = new ArraySet<>(Arrays.asList(UID_1, UID_2)); + mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener); + + mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uids); + TestableLooper.get(this).processAllMessages(); + + verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(uids); + } + + @Test + public void onAppsOnVirtualDeviceChanged_multipleVirtualDevices_listenersNotified() { + ArraySet uidsOnDevice1 = new ArraySet<>(Arrays.asList(UID_1, UID_2)); + ArraySet uidsOnDevice2 = new ArraySet<>(Arrays.asList(UID_3, UID_4)); + mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener); + + // Notifies that the running apps on the first virtual device has changed. + mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1); + TestableLooper.get(this).processAllMessages(); + verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged( + new ArraySet<>(Arrays.asList(UID_1, UID_2))); + + // Notifies that the running apps on the second virtual device has changed. + mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_2, uidsOnDevice2); + TestableLooper.get(this).processAllMessages(); + // The union of the apps running on both virtual devices are sent to the listeners. + verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged( + new ArraySet<>(Arrays.asList(UID_1, UID_2, UID_3, UID_4))); + + // Notifies that the running apps on the first virtual device has changed again. + uidsOnDevice1.remove(UID_2); + mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1); + mLocalService.onAppsOnVirtualDeviceChanged(); + TestableLooper.get(this).processAllMessages(); + // The union of the apps running on both virtual devices are sent to the listeners. + verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged( + new ArraySet<>(Arrays.asList(UID_1, UID_3, UID_4))); + + // Notifies that the running apps on the first virtual device has changed but with the same + // set of UIDs. + mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1); + mLocalService.onAppsOnVirtualDeviceChanged(); + TestableLooper.get(this).processAllMessages(); + // Listeners should not be notified. + verifyNoMoreInteractions(mAppsOnVirtualDeviceListener); + } + @Test public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException { verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(), @@ -270,7 +327,7 @@ public class VirtualDeviceManagerServiceTest { nullable(String.class), anyInt(), eq(null)); mDeviceImpl.onVirtualDisplayCreatedLocked( mDeviceImpl.createWindowPolicyController(), DISPLAY_ID); - verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(), + verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), nullable(String.class), eq(DISPLAY_ID), eq(null)); } @@ -284,7 +341,7 @@ public class VirtualDeviceManagerServiceTest { assertThrows(IllegalStateException.class, () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID)); TestableLooper.get(this).processAllMessages(); - verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(), + verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), nullable(String.class), eq(DISPLAY_ID), eq(null)); } @@ -302,14 +359,14 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createWindowPolicyController(), DISPLAY_ID); ArgumentCaptor wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class); TestableLooper.get(this).processAllMessages(); - verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(), + verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), nullable(String.class), eq(DISPLAY_ID), eq(null)); IBinder wakeLock = wakeLockCaptor.getValue(); mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID); - verify(mIPowerManagerMock, Mockito.times(1)).releaseWakeLock(eq(wakeLock), anyInt()); + verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt()); } @Test @@ -318,7 +375,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createWindowPolicyController(), DISPLAY_ID); ArgumentCaptor wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class); TestableLooper.get(this).processAllMessages(); - verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(), + verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), nullable(String.class), eq(DISPLAY_ID), eq(null)); @@ -326,7 +383,7 @@ public class VirtualDeviceManagerServiceTest { // Close the VirtualDevice without first notifying it of the VirtualDisplay removal. mDeviceImpl.close(); - verify(mIPowerManagerMock, Mockito.times(1)).releaseWakeLock(eq(wakeLock), anyInt()); + verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt()); } @Test