diff options
8 files changed, 366 insertions, 33 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b90ee2b7e57f..8a2d767e9ad1 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6918,6 +6918,14 @@ entering the corresponding modes --> <string name="config_rearDisplayPhysicalAddress" translatable="false"></string> + <!-- The maximum number of virtual displays that can exist at the same time. + It must be >= 1. --> + <integer name="config_virtualDisplayLimit">100</integer> + + <!-- The maximum number of virtual displays per package that can exist at the same time. + It must be >= 1. --> + <integer name="config_virtualDisplayLimitPerPackage">50</integer> + <!-- List of certificate to be used for font fs-verity integrity verification --> <string-array translatable="false" name="config_fontManagerServiceCerts"> </string-array> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b7c876545f05..c1893ab85d2b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5240,6 +5240,9 @@ <java-symbol type="integer" name="config_deviceStateConcurrentRearDisplay" /> <java-symbol type="string" name="config_rearDisplayPhysicalAddress" /> + <java-symbol type="integer" name="config_virtualDisplayLimit" /> + <java-symbol type="integer" name="config_virtualDisplayLimitPerPackage" /> + <!-- For app language picker --> <java-symbol type="string" name="system_locale_title" /> <java-symbol type="layout" name="app_language_picker_system_default" /> diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d71826ffc273..179ec63458dd 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1978,7 +1978,7 @@ public final class DisplayManagerService extends SystemService { // handles stopping the projection. Slog.w(TAG, "Content Recording: failed to start mirroring - " + "releasing virtual display " + displayId); - releaseVirtualDisplayInternal(callback.asBinder()); + releaseVirtualDisplayInternal(callback.asBinder(), callingUid); return Display.INVALID_DISPLAY; } else if (projection != null) { // Indicate that this projection has been used to record, and can't be used @@ -2067,7 +2067,7 @@ public final class DisplayManagerService extends SystemService { // Something weird happened and the logical display was not created. Slog.w(TAG, "Rejecting request to create virtual display " + "because the logical display was not created."); - mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder()); + mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder(), callingUid); mDisplayDeviceRepo.onDisplayDeviceEvent(device, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED); return -1; @@ -2094,14 +2094,14 @@ public final class DisplayManagerService extends SystemService { } } - private void releaseVirtualDisplayInternal(IBinder appToken) { + private void releaseVirtualDisplayInternal(IBinder appToken, int callingUid) { synchronized (mSyncRoot) { if (mVirtualDisplayAdapter == null) { return; } DisplayDevice device = - mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken); + mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken, callingUid); Slog.d(TAG, "Virtual Display: Display Device released"); if (device != null) { // TODO: multi-display - handle virtual displays the same as other display adapters. @@ -4620,9 +4620,10 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public void releaseVirtualDisplay(IVirtualDisplayCallback callback) { + final int callingUid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - releaseVirtualDisplayInternal(callback.asBinder()); + releaseVirtualDisplayInternal(callback.asBinder(), callingUid); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index e77c5ec50f8b..421145390190 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -55,12 +55,14 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Slog; +import android.util.SparseIntArray; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayShape; import android.view.Surface; import android.view.SurfaceControl; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.feature.DisplayManagerFlags; @@ -85,6 +87,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private static final AtomicInteger sNextUniqueIndex = new AtomicInteger(0); private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices = new ArrayMap<>(); + + private final int mMaxDevices; + private final int mMaxDevicesPerPackage; + private final SparseIntArray mNoOfDevicesPerPackage = new SparseIntArray(); + private final Handler mHandler; private final SurfaceControlDisplayFactory mSurfaceControlDisplayFactory; @@ -114,8 +121,31 @@ public class VirtualDisplayAdapter extends DisplayAdapter { super(syncRoot, context, handler, listener, TAG, featureFlags); mHandler = handler; mSurfaceControlDisplayFactory = surfaceControlDisplayFactory; + + mMaxDevices = context.getResources().getInteger(R.integer.config_virtualDisplayLimit); + if (mMaxDevices < 1) { + throw new IllegalArgumentException("The limit of virtual displays must be >= 1"); + } + mMaxDevicesPerPackage = + context.getResources().getInteger(R.integer.config_virtualDisplayLimitPerPackage); + if (mMaxDevicesPerPackage < 1) { + throw new IllegalArgumentException( + "The limit of virtual displays per package must be >= 1"); + } } + /** + * Create a virtual display + * @param callback The callback + * @param projection The media projection + * @param ownerUid The UID of the package creating a display + * @param ownerPackageName The name of the package creating a display + * @param uniqueId The unique ID of the display device + * @param surface The surface + * @param flags The flags + * @param virtualDisplayConfig The config + * @return The display device created + */ public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback, IMediaProjection projection, int ownerUid, String ownerPackageName, String uniqueId, Surface surface, int flags, VirtualDisplayConfig virtualDisplayConfig) { @@ -126,6 +156,22 @@ public class VirtualDisplayAdapter extends DisplayAdapter { return null; } + if (getFeatureFlags().isVirtualDisplayLimitEnabled() + && mVirtualDisplayDevices.size() >= mMaxDevices) { + Slog.w(TAG, "Rejecting request to create private virtual display because " + + mMaxDevices + " devices already exist."); + return null; + } + + int noOfDevices = mNoOfDevicesPerPackage.get(ownerUid, /* valueIfKeyNotFound= */ 0); + if (getFeatureFlags().isVirtualDisplayLimitEnabled() + && noOfDevices >= mMaxDevicesPerPackage) { + Slog.w(TAG, "Rejecting request to create private virtual display because " + + mMaxDevicesPerPackage + " devices already exist for package " + + ownerPackageName + "."); + return null; + } + String name = virtualDisplayConfig.getName(); boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0; @@ -140,6 +186,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { projection, mediaProjectionCallback, uniqueId, virtualDisplayConfig); mVirtualDisplayDevices.put(appToken, device); + if (getFeatureFlags().isVirtualDisplayLimitEnabled()) { + mNoOfDevicesPerPackage.put(ownerUid, noOfDevices + 1); + } try { if (projection != null) { @@ -150,7 +199,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { appToken.linkToDeath(device, 0); } catch (RemoteException ex) { Slog.e(TAG, "Virtual Display: error while setting up VirtualDisplayDevice", ex); - mVirtualDisplayDevices.remove(appToken); + removeVirtualDisplayDeviceLocked(appToken, ownerUid); device.destroyLocked(false); return null; } @@ -194,8 +243,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter { } } - public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) { - VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken); + /** + * Release a virtual display that was previously created + * @param appToken The token to identify the display + * @param ownerUid The UID of the package, used to keep track of and limit the number of + * displays created per package + * @return The display device that has been removed + */ + public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken, int ownerUid) { + VirtualDisplayDevice device = removeVirtualDisplayDeviceLocked(appToken, ownerUid); if (device != null) { Slog.v(TAG, "Release VirtualDisplay " + device.mName); device.destroyLocked(true); @@ -228,10 +284,6 @@ public class VirtualDisplayAdapter extends DisplayAdapter { : ("," + uid + "," + config.getName() + "," + sNextUniqueIndex.getAndIncrement())); } - private void handleBinderDiedLocked(IBinder appToken) { - mVirtualDisplayDevices.remove(appToken); - } - private void handleMediaProjectionStoppedLocked(IBinder appToken) { VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken); if (device != null) { @@ -241,6 +293,18 @@ public class VirtualDisplayAdapter extends DisplayAdapter { } } + private VirtualDisplayDevice removeVirtualDisplayDeviceLocked(IBinder appToken, int ownerUid) { + int noOfDevices = mNoOfDevicesPerPackage.get(ownerUid, /* valueIfKeyNotFound= */ 0); + if (getFeatureFlags().isVirtualDisplayLimitEnabled()) { + if (noOfDevices <= 1) { + mNoOfDevicesPerPackage.delete(ownerUid); + } else { + mNoOfDevicesPerPackage.put(ownerUid, noOfDevices - 1); + } + } + return mVirtualDisplayDevices.remove(appToken); + } + private final class VirtualDisplayDevice extends DisplayDevice implements DeathRecipient { private static final int PENDING_SURFACE_CHANGE = 0x01; private static final int PENDING_RESIZE = 0x02; @@ -300,7 +364,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { @Override public void binderDied() { synchronized (getSyncRoot()) { - handleBinderDiedLocked(mAppToken); + removeVirtualDisplayDeviceLocked(mAppToken, mOwnerUid); Slog.i(TAG, "Virtual display device released because application token died: " + mOwnerPackageName); destroyLocked(false); diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 5284d1c423f6..eeec36919480 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -199,6 +199,11 @@ public class DisplayManagerFlags { Flags.FLAG_IDLE_SCREEN_CONFIG_IN_SUBSCRIBING_LIGHT_SENSOR, Flags::idleScreenConfigInSubscribingLightSensor); + private final FlagState mVirtualDisplayLimit = + new FlagState( + Flags.FLAG_VIRTUAL_DISPLAY_LIMIT, + Flags::virtualDisplayLimit); + private final FlagState mNormalBrightnessForDozeParameter = new FlagState( Flags.FLAG_NORMAL_BRIGHTNESS_FOR_DOZE_PARAMETER, Flags::normalBrightnessForDozeParameter @@ -416,6 +421,10 @@ public class DisplayManagerFlags { return mNewHdrBrightnessModifier.isEnabled(); } + public boolean isVirtualDisplayLimitEnabled() { + return mVirtualDisplayLimit.isEnabled(); + } + /** * @return Whether the useDozeBrightness parameter should be used */ @@ -487,6 +496,7 @@ public class DisplayManagerFlags { pw.println(" " + mOffloadDozeOverrideHoldsWakelock); pw.println(" " + mOffloadSessionCancelBlockScreenOn); pw.println(" " + mNewHdrBrightnessModifier); + pw.println(" " + mVirtualDisplayLimit); pw.println(" " + mNormalBrightnessForDozeParameter); pw.println(" " + mIdleScreenConfigInSubscribingLightSensor); pw.println(" " + mEnableBatteryStatsForAllDisplays); diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 252ed09fd125..20ecd2d8303e 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -348,6 +348,14 @@ flag { } flag { + name: "virtual_display_limit" + namespace: "display_manager" + description: "Limit the number of virtual displays that can be created." + bug: "261791612" + is_fixed_read_only: true +} + +flag { name: "idle_screen_config_in_subscribing_light_sensor" namespace: "display_manager" description: "Account for Idle screen refresh rate configs while subscribing to light sensor" diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 6093a67cd320..1a1c8e50ba0a 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -50,6 +50,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; @@ -3445,6 +3446,31 @@ public class DisplayManagerServiceTest { verify(dpc).onDisplayChanged(/* hbmMetadata= */ null, Layout.NO_LEAD_DISPLAY); } + @Test + public void testCreateAndReleaseVirtualDisplay_CalledWithTheSameUid() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mShortMockedInjector); + registerDefaultDisplays(displayManager); + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + VirtualDisplayConfig config = mock(VirtualDisplayConfig.class); + Surface surface = mock(Surface.class); + when(config.getSurface()).thenReturn(surface); + int callingUid = Binder.getCallingUid(); + IBinder binder = mock(IBinder.class); + when(mMockAppToken.asBinder()).thenReturn(binder); + String uniqueId = "123"; + when(config.getUniqueId()).thenReturn(uniqueId); + + bs.createVirtualDisplay(config, mMockAppToken, /* projection= */ null, PACKAGE_NAME); + verify(mMockVirtualDisplayAdapter).createVirtualDisplayLocked(eq(mMockAppToken), + /* projection= */ isNull(), eq(callingUid), eq(PACKAGE_NAME), + eq("virtual:" + PACKAGE_NAME + ":" + uniqueId), eq(surface), /* flags= */ anyInt(), + eq(config)); + + bs.releaseVirtualDisplay(mMockAppToken); + verify(mMockVirtualDisplayAdapter).releaseVirtualDisplayLocked(binder, callingUid); + } + private void initDisplayPowerController(DisplayManagerInternal localService) { localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() { @Override diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java index 81e6cc3f546b..3ac7fb0dbe53 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java @@ -21,71 +21,104 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; -import android.content.Context; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; +import android.media.projection.IMediaProjection; import android.os.IBinder; import android.os.Process; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.testing.TestableContext; +import android.view.Surface; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.R; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.testutils.TestHandler; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; + @SmallTest @RunWith(AndroidJUnit4.class) public class VirtualDisplayAdapterTest { + private static final int MAX_DEVICES = 3; + private static final int MAX_DEVICES_PER_PACKAGE = 2; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + @Mock - Context mContextMock; + private VirtualDisplayAdapter.SurfaceControlDisplayFactory mMockSufaceControlDisplayFactory; @Mock - VirtualDisplayAdapter.SurfaceControlDisplayFactory mMockSufaceControlDisplayFactory; + private DisplayAdapter.Listener mMockListener; @Mock - DisplayAdapter.Listener mMockListener; + private IVirtualDisplayCallback mMockCallback; @Mock - IVirtualDisplayCallback mMockCallback; + private IBinder mMockBinder; @Mock - IBinder mMockBinder; + private IMediaProjection mMediaProjectionMock; - private TestHandler mHandler; + @Mock + private Surface mSurfaceMock; + + @Mock + private VirtualDisplayConfig mVirtualDisplayConfigMock; - private VirtualDisplayAdapter mVirtualDisplayAdapter; + private TestHandler mHandler; @Mock private DisplayManagerFlags mFeatureFlags; + private VirtualDisplayAdapter mAdapter; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Before public void setUp() { MockitoAnnotations.initMocks(this); + + mContext.getOrCreateTestableResources().addOverride(R.integer.config_virtualDisplayLimit, + MAX_DEVICES); + mContext.getOrCreateTestableResources().addOverride( + R.integer.config_virtualDisplayLimitPerPackage, MAX_DEVICES_PER_PACKAGE); + mHandler = new TestHandler(null); - mVirtualDisplayAdapter = new VirtualDisplayAdapter(new DisplayManagerService.SyncRoot(), - mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory, - mFeatureFlags); + mAdapter = new VirtualDisplayAdapter(new DisplayManagerService.SyncRoot(), mContext, + mHandler, mMockListener, mMockSufaceControlDisplayFactory, mFeatureFlags); when(mMockCallback.asBinder()).thenReturn(mMockBinder); } @Test - public void testCreatesVirtualDisplay() { + public void testCreateAndReleaseVirtualDisplay() { VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1, /* height= */ 1, /* densityDpi= */ 1).build(); + int ownerUid = 10; - DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, - /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage", + DisplayDevice result = mAdapter.createVirtualDisplayLocked(mMockCallback, + /* projection= */ null, ownerUid, /* packageName= */ "testpackage", /* uniqueId= */ "uniqueId", /* surface= */ null, /* flags= */ 0, config); + assertNotNull(result); + result = mAdapter.releaseVirtualDisplayLocked(mMockBinder, ownerUid); assertNotNull(result); } @@ -98,7 +131,7 @@ public class VirtualDisplayAdapterTest { final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId( packageName, Process.myUid(), config); - DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked( + DisplayDevice result = mAdapter.createVirtualDisplayLocked( mMockCallback, /* projection= */ null, /* ownerUid= */ 10, packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config); @@ -114,14 +147,194 @@ public class VirtualDisplayAdapterTest { /* height= */ 1, /* densityDpi= */ 1).build(); VirtualDisplayConfig config2 = new VirtualDisplayConfig.Builder("test2", /* width= */ 1, /* height= */ 1, /* densityDpi= */ 1).build(); - mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, /* projection= */ null, - /* ownerUid= */ 10, /* packageName= */ "testpackage", /* uniqueId= */ "uniqueId1", - /* surface= */ null, /* flags= */ 0, config1); - DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, + DisplayDevice result = mAdapter.createVirtualDisplayLocked(mMockCallback, /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage", - /* uniqueId= */ "uniqueId2", /* surface= */ null, /* flags= */ 0, config2); + /* uniqueId= */ "uniqueId1", /* surface= */ null, /* flags= */ 0, config1); + assertNotNull(result); + result = mAdapter.createVirtualDisplayLocked(mMockCallback, + /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage", + /* uniqueId= */ "uniqueId2", /* surface= */ null, /* flags= */ 0, config2); assertNull(result); } + + @Test + public void testCreateManyVirtualDisplays_LimitFlagDisabled() { + when(mFeatureFlags.isVirtualDisplayLimitEnabled()).thenReturn(false); + + // Displays for the same package + for (int i = 0; i < MAX_DEVICES_PER_PACKAGE * 2; i++) { + // Same owner UID + IVirtualDisplayCallback callback = createCallback(); + DisplayDevice device = mAdapter.createVirtualDisplayLocked(callback, + mMediaProjectionMock, 1234, "test.package", "123", + mSurfaceMock, /* flags= */ 0, mVirtualDisplayConfigMock); + assertNotNull(device); + } + + // Displays for different packages + for (int i = 0; i < MAX_DEVICES * 2; i++) { + // Same owner UID + IVirtualDisplayCallback callback = createCallback(); + DisplayDevice device = mAdapter.createVirtualDisplayLocked(callback, + mMediaProjectionMock, 1234 + i, "test.package", "123", + mSurfaceMock, /* flags= */ 0, mVirtualDisplayConfigMock); + assertNotNull(device); + } + } + + @Test + public void testCreateVirtualDisplay_MaxDisplaysPerPackage() { + when(mFeatureFlags.isVirtualDisplayLimitEnabled()).thenReturn(true); + List<IVirtualDisplayCallback> callbacks = new ArrayList<>(); + int ownerUid = 1234; + + for (int i = 0; i < MAX_DEVICES_PER_PACKAGE * 2; i++) { + // Same owner UID + IVirtualDisplayCallback callback = createCallback(); + DisplayDevice device = mAdapter.createVirtualDisplayLocked(callback, + mMediaProjectionMock, ownerUid, "test.package", "123", + mSurfaceMock, /* flags= */ 0, mVirtualDisplayConfigMock); + if (i < MAX_DEVICES_PER_PACKAGE) { + assertNotNull(device); + callbacks.add(callback); + } else { + assertNull(device); + } + } + + // Release one display + DisplayDevice device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(0).asBinder(), + ownerUid); + assertNotNull(device); + callbacks.remove(0); + + // We should be able to create another display + IVirtualDisplayCallback callback = createCallback(); + device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, ownerUid, + "test.package", "123", mSurfaceMock, /* flags= */ 0, + mVirtualDisplayConfigMock); + assertNotNull(device); + callbacks.add(callback); + + // But only one + callback = createCallback(); + device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, ownerUid, + "test.package", "123", mSurfaceMock, /* flags= */ 0, + mVirtualDisplayConfigMock); + assertNull(device); + + // Release all the displays + for (IVirtualDisplayCallback cb : callbacks) { + device = mAdapter.releaseVirtualDisplayLocked(cb.asBinder(), ownerUid); + assertNotNull(device); + } + callbacks.clear(); + + // We should be able to create the max number of displays again + for (int i = 0; i < MAX_DEVICES_PER_PACKAGE * 2; i++) { + // Same owner UID + callback = createCallback(); + device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, ownerUid, + "test.package", "123", mSurfaceMock, /* flags= */ 0, + mVirtualDisplayConfigMock); + if (i < MAX_DEVICES_PER_PACKAGE) { + assertNotNull(device); + callbacks.add(callback); + } else { + assertNull(device); + } + } + + // We should be able to create a display for a different package + callback = createCallback(); + device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, ownerUid + 1, + "test.package", "123", mSurfaceMock, /* flags= */ 0, + mVirtualDisplayConfigMock); + assertNotNull(device); + callbacks.add(callback); + } + + @Test + public void testCreateVirtualDisplay_MaxDisplays() { + when(mFeatureFlags.isVirtualDisplayLimitEnabled()).thenReturn(true); + List<IVirtualDisplayCallback> callbacks = new ArrayList<>(); + int firstOwnerUid = 1000; + + for (int i = 0; i < MAX_DEVICES * 2; i++) { + // Different owner UID + IVirtualDisplayCallback callback = createCallback(); + DisplayDevice device = mAdapter.createVirtualDisplayLocked(callback, + mMediaProjectionMock, firstOwnerUid + i, "test.package", + "123", mSurfaceMock, /* flags= */ 0, mVirtualDisplayConfigMock); + if (i < MAX_DEVICES) { + assertNotNull(device); + callbacks.add(callback); + } else { + assertNull(device); + } + } + + // Release one display + DisplayDevice device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(0).asBinder(), + firstOwnerUid); + assertNotNull(device); + callbacks.remove(0); + + // We should be able to create another display + IVirtualDisplayCallback callback = createCallback(); + device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, + firstOwnerUid, "test.package", "123", mSurfaceMock, /* flags= */ 0, + mVirtualDisplayConfigMock); + assertNotNull(device); + callbacks.add(callback); + + // But only one + callback = createCallback(); + device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, + firstOwnerUid, "test.package", "123", mSurfaceMock, /* flags= */ 0, + mVirtualDisplayConfigMock); + assertNull(device); + + // Release all the displays + for (int i = 0; i < callbacks.size(); i++) { + device = mAdapter.releaseVirtualDisplayLocked(callbacks.get(i).asBinder(), + firstOwnerUid + i); + assertNotNull(device); + } + callbacks.clear(); + + // We should be able to create the max number of displays again + for (int i = 0; i < MAX_DEVICES * 2; i++) { + // Different owner UID + callback = createCallback(); + device = mAdapter.createVirtualDisplayLocked(callback, mMediaProjectionMock, + firstOwnerUid + i, "test.package", "123", mSurfaceMock, + /* flags= */ 0, mVirtualDisplayConfigMock); + if (i < MAX_DEVICES) { + assertNotNull(device); + callbacks.add(callback); + } else { + assertNull(device); + } + } + } + + private IVirtualDisplayCallback createCallback() { + return new IVirtualDisplayCallback.Stub() { + + @Override + public void onPaused() { + } + + @Override + public void onResumed() { + } + + @Override + public void onStopped() { + } + }; + } } |