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);