Merge "Remove usages of isDisplayOccluded" into main
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index fa2d677..1d25ac7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -106,5 +106,10 @@
         Settings.Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV,
         Settings.Global.Wearable.REDUCE_MOTION,
         Settings.Global.Wearable.WEAR_LAUNCHER_UI_MODE,
+        Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
+        Settings.Global.Wearable.RSB_WAKE_ENABLED,
+        Settings.Global.Wearable.SCREENSHOT_ENABLED,
+        Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
+        Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
     };
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 203efbf..92f65d6 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -658,7 +658,6 @@
                     Settings.Global.Wearable.COMPANION_BLE_ROLE,
                     Settings.Global.Wearable.COMPANION_NAME,
                     Settings.Global.Wearable.COMPANION_APP_NAME,
-                    Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
                     Settings.Global.Wearable.COMPANION_OS_VERSION,
                     Settings.Global.Wearable.ENABLE_ALL_LANGUAGES,
                     Settings.Global.Wearable.SETUP_LOCALE,
@@ -673,16 +672,12 @@
                     Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED,
                     Settings.Global.Wearable.WET_MODE_ON,
                     Settings.Global.Wearable.COOLDOWN_MODE_ON,
-                    Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
-                    Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
                     Settings.Global.Wearable.BEDTIME_MODE,
                     Settings.Global.Wearable.BEDTIME_HARD_MODE,
-                    Settings.Global.Wearable.RSB_WAKE_ENABLED,
                     Settings.Global.Wearable.LOCK_SCREEN_STATE,
                     Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
                     Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
                     Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
-                    Settings.Global.Wearable.SCREENSHOT_ENABLED,
                     Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
                     Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
                     Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6778d5a..b5b873c 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -349,6 +349,9 @@
 
     <uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
 
+    <!-- Listen to (dis-)connection of external displays and enable / disable them. -->
+    <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
+
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
new file mode 100644
index 0000000..569dd4c
--- /dev/null
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -0,0 +1,69 @@
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_horizontal"
+    android:orientation="vertical"
+    android:paddingHorizontal="@dimen/dialog_side_padding"
+    android:paddingTop="@dimen/dialog_top_padding"
+    android:background="@*android:drawable/bottomsheet_background"
+    android:paddingBottom="@dimen/dialog_bottom_padding">
+
+    <ImageView
+        android:id="@+id/connected_display_dialog_icon"
+        android:layout_width="@dimen/screenrecord_logo_size"
+        android:layout_height="@dimen/screenrecord_logo_size"
+        android:importantForAccessibility="no"
+        android:src="@drawable/stat_sys_connected_display"
+        android:tint="?androidprv:attr/materialColorPrimary" />
+
+    <TextView
+        android:id="@+id/connected_display_dialog_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/screenrecord_title_margin_top"
+        android:gravity="center"
+        android:text="@string/connected_display_dialog_start_mirroring"
+        android:textAppearance="@style/TextAppearance.Dialog.Title" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/screenrecord_buttons_margin_top"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/cancel"
+            style="@style/Widget.Dialog.Button.BorderButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/cancel" />
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1" />
+
+        <Button
+            android:id="@+id/enable_display"
+            style="@style/Widget.Dialog.Button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/enable_display" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b37aeee..6840108 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3184,6 +3184,12 @@
     <!-- Label for a button that, when clicked, sends the user to the app store to install an app. [CHAR LIMIT=64]. -->
     <string name="install_app">Install app</string>
 
+    <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
+    <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
+
+    <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
+    <string name="enable_display">Enable display</string>
+
     <!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
     <string name="privacy_dialog_title">Microphone &amp; Camera</string>
     <!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index d3174f1..adb0bf3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -71,14 +71,9 @@
     private var resolved: Boolean = false
 
     @WorkerThread
-    fun resolvePanelActivity(
-        allowAllApps: Boolean = false
-    ) {
+    fun resolvePanelActivity() {
         if (resolved) return
         resolved = true
-        val validPackages = context.resources
-            .getStringArray(R.array.config_controlsPreferredPackages)
-        if (componentName.packageName !in validPackages && !allowAllApps) return
         panelActivity = _panelActivity?.let {
             val resolveInfos = mPm.queryIntentActivitiesAsUser(
                 Intent().setComponent(it),
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 83bec66..74e1dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.ActivityTaskManagerProxy
 import com.android.systemui.util.asIndenting
@@ -125,9 +124,8 @@
 
     private fun updateServices(newServices: List<ControlsServiceInfo>) {
         if (activityTaskManagerProxy.supportsMultiWindow(context)) {
-            val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
             newServices.forEach {
-                it.resolvePanelActivity(allowAllApps) }
+                it.resolvePanelActivity() }
         }
 
         if (newServices != availableServices) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 6fdb4ca..dcacd09 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -22,6 +22,7 @@
 import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactoryBase;
 import com.android.systemui.dagger.qualifiers.PerUser;
+import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
 import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
@@ -140,6 +141,7 @@
         getMediaMuteAwaitConnectionCli();
         getNearbyMediaDevicesManager();
         getUnfoldLatencyTracker().init();
+        getConnectingDisplayViewModel().init();
         getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
         getFoldStateLogger().ifPresent(FoldStateLogger::init);
         getUnfoldTransitionProgressProvider().ifPresent((progressProvider) ->
@@ -229,6 +231,11 @@
     NearbyMediaDevicesManager getNearbyMediaDevicesManager();
 
     /**
+     * Creates a ConnectingDisplayViewModel
+     */
+    ConnectingDisplayViewModel getConnectingDisplayViewModel();
+
+    /**
      * Returns {@link CoreStartable}s that should be started with the application.
      */
     Map<Class<?>, Provider<CoreStartable>> getStartables();
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 b18f7bf..0c8e293 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
@@ -22,6 +22,7 @@
 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
 import android.os.Handler
+import android.util.Log
 import android.view.Display
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -41,6 +42,13 @@
 interface DisplayRepository {
     /** Provides a nullable set of displays. */
     val displays: Flow<Set<Display>>
+
+    /**
+     * Pending display id that can be enabled/disabled.
+     *
+     * When `null`, it means there is no pending display waiting to be enabled.
+     */
+    val pendingDisplay: Flow<Int?>
 }
 
 @SysUISingleton
@@ -85,8 +93,59 @@
                 initialValue = getDisplays()
             )
 
-    fun getDisplays(): Set<Display> =
+    private fun getDisplays(): Set<Display> =
         traceSection("DisplayRepository#getDisplays()") {
             displayManager.displays?.toSet() ?: emptySet()
         }
+
+    override val pendingDisplay: Flow<Int?> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DisplayConnectionListener {
+                        private val pendingIds = mutableSetOf<Int>()
+                        override fun onDisplayConnected(id: Int) {
+                            pendingIds += id
+                            trySend(id)
+                        }
+
+                        override fun onDisplayDisconnected(id: Int) {
+                            if (id in pendingIds) {
+                                pendingIds -= id
+                                trySend(null)
+                            } else {
+                                Log.e(
+                                    TAG,
+                                    "onDisplayDisconnected received for unknown display. " +
+                                        "id=$id, knownIds=$pendingIds"
+                                )
+                            }
+                        }
+                    }
+                displayManager.registerDisplayListener(
+                    callback,
+                    backgroundHandler,
+                    DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+                )
+                awaitClose { displayManager.unregisterDisplayListener(callback) }
+            }
+            .flowOn(backgroundCoroutineDispatcher)
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null
+            )
+
+    private companion object {
+        const val TAG = "DisplayRepository"
+    }
+}
+
+/** Used to provide default implementations for all methods. */
+private interface DisplayConnectionListener : DisplayListener {
+
+    override fun onDisplayConnected(id: Int) {}
+    override fun onDisplayDisconnected(id: Int) {}
+    override fun onDisplayAdded(id: Int) {}
+    override fun onDisplayRemoved(id: Int) {}
+    override fun onDisplayChanged(id: Int) {}
 }
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 4b957c7..308b7d7 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,10 +16,13 @@
 
 package com.android.systemui.display.domain.interactor
 
+import android.hardware.display.DisplayManager
 import android.view.Display
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.util.traceSection
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -37,18 +40,31 @@
      */
     val connectedDisplayState: Flow<State>
 
+    /** Pending display that can be enabled to be used by the system. */
+    val pendingDisplay: Flow<PendingDisplay?>
+
     /** Possible connected display state. */
     enum class State {
         DISCONNECTED,
         CONNECTED,
         CONNECTED_SECURE,
     }
+
+    /** Represents a connected display that has not been enabled yet. */
+    interface PendingDisplay {
+        /** Enables the display, making it available to the system. */
+        fun enable()
+
+        /** Disables the display, making it unavailable to the system. */
+        fun disable()
+    }
 }
 
 @SysUISingleton
 class ConnectedDisplayInteractorImpl
 @Inject
 constructor(
+    private val displayManager: DisplayManager,
     displayRepository: DisplayRepository,
 ) : ConnectedDisplayInteractor {
 
@@ -70,4 +86,22 @@
                 }
             }
             .distinctUntilChanged()
+
+    override val pendingDisplay: Flow<PendingDisplay?> =
+        displayRepository.pendingDisplay.distinctUntilChanged().map { it?.toPendingDisplay() }
+
+    private fun Int.toPendingDisplay() =
+        object : PendingDisplay {
+            val id = this@toPendingDisplay
+            override fun enable() {
+                traceSection("DisplayRepository#enable($id)") {
+                    displayManager.enableConnectedDisplay(id)
+                }
+            }
+            override fun disable() {
+                traceSection("DisplayRepository#enable($id)") {
+                    displayManager.disableConnectedDisplay(id)
+                }
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
new file mode 100644
index 0000000..174c6ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.display.ui.view
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.WindowManager
+import android.widget.TextView
+import com.android.systemui.R
+
+/** Dialog used to decide what to do with a connected display. */
+class MirroringConfirmationDialog(
+    context: Context,
+    private val onStartMirroringClickListener: View.OnClickListener,
+    private val onDismissClickListener: View.OnClickListener,
+) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
+
+    private lateinit var mirrorButton: TextView
+    private lateinit var dismissButton: TextView
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        window?.apply {
+            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+            setGravity(Gravity.BOTTOM)
+        }
+        setContentView(R.layout.connected_display_dialog)
+        setCanceledOnTouchOutside(true)
+        mirrorButton =
+            requireViewById<TextView>(R.id.enable_display).apply {
+                setOnClickListener(onStartMirroringClickListener)
+            }
+        dismissButton =
+            requireViewById<TextView>(R.id.cancel).apply {
+                setOnClickListener(onDismissClickListener)
+            }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
new file mode 100644
index 0000000..ece33b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.display.ui.viewmodel
+
+import android.app.Dialog
+import android.content.Context
+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.PendingDisplay
+import com.android.systemui.display.ui.view.MirroringConfirmationDialog
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Shows/hides a dialog to allow the user to decide whether to use the external display for
+ * mirroring.
+ */
+@SysUISingleton
+class ConnectingDisplayViewModel
+@Inject
+constructor(
+    private val context: Context,
+    private val connectedDisplayInteractor: ConnectedDisplayInteractor,
+    @Application private val scope: CoroutineScope,
+) {
+
+    private var dialog: Dialog? = null
+
+    /** Starts listening for pending displays. */
+    fun init() {
+        connectedDisplayInteractor.pendingDisplay
+            .onEach { pendingDisplay ->
+                if (pendingDisplay == null) {
+                    hideDialog()
+                } else {
+                    showDialog(pendingDisplay)
+                }
+            }
+            .launchIn(scope)
+    }
+
+    private fun showDialog(pendingDisplay: PendingDisplay) {
+        hideDialog()
+        dialog =
+            MirroringConfirmationDialog(
+                    context,
+                    onStartMirroringClickListener = {
+                        pendingDisplay.enable()
+                        hideDialog()
+                    },
+                    onDismissClickListener = { hideDialog() }
+                )
+                .apply { show() }
+    }
+
+    private fun hideDialog() {
+        dialog?.hide()
+        dialog = null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6b578ba..dfbaf55 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -634,9 +634,6 @@
     // 1900
     @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
 
-    // 2000 - device controls
-    @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag("app_panels_all_apps_allowed")
-
     // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
     // TODO(b/259264861): Tracking Bug
     @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index b1061ba..74d0d21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.ActivityTaskManagerProxy
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -123,9 +122,6 @@
                         arrayOf(componentName.packageName)
                 )
 
-        // Return false by default, we'll test the true path
-        `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(false)
-
         val wrapper = object : ContextWrapper(mContext) {
             override fun createContextAsUser(user: UserHandle, flags: Int): Context {
                 return baseContext
@@ -469,38 +465,7 @@
     }
 
     @Test
-    fun testPackageNotPreferred_nullPanel() {
-        mContext.orCreateTestableResources
-                .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
-
-        val serviceInfo = ServiceInfo(
-                componentName,
-                activityName
-        )
-
-        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
-                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
-
-        setUpQueryResult(listOf(
-                ActivityInfo(
-                        activityName,
-                        exported = true,
-                        permission = Manifest.permission.BIND_CONTROLS
-                )
-        ))
-
-        val list = listOf(serviceInfo)
-        serviceListingCallbackCaptor.value.onServicesReloaded(list)
-
-        executor.runAllReady()
-
-        assertNull(controller.getCurrentServices()[0].panelActivity)
-    }
-
-    @Test
-    fun testPackageNotPreferred_allowAllApps_correctPanel() {
-        `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(true)
-
+    fun testPackageNotPreferred_correctPanel() {
         mContext.orCreateTestableResources
                 .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
 
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 9be54fb..7d836a0 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
@@ -165,13 +165,78 @@
             assertThat(value?.ids()).containsExactly(1, 2, 3, 4)
         }
 
+    @Test
+    fun onDisplayConnected_pendingDisplayReceived() =
+        testScope.runTest {
+            val pendingDisplay by latestPendingDisplayFlowValue()
+
+            displayListener.value.onDisplayConnected(1)
+
+            assertThat(pendingDisplay).isEqualTo(1)
+        }
+
+    @Test
+    fun onDisplayDisconnected_pendingDisplayNull() =
+        testScope.runTest {
+            val pendingDisplay by latestPendingDisplayFlowValue()
+            displayListener.value.onDisplayConnected(1)
+
+            assertThat(pendingDisplay).isNotNull()
+
+            displayListener.value.onDisplayDisconnected(1)
+
+            assertThat(pendingDisplay).isNull()
+        }
+
+    @Test
+    fun onDisplayDisconnected_unknownDisplay_doesNotSendNull() =
+        testScope.runTest {
+            val pendingDisplay by latestPendingDisplayFlowValue()
+            displayListener.value.onDisplayConnected(1)
+
+            assertThat(pendingDisplay).isNotNull()
+
+            displayListener.value.onDisplayDisconnected(2)
+
+            assertThat(pendingDisplay).isNotNull()
+        }
+
+    @Test
+    fun onDisplayConnected_multipleTimes_sendsOnlyTheLastOne() =
+        testScope.runTest {
+            val pendingDisplay by latestPendingDisplayFlowValue()
+            displayListener.value.onDisplayConnected(1)
+            displayListener.value.onDisplayConnected(2)
+
+            assertThat(pendingDisplay).isEqualTo(2)
+        }
+
     private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
 
     // Wrapper to capture the displayListener.
     private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
         val flowValue = collectLastValue(displayRepository.displays)
         verify(displayManager)
-            .registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong())
+            .registerDisplayListener(
+                displayListener.capture(),
+                eq(testHandler),
+                eq(
+                    DisplayManager.EVENT_FLAG_DISPLAY_ADDED or
+                        DisplayManager.EVENT_FLAG_DISPLAY_CHANGED or
+                        DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
+                )
+            )
+        return flowValue
+    }
+
+    private fun TestScope.latestPendingDisplayFlowValue(): FlowValue<Int?> {
+        val flowValue = collectLastValue(displayRepository.pendingDisplay)
+        verify(displayManager)
+            .registerDisplayListener(
+                displayListener.capture(),
+                eq(testHandler),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+            )
         return flowValue
     }
 
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 eb0ad69..fb19eca 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,6 +16,7 @@
 
 package com.android.systemui.display.domain.interactor
 
+import android.hardware.display.DisplayManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.Display
@@ -27,7 +28,10 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.display.data.repository.display
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -35,6 +39,7 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
@@ -42,9 +47,10 @@
 @SmallTest
 class ConnectedDisplayInteractorTest : SysuiTestCase() {
 
+    private val displayManager = mock<DisplayManager>()
     private val fakeDisplayRepository = FakeDisplayRepository()
     private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
-        ConnectedDisplayInteractorImpl(fakeDisplayRepository)
+        ConnectedDisplayInteractorImpl(displayManager, fakeDisplayRepository)
     private val testScope = TestScope(UnconfinedTestDispatcher())
 
     @Test
@@ -126,6 +132,42 @@
             assertThat(value).isEqualTo(State.CONNECTED_SECURE)
         }
 
+    @Test
+    fun pendingDisplay_propagated() =
+        testScope.runTest {
+            val value by lastPendingDisplay()
+            val pendingDisplayId = 4
+
+            fakeDisplayRepository.emit(pendingDisplayId)
+
+            assertThat(value).isNotNull()
+        }
+
+    @Test
+    fun onPendingDisplay_enable_displayEnabled() =
+        testScope.runTest {
+            val pendingDisplay by lastPendingDisplay()
+
+            fakeDisplayRepository.emit(1)
+            pendingDisplay!!.enable()
+
+            Mockito.verify(displayManager).enableConnectedDisplay(eq(1))
+        }
+
+    @Test
+    fun onPendingDisplay_disable_displayDisabled() =
+        testScope.runTest {
+            val pendingDisplay by lastPendingDisplay()
+
+            fakeDisplayRepository.emit(1)
+            pendingDisplay!!.disable()
+
+            Mockito.verify(displayManager).disableConnectedDisplay(eq(1))
+        }
+
     private fun TestScope.lastValue(): FlowValue<State?> =
         collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
+
+    private fun TestScope.lastPendingDisplay(): FlowValue<PendingDisplay?> =
+        collectLastValue(connectedDisplayStateProvider.pendingDisplay)
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
new file mode 100644
index 0000000..7059647
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.ui.view
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MirroringConfirmationDialogTest : SysuiTestCase() {
+
+    private lateinit var dialog: MirroringConfirmationDialog
+
+    private val onStartMirroringCallback = mock<View.OnClickListener>()
+    private val onCancelCallback = mock<View.OnClickListener>()
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        dialog = MirroringConfirmationDialog(context, onStartMirroringCallback, onCancelCallback)
+    }
+
+    @Test
+    fun startMirroringButton_clicked_callsCorrectCallback() {
+        dialog.show()
+
+        dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+        verify(onStartMirroringCallback).onClick(any())
+        verify(onCancelCallback, never()).onClick(any())
+    }
+
+    @Test
+    fun cancelButton_clicked_callsCorrectCallback() {
+        dialog.show()
+
+        dialog.requireViewById<View>(R.id.cancel).callOnClick()
+
+        verify(onCancelCallback).onClick(any())
+        verify(onStartMirroringCallback, never()).onClick(any())
+    }
+
+    @After
+    fun teardown() {
+        if (::dialog.isInitialized) {
+            dialog.dismiss()
+        }
+    }
+}
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 786856b..66d2465 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
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 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
@@ -105,5 +106,7 @@
         suspend fun emit(value: ConnectedDisplayInteractor.State) = flow.emit(value)
         override val connectedDisplayState: Flow<ConnectedDisplayInteractor.State>
             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 9795b9d..71c27de 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
@@ -29,6 +29,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 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
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
@@ -316,5 +317,7 @@
         suspend fun emit(value: State) = flow.emit(value)
         override val connectedDisplayState: Flow<State>
             get() = flow
+        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 715d661..d54ef73 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
@@ -33,10 +33,17 @@
 /** Fake [DisplayRepository] implementation for testing. */
 class FakeDisplayRepository : DisplayRepository {
     private val flow = MutableSharedFlow<Set<Display>>()
+    private val pendingDisplayFlow = MutableSharedFlow<Int?>()
 
     /** Emits [value] as [displays] flow value. */
     suspend fun emit(value: Set<Display>) = flow.emit(value)
 
+    /** Emits [value] as [pendingDisplay] flow value. */
+    suspend fun emit(value: Int?) = pendingDisplayFlow.emit(value)
+
     override val displays: Flow<Set<Display>>
         get() = flow
+
+    override val pendingDisplay: Flow<Int?>
+        get() = pendingDisplayFlow
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index c3db241..4165911 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -26,7 +26,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -35,9 +34,14 @@
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import static java.util.Objects.requireNonNull;
+
 import android.app.ActivityThread;
 import android.app.IApplicationThread;
+import android.app.WindowConfiguration;
+import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
@@ -50,6 +54,8 @@
 import android.window.WindowContextInfo;
 import android.window.WindowTokenClient;
 
+import androidx.annotation.NonNull;
+
 import com.android.server.inputmethod.InputMethodDialogWindowContext;
 
 import org.junit.After;
@@ -58,6 +64,9 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 // TODO(b/157888351): Move the test to inputmethod package once we find the way to test the
 //  scenario there.
 /**
@@ -138,44 +147,96 @@
     @Test
     public void testGetSettingsContextOnDualDisplayContent() {
         final Context context = mWindowContext.get(mSecondaryDisplay.getDisplayId());
+        final MaxBoundsVerifier maxBoundsVerifier = new MaxBoundsVerifier();
+        context.registerComponentCallbacks(maxBoundsVerifier);
+
         final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken();
-        assertNotNull(tokenClient);
-        spyOn(tokenClient);
+        spyOn(requireNonNull(tokenClient));
 
         final DisplayArea.Tokens imeContainer = mSecondaryDisplay.getImeContainer();
         spyOn(imeContainer);
         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay);
 
-        mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer);
+        final DisplayAreaGroup firstDaGroup = mSecondaryDisplay.mFirstRoot;
+        maxBoundsVerifier.setMaxBounds(firstDaGroup.getMaxBounds());
+
+        firstDaGroup.placeImeContainer(imeContainer);
 
         verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
-                eq(mSecondaryDisplay.mFirstRoot.getConfiguration()));
+                eq(firstDaGroup.getConfiguration()));
         verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
-                eq(mSecondaryDisplay.mFirstRoot.getConfiguration()),
+                eq(firstDaGroup.getConfiguration()),
                 eq(mSecondaryDisplay.mDisplayId));
-        assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot);
+        assertThat(imeContainer.getRootDisplayArea()).isEqualTo(firstDaGroup);
+        maxBoundsVerifier.waitAndAssertMaxMetricsMatches();
         assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
 
         // Clear the previous invocation histories in case we may count the previous
         // onConfigurationChanged invocation into the next verification.
         clearInvocations(tokenClient, imeContainer);
-        mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer);
+        final DisplayAreaGroup secondDaGroup = mSecondaryDisplay.mSecondRoot;
+        maxBoundsVerifier.setMaxBounds(secondDaGroup.getMaxBounds());
+
+        secondDaGroup.placeImeContainer(imeContainer);
 
         verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
-                eq(mSecondaryDisplay.mSecondRoot.getConfiguration()));
+                eq(secondDaGroup.getConfiguration()));
         verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
-                eq(mSecondaryDisplay.mSecondRoot.getConfiguration()),
+                eq(secondDaGroup.getConfiguration()),
                 eq(mSecondaryDisplay.mDisplayId));
-        assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot);
+        assertThat(imeContainer.getRootDisplayArea()).isEqualTo(secondDaGroup);
+        maxBoundsVerifier.waitAndAssertMaxMetricsMatches();
         assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
     }
 
     private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) {
         assertThat(context.getDisplayId()).isEqualTo(dc.getDisplayId());
 
+        final Rect imeContainerBounds = dc.getImeContainer().getBounds();
         final Rect contextBounds = context.getSystemService(WindowManager.class)
                 .getMaximumWindowMetrics().getBounds();
-        final Rect imeContainerBounds = dc.getImeContainer().getBounds();
         assertThat(contextBounds).isEqualTo(imeContainerBounds);
     }
+
+    private static final class MaxBoundsVerifier implements ComponentCallbacks {
+
+        private CountDownLatch mLatch;
+
+        private Rect mMaxBounds;
+
+        /**
+         * Sets max bounds to verify whether it matches the
+         * {@link WindowConfiguration#getMaxBounds()} reported from
+         * {@link #onConfigurationChanged(Configuration)} callback, and also resets the count down
+         * latch.
+         *
+         * @param maxBounds max bounds to verify
+         */
+        private void setMaxBounds(@NonNull Rect maxBounds) {
+            mMaxBounds = maxBounds;
+            mLatch = new CountDownLatch(1);
+        }
+
+        /**
+         * Waits for the {@link #onConfigurationChanged(Configuration)} callback whose the reported
+         * {@link WindowConfiguration#getMaxBounds()} matches {@link #mMaxBounds}.
+         */
+        private void waitAndAssertMaxMetricsMatches() {
+            try {
+                assertThat(mLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Test failed because of " + e);
+            }
+        }
+
+        @Override
+        public void onConfigurationChanged(@NonNull Configuration newConfig) {
+            if (newConfig.windowConfiguration.getMaxBounds().equals(mMaxBounds)) {
+                mLatch.countDown();
+            }
+        }
+
+        @Override
+        public void onLowMemory() {}
+    }
 }