Merge changes from topics "vdm-mirror-1", "vdm-mirror-icon" into main
* changes:
Show status bar icon during VDM screen mirroring
VDM interactive screen mirroring
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3c6e3e6..74a4440 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -866,6 +866,14 @@
}
+package android.companion.virtual {
+
+ public final class VirtualDeviceManager {
+ method @FlaggedApi("android.companion.virtual.flags.interactive_screen_mirror") public boolean isVirtualDeviceOwnedMirrorDisplay(int);
+ }
+
+}
+
package android.content {
public final class AttributionSource implements android.os.Parcelable {
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index b665036..0493312 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -122,4 +122,10 @@
* {@code android.media.AudioManager.SystemSoundEffect}
*/
void playSoundEffect(int deviceId, int effectType);
+
+ /**
+ * Returns whether the given display is an auto-mirror display owned by a virtual
+ * device.
+ */
+ boolean isVirtualDeviceOwnedMirrorDisplay(int displayId);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 2569366..d40a591 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -28,6 +28,7 @@
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
@@ -436,6 +437,25 @@
}
/**
+ * Returns whether the given display is an auto-mirror display owned by a virtual device.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR)
+ @TestApi
+ public boolean isVirtualDeviceOwnedMirrorDisplay(int displayId) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
+ return false;
+ }
+ try {
+ return mService.isVirtualDeviceOwnedMirrorDisplay(displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* A representation of a virtual device.
*
* <p>A virtual device can have its own virtual displays, audio input/output, sensors, etc.
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 21427ac..55ae8ee 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -56,3 +56,10 @@
description: "Enable express metrics in VDM"
bug: "307297730"
}
+
+flag {
+ name: "interactive_screen_mirror"
+ namespace: "virtual_devices"
+ description: "Enable interactive screen mirroring using Virtual Devices"
+ bug: "292212199"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index d57f31f..8b992fc 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -41,6 +41,7 @@
import android.app.trust.TrustManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -236,6 +237,12 @@
@Provides
@Singleton
+ static VirtualDeviceManager provideVirtualDeviceManager(Context context) {
+ return context.getSystemService(VirtualDeviceManager.class);
+ }
+
+ @Provides
+ @Singleton
static DeviceStateManager provideDeviceStateManager(Context context) {
return context.getSystemService(DeviceStateManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 3e2ecee..0c8dbe7 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -56,6 +56,9 @@
/** Display change event indicating a change to the given displayId has occurred. */
val displayChangeEvent: Flow<Int>
+ /** Display addition event indicating a new display has been added. */
+ val displayAdditionEvent: Flow<Display?>
+
/** Provides the current set of displays. */
val displays: Flow<Set<Display>>
@@ -126,6 +129,11 @@
override val displayChangeEvent: Flow<Int> =
allDisplayEvents.filter { it is DisplayEvent.Changed }.map { it.displayId }
+ override val displayAdditionEvent: Flow<Display?> =
+ allDisplayEvents
+ .filter { it is DisplayEvent.Added }
+ .map { displayManager.getDisplay(it.displayId) }
+
private val enabledDisplays =
allDisplayEvents
.map { getDisplays() }
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index 11ed96d..cf86885 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.display.domain.interactor
+import android.companion.virtual.VirtualDeviceManager
+import android.companion.virtual.flags.Flags
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
@@ -26,6 +28,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
/** Provides information about an external connected display. */
@@ -40,6 +43,12 @@
*/
val connectedDisplayState: Flow<State>
+ /**
+ * Indicates that there is a new connected display (either an external display or a virtual
+ * device owned mirror display).
+ */
+ val connectedDisplayAddition: Flow<Unit>
+
/** Pending display that can be enabled to be used by the system. */
val pendingDisplay: Flow<PendingDisplay?>
@@ -69,6 +78,7 @@
class ConnectedDisplayInteractorImpl
@Inject
constructor(
+ private val virtualDeviceManager: VirtualDeviceManager,
keyguardRepository: KeyguardRepository,
displayRepository: DisplayRepository,
) : ConnectedDisplayInteractor {
@@ -76,13 +86,14 @@
override val connectedDisplayState: Flow<State> =
displayRepository.displays
.map { displays ->
- val externalDisplays =
- displays.filter { display -> display.type == Display.TYPE_EXTERNAL }
+ val externalDisplays = displays.filter { isExternalDisplay(it) }
- val secureExternalDisplays =
- externalDisplays.filter { it.flags and Display.FLAG_SECURE != 0 }
+ val secureExternalDisplays = externalDisplays.filter { isSecureDisplay(it) }
- if (externalDisplays.isEmpty()) {
+ val virtualDeviceMirrorDisplays =
+ displays.filter { isVirtualDeviceOwnedMirrorDisplay(it) }
+
+ if (externalDisplays.isEmpty() && virtualDeviceMirrorDisplays.isEmpty()) {
State.DISCONNECTED
} else if (!secureExternalDisplays.isEmpty()) {
State.CONNECTED_SECURE
@@ -92,6 +103,13 @@
}
.distinctUntilChanged()
+ override val connectedDisplayAddition: Flow<Unit> =
+ displayRepository.displayAdditionEvent
+ .filter {
+ it != null && (isExternalDisplay(it) || isVirtualDeviceOwnedMirrorDisplay(it))
+ }
+ .map {} // map to Unit
+
// Provides the pending display only if the lockscreen is unlocked
override val pendingDisplay: Flow<PendingDisplay?> =
displayRepository.pendingDisplay.combine(keyguardRepository.isKeyguardShowing) {
@@ -109,4 +127,17 @@
override suspend fun enable() = this@toInteractorPendingDisplay.enable()
override suspend fun ignore() = this@toInteractorPendingDisplay.ignore()
}
+
+ private fun isExternalDisplay(display: Display): Boolean {
+ return display.type == Display.TYPE_EXTERNAL
+ }
+
+ private fun isSecureDisplay(display: Display): Boolean {
+ return display.flags and Display.FLAG_SECURE != 0
+ }
+
+ private fun isVirtualDeviceOwnedMirrorDisplay(display: Display): Boolean {
+ return Flags.interactiveScreenMirror() &&
+ virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(display.displayId)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 8ee1ade..e9d5dec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -24,7 +24,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
-import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.privacy.PrivacyChipBuilder
@@ -35,7 +34,6 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -56,8 +54,7 @@
connectedDisplayInteractor: ConnectedDisplayInteractor
) {
private val onDisplayConnectedFlow =
- connectedDisplayInteractor.connectedDisplayState
- .filter { it != State.DISCONNECTED }
+ connectedDisplayInteractor.connectedDisplayAddition
private var connectedDisplayCollectionJob: Job? = null
private lateinit var scheduler: SystemStatusAnimationScheduler
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 511562f..d80dd76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -390,6 +390,17 @@
assertThat(pendingDisplay!!.id).isEqualTo(1)
}
+ @Test
+ fun onDisplayAdded_emitsDisplayAdditionEvent() =
+ testScope.runTest {
+ val display by lastDisplayAdditionEvent()
+
+ sendOnDisplayAdded(1, TYPE_EXTERNAL)
+
+ assertThat(display!!.displayId).isEqualTo(1)
+ assertThat(display!!.type).isEqualTo(TYPE_EXTERNAL)
+ }
+
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
// Wrapper to capture the displayListener.
@@ -411,6 +422,12 @@
return flowValue
}
+ private fun TestScope.lastDisplayAdditionEvent(): FlowValue<Display?> {
+ val flowValue = collectLastValue(displayRepository.displayAdditionEvent)
+ captureAddedRemovedListener()
+ return flowValue
+ }
+
private fun captureAddedRemovedListener() {
verify(displayManager)
.registerDisplayListener(
@@ -423,9 +440,17 @@
)
)
}
+
+ private fun sendOnDisplayAdded(id: Int, displayType: Int) {
+ val mockDisplay = display(id = id, type = displayType)
+ whenever(displayManager.getDisplay(eq(id))).thenReturn(mockDisplay)
+ displayListener.value.onDisplayAdded(id)
+ }
+
private fun sendOnDisplayAdded(id: Int) {
displayListener.value.onDisplayAdded(id)
}
+
private fun sendOnDisplayRemoved(id: Int) {
displayListener.value.onDisplayRemoved(id)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index 26ee094..0db3de2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -16,11 +16,14 @@
package com.android.systemui.display.domain.interactor
+import android.companion.virtual.VirtualDeviceManager
+import android.companion.virtual.flags.Flags.FLAG_INTERACTIVE_SCREEN_MIRROR
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
import android.view.Display.TYPE_EXTERNAL
import android.view.Display.TYPE_INTERNAL
+import android.view.Display.TYPE_VIRTUAL
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
@@ -31,14 +34,20 @@
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -46,14 +55,22 @@
@SmallTest
class ConnectedDisplayInteractorTest : SysuiTestCase() {
+ private val virtualDeviceManager = mock<VirtualDeviceManager>()
+
private val fakeDisplayRepository = FakeDisplayRepository()
private val fakeKeyguardRepository = FakeKeyguardRepository()
private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
- ConnectedDisplayInteractorImpl(fakeKeyguardRepository, fakeDisplayRepository)
+ ConnectedDisplayInteractorImpl(
+ virtualDeviceManager,
+ fakeKeyguardRepository,
+ fakeDisplayRepository
+ )
private val testScope = TestScope(UnconfinedTestDispatcher())
@Before
fun setup() {
+ mSetFlagsRule.disableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR)
+ whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt())).thenReturn(false)
fakeKeyguardRepository.setKeyguardShowing(false)
}
@@ -137,6 +154,96 @@
}
@Test
+ fun displayState_virtualDeviceOwnedMirrorVirtualDisplay_connected() =
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR)
+ whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt()))
+ .thenReturn(true)
+ val value by lastValue()
+
+ fakeDisplayRepository.emit(setOf(display(type = TYPE_VIRTUAL)))
+
+ assertThat(value).isEqualTo(State.CONNECTED)
+ }
+
+ @Test
+ fun displayState_virtualDeviceUnownedMirrorVirtualDisplay_disconnected() =
+ testScope.runTest {
+ val value by lastValue()
+
+ fakeDisplayRepository.emit(setOf(display(type = TYPE_VIRTUAL)))
+
+ assertThat(value).isEqualTo(State.DISCONNECTED)
+ }
+
+ @Test
+ fun virtualDeviceOwnedMirrorVirtualDisplay_emitsConnectedDisplayAddition() =
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR)
+ whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt()))
+ .thenReturn(true)
+ var count = 0
+ val job =
+ connectedDisplayStateProvider.connectedDisplayAddition
+ .onEach { count++ }
+ .launchIn(this)
+
+ fakeDisplayRepository.emit(display(type = TYPE_VIRTUAL))
+
+ runCurrent()
+ assertThat(count).isEqualTo(1)
+ job.cancel()
+ }
+
+ @Test
+ fun virtualDeviceUnownedMirrorVirtualDisplay_doesNotEmitConnectedDisplayAddition() =
+ testScope.runTest {
+ var count = 0
+ val job =
+ connectedDisplayStateProvider.connectedDisplayAddition
+ .onEach { count++ }
+ .launchIn(this)
+
+ fakeDisplayRepository.emit(display(type = TYPE_VIRTUAL))
+
+ runCurrent()
+ assertThat(count).isEqualTo(0)
+ job.cancel()
+ }
+
+ @Test
+ fun externalDisplay_emitsConnectedDisplayAddition() =
+ testScope.runTest {
+ var count = 0
+ val job =
+ connectedDisplayStateProvider.connectedDisplayAddition
+ .onEach { count++ }
+ .launchIn(this)
+
+ fakeDisplayRepository.emit(display(type = TYPE_EXTERNAL))
+
+ runCurrent()
+ assertThat(count).isEqualTo(1)
+ job.cancel()
+ }
+
+ @Test
+ fun internalDisplay_doesNotEmitConnectedDisplayAddition() =
+ testScope.runTest {
+ var count = 0
+ val job =
+ connectedDisplayStateProvider.connectedDisplayAddition
+ .onEach { count++ }
+ .launchIn(this)
+
+ fakeDisplayRepository.emit(display(type = TYPE_INTERNAL))
+
+ runCurrent()
+ assertThat(count).isEqualTo(0)
+ job.cancel()
+ }
+
+ @Test
fun pendingDisplay_propagated() =
testScope.runTest {
val value by lastPendingDisplay()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
index 66d2465..c289ff3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
@@ -21,7 +21,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
-import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State.CONNECTED
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.statusbar.policy.BatteryController
@@ -79,8 +78,8 @@
testScope.runTest {
systemEventCoordinator.startObserving()
- connectedDisplayInteractor.emit(CONNECTED)
- connectedDisplayInteractor.emit(CONNECTED)
+ connectedDisplayInteractor.emit()
+ connectedDisplayInteractor.emit()
verify(scheduler, times(2)).onStatusEvent(any<ConnectedDisplayEvent>())
}
@@ -90,21 +89,23 @@
testScope.runTest {
systemEventCoordinator.startObserving()
- connectedDisplayInteractor.emit(CONNECTED)
+ connectedDisplayInteractor.emit()
verify(scheduler).onStatusEvent(any<ConnectedDisplayEvent>())
systemEventCoordinator.stopObserving()
- connectedDisplayInteractor.emit(CONNECTED)
+ connectedDisplayInteractor.emit()
verifyNoMoreInteractions(scheduler)
}
class FakeConnectedDisplayInteractor : ConnectedDisplayInteractor {
- private val flow = MutableSharedFlow<ConnectedDisplayInteractor.State>()
- suspend fun emit(value: ConnectedDisplayInteractor.State) = flow.emit(value)
+ private val flow = MutableSharedFlow<Unit>()
+ suspend fun emit() = flow.emit(Unit)
override val connectedDisplayState: Flow<ConnectedDisplayInteractor.State>
+ get() = MutableSharedFlow<ConnectedDisplayInteractor.State>()
+ override val connectedDisplayAddition: Flow<Unit>
get() = flow
override val pendingDisplay: Flow<PendingDisplay?>
get() = MutableSharedFlow<PendingDisplay>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 71c27de..da6c28a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -317,6 +317,8 @@
suspend fun emit(value: State) = flow.emit(value)
override val connectedDisplayState: Flow<State>
get() = flow
+ override val connectedDisplayAddition: Flow<Unit>
+ get() = TODO("Not yet implemented")
override val pendingDisplay: Flow<PendingDisplay?>
get() = TODO("Not yet implemented")
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 5fd0b4f..d8098b7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -47,6 +47,10 @@
private val flow = MutableSharedFlow<Set<Display>>(replay = 1)
private val pendingDisplayFlow =
MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
+ private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 1)
+
+ /** Emits [value] as [displayAdditionEvent] flow value. */
+ suspend fun emit(value: Display?) = displayAdditionEventFlow.emit(value)
/** Emits [value] as [displays] flow value. */
suspend fun emit(value: Set<Display>) = flow.emit(value)
@@ -60,6 +64,9 @@
override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
get() = pendingDisplayFlow
+ override val displayAdditionEvent: Flow<Display?>
+ get() = displayAdditionEventFlow
+
private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 852e36d..8c728f1 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -118,6 +118,7 @@
private final Object mGenericWindowPolicyControllerLock = new Object();
@Nullable private final ActivityBlockedCallback mActivityBlockedCallback;
private int mDisplayId = Display.INVALID_DISPLAY;
+ private boolean mIsMirrorDisplay = false;
@NonNull
@GuardedBy("mGenericWindowPolicyControllerLock")
@@ -203,8 +204,9 @@
/**
* Expected to be called once this object is associated with a newly created display.
*/
- public void setDisplayId(int displayId) {
+ void setDisplayId(int displayId, boolean isMirrorDisplay) {
mDisplayId = displayId;
+ mIsMirrorDisplay = isMirrorDisplay;
}
/**
@@ -256,9 +258,7 @@
@Nullable Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
int launchingFromDisplayId, boolean isNewTask) {
if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId, isNewTask)) {
- if (mActivityBlockedCallback != null) {
- mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
- }
+ notifyActivityBlocked(activityInfo);
return false;
}
if (mIntentListenerCallback != null && intent != null
@@ -273,6 +273,11 @@
public boolean canContainActivity(@NonNull ActivityInfo activityInfo,
@WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
boolean isNewTask) {
+ // Mirror displays cannot contain activities.
+ if (mIsMirrorDisplay) {
+ Slog.d(TAG, "Mirror virtual displays cannot contain activities.");
+ return false;
+ }
if (!isWindowingModeSupported(windowingMode)) {
Slog.d(TAG, "Virtual device doesn't support windowing mode " + windowingMode);
return false;
@@ -344,9 +349,7 @@
// TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
if ((windowFlags & FLAG_SECURE) != 0
|| (systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
- if (mActivityBlockedCallback != null) {
- mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
- }
+ notifyActivityBlocked(activityInfo);
return false;
}
}
@@ -428,6 +431,14 @@
&& mDisplayCategories.contains(activityInfo.requiredDisplayCategory);
}
+ private void notifyActivityBlocked(ActivityInfo activityInfo) {
+ // Don't trigger activity blocked callback for mirror displays, because we can't show
+ // any activity or presentation on it anyway.
+ if (!mIsMirrorDisplay && mActivityBlockedCallback != null) {
+ mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
+ }
+ }
+
private static boolean isAllowedByPolicy(boolean allowedByDefault,
Set<ComponentName> exemptions, ComponentName component) {
// Either allowed and the exemptions do not contain the component,
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 4298c07..70c449f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -167,7 +167,8 @@
private final VirtualDeviceLog mVirtualDeviceLog;
private final String mOwnerPackageName;
private final int mDeviceId;
- private @Nullable final String mPersistentDeviceId;
+ @Nullable
+ private final String mPersistentDeviceId;
// Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
// Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
// 1. After display is created the window manager calls into VDM during construction
@@ -191,6 +192,7 @@
private final IVirtualDeviceActivityListener mActivityListener;
private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
private final DisplayManagerGlobal mDisplayManager;
+ private final DisplayManagerInternal mDisplayManagerInternal;
@GuardedBy("mVirtualDeviceLock")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
@@ -313,6 +315,7 @@
mParams = params;
mDevicePolicies = params.getDevicePolicies();
mDisplayManager = displayManager;
+ mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
if (inputController == null) {
mInputController = new InputController(
context.getMainThreadHandler(),
@@ -971,8 +974,8 @@
@NonNull Set<String> displayCategories) {
final boolean activityLaunchAllowedByDefault =
Flags.dynamicPolicy()
- ? getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
- : mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED;
+ ? getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
+ : mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED;
final boolean crossTaskNavigationAllowedByDefault =
mParams.getDefaultNavigationPolicy() == NAVIGATION_POLICY_DEFAULT_ALLOWED;
final boolean showTasksInHostDeviceRecents =
@@ -987,7 +990,7 @@
activityLaunchAllowedByDefault,
mActivityPolicyExemptions,
crossTaskNavigationAllowedByDefault,
- /*crossTaskNavigationExemptions=*/crossTaskNavigationAllowedByDefault
+ /* crossTaskNavigationExemptions= */crossTaskNavigationAllowedByDefault
? mParams.getBlockedCrossTaskNavigations()
: mParams.getAllowedCrossTaskNavigations(),
mPermissionDialogComponent,
@@ -1016,12 +1019,12 @@
synchronized (mVirtualDeviceLock) {
gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
}
- DisplayManagerInternal displayManager = LocalServices.getService(
- DisplayManagerInternal.class);
int displayId;
- displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
+ displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, callback,
this, gwpc, packageName);
- gwpc.setDisplayId(displayId);
+ gwpc.setDisplayId(displayId, /* isMirrorDisplay= */ Flags.interactiveScreenMirror()
+ && mDisplayManagerInternal.getDisplayIdToMirror(displayId)
+ != Display.INVALID_DISPLAY);
boolean showPointer;
synchronized (mVirtualDeviceLock) {
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 0604510..3031a84 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -42,6 +42,7 @@
import android.content.AttributionSource;
import android.content.Context;
import android.content.Intent;
+import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.os.Binder;
@@ -66,6 +67,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.modules.expresslog.Counter;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceImpl.PendingTrampoline;
import com.android.server.wm.ActivityInterceptorCallback;
@@ -545,6 +547,17 @@
}
}
+ @Override // Binder call
+ public boolean isVirtualDeviceOwnedMirrorDisplay(int displayId) {
+ if (getDeviceIdForDisplayId(displayId) == Context.DEVICE_ID_DEFAULT) {
+ return false;
+ }
+
+ DisplayManagerInternal displayManager = LocalServices.getService(
+ DisplayManagerInternal.class);
+ return displayManager.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY;
+ }
+
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
final UserHandle userHandle = getCallingUserHandle();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 087cf20..e475fe6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -31,6 +31,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
@@ -57,6 +58,7 @@
import android.app.compat.CompatChanges;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.flags.Flags;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.BroadcastReceiver;
@@ -1482,7 +1484,12 @@
if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
}
- if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0 && virtualDevice != null) {
+ // Put the display in the virtual device's display group only if it's not a mirror display,
+ // and if it doesn't need its own display group. So effectively, mirror displays go into the
+ // default display group.
+ if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0
+ && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0
+ && virtualDevice != null) {
flags |= VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
}
@@ -1516,11 +1523,16 @@
if (callingUid != Process.SYSTEM_UID
&& (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
- if (!canProjectVideo(projection)) {
+ // Only a valid media projection or a virtual device can create a mirror virtual
+ // display.
+ if (!canProjectVideo(projection)
+ && !isMirroringSupportedByVirtualDevice(virtualDevice)) {
throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+ "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+ "MediaProjection token in order to create a screen sharing virtual "
- + "display.");
+ + "display. In order to create a virtual display that does not perform"
+ + "screen sharing (mirroring), please use the flag"
+ + "VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY.");
}
}
if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
@@ -1540,6 +1552,15 @@
}
}
+ // Mirror virtual displays created by a virtual device are not allowed to show
+ // presentations.
+ if (virtualDevice != null && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0
+ && (flags & VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) {
+ Slog.d(TAG, "Mirror displays created by a virtual device cannot show "
+ + "presentations, hence ignoring flag VIRTUAL_DISPLAY_FLAG_PRESENTATION.");
+ flags &= ~VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+ }
+
if (callingUid != Process.SYSTEM_UID
&& (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
// The virtualDevice instance has been validated above using isValidVirtualDevice
@@ -1739,6 +1760,10 @@
return -1;
}
+ private static boolean isMirroringSupportedByVirtualDevice(IVirtualDevice virtualDevice) {
+ return Flags.interactiveScreenMirror() && virtualDevice != null;
+ }
+
private void resizeVirtualDisplayInternal(IBinder appToken,
int width, int height, int densityDpi) {
synchronized (mSyncRoot) {
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 16d72e4..163d248 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -18,10 +18,13 @@
import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
@@ -60,6 +63,7 @@
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceManager;
import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.flags.Flags;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.content.ContextWrapper;
@@ -92,6 +96,7 @@
import android.os.MessageQueue;
import android.os.Process;
import android.os.RemoteException;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayCutout;
@@ -181,6 +186,8 @@
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Rule(order = 1)
public Expect expect = Expect.create();
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@@ -312,7 +319,6 @@
@Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
@Mock PackageManagerInternal mMockPackageManagerInternal;
-
@Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@Mock DisplayManagerFlags mMockFlags;
@@ -320,6 +326,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
+ mSetFlagsRule.disableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mMockInputManagerInternal);
@@ -1140,6 +1147,236 @@
0);
}
+ /**
+ * Tests that it's not allowed to create an auto-mirror virtual display without
+ * CAPTURE_VIDEO_OUTPUT permission or a virtual device.
+ */
+ @Test
+ public void createAutoMirrorDisplay_withoutPermission_withoutVirtualDevice_throwsException() {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT)).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+ .setUniqueId("uniqueId --- mirror display");
+ assertThrows(SecurityException.class, () -> {
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ null /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ });
+ }
+
+ /**
+ * Tests that it's not allowed to create an auto-mirror virtual display when display mirroring
+ * is not supported in a virtual device.
+ */
+ @Test
+ public void createAutoMirrorDisplay_virtualDeviceDoesntSupportMirroring_throwsException()
+ throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT)).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+ .setUniqueId("uniqueId --- mirror display");
+ assertThrows(SecurityException.class, () -> {
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ });
+ }
+
+ /**
+ * Tests that the virtual display is added to the default display group when created with
+ * VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR using a virtual device.
+ */
+ @Test
+ public void createAutoMirrorVirtualDisplay_addsDisplayToDefaultDisplayGroup() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+ // Create an auto-mirror virtual display using a virtual device.
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+ .setUniqueId("uniqueId --- default display group");
+ int displayId =
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // The virtual display should be in the default display group.
+ assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ localService.getDisplayInfo(displayId).displayGroupId);
+ }
+
+ /**
+ * Tests that the virtual display mirrors the default display when created with
+ * VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR using a virtual device.
+ */
+ @Test
+ public void createAutoMirrorVirtualDisplay_mirrorsDefaultDisplay() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+ // Create an auto-mirror virtual display using a virtual device.
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+ .setUniqueId("uniqueId --- mirror display");
+ int displayId =
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // The virtual display should mirror the default display.
+ assertEquals(Display.DEFAULT_DISPLAY, localService.getDisplayIdToMirror(displayId));
+ }
+
+ /**
+ * Tests that the virtual display does not mirror any other display when created with
+ * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY using a virtual device.
+ */
+ @Test
+ public void createOwnContentOnlyVirtualDisplay_doesNotMirrorAnyDisplay() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+ // Create an auto-mirror virtual display using a virtual device.
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY)
+ .setUniqueId("uniqueId --- own content only display");
+ int displayId =
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // The virtual display should not mirror any display.
+ assertEquals(Display.INVALID_DISPLAY, localService.getDisplayIdToMirror(displayId));
+ // The virtual display should have FLAG_OWN_CONTENT_ONLY set.
+ assertEquals(DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY,
+ (displayManager.getDisplayDeviceInfoInternal(displayId).flags
+ & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY));
+ }
+
+ /**
+ * Tests that the virtual display should not be always unlocked when created with
+ * VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR using a virtual device.
+ */
+ @Test
+ public void createAutoMirrorVirtualDisplay_flagAlwaysUnlockedNotSet() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+ when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ // Create an auto-mirror virtual display using a virtual device.
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+ | VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+ .setUniqueId("uniqueId --- mirror display");
+ int displayId =
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // The virtual display should not have FLAG_ALWAYS_UNLOCKED set.
+ assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags
+ & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED));
+ }
+
+ /**
+ * Tests that the virtual display should not allow presentation when created with
+ * VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR using a virtual device.
+ */
+ @Test
+ public void createAutoMirrorVirtualDisplay_flagPresentationNotSet() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+ // Create an auto-mirror virtual display using a virtual device.
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+ | VIRTUAL_DISPLAY_FLAG_PRESENTATION)
+ .setUniqueId("uniqueId --- mirror display");
+ int displayId =
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // The virtual display should not have FLAG_PRESENTATION set.
+ assertEquals(0, (displayManager.getDisplayDeviceInfoInternal(displayId).flags
+ & DisplayDeviceInfo.FLAG_PRESENTATION));
+ }
+
@Test
public void testGetDisplayIdToMirror() throws Exception {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index a7c8a6c..b732d38 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -150,7 +150,7 @@
@Test
public void openNonBlockedAppOnVirtualDisplay_isNotBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -163,7 +163,7 @@
@Test
public void activityDoesNotSupportDisplayOnRemoteDevices_isBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -176,7 +176,7 @@
@Test
public void openBlockedComponentOnVirtualDisplay_isBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithBlockedComponent(BLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
BLOCKED_PACKAGE_NAME,
@@ -189,7 +189,7 @@
@Test
public void addActivityPolicyExemption_openBlockedOnVirtualDisplay_isBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
gwpc.setActivityLaunchDefaultAllowed(true);
gwpc.addActivityPolicyExemption(BLOCKED_COMPONENT);
@@ -204,7 +204,7 @@
@Test
public void openNotAllowedComponentOnBlocklistVirtualDisplay_isBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
BLOCKED_PACKAGE_NAME,
@@ -217,7 +217,7 @@
@Test
public void addActivityPolicyExemption_openNotAllowedOnVirtualDisplay_isBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
gwpc.setActivityLaunchDefaultAllowed(false);
gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT);
@@ -232,7 +232,7 @@
@Test
public void openAllowedComponentOnBlocklistVirtualDisplay_startsActivity() {
GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -245,7 +245,7 @@
@Test
public void addActivityPolicyExemption_openAllowedOnVirtualDisplay_startsActivity() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
gwpc.setActivityLaunchDefaultAllowed(false);
gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT);
@@ -258,9 +258,22 @@
}
@Test
+ public void openNonBlockedAppOnMirrorVirtualDisplay_isBlocked() {
+ GenericWindowPolicyController gwpc = createGwpc();
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ true);
+
+ ActivityInfo activityInfo = getActivityInfo(
+ NONBLOCKED_APP_PACKAGE_NAME,
+ NONBLOCKED_APP_PACKAGE_NAME,
+ /* displayOnRemoteDevices */ true,
+ /* targetDisplayCategory */ null);
+ assertNoActivityLaunched(gwpc, DISPLAY_ID, activityInfo);
+ }
+
+ @Test
public void canActivityBeLaunched_mismatchingUserHandle_isBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -274,7 +287,7 @@
@Test
public void canActivityBeLaunched_blockedAppStreamingComponent_isNeverBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
@@ -288,7 +301,7 @@
public void canActivityBeLaunched_blockedAppStreamingComponentExplicitlyBlocked_isNeverBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithBlockedComponent(
BLOCKED_APP_STREAMING_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
@@ -302,7 +315,7 @@
@Test
public void canActivityBeLaunched_blockedAppStreamingComponentExemptFromStreaming_isNeverBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
gwpc.setActivityLaunchDefaultAllowed(true);
gwpc.addActivityPolicyExemption(BLOCKED_APP_STREAMING_COMPONENT);
@@ -318,7 +331,7 @@
@Test
public void canActivityBeLaunched_blockedAppStreamingComponentNotAllowlisted_isNeverBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
@@ -332,7 +345,7 @@
@Test
public void canActivityBeLaunched_blockedAppStreamingComponentNotExemptFromBlocklist_isNeverBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
gwpc.setActivityLaunchDefaultAllowed(false);
gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT);
@@ -348,7 +361,7 @@
@Test
public void canActivityBeLaunched_customDisplayCategoryMatches_isNotBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithDisplayCategory(DISPLAY_CATEGORY);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -362,7 +375,7 @@
@Test
public void canActivityBeLaunched_customDisplayCategoryDoesNotMatch_isBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithDisplayCategory(DISPLAY_CATEGORY);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -375,7 +388,7 @@
@Test
public void canActivityBeLaunched_crossTaskLaunch_fromDefaultDisplay_isNotBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -390,7 +403,7 @@
public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_notExplicitlyBlocked_isNotBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationBlockedFor(
BLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -406,7 +419,7 @@
public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_explicitlyBlocked_isBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationBlockedFor(
BLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
BLOCKED_PACKAGE_NAME,
@@ -421,7 +434,7 @@
public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_notAllowed_isBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationAllowed(
NONBLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
BLOCKED_PACKAGE_NAME,
@@ -436,7 +449,7 @@
public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_allowed_isNotBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationAllowed(
NONBLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -450,7 +463,7 @@
@Test
public void canActivityBeLaunched_unsupportedWindowingMode_isBlocked() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -464,7 +477,7 @@
@Test
public void canActivityBeLaunched_permissionComponent_isBlocked() {
GenericWindowPolicyController gwpc = createGwpcWithPermissionComponent(BLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
BLOCKED_PACKAGE_NAME,
@@ -490,7 +503,7 @@
public void onRunningAppsChanged_empty_onDisplayEmpty() {
ArraySet<Integer> uids = new ArraySet<>();
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
gwpc.onRunningAppsChanged(uids);
@@ -585,7 +598,7 @@
public void onTopActivitychanged_activityListenerCallbackObserved() {
int userId = 1000;
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
gwpc.onTopActivityChanged(BLOCKED_COMPONENT, 0, userId);
verify(mActivityListener)
@@ -595,7 +608,7 @@
@Test
public void keepActivityOnWindowFlagsChanged_noChange() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -613,7 +626,7 @@
@Test
public void keepActivityOnWindowFlagsChanged_flagSecure_isAllowedAfterTM() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -631,7 +644,7 @@
@Test
public void keepActivityOnWindowFlagsChanged_systemFlagHideNonSystemOverlayWindows_isAllowedAfterTM() {
GenericWindowPolicyController gwpc = createGwpc();
- gwpc.setDisplayId(DISPLAY_ID);
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
ActivityInfo activityInfo = getActivityInfo(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -887,4 +900,14 @@
verify(mActivityBlockedCallback).onActivityBlocked(fromDisplay, activityInfo);
verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
}
+
+ private void assertNoActivityLaunched(GenericWindowPolicyController gwpc, int fromDisplay,
+ ActivityInfo activityInfo) {
+ assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, true))
+ .isFalse();
+
+ verify(mActivityBlockedCallback, never()).onActivityBlocked(fromDisplay, activityInfo);
+ verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
+ }
}
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 a3d415e..461d637 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
@@ -59,7 +59,6 @@
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.flags.Flags;
-import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
@@ -250,8 +249,6 @@
@Mock
private SensorManagerInternal mSensorManagerInternalMock;
@Mock
- private IVirtualSensorCallback mVirtualSensorCallback;
- @Mock
private VirtualSensorCallback mSensorCallback;
@Mock
private IVirtualDeviceActivityListener mActivityListener;
@@ -269,7 +266,6 @@
IPowerManager mIPowerManagerMock;
@Mock
IThermalService mIThermalServiceMock;
- private PowerManager mPowerManager;
@Mock
private IAudioRoutingCallback mRoutingCallback;
@Mock
@@ -361,9 +357,10 @@
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
mDevicePolicyManagerMock);
- mPowerManager = new PowerManager(mContext, mIPowerManagerMock, mIThermalServiceMock,
+ PowerManager powerManager = new PowerManager(mContext, mIPowerManagerMock,
+ mIThermalServiceMock,
new Handler(TestableLooper.get(this).getLooper()));
- when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+ when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
mInputManagerMockHelper = new InputManagerMockHelper(
TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
@@ -1604,6 +1601,54 @@
}
@Test
+ public void openNonBlockedAppOnMirrorDisplay_flagEnabled_cannotBeLaunched() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
+ when(mDisplayManagerInternalMock.getDisplayIdToMirror(anyInt()))
+ .thenReturn(Display.DEFAULT_DISPLAY);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
+ DISPLAY_ID_1);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ActivityInfo activityInfo = getActivityInfo(
+ NONBLOCKED_APP_PACKAGE_NAME,
+ NONBLOCKED_APP_PACKAGE_NAME,
+ /* displayOnRemoteDevices */ true,
+ /* targetDisplayCategory */ null);
+ assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/ false))
+ .isFalse();
+ // Verify that BlockedAppStreamingActivity also doesn't launch for mirror displays.
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfo, mAssociationInfo.getDisplayName());
+ verify(mContext, never()).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openNonBlockedAppOnMirrorDisplay_flagDisabled_launchesActivity() {
+ when(mDisplayManagerInternalMock.getDisplayIdToMirror(anyInt()))
+ .thenReturn(Display.DEFAULT_DISPLAY);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
+ DISPLAY_ID_1);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ActivityInfo activityInfo = getActivityInfo(
+ NONBLOCKED_APP_PACKAGE_NAME,
+ NONBLOCKED_APP_PACKAGE_NAME,
+ /* displayOnRemoteDevices */ true,
+ /* targetDisplayCategory */ null);
+ assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/ false))
+ .isTrue();
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfo, mAssociationInfo.getDisplayName());
+ verify(mContext, never()).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);