Merge "Introduce vibrator service effect pipeline support" into main
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 8e35843e..05dc910 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -64,7 +64,9 @@
     ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
     ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS(
             Flags::enableDesktopWindowingTaskbarRunningApps, true),
+    // TODO: b/369763947 - remove this once ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS is ramped up
     ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
+    ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
     ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false),
     ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
             Flags::enableWindowingTransitionHandlersObservers, false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 52262e6..97397ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.dagger;
 
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS;
 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS;
 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
 
@@ -727,7 +728,8 @@
             Transitions transitions,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             InteractionJankMonitor interactionJankMonitor) {
-        return Flags.enableDesktopWindowingTransitions()
+        return (Flags.enableDesktopWindowingTransitions() ||
+            ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue())
                 ? new SpringDragToDesktopTransitionHandler(context, transitions,
                         rootTaskDisplayAreaOrganizer, interactionJankMonitor)
                 : new DefaultDragToDesktopTransitionHandler(context, transitions,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d7b9683..ef3b677 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -318,9 +318,6 @@
         "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt",
         "tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt",
         "tests/src/**/keyguard/ClockEventControllerTest.kt",
-        "tests/src/**/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt",
-        "tests/src/**/keyguard/LegacyLockIconViewControllerBaseTest.kt",
-        "tests/src/**/keyguard/LegacyLockIconViewControllerTest.java",
         "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
@@ -417,7 +414,6 @@
         "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt",
         "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt",
         "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
-        "tests/src/**/keyguard/LegacyLockIconViewControllerBaseTest.java",
         "tests/src/**/keyguard/CarrierTextManagerTest.java",
         "tests/src/**/systemui/ScreenDecorationsTest.java",
         "tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c79c044..540115d 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1487,3 +1487,13 @@
   description: "Enables notes role qs tile which opens default notes role app in app bubbles"
   bug: "357863750"
 }
+
+flag {
+   name: "media_projection_request_attribution_fix"
+   namespace: "systemui"
+   description: "Ensure MediaProjection consent requests are properly attributed"
+   bug: "373581993"
+   metadata {
+       purpose: PURPOSE_BUGFIX
+   }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
new file mode 100644
index 0000000..cdf6bda
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.hearingdevices.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel
+import com.android.systemui.qs.tiles.impl.hearingdevices.qsHearingDevicesTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HearingDevicesTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val qsTileConfig = kosmos.qsHearingDevicesTileConfig
+    private val mapper by lazy {
+        HearingDevicesTileMapper(
+            context.orCreateTestableResources
+                .apply { addOverride(R.drawable.qs_hearing_devices_icon, TestStubDrawable()) }
+                .resources,
+            context.theme,
+        )
+    }
+
+    @Test
+    fun map_anyActiveHearingDevice_anyPairedHearingDevice_activeState() {
+        val tileState: QSTileState =
+            mapper.map(
+                qsTileConfig,
+                HearingDevicesTileModel(
+                    isAnyActiveHearingDevice = true,
+                    isAnyPairedHearingDevice = true,
+                ),
+            )
+        val expectedState =
+            createHearingDevicesTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.quick_settings_hearing_devices_connected),
+            )
+        QSTileStateSubject.assertThat(tileState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_noActiveHearingDevice_anyPairedHearingDevice_inactiveState() {
+        val tileState: QSTileState =
+            mapper.map(
+                qsTileConfig,
+                HearingDevicesTileModel(
+                    isAnyActiveHearingDevice = false,
+                    isAnyPairedHearingDevice = true,
+                ),
+            )
+        val expectedState =
+            createHearingDevicesTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(R.string.quick_settings_hearing_devices_disconnected),
+            )
+        QSTileStateSubject.assertThat(tileState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun map_noActiveHearingDevice_noPairedHearingDevice_inactiveState() {
+        val tileState: QSTileState =
+            mapper.map(
+                qsTileConfig,
+                HearingDevicesTileModel(
+                    isAnyActiveHearingDevice = false,
+                    isAnyPairedHearingDevice = false,
+                ),
+            )
+        val expectedState =
+            createHearingDevicesTileState(QSTileState.ActivationState.INACTIVE, secondaryLabel = "")
+        QSTileStateSubject.assertThat(tileState).isEqualTo(expectedState)
+    }
+
+    private fun createHearingDevicesTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String,
+    ): QSTileState {
+        val label = context.getString(R.string.quick_settings_hearing_devices_label)
+        val iconRes = R.drawable.qs_hearing_devices_icon
+        return QSTileState(
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            iconRes,
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.Chevron,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName,
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt
new file mode 100644
index 0000000..1dfa2cd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.hearingdevices.domain.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.hearingaid.HearingDevicesChecker
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel
+import com.android.systemui.statusbar.policy.fakeBluetoothController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class HearingDevicesTileDataInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val testUser = UserHandle.of(1)
+
+    private val controller = kosmos.fakeBluetoothController
+    private lateinit var underTest: HearingDevicesTileDataInteractor
+
+    @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+    @Mock private lateinit var checker: HearingDevicesChecker
+
+    @Before
+    fun setup() {
+        underTest = HearingDevicesTileDataInteractor(testScope.testScheduler, controller, checker)
+    }
+
+    @EnableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
+    @Test
+    fun availability_flagEnabled_returnTrue() =
+        testScope.runTest {
+            val availability by collectLastValue(underTest.availability(testUser))
+
+            assertThat(availability).isTrue()
+        }
+
+    @DisableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
+    @Test
+    fun availability_flagDisabled_returnFalse() =
+        testScope.runTest {
+            val availability by collectLastValue(underTest.availability(testUser))
+
+            assertThat(availability).isFalse()
+        }
+
+    @Test
+    fun tileData_bluetoothStateChanged_dataMatchesChecker() =
+        testScope.runTest {
+            val flowValues: List<HearingDevicesTileModel> by
+                collectValues(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+            assertThat(flowValues.size).isEqualTo(1) // from addCallback in setup()
+
+            whenever(checker.isAnyPairedHearingDevice).thenReturn(false)
+            whenever(checker.isAnyActiveHearingDevice).thenReturn(false)
+            controller.isBluetoothEnabled = false
+            runCurrent()
+            assertThat(flowValues.size).isEqualTo(1) // model unchanged, no new flow value
+
+            whenever(checker.isAnyPairedHearingDevice).thenReturn(true)
+            whenever(checker.isAnyActiveHearingDevice).thenReturn(false)
+            controller.isBluetoothEnabled = true
+            runCurrent()
+            assertThat(flowValues.size).isEqualTo(2)
+
+            whenever(checker.isAnyPairedHearingDevice).thenReturn(true)
+            whenever(checker.isAnyActiveHearingDevice).thenReturn(true)
+            controller.isBluetoothEnabled = true
+            runCurrent()
+            assertThat(flowValues.size).isEqualTo(3)
+
+            assertThat(flowValues.map { it.isAnyPairedHearingDevice })
+                .containsExactly(false, true, true)
+                .inOrder()
+            assertThat(flowValues.map { it.isAnyActiveHearingDevice })
+                .containsExactly(false, false, true)
+                .inOrder()
+        }
+
+    @Test
+    fun tileData_bluetoothDeviceChanged_dataMatchesChecker() =
+        testScope.runTest {
+            val flowValues: List<HearingDevicesTileModel> by
+                collectValues(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+            assertThat(flowValues.size).isEqualTo(1) // from addCallback in setup()
+
+            whenever(checker.isAnyPairedHearingDevice).thenReturn(false)
+            whenever(checker.isAnyActiveHearingDevice).thenReturn(false)
+            controller.onBluetoothDevicesChanged()
+            runCurrent()
+            assertThat(flowValues.size).isEqualTo(1) // model unchanged, no new flow value
+
+            whenever(checker.isAnyPairedHearingDevice).thenReturn(true)
+            whenever(checker.isAnyActiveHearingDevice).thenReturn(false)
+            controller.onBluetoothDevicesChanged()
+            runCurrent()
+            assertThat(flowValues.size).isEqualTo(2)
+
+            whenever(checker.isAnyPairedHearingDevice).thenReturn(true)
+            whenever(checker.isAnyActiveHearingDevice).thenReturn(true)
+            controller.onBluetoothDevicesChanged()
+            runCurrent()
+            assertThat(flowValues.size).isEqualTo(3)
+
+            assertThat(flowValues.map { it.isAnyPairedHearingDevice })
+                .containsExactly(false, true, true)
+                .inOrder()
+            assertThat(flowValues.map { it.isAnyActiveHearingDevice })
+                .containsExactly(false, false, true)
+                .inOrder()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..00ee1c3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.hearingdevices.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager
+import com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.Companion.LAUNCH_SOURCE_QS_TILE
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.verify
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class HearingDevicesTileUserActionInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+    private lateinit var underTest: HearingDevicesTileUserActionInteractor
+
+    @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+    @Mock private lateinit var dialogManager: HearingDevicesDialogManager
+
+    @Before
+    fun setUp() {
+        underTest =
+            HearingDevicesTileUserActionInteractor(
+                testScope.coroutineContext,
+                inputHandler,
+                dialogManager,
+            )
+    }
+
+    @Test
+    fun handleClick_launchDialog() =
+        testScope.runTest {
+            val input =
+                HearingDevicesTileModel(
+                    isAnyActiveHearingDevice = true,
+                    isAnyPairedHearingDevice = true,
+                )
+
+            underTest.handleInput(QSTileInputTestKtx.click(input))
+
+            verify(dialogManager).showDialog(anyOrNull(), eq(LAUNCH_SOURCE_QS_TILE))
+        }
+
+    @Test
+    fun handleLongClick_launchSettings() =
+        testScope.runTest {
+            val input =
+                HearingDevicesTileModel(
+                    isAnyActiveHearingDevice = true,
+                    isAnyPairedHearingDevice = true,
+                )
+
+            underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+            QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+                assertThat(it.intent.action).isEqualTo(Settings.ACTION_HEARING_DEVICES_SETTINGS)
+            }
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
new file mode 100644
index 0000000..8dcc444
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2024 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.statusbar.core
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Expect
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
+    @get:Rule val expect: Expect = Expect.create()
+
+    private val kosmos =
+        testKosmos().also {
+            it.statusBarOrchestratorFactory = it.fakeStatusBarOrchestratorFactory
+            it.statusBarInitializerStore = it.fakeStatusBarInitializerStore
+        }
+    private val testScope = kosmos.testScope
+    private val fakeDisplayRepository = kosmos.displayRepository
+    private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory
+    private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
+
+    // Lazy, so that @EnableFlags is set before initializer is instantiated.
+    private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
+
+    @Test
+    fun start_startsInitializersForCurrentDisplays() =
+        testScope.runTest {
+            fakeDisplayRepository.addDisplay(displayId = 1)
+            fakeDisplayRepository.addDisplay(displayId = 2)
+
+            underTest.start()
+            runCurrent()
+
+            expect
+                .that(fakeInitializerStore.forDisplay(displayId = 1).startedByCoreStartable)
+                .isTrue()
+            expect
+                .that(fakeInitializerStore.forDisplay(displayId = 2).startedByCoreStartable)
+                .isTrue()
+        }
+
+    @Test
+    fun start_startsOrchestratorForCurrentDisplays() =
+        testScope.runTest {
+            fakeDisplayRepository.addDisplay(displayId = 1)
+            fakeDisplayRepository.addDisplay(displayId = 2)
+
+            underTest.start()
+            runCurrent()
+
+            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 1)!!).start()
+            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 2)!!).start()
+        }
+
+    @Test
+    fun displayAdded_orchestratorForNewDisplayIsStarted() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+        }
+
+    @Test
+    fun displayAdded_initializerForNewDisplayIsStarted() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            expect
+                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
+                .isTrue()
+        }
+
+    @Test
+    fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
+        testScope.runTest {
+            underTest.start()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            expect
+                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
+                .isTrue()
+        }
+
+    @Test
+    fun displayAddedDuringStart_orchestratorForNewDisplayIsStarted() =
+        testScope.runTest {
+            underTest.start()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            expect
+                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
+                .isTrue()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
index f64387c..c737bf7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
@@ -60,10 +60,9 @@
 
     val underTest =
         StatusBarInitializerImpl(
-            displayId = context.displayId,
-            statusBarWindowControllerStore = windowControllerStore,
             collapsedStatusBarFragmentProvider = { mock(CollapsedStatusBarFragment::class.java) },
             creationListeners = setOf(),
+            statusBarWindowController = windowController,
         )
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
index bb3fb1e..ab8e878 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
@@ -38,13 +38,10 @@
 import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT_TRANSPARENT
 import com.android.systemui.statusbar.data.model.StatusBarMode.OPAQUE
 import com.android.systemui.statusbar.data.model.StatusBarMode.TRANSPARENT
-import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
-import com.android.systemui.statusbar.phone.mockPhoneStatusBarTransitions
-import com.android.systemui.statusbar.phone.mockPhoneStatusBarViewController
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
 import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
-import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStateRepositoryStore
-import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
-import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
+import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStatePerDisplayRepository
+import com.android.systemui.statusbar.window.fakeStatusBarWindowController
 import com.android.systemui.testKosmos
 import com.android.wm.shell.bubbles.bubbles
 import com.google.common.truth.Truth.assertThat
@@ -60,25 +57,20 @@
 @RunWith(AndroidJUnit4::class)
 class StatusBarOrchestratorTest : SysuiTestCase() {
 
-    private val kosmos =
-        testKosmos().also {
-            it.testDispatcher = it.unconfinedTestDispatcher
-            it.statusBarWindowStateRepositoryStore = it.fakeStatusBarWindowStateRepositoryStore
-        }
+    private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher }
     private val testScope = kosmos.testScope
-    private val statusBarViewController = kosmos.mockPhoneStatusBarViewController
-    private val statusBarWindowControllerStore = kosmos.fakeStatusBarWindowControllerStore
-    private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
-    private val pluginDependencyProvider = kosmos.mockPluginDependencyProvider
-    private val notificationShadeWindowViewController =
+    private val fakeStatusBarModePerDisplayRepository = kosmos.fakeStatusBarModePerDisplayRepository
+    private val mockPluginDependencyProvider = kosmos.mockPluginDependencyProvider
+    private val mockNotificationShadeWindowViewController =
         kosmos.mockNotificationShadeWindowViewController
-    private val shadeSurface = kosmos.mockShadeSurface
-    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
-    private val fakeStatusBarWindowStateRepositoryStore =
-        kosmos.fakeStatusBarWindowStateRepositoryStore
+    private val mockShadeSurface = kosmos.mockShadeSurface
+    private val fakeBouncerRepository = kosmos.fakeKeyguardBouncerRepository
+    private val fakeStatusBarWindowStatePerDisplayRepository =
+        kosmos.fakeStatusBarWindowStatePerDisplayRepository
     private val fakePowerRepository = kosmos.fakePowerRepository
-    private val mockPhoneStatusBarTransitions = kosmos.mockPhoneStatusBarTransitions
     private val mockBubbles = kosmos.bubbles
+    private val fakeStatusBarWindowController = kosmos.fakeStatusBarWindowController
+    private val fakeStatusBarInitializer = kosmos.fakeStatusBarInitializer
 
     private val orchestrator = kosmos.statusBarOrchestrator
 
@@ -86,30 +78,31 @@
     fun start_setsUpPluginDependencies() {
         orchestrator.start()
 
-        verify(pluginDependencyProvider).allowPluginDependency(DarkIconDispatcher::class.java)
-        verify(pluginDependencyProvider).allowPluginDependency(StatusBarStateController::class.java)
+        verify(mockPluginDependencyProvider).allowPluginDependency(DarkIconDispatcher::class.java)
+        verify(mockPluginDependencyProvider)
+            .allowPluginDependency(StatusBarStateController::class.java)
     }
 
     @Test
     fun start_attachesWindow() {
         orchestrator.start()
 
-        assertThat(statusBarWindowControllerStore.defaultDisplay.isAttached).isTrue()
+        assertThat(fakeStatusBarWindowController.isAttached).isTrue()
     }
 
     @Test
     fun start_setsStatusBarControllerOnShade() {
         orchestrator.start()
 
-        verify(notificationShadeWindowViewController)
-            .setStatusBarViewController(statusBarViewController)
+        verify(mockNotificationShadeWindowViewController)
+            .setStatusBarViewController(fakeStatusBarInitializer.statusBarViewController)
     }
 
     @Test
     fun start_updatesShadeExpansion() {
         orchestrator.start()
 
-        verify(shadeSurface).updateExpansionAndVisibility()
+        verify(mockShadeSurface).updateExpansionAndVisibility()
     }
 
     @Test
@@ -117,9 +110,9 @@
         testScope.runTest {
             orchestrator.start()
 
-            bouncerRepository.setPrimaryShow(isShowing = true)
+            fakeBouncerRepository.setPrimaryShow(isShowing = true)
 
-            verify(statusBarViewController)
+            verify(fakeStatusBarInitializer.statusBarViewController)
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
         }
 
@@ -128,9 +121,9 @@
         testScope.runTest {
             orchestrator.start()
 
-            bouncerRepository.setPrimaryShow(isShowing = false)
+            fakeBouncerRepository.setPrimaryShow(isShowing = false)
 
-            verify(statusBarViewController)
+            verify(fakeStatusBarInitializer.statusBarViewController)
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
         }
 
@@ -141,7 +134,7 @@
 
             orchestrator.start()
 
-            verify(mockPhoneStatusBarTransitions).finishAnimations()
+            verify(fakeStatusBarInitializer.statusBarTransitions).finishAnimations()
         }
 
     @Test
@@ -151,7 +144,7 @@
 
             orchestrator.start()
 
-            verify(mockPhoneStatusBarTransitions, never()).finishAnimations()
+            verify(fakeStatusBarInitializer.statusBarTransitions, never()).finishAnimations()
         }
 
     @Test
@@ -208,7 +201,7 @@
 
             orchestrator.start()
 
-            verify(mockPhoneStatusBarTransitions)
+            verify(fakeStatusBarInitializer.statusBarTransitions)
                 .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
         }
 
@@ -222,19 +215,19 @@
 
             orchestrator.start()
 
-            verify(mockPhoneStatusBarTransitions)
+            verify(fakeStatusBarInitializer.statusBarTransitions)
                 .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
 
             setStatusBarMode(OPAQUE)
-            verify(mockPhoneStatusBarTransitions)
+            verify(fakeStatusBarInitializer.statusBarTransitions)
                 .transitionTo(OPAQUE.toTransitionModeInt(), /* animate= */ true)
 
             setStatusBarMode(LIGHTS_OUT)
-            verify(mockPhoneStatusBarTransitions)
+            verify(fakeStatusBarInitializer.statusBarTransitions)
                 .transitionTo(LIGHTS_OUT.toTransitionModeInt(), /* animate= */ true)
 
             setStatusBarMode(LIGHTS_OUT_TRANSPARENT)
-            verify(mockPhoneStatusBarTransitions)
+            verify(fakeStatusBarInitializer.statusBarTransitions)
                 .transitionTo(LIGHTS_OUT_TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
         }
 
@@ -248,7 +241,7 @@
 
             orchestrator.start()
 
-            verify(mockPhoneStatusBarTransitions)
+            verify(fakeStatusBarInitializer.statusBarTransitions)
                 .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
         }
 
@@ -262,7 +255,7 @@
 
             orchestrator.start()
 
-            verify(mockPhoneStatusBarTransitions)
+            verify(fakeStatusBarInitializer.statusBarTransitions)
                 .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
         }
 
@@ -276,7 +269,7 @@
 
             orchestrator.start()
 
-            verify(mockPhoneStatusBarTransitions)
+            verify(fakeStatusBarInitializer.statusBarTransitions)
                 .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
         }
 
@@ -295,7 +288,7 @@
             setTransientStatusBar()
             clearTransientStatusBar()
 
-            verify(mockPhoneStatusBarTransitions, times(1))
+            verify(fakeStatusBarInitializer.statusBarTransitions, times(1))
                 .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
         }
 
@@ -318,18 +311,18 @@
     }
 
     private fun setTransientStatusBar() {
-        statusBarModeRepository.defaultDisplay.showTransient()
+        fakeStatusBarModePerDisplayRepository.showTransient()
     }
 
     private fun clearTransientStatusBar() {
-        statusBarModeRepository.defaultDisplay.clearTransient()
+        fakeStatusBarModePerDisplayRepository.clearTransient()
     }
 
     private fun setStatusBarWindowState(state: StatusBarWindowState) {
-        fakeStatusBarWindowStateRepositoryStore.defaultDisplay.setWindowState(state)
+        fakeStatusBarWindowStatePerDisplayRepository.setWindowState(state)
     }
 
     private fun setStatusBarMode(statusBarMode: StatusBarMode) {
-        statusBarModeRepository.defaultDisplay.statusBarMode.value = statusBarMode
+        fakeStatusBarModePerDisplayRepository.statusBarMode.value = statusBarMode
     }
 }
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index e214666..77fbb64 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -128,12 +128,6 @@
 
     <include layout="@layout/dock_info_bottom_area_overlay" />
 
-    <com.android.keyguard.LockIconView
-        android:id="@+id/lock_icon_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content">
-    </com.android.keyguard.LockIconView>
-
     <include
         layout="@layout/keyguard_bottom_area"
         android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
deleted file mode 100644
index 530d752..0000000
--- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 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.
-  -->
-<com.android.systemui.biometrics.UdfpsKeyguardViewLegacy
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/udfps_animation_view_legacy"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. -->
-
-</com.android.systemui.biometrics.UdfpsKeyguardViewLegacy>
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
deleted file mode 100644
index 257d238..0000000
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 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.
-  -->
-<com.android.systemui.biometrics.UdfpsView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/udfps_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    systemui:sensorTouchAreaCoefficient="1.0"
-    android:contentDescription="@string/accessibility_fingerprint_label">
-
-    <ViewStub
-        android:id="@+id/animation_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"/>
-
-</com.android.systemui.biometrics.UdfpsView>
diff --git a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt b/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt
index b792db3..306d682 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt
@@ -17,6 +17,7 @@
 package com.android.keyguard
 
 import android.view.MotionEvent
+import android.view.View
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.res.R
@@ -34,11 +35,10 @@
 @SysUISingleton
 class EmptyLockIconViewController
 @Inject
-constructor(
-    private val keyguardRootView: Lazy<KeyguardRootView>,
-) : LockIconViewController {
+constructor(private val keyguardRootView: Lazy<KeyguardRootView>) : LockIconViewController {
     private val deviceEntryIconViewId = R.id.device_entry_icon_view
-    override fun setLockIconView(lockIconView: LockIconView) {
+
+    override fun setLockIconView(lockIconView: View) {
         // no-op
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
deleted file mode 100644
index 03b13fe..0000000
--- a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
+++ /dev/null
@@ -1,843 +0,0 @@
-/*
- * Copyright (C) 2020 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.keyguard;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
-
-import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
-import static com.android.keyguard.LockIconView.ICON_LOCK;
-import static com.android.keyguard.LockIconView.ICON_UNLOCK;
-import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
-import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
-import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
-import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.VibrationAttributes;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.HapticFeedbackConstants;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.AuthRippleController;
-import com.android.systemui.biometrics.UdfpsController;
-import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.model.KeyguardState;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import dagger.Lazy;
-
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-/**
- * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
- *
- * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock
- * icon will show a set distance from the bottom of the device.
- */
-@SysUISingleton
-public class LegacyLockIconViewController implements Dumpable, LockIconViewController {
-    private static final String TAG = "LockIconViewController";
-    private static final float sDefaultDensity =
-            (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT;
-    private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36);
-    private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
-            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
-
-    private static final long FADE_OUT_DURATION_MS = 250L;
-
-    private final long mLongPressTimeout;
-    @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @NonNull private final KeyguardViewController mKeyguardViewController;
-    @NonNull private final StatusBarStateController mStatusBarStateController;
-    @NonNull private final KeyguardStateController mKeyguardStateController;
-    @NonNull private final FalsingManager mFalsingManager;
-    @NonNull private final AuthController mAuthController;
-    @NonNull private final AccessibilityManager mAccessibilityManager;
-    @NonNull private final ConfigurationController mConfigurationController;
-    @NonNull private final DelayableExecutor mExecutor;
-    private boolean mUdfpsEnrolled;
-    private Resources mResources;
-    private Context mContext;
-    @NonNull private CharSequence mUnlockedLabel;
-    @NonNull private CharSequence mLockedLabel;
-    @NonNull private final VibratorHelper mVibrator;
-    @Nullable private final AuthRippleController mAuthRippleController;
-    @NonNull private final FeatureFlags mFeatureFlags;
-    @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
-    @NonNull private final KeyguardTransitionInteractor mTransitionInteractor;
-    @NonNull private final KeyguardInteractor mKeyguardInteractor;
-    @NonNull private final View.AccessibilityDelegate mAccessibilityDelegate;
-    @NonNull private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor;
-
-    // Tracks the velocity of a touch to help filter out the touches that move too fast.
-    private VelocityTracker mVelocityTracker;
-    // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
-    private int mActivePointerId = -1;
-
-    private boolean mIsDozing;
-    private boolean mIsActiveDreamLockscreenHosted;
-    private boolean mIsBouncerShowing;
-    private boolean mRunningFPS;
-    private boolean mCanDismissLockScreen;
-    private int mStatusBarState;
-    private boolean mIsKeyguardShowing;
-    private Runnable mLongPressCancelRunnable;
-
-    private boolean mUdfpsSupported;
-    private float mHeightPixels;
-    private float mWidthPixels;
-    private int mBottomPaddingPx;
-    private int mDefaultPaddingPx;
-
-    private boolean mShowUnlockIcon;
-    private boolean mShowLockIcon;
-
-    // for udfps when strong auth is required or unlocked on AOD
-    private boolean mShowAodLockIcon;
-    private boolean mShowAodUnlockedIcon;
-    private final int mMaxBurnInOffsetX;
-    private final int mMaxBurnInOffsetY;
-    private float mInterpolatedDarkAmount;
-
-    private boolean mDownDetected;
-    private final Rect mSensorTouchLocation = new Rect();
-    private LockIconView mView;
-
-    @VisibleForTesting
-    final Consumer<Float> mDozeTransitionCallback = (Float value) -> {
-        mInterpolatedDarkAmount = value;
-        mView.setDozeAmount(value);
-        updateBurnInOffsets();
-    };
-
-    @VisibleForTesting
-    final Consumer<Boolean> mIsDozingCallback = (Boolean isDozing) -> {
-        mIsDozing = isDozing;
-        updateBurnInOffsets();
-        updateVisibility();
-    };
-
-    @VisibleForTesting
-    final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback =
-            (Boolean isLockscreenHosted) -> {
-                mIsActiveDreamLockscreenHosted = isLockscreenHosted;
-                updateVisibility();
-            };
-
-    @Inject
-    public LegacyLockIconViewController(
-            @NonNull StatusBarStateController statusBarStateController,
-            @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
-            @NonNull KeyguardViewController keyguardViewController,
-            @NonNull KeyguardStateController keyguardStateController,
-            @NonNull FalsingManager falsingManager,
-            @NonNull AuthController authController,
-            @NonNull DumpManager dumpManager,
-            @NonNull AccessibilityManager accessibilityManager,
-            @NonNull ConfigurationController configurationController,
-            @NonNull @Main DelayableExecutor executor,
-            @NonNull VibratorHelper vibrator,
-            @Nullable AuthRippleController authRippleController,
-            @NonNull @Main Resources resources,
-            @NonNull KeyguardTransitionInteractor transitionInteractor,
-            @NonNull KeyguardInteractor keyguardInteractor,
-            @NonNull FeatureFlags featureFlags,
-            PrimaryBouncerInteractor primaryBouncerInteractor,
-            Context context,
-            Lazy<DeviceEntryInteractor> deviceEntryInteractor
-    ) {
-        mStatusBarStateController = statusBarStateController;
-        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mAuthController = authController;
-        mKeyguardViewController = keyguardViewController;
-        mKeyguardStateController = keyguardStateController;
-        mFalsingManager = falsingManager;
-        mAccessibilityManager = accessibilityManager;
-        mConfigurationController = configurationController;
-        mExecutor = executor;
-        mVibrator = vibrator;
-        mAuthRippleController = authRippleController;
-        mTransitionInteractor = transitionInteractor;
-        mKeyguardInteractor = keyguardInteractor;
-        mFeatureFlags = featureFlags;
-        mPrimaryBouncerInteractor = primaryBouncerInteractor;
-
-        mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
-        mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
-        mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
-        mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
-        mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress);
-        dumpManager.registerDumpable(TAG, this);
-        mResources = resources;
-        mContext = context;
-        mDeviceEntryInteractor = deviceEntryInteractor;
-
-        mAccessibilityDelegate = new View.AccessibilityDelegate() {
-            private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint =
-                    new AccessibilityNodeInfo.AccessibilityAction(
-                            AccessibilityNodeInfoCompat.ACTION_CLICK,
-                            mResources.getString(R.string.accessibility_authenticate_hint));
-            private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityEnterHint =
-                    new AccessibilityNodeInfo.AccessibilityAction(
-                            AccessibilityNodeInfoCompat.ACTION_CLICK,
-                            mResources.getString(R.string.accessibility_enter_hint));
-            public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
-                super.onInitializeAccessibilityNodeInfo(v, info);
-                if (isActionable()) {
-                    if (mShowLockIcon) {
-                        info.addAction(mAccessibilityAuthenticateHint);
-                    } else if (mShowUnlockIcon) {
-                        info.addAction(mAccessibilityEnterHint);
-                    }
-                }
-            }
-        };
-    }
-
-    /** Sets the LockIconView to the controller and rebinds any that depend on it. */
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public void setLockIconView(LockIconView lockIconView) {
-        mView = lockIconView;
-        mView.setAccessibilityDelegate(mAccessibilityDelegate);
-
-        if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
-            collectFlow(mView, mTransitionInteractor.transitionValue(KeyguardState.AOD),
-                    mDozeTransitionCallback);
-            collectFlow(mView, mKeyguardInteractor.isDozing(), mIsDozingCallback);
-        }
-
-        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
-            collectFlow(mView, mKeyguardInteractor.isActiveDreamLockscreenHosted(),
-                    mIsActiveDreamLockscreenHostedCallback);
-        }
-
-        updateIsUdfpsEnrolled();
-        updateConfiguration();
-        updateKeyguardShowing();
-
-        mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
-        mIsDozing = mStatusBarStateController.isDozing();
-        mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount();
-        mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
-        mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
-        mStatusBarState = mStatusBarStateController.getState();
-
-        updateColors();
-        mDownDetected = false;
-        updateBurnInOffsets();
-        updateVisibility();
-
-        updateAccessibility();
-
-        lockIconView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-            @Override
-            public void onViewAttachedToWindow(View view) {
-                registerCallbacks();
-            }
-
-            @Override
-            public void onViewDetachedFromWindow(View view) {
-                unregisterCallbacks();
-            }
-        });
-
-        if (lockIconView.isAttachedToWindow()) {
-            registerCallbacks();
-        }
-
-        lockIconView.setOnTouchListener((view, motionEvent) -> onTouchEvent(motionEvent));
-    }
-
-    private void registerCallbacks() {
-        mConfigurationController.addCallback(mConfigurationListener);
-        mAuthController.addCallback(mAuthControllerCallback);
-        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-        mKeyguardStateController.addCallback(mKeyguardStateCallback);
-        mAccessibilityManager.addAccessibilityStateChangeListener(
-                mAccessibilityStateChangeListener);
-
-    }
-
-    private void unregisterCallbacks() {
-        mAuthController.removeCallback(mAuthControllerCallback);
-        mConfigurationController.removeCallback(mConfigurationListener);
-        mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
-        mStatusBarStateController.removeCallback(mStatusBarStateListener);
-        mKeyguardStateController.removeCallback(mKeyguardStateCallback);
-        mAccessibilityManager.removeAccessibilityStateChangeListener(
-                mAccessibilityStateChangeListener);
-
-    }
-
-    private void updateAccessibility() {
-        if (mAccessibilityManager.isEnabled()) {
-            mView.setOnClickListener(mA11yClickListener);
-        } else {
-            mView.setOnClickListener(null);
-        }
-    }
-
-    @Override
-    public float getTop() {
-        return mView.getLocationTop();
-    }
-
-    @Override
-    public float getBottom() {
-        return mView.getLocationBottom();
-    }
-
-    private void updateVisibility() {
-        if (!mIsKeyguardShowing && !mIsDozing) {
-            mView.setVisibility(View.INVISIBLE);
-            return;
-        }
-
-        if (mIsKeyguardShowing && mIsActiveDreamLockscreenHosted) {
-            mView.setVisibility(View.INVISIBLE);
-            return;
-        }
-
-        boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon
-                && !mShowAodUnlockedIcon && !mShowAodLockIcon;
-        mShowLockIcon = !mCanDismissLockScreen && isLockScreen()
-                && (!mUdfpsEnrolled || !mRunningFPS);
-        mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
-        mShowAodUnlockedIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen;
-        mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen;
-
-        final CharSequence prevContentDescription = mView.getContentDescription();
-        if (mShowLockIcon) {
-            if (wasShowingFpIcon) {
-                // fp icon was shown by UdfpsView, and now we still want to animate the transition
-                // in this drawable
-                mView.updateIcon(ICON_FINGERPRINT, false);
-            }
-            mView.updateIcon(ICON_LOCK, false);
-            mView.setContentDescription(mLockedLabel);
-            mView.setVisibility(View.VISIBLE);
-        } else if (mShowUnlockIcon) {
-            if (wasShowingFpIcon) {
-                // fp icon was shown by UdfpsView, and now we still want to animate the transition
-                // in this drawable
-                mView.updateIcon(ICON_FINGERPRINT, false);
-            }
-            mView.updateIcon(ICON_UNLOCK, false);
-            mView.setContentDescription(mUnlockedLabel);
-            mView.setVisibility(View.VISIBLE);
-        } else if (mShowAodUnlockedIcon) {
-            mView.updateIcon(ICON_UNLOCK, true);
-            mView.setContentDescription(mUnlockedLabel);
-            mView.setVisibility(View.VISIBLE);
-        } else if (mShowAodLockIcon) {
-            mView.updateIcon(ICON_LOCK, true);
-            mView.setContentDescription(mLockedLabel);
-            mView.setVisibility(View.VISIBLE);
-        } else {
-            mView.clearIcon();
-            mView.setVisibility(View.INVISIBLE);
-            mView.setContentDescription(null);
-        }
-
-        boolean accessibilityEnabled =
-                !mPrimaryBouncerInteractor.isAnimatingAway() && mView.isVisibleToUser();
-        mView.setImportantForAccessibility(
-                accessibilityEnabled ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
-                        : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-
-        if (!Objects.equals(prevContentDescription, mView.getContentDescription())
-                && mView.getContentDescription() != null && accessibilityEnabled) {
-            mView.announceForAccessibility(mView.getContentDescription());
-        }
-    }
-
-    private boolean isLockScreen() {
-        return !mIsDozing
-                && !mIsBouncerShowing
-                && mStatusBarState == StatusBarState.KEYGUARD;
-    }
-
-    private void updateKeyguardShowing() {
-        mIsKeyguardShowing = mKeyguardStateController.isShowing()
-                && !mKeyguardStateController.isKeyguardGoingAway();
-    }
-
-    private void updateColors() {
-        mView.updateColorAndBackgroundVisibility();
-    }
-
-    private void updateConfiguration() {
-        WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
-        mWidthPixels = bounds.right;
-        if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
-            // Assumed to be initially neglected as there are no left or right insets in portrait
-            // However, on landscape, these insets need to included when calculating the midpoint
-            WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets();
-            mWidthPixels -= insets.getSystemWindowInsetLeft() + insets.getSystemWindowInsetRight();
-        }
-        mHeightPixels = bounds.bottom;
-        mBottomPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom);
-        mDefaultPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_padding);
-        mUnlockedLabel = mResources.getString(
-                R.string.accessibility_unlock_button);
-        mLockedLabel = mResources.getString(R.string.accessibility_lock_icon);
-        updateLockIconLocation();
-    }
-
-    private void updateLockIconLocation() {
-        final float scaleFactor = mAuthController.getScaleFactor();
-        final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
-        if (KeyguardBottomAreaRefactor.isEnabled() || MigrateClocksToBlueprint.isEnabled()) {
-            // positioning in this case is handled by [DefaultDeviceEntrySection]
-            mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding,
-                    scaledPadding);
-        } else {
-            if (mUdfpsSupported) {
-                mView.setCenterLocation(mAuthController.getUdfpsLocation(),
-                        mAuthController.getUdfpsRadius(), scaledPadding);
-            } else {
-                mView.setCenterLocation(
-                        new Point((int) mWidthPixels / 2,
-                                (int) (mHeightPixels
-                                        - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
-                        sLockIconRadiusPx * scaleFactor, scaledPadding);
-            }
-        }
-    }
-
-    @Override
-    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("mUdfpsSupported: " + mUdfpsSupported);
-        pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
-        pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
-        pw.println();
-        pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
-        pw.println(" mShowLockIcon: " + mShowLockIcon);
-        pw.println(" mShowAodUnlockedIcon: " + mShowAodUnlockedIcon);
-        pw.println();
-        pw.println(" mIsDozing: " + mIsDozing);
-        pw.println(" isFlagEnabled(DOZING_MIGRATION_1): "
-                + mFeatureFlags.isEnabled(DOZING_MIGRATION_1));
-        pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
-        pw.println(" mRunningFPS: " + mRunningFPS);
-        pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
-        pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
-        pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
-        pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
-        pw.println(" mDefaultPaddingPx: " + mDefaultPaddingPx);
-        pw.println(" mIsActiveDreamLockscreenHosted: " + mIsActiveDreamLockscreenHosted);
-
-        if (mView != null) {
-            mView.dump(pw, args);
-        }
-    }
-
-    /** Every minute, update the aod icon's burn in offset */
-    @Override
-    public void dozeTimeTick() {
-        updateBurnInOffsets();
-    }
-
-    private void updateBurnInOffsets() {
-        float offsetX = MathUtils.lerp(0f,
-                getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
-                        - mMaxBurnInOffsetX, mInterpolatedDarkAmount);
-        float offsetY = MathUtils.lerp(0f,
-                getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
-                        - mMaxBurnInOffsetY, mInterpolatedDarkAmount);
-
-        mView.setTranslationX(offsetX);
-        mView.setTranslationY(offsetY);
-    }
-
-    private void updateIsUdfpsEnrolled() {
-        boolean wasUdfpsSupported = mUdfpsSupported;
-        boolean wasUdfpsEnrolled = mUdfpsEnrolled;
-
-        mUdfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported();
-        mView.setUseBackground(mUdfpsSupported);
-
-        mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
-        if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
-            updateVisibility();
-        }
-    }
-
-    private StatusBarStateController.StateListener mStatusBarStateListener =
-            new StatusBarStateController.StateListener() {
-                @Override
-                public void onDozeAmountChanged(float linear, float eased) {
-                    if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
-                        mInterpolatedDarkAmount = eased;
-                        mView.setDozeAmount(eased);
-                        updateBurnInOffsets();
-                    }
-                }
-
-                @Override
-                public void onDozingChanged(boolean isDozing) {
-                    if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
-                        mIsDozing = isDozing;
-                        updateBurnInOffsets();
-                        updateVisibility();
-                    }
-                }
-
-                @Override
-                public void onStateChanged(int statusBarState) {
-                    mStatusBarState = statusBarState;
-                    updateVisibility();
-                }
-            };
-
-    private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
-            new KeyguardUpdateMonitorCallback() {
-                @Override
-                public void onKeyguardBouncerStateChanged(boolean bouncer) {
-                    mIsBouncerShowing = bouncer;
-                    updateVisibility();
-                }
-
-                @Override
-                public void onBiometricRunningStateChanged(boolean running,
-                        BiometricSourceType biometricSourceType) {
-                    final boolean wasRunningFps = mRunningFPS;
-
-                    if (biometricSourceType == FINGERPRINT) {
-                        mRunningFPS = running;
-                    }
-
-                    if (wasRunningFps != mRunningFPS) {
-                        updateVisibility();
-                    }
-                }
-            };
-
-    private final KeyguardStateController.Callback mKeyguardStateCallback =
-            new KeyguardStateController.Callback() {
-        @Override
-        public void onUnlockedChanged() {
-            mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
-            updateKeyguardShowing();
-            updateVisibility();
-        }
-
-        @Override
-        public void onKeyguardShowingChanged() {
-            // Reset values in case biometrics were removed (ie: pin/pattern/password => swipe).
-            // If biometrics were removed, local vars mCanDismissLockScreen and
-            // mUserUnlockedWithBiometric may not be updated.
-            mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
-
-            // reset mIsBouncerShowing state in case it was preemptively set
-            // onLongPress
-            mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
-
-            updateKeyguardShowing();
-            updateVisibility();
-        }
-
-        @Override
-        public void onKeyguardFadingAwayChanged() {
-            updateKeyguardShowing();
-            updateVisibility();
-        }
-    };
-
-    private final ConfigurationController.ConfigurationListener mConfigurationListener =
-            new ConfigurationController.ConfigurationListener() {
-        @Override
-        public void onUiModeChanged() {
-            updateColors();
-        }
-
-        @Override
-        public void onThemeChanged() {
-            updateColors();
-        }
-
-        @Override
-        public void onConfigChanged(Configuration newConfig) {
-            updateConfiguration();
-            updateColors();
-        }
-    };
-
-    /**
-     * Handles the touch if {@link #isActionable()} is true.
-     * Subsequently, will trigger {@link #onLongPress()} if a touch is continuously in the lock icon
-     * area for {@link #mLongPressTimeout} ms.
-     *
-     * Touch speed debouncing mimics logic from the velocity tracker in {@link UdfpsController}.
-     */
-    private boolean onTouchEvent(MotionEvent event) {
-        if (!actionableDownEventStartedOnView(event)) {
-            cancelTouches();
-            return false;
-        }
-
-        switch(event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_HOVER_ENTER:
-                if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) {
-                    vibrateOnTouchExploration();
-                }
-
-                // The pointer that causes ACTION_DOWN is always at index 0.
-                // We need to persist its ID to track it during ACTION_MOVE that could include
-                // data for many other pointers because of multi-touch support.
-                mActivePointerId = event.getPointerId(0);
-                if (mVelocityTracker == null) {
-                    // To simplify the lifecycle of the velocity tracker, make sure it's never null
-                    // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
-                    mVelocityTracker = VelocityTracker.obtain();
-                } else {
-                    // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
-                    // ACTION_DOWN, in that case we should just reuse the old instance.
-                    mVelocityTracker.clear();
-                }
-                mVelocityTracker.addMovement(event);
-
-                mDownDetected = true;
-                mLongPressCancelRunnable = mExecutor.executeDelayed(
-                        this::onLongPress, mLongPressTimeout);
-                break;
-            case MotionEvent.ACTION_MOVE:
-            case MotionEvent.ACTION_HOVER_MOVE:
-                mVelocityTracker.addMovement(event);
-                // Compute pointer velocity in pixels per second.
-                mVelocityTracker.computeCurrentVelocity(1000);
-                float velocity = computePointerSpeed(mVelocityTracker,
-                        mActivePointerId);
-                if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS
-                        && exceedsVelocityThreshold(velocity)) {
-                    Log.v(TAG, "lock icon long-press rescheduled due to "
-                            + "high pointer velocity=" + velocity);
-                    mLongPressCancelRunnable.run();
-                    mLongPressCancelRunnable = mExecutor.executeDelayed(
-                            this::onLongPress, mLongPressTimeout);
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_HOVER_EXIT:
-                cancelTouches();
-                break;
-        }
-
-        return true;
-    }
-
-    /**
-     * Calculate the pointer speed given a velocity tracker and the pointer id.
-     * This assumes that the velocity tracker has already been passed all relevant motion events.
-     */
-    private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) {
-        final float vx = tracker.getXVelocity(pointerId);
-        final float vy = tracker.getYVelocity(pointerId);
-        return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0));
-    }
-
-    /**
-     * Whether the velocity exceeds the acceptable UDFPS debouncing threshold.
-     */
-    private static boolean exceedsVelocityThreshold(float velocity) {
-        return velocity > 750f;
-    }
-
-    private boolean actionableDownEventStartedOnView(MotionEvent event) {
-        if (!isActionable()) {
-            return false;
-        }
-
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            return true;
-        }
-
-        return mDownDetected;
-    }
-
-    @ExperimentalCoroutinesApi
-    @VisibleForTesting
-    protected void onLongPress() {
-        cancelTouches();
-        if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
-            Log.v(TAG, "lock icon long-press rejected by the falsing manager.");
-            return;
-        }
-
-        // pre-emptively set to true to hide view
-        mIsBouncerShowing = true;
-        if (!DeviceEntryUdfpsRefactor.isEnabled()
-                && mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
-            mAuthRippleController.showUnlockRipple(FINGERPRINT);
-        }
-        updateVisibility();
-
-        // play device entry haptic (consistent with UDFPS controller longpress)
-        vibrateOnLongPress();
-
-        if (SceneContainerFlag.isEnabled()) {
-            mDeviceEntryInteractor.get().attemptDeviceEntry();
-        } else {
-            mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
-        }
-    }
-
-
-    private void cancelTouches() {
-        mDownDetected = false;
-        if (mLongPressCancelRunnable != null) {
-            mLongPressCancelRunnable.run();
-        }
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-    }
-
-    private boolean isActionable() {
-        if (mIsBouncerShowing) {
-            Log.v(TAG, "lock icon long-press ignored, bouncer already showing.");
-            // a long press gestures from AOD may have already triggered the bouncer to show,
-            // so this touch is no longer actionable
-            return false;
-        }
-        return mUdfpsSupported || mShowUnlockIcon;
-    }
-
-    /**
-     * Set the alpha of this view.
-     */
-    @Override
-    public void setAlpha(float alpha) {
-        mView.setAlpha(alpha);
-    }
-
-    private void updateUdfpsConfig() {
-        // must be called from the main thread since it may update the views
-        mExecutor.execute(() -> {
-            updateIsUdfpsEnrolled();
-            updateConfiguration();
-        });
-    }
-
-    @VisibleForTesting
-    void vibrateOnTouchExploration() {
-        mVibrator.performHapticFeedback(
-                mView,
-                HapticFeedbackConstants.CONTEXT_CLICK
-        );
-    }
-
-    @VisibleForTesting
-    void vibrateOnLongPress() {
-        mVibrator.performHapticFeedback(mView, UdfpsController.LONG_PRESS);
-    }
-
-    private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
-        @Override
-        public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
-            if (modality == TYPE_FINGERPRINT) {
-                updateUdfpsConfig();
-            }
-        }
-
-        @Override
-        public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
-            if (modality == TYPE_FINGERPRINT) {
-                updateUdfpsConfig();
-            }
-        }
-
-        @Override
-        public void onUdfpsLocationChanged(UdfpsOverlayParams udfpsOverlayParams) {
-            updateUdfpsConfig();
-        }
-    };
-
-    /**
-     * Whether the lock icon will handle a touch while dozing.
-     */
-    @Override
-    public boolean willHandleTouchWhileDozing(MotionEvent event) {
-        // is in lock icon area
-        mView.getHitRect(mSensorTouchLocation);
-        final boolean inLockIconArea =
-                mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
-                        && mView.getVisibility() == View.VISIBLE;
-
-        return inLockIconArea && actionableDownEventStartedOnView(event);
-    }
-
-    private final View.OnClickListener mA11yClickListener = v -> onLongPress();
-
-    private final AccessibilityManager.AccessibilityStateChangeListener
-            mAccessibilityStateChangeListener = enabled -> updateAccessibility();
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
deleted file mode 100644
index ff6a3d0..0000000
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2020 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.keyguard;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.settingslib.Utils;
-import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
-
-import java.io.PrintWriter;
-
-/**
- * A view positioned under the notification shade.
- */
-public class LockIconView extends FrameLayout implements Dumpable {
-    @IntDef({ICON_NONE, ICON_LOCK, ICON_FINGERPRINT, ICON_UNLOCK})
-    public @interface IconType {}
-
-    public static final int ICON_NONE = -1;
-    public static final int ICON_LOCK = 0;
-    public static final int ICON_FINGERPRINT = 1;
-    public static final int ICON_UNLOCK = 2;
-
-    private @IconType int mIconType;
-    private boolean mAod;
-
-    @NonNull private final RectF mSensorRect;
-    @NonNull private Point mLockIconCenter = new Point(0, 0);
-    private float mRadius;
-    private int mLockIconPadding;
-
-    private ImageView mLockIcon;
-    private ImageView mBgView;
-
-    private int mLockIconColor;
-    private boolean mUseBackground = false;
-    private float mDozeAmount = 0f;
-
-    @SuppressLint("ClickableViewAccessibility")
-    public LockIconView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mSensorRect = new RectF();
-
-        addBgImageView(context, attrs);
-        addLockIconImageView(context, attrs);
-    }
-
-    void setDozeAmount(float dozeAmount) {
-        mDozeAmount = dozeAmount;
-        updateColorAndBackgroundVisibility();
-    }
-
-    void updateColorAndBackgroundVisibility() {
-        if (mUseBackground && mLockIcon.getDrawable() != null) {
-            mLockIconColor = ColorUtils.blendARGB(
-                    Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary),
-                    Color.WHITE,
-                    mDozeAmount);
-            int backgroundColor = Utils.getColorAttrDefaultColor(getContext(),
-                    com.android.internal.R.attr.colorSurface);
-            mBgView.setImageTintList(ColorStateList.valueOf(backgroundColor));
-            mBgView.setAlpha(1f - mDozeAmount);
-            mBgView.setVisibility(View.VISIBLE);
-        } else {
-            mLockIconColor = ColorUtils.blendARGB(
-                    Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColorAccent),
-                    Color.WHITE,
-                    mDozeAmount);
-            mBgView.setVisibility(View.GONE);
-        }
-
-        mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor));
-    }
-
-    /**
-     * Whether or not to render the lock icon background. Mainly used for UDPFS.
-     */
-    public void setUseBackground(boolean useBackground) {
-        mUseBackground = useBackground;
-        updateColorAndBackgroundVisibility();
-    }
-
-    /**
-     * Set the location of the lock icon.
-     */
-    public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) {
-        mLockIconCenter = center;
-        mRadius = radius;
-        mLockIconPadding = drawablePadding;
-
-        mLockIcon.setPadding(mLockIconPadding, mLockIconPadding, mLockIconPadding,
-                mLockIconPadding);
-
-        mSensorRect.set(mLockIconCenter.x - mRadius,
-                mLockIconCenter.y - mRadius,
-                mLockIconCenter.x + mRadius,
-                mLockIconCenter.y + mRadius);
-
-        final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-        if (lp != null) {
-            lp.width = (int) (mSensorRect.right - mSensorRect.left);
-            lp.height = (int) (mSensorRect.bottom - mSensorRect.top);
-            lp.topMargin = (int) mSensorRect.top;
-            lp.setMarginStart((int) mSensorRect.left);
-            setLayoutParams(lp);
-        }
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    float getLocationTop() {
-        Rect r = new Rect();
-        mLockIcon.getGlobalVisibleRect(r);
-        return r.top;
-    }
-
-    float getLocationBottom() {
-        Rect r = new Rect();
-        mLockIcon.getGlobalVisibleRect(r);
-        return r.bottom;
-
-    }
-
-    /**
-     * Updates the icon its default state where no visual is shown.
-     */
-    public void clearIcon() {
-        updateIcon(ICON_NONE, false);
-    }
-
-    /**
-     * Transition the current icon to a new state
-     * @param icon type (ie: lock icon, unlock icon, fingerprint icon)
-     * @param aod whether to use the aod icon variant (some icons don't have aod variants and will
-     *            therefore show no icon)
-     */
-    public void updateIcon(@IconType int icon, boolean aod) {
-        mIconType = icon;
-        mAod = aod;
-
-        mLockIcon.setImageState(getLockIconState(mIconType, mAod), true);
-    }
-
-    public ImageView getLockIcon() {
-        return mLockIcon;
-    }
-
-    private void addLockIconImageView(Context context, AttributeSet attrs) {
-        mLockIcon = new ImageView(context, attrs);
-        mLockIcon.setId(R.id.lock_icon);
-        mLockIcon.setScaleType(ImageView.ScaleType.CENTER_CROP);
-        mLockIcon.setImageDrawable(context.getDrawable(R.drawable.super_lock_icon));
-        addView(mLockIcon);
-        LayoutParams lp = (LayoutParams) mLockIcon.getLayoutParams();
-        lp.height = MATCH_PARENT;
-        lp.width = MATCH_PARENT;
-        lp.gravity = Gravity.CENTER;
-        mLockIcon.setLayoutParams(lp);
-    }
-
-    private void addBgImageView(Context context, AttributeSet attrs) {
-        mBgView = new ImageView(context, attrs);
-        mBgView.setId(R.id.lock_icon_bg);
-        mBgView.setImageDrawable(context.getDrawable(R.drawable.fingerprint_bg));
-        mBgView.setVisibility(View.INVISIBLE);
-        addView(mBgView);
-        LayoutParams lp = (LayoutParams) mBgView.getLayoutParams();
-        lp.height = MATCH_PARENT;
-        lp.width = MATCH_PARENT;
-        mBgView.setLayoutParams(lp);
-    }
-
-    private static int[] getLockIconState(@IconType int icon, boolean aod) {
-        if (icon == ICON_NONE) {
-            return new int[0];
-        }
-
-        int[] lockIconState = new int[2];
-        switch (icon) {
-            case ICON_LOCK:
-                lockIconState[0] = android.R.attr.state_first;
-                break;
-            case ICON_FINGERPRINT:
-                lockIconState[0] = android.R.attr.state_middle;
-                break;
-            case ICON_UNLOCK:
-                lockIconState[0] = android.R.attr.state_last;
-                break;
-        }
-
-        if (aod) {
-            lockIconState[1] = android.R.attr.state_single;
-        } else {
-            lockIconState[1] = -android.R.attr.state_single;
-        }
-
-        return lockIconState;
-    }
-
-    private String typeToString(@IconType int type) {
-        switch (type) {
-            case ICON_NONE:
-                return "none";
-            case ICON_LOCK:
-                return "lock";
-            case ICON_FINGERPRINT:
-                return "fingerprint";
-            case ICON_UNLOCK:
-                return "unlock";
-        }
-
-        return "invalid";
-    }
-
-    @Override
-    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("Lock Icon View Parameters:");
-        pw.println("    Center in px (x, y)= ("
-                + mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
-        pw.println("    Radius in pixels: " + mRadius);
-        pw.println("    Drawable padding: " + mLockIconPadding);
-        pw.println("    mIconType=" + typeToString(mIconType));
-        pw.println("    mAod=" + mAod);
-        pw.println("Lock Icon View actual measurements:");
-        pw.println("    topLeft= (" + getX() + ", " + getY() + ")");
-        pw.println("    width=" + getWidth() + " height=" + getHeight());
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt
index 10d5a0c..c5012b0 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt
@@ -17,13 +17,19 @@
 package com.android.keyguard
 
 import android.view.MotionEvent
+import android.view.View
 
 /** Controls the [LockIconView]. */
 interface LockIconViewController {
-    fun setLockIconView(lockIconView: LockIconView)
+    fun setLockIconView(lockIconView: View)
+
     fun getTop(): Float
+
     fun getBottom(): Float
+
     fun dozeTimeTick()
+
     fun setAlpha(alpha: Float)
+
     fun willHandleTouchWhileDozing(event: MotionEvent): Boolean
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index cd9efaf..610e3f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -39,6 +39,10 @@
 import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileDataInteractor
 import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.impl.hearingdevices.domain.HearingDevicesTileMapper
+import com.android.systemui.qs.tiles.impl.hearingdevices.domain.interactor.HearingDevicesTileDataInteractor
+import com.android.systemui.qs.tiles.impl.hearingdevices.domain.interactor.HearingDevicesTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel
 import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMapper
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
@@ -159,6 +163,13 @@
         impl: NightDisplayTileDataInteractor
     ): QSTileAvailabilityInteractor
 
+    @Binds
+    @IntoMap
+    @StringKey(HEARING_DEVICES_TILE_SPEC)
+    fun provideHearingDevicesAvailabilityInteractor(
+        impl: HearingDevicesTileDataInteractor
+    ): QSTileAvailabilityInteractor
+
     companion object {
         const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
         const val COLOR_INVERSION_TILE_SPEC = "inversion"
@@ -191,7 +202,7 @@
             factory: QSTileViewModelFactory.Static<ColorCorrectionTileModel>,
             mapper: ColorCorrectionTileMapper,
             stateInteractor: ColorCorrectionTileDataInteractor,
-            userActionInteractor: ColorCorrectionUserActionInteractor
+            userActionInteractor: ColorCorrectionUserActionInteractor,
         ): QSTileViewModel =
             factory.create(
                 TileSpec.create(COLOR_CORRECTION_TILE_SPEC),
@@ -223,7 +234,7 @@
             factory: QSTileViewModelFactory.Static<ColorInversionTileModel>,
             mapper: ColorInversionTileMapper,
             stateInteractor: ColorInversionTileDataInteractor,
-            userActionInteractor: ColorInversionUserActionInteractor
+            userActionInteractor: ColorInversionUserActionInteractor,
         ): QSTileViewModel =
             factory.create(
                 TileSpec.create(COLOR_INVERSION_TILE_SPEC),
@@ -255,7 +266,7 @@
             factory: QSTileViewModelFactory.Static<FontScalingTileModel>,
             mapper: FontScalingTileMapper,
             stateInteractor: FontScalingTileDataInteractor,
-            userActionInteractor: FontScalingTileUserActionInteractor
+            userActionInteractor: FontScalingTileUserActionInteractor,
         ): QSTileViewModel =
             factory.create(
                 TileSpec.create(FONT_SCALING_TILE_SPEC),
@@ -279,21 +290,6 @@
                 category = TileCategory.DISPLAY,
             )
 
-        @Provides
-        @IntoMap
-        @StringKey(HEARING_DEVICES_TILE_SPEC)
-        fun provideHearingDevicesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
-            QSTileConfig(
-                tileSpec = TileSpec.create(HEARING_DEVICES_TILE_SPEC),
-                uiConfig =
-                    QSTileUIConfig.Resource(
-                        iconRes = R.drawable.qs_hearing_devices_icon,
-                        labelRes = R.string.quick_settings_hearing_devices_label,
-                    ),
-                instanceId = uiEventLogger.getNewInstanceId(),
-                category = TileCategory.ACCESSIBILITY,
-            )
-
         /**
          * Inject Reduce Bright Colors Tile into tileViewModelMap in QSModule. The tile is hidden
          * behind a flag.
@@ -305,7 +301,7 @@
             factory: QSTileViewModelFactory.Static<ReduceBrightColorsTileModel>,
             mapper: ReduceBrightColorsTileMapper,
             stateInteractor: ReduceBrightColorsTileDataInteractor,
-            userActionInteractor: ReduceBrightColorsTileUserActionInteractor
+            userActionInteractor: ReduceBrightColorsTileUserActionInteractor,
         ): QSTileViewModel =
             if (Flags.qsNewTilesFuture())
                 factory.create(
@@ -339,7 +335,7 @@
             factory: QSTileViewModelFactory.Static<OneHandedModeTileModel>,
             mapper: OneHandedModeTileMapper,
             stateInteractor: OneHandedModeTileDataInteractor,
-            userActionInteractor: OneHandedModeTileUserActionInteractor
+            userActionInteractor: OneHandedModeTileUserActionInteractor,
         ): QSTileViewModel =
             if (Flags.qsNewTilesFuture())
                 factory.create(
@@ -376,7 +372,7 @@
             factory: QSTileViewModelFactory.Static<NightDisplayTileModel>,
             mapper: NightDisplayTileMapper,
             stateInteractor: NightDisplayTileDataInteractor,
-            userActionInteractor: NightDisplayTileUserActionInteractor
+            userActionInteractor: NightDisplayTileUserActionInteractor,
         ): QSTileViewModel =
             if (Flags.qsNewTilesFuture())
                 factory.create(
@@ -386,5 +382,43 @@
                     mapper,
                 )
             else StubQSTileViewModel
+
+        @Provides
+        @IntoMap
+        @StringKey(HEARING_DEVICES_TILE_SPEC)
+        fun provideHearingDevicesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(HEARING_DEVICES_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_hearing_devices_icon,
+                        labelRes = R.string.quick_settings_hearing_devices_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.ACCESSIBILITY,
+            )
+
+        /**
+         * Inject HearingDevices Tile into tileViewModelMap in QSModule. The tile is hidden behind a
+         * flag.
+         */
+        @Provides
+        @IntoMap
+        @StringKey(HEARING_DEVICES_TILE_SPEC)
+        fun provideHearingDevicesTileViewModel(
+            factory: QSTileViewModelFactory.Static<HearingDevicesTileModel>,
+            mapper: HearingDevicesTileMapper,
+            stateInteractor: HearingDevicesTileDataInteractor,
+            userActionInteractor: HearingDevicesTileUserActionInteractor,
+        ): QSTileViewModel {
+            return if (Flags.hearingAidsQsTileDialog() && Flags.qsNewTilesFuture()) {
+                factory.create(
+                    TileSpec.create(HEARING_DEVICES_TILE_SPEC),
+                    userActionInteractor,
+                    stateInteractor,
+                    mapper,
+                )
+            } else StubQSTileViewModel
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index c95a94e..f6cc724 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -37,11 +37,9 @@
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.CircleReveal
@@ -87,7 +85,7 @@
     private val lightRevealScrim: LightRevealScrim,
     private val authRippleInteractor: AuthRippleInteractor,
     private val facePropertyRepository: FacePropertyRepository,
-    rippleView: AuthRippleView?
+    rippleView: AuthRippleView?,
 ) :
     ViewController<AuthRippleView>(rippleView),
     CoreStartable,
@@ -108,15 +106,13 @@
     }
 
     init {
-        if (DeviceEntryUdfpsRefactor.isEnabled) {
-            rippleView?.repeatWhenAttached {
-                repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) {
-                    authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource ->
-                        if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) {
-                            showUnlockRippleInternal(BiometricSourceType.FINGERPRINT)
-                        } else {
-                            showUnlockRippleInternal(BiometricSourceType.FACE)
-                        }
+        rippleView?.repeatWhenAttached {
+            repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) {
+                authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource ->
+                    if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) {
+                        showUnlockRippleInternal(BiometricSourceType.FINGERPRINT)
+                    } else {
+                        showUnlockRippleInternal(BiometricSourceType.FACE)
                     }
                 }
             }
@@ -134,29 +130,8 @@
         keyguardStateController.addCallback(this)
         wakefulnessLifecycle.addObserver(this)
         commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
-        if (!DeviceEntryUdfpsRefactor.isEnabled) {
-            biometricUnlockController.addListener(biometricModeListener)
-        }
     }
 
-    private val biometricModeListener =
-        object : BiometricUnlockController.BiometricUnlockEventsListener {
-            override fun onBiometricUnlockedWithKeyguardDismissal(
-                biometricSourceType: BiometricSourceType?
-            ) {
-                DeviceEntryUdfpsRefactor.assertInLegacyMode()
-                if (biometricSourceType != null) {
-                    showUnlockRippleInternal(biometricSourceType)
-                } else {
-                    logger.log(
-                        TAG,
-                        LogLevel.ERROR,
-                        "Unexpected scenario where biometricSourceType is null"
-                    )
-                }
-            }
-        }
-
     @VisibleForTesting
     public override fun onViewDetached() {
         udfpsController?.removeCallback(udfpsControllerCallback)
@@ -166,17 +141,10 @@
         keyguardStateController.removeCallback(this)
         wakefulnessLifecycle.removeObserver(this)
         commandRegistry.unregisterCommand("auth-ripple")
-        biometricUnlockController.removeListener(biometricModeListener)
 
         notificationShadeWindowController.setForcePluginOpen(false, this)
     }
 
-    @Deprecated("Update authRippleInteractor.showUnlockRipple instead of calling this.")
-    fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
-        DeviceEntryUdfpsRefactor.assertInLegacyMode()
-        showUnlockRippleInternal(biometricSourceType)
-    }
-
     private fun showUnlockRippleInternal(biometricSourceType: BiometricSourceType) {
         val keyguardNotShowing = !keyguardStateController.isShowing
         val unlockNotAllowed =
@@ -197,8 +165,8 @@
                         0,
                         Math.max(
                             Math.max(it.x, displayMetrics.widthPixels - it.x),
-                            Math.max(it.y, displayMetrics.heightPixels - it.y)
-                        )
+                            Math.max(it.y, displayMetrics.heightPixels - it.y),
+                        ),
                     )
                 logger.showingUnlockRippleAt(it.x, it.y, "FP sensor radius: $udfpsRadius")
                 showUnlockedRipple()
@@ -213,8 +181,8 @@
                         0,
                         Math.max(
                             Math.max(it.x, displayMetrics.widthPixels - it.x),
-                            Math.max(it.y, displayMetrics.heightPixels - it.y)
-                        )
+                            Math.max(it.y, displayMetrics.heightPixels - it.y),
+                        ),
                     )
                 logger.showingUnlockRippleAt(it.x, it.y, "Face unlock ripple")
                 showUnlockedRipple()
@@ -322,7 +290,7 @@
             override fun onBiometricAuthenticated(
                 userId: Int,
                 biometricSourceType: BiometricSourceType,
-                isStrongBiometric: Boolean
+                isStrongBiometric: Boolean,
             ) {
                 if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
                     mView.fadeDwellRipple()
@@ -337,7 +305,7 @@
 
             override fun onBiometricAcquired(
                 biometricSourceType: BiometricSourceType,
-                acquireInfo: Int
+                acquireInfo: Int,
             ) {
                 if (
                     biometricSourceType == BiometricSourceType.FINGERPRINT &&
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
deleted file mode 100644
index 76bcd6e..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.Rect
-import android.graphics.RectF
-import android.util.AttributeSet
-import android.util.Log
-import android.view.MotionEvent
-import android.widget.FrameLayout
-import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
-import com.android.systemui.doze.DozeReceiver
-
-private const val TAG = "UdfpsView"
-
-/**
- * The main view group containing all UDFPS animations.
- */
-class UdfpsView(
-    context: Context,
-    attrs: AttributeSet?
-) : FrameLayout(context, attrs), DozeReceiver {
-    // sensorRect may be bigger than the sensor. True sensor dimensions are defined in
-    // overlayParams.sensorBounds
-    var sensorRect = Rect()
-    private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null
-    private val debugTextPaint = Paint().apply {
-        isAntiAlias = true
-        color = Color.BLUE
-        textSize = 32f
-    }
-
-    /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
-    var animationViewController: UdfpsAnimationViewController<*>? = null
-
-    /** Parameters that affect the position and size of the overlay. */
-    var overlayParams = UdfpsOverlayParams()
-
-    /** Debug message. */
-    var debugMessage: String? = null
-        set(value) {
-            field = value
-            postInvalidate()
-        }
-
-    /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
-    var isDisplayConfigured: Boolean = false
-        private set
-
-    fun setUdfpsDisplayModeProvider(udfpsDisplayModeProvider: UdfpsDisplayModeProvider?) {
-        mUdfpsDisplayMode = udfpsDisplayModeProvider
-    }
-
-    // Don't propagate any touch events to the child views.
-    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
-        return (animationViewController == null || !animationViewController!!.shouldPauseAuth())
-    }
-
-    override fun dozeTimeTick() {
-        animationViewController?.dozeTimeTick()
-    }
-
-    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
-        super.onLayout(changed, left, top, right, bottom)
-
-        // Updates sensor rect in relation to the overlay view
-        animationViewController?.onSensorRectUpdated(RectF(sensorRect))
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        Log.v(TAG, "onAttachedToWindow")
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        Log.v(TAG, "onDetachedFromWindow")
-    }
-
-    override fun onDraw(canvas: Canvas) {
-        super.onDraw(canvas)
-        if (!isDisplayConfigured) {
-            if (!debugMessage.isNullOrEmpty()) {
-                canvas.drawText(debugMessage!!, 0f, 160f, debugTextPaint)
-            }
-        }
-    }
-
-    fun configureDisplay(onDisplayConfigured: Runnable) {
-        isDisplayConfigured = true
-        animationViewController?.onDisplayConfiguring()
-        mUdfpsDisplayMode?.enable(onDisplayConfigured)
-    }
-
-    fun unconfigureDisplay() {
-        isDisplayConfigured = false
-        animationViewController?.onDisplayUnconfigured()
-        mUdfpsDisplayMode?.disable(null /* onDisabled */)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
index ffe392a..88daa5d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.biometrics.FaceHelpMessageDeferralFactory
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
 import javax.inject.Inject
@@ -55,9 +54,7 @@
         faceAuthInteractor.authenticationStatus.filterIsInstance<HelpFaceAuthenticationStatus>()
 
     init {
-        if (DeviceEntryUdfpsRefactor.isEnabled) {
-            startUpdatingFaceHelpMessageDeferral()
-        }
+        startUpdatingFaceHelpMessageDeferral()
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryUdfpsRefactor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryUdfpsRefactor.kt
deleted file mode 100644
index b5d5803..0000000
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryUdfpsRefactor.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.deviceentry.shared
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the device entry udfps refactor flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object DeviceEntryUdfpsRefactor {
-    /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR
-
-    /** A token used for dependency declaration */
-    val token: FlagToken
-        get() = FlagToken(FLAG_NAME, isEnabled)
-
-    /** Is the refactor enabled */
-    @JvmStatic
-    inline val isEnabled
-        get() = Flags.deviceEntryUdfpsRefactor()
-
-    /**
-     * Called to ensure code is only run when the flag is enabled. This protects users from the
-     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
-     * build to ensure that the refactor author catches issues in testing.
-     */
-    @JvmStatic
-    inline fun isUnexpectedlyInLegacyMode() =
-        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
-    /**
-     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
-     * the flag is enabled to ensure that the refactor author catches issues in testing.
-     */
-    @JvmStatic
-    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 210f4cd..21d1db4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -129,15 +129,19 @@
             constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0)
             val largeClockTopMargin =
                 SystemBarUtils.getStatusBarHeight(context) +
-                    context.resources.getDimensionPixelSize(
-                        customR.dimen.small_clock_padding_top
-                    ) +
+                    context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
                     context.resources.getDimensionPixelSize(
                         R.dimen.keyguard_smartspace_top_offset
                     ) +
                     getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
                     getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
-            connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+            connect(
+                customR.id.lockscreen_clock_view_large,
+                TOP,
+                PARENT_ID,
+                TOP,
+                largeClockTopMargin,
+            )
             connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
             connect(
                 customR.id.lockscreen_clock_view_large,
@@ -146,12 +150,11 @@
                 ConstraintSet.END,
             )
 
-
             // In preview, we'll show UDFPS icon for UDFPS devices and nothing for non-UDFPS
             // devices, but we need position of device entry icon to constrain clock
             if (getConstraint(lockId) != null) {
                 connect(customR.id.lockscreen_clock_view_large, BOTTOM, lockId, TOP)
-           } else {
+            } else {
                 // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
                 val bottomPaddingPx =
                     context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index c3729c0..212da9f 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -122,8 +122,11 @@
         final Intent launchingIntent = getIntent();
         mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra(
                 EXTRA_USER_REVIEW_GRANTED_CONSENT, false);
-
-        mPackageName = getCallingPackage();
+        if (com.android.systemui.Flags.mediaProjectionRequestAttributionFix()) {
+            mPackageName = getLaunchedFromPackage();
+        } else {
+            mPackageName = getCallingPackage();
+        }
 
         // This activity is launched directly by an app, or system server. System server provides
         // the package name through the intent if so.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
new file mode 100644
index 0000000..8dd611f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.hearingdevices.domain
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [HearingDevicesTileModel] to [QSTileState]. */
+class HearingDevicesTileMapper
+@Inject
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<HearingDevicesTileModel> {
+
+    override fun map(config: QSTileConfig, data: HearingDevicesTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            label = resources.getString(R.string.quick_settings_hearing_devices_label)
+            iconRes = R.drawable.qs_hearing_devices_icon
+            val loadedIcon =
+                Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+            icon = { loadedIcon }
+            sideViewIcon = QSTileState.SideViewIcon.Chevron
+            contentDescription = label
+            if (data.isAnyActiveHearingDevice) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                secondaryLabel =
+                    resources.getString(R.string.quick_settings_hearing_devices_connected)
+            } else if (data.isAnyPairedHearingDevice) {
+                activationState = QSTileState.ActivationState.INACTIVE
+                secondaryLabel =
+                    resources.getString(R.string.quick_settings_hearing_devices_disconnected)
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                secondaryLabel = ""
+            }
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractor.kt
new file mode 100644
index 0000000..ec0a4e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.hearingdevices.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.Flags
+import com.android.systemui.accessibility.hearingaid.HearingDevicesChecker
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel
+import com.android.systemui.statusbar.policy.BluetoothController
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+
+/** Observes hearing devices state changes providing the [HearingDevicesTileModel]. */
+class HearingDevicesTileDataInteractor
+@Inject
+constructor(
+    @Background private val backgroundContext: CoroutineContext,
+    private val bluetoothController: BluetoothController,
+    private val hearingDevicesChecker: HearingDevicesChecker,
+) : QSTileDataInteractor<HearingDevicesTileModel> {
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>,
+    ): Flow<HearingDevicesTileModel> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : BluetoothController.Callback {
+                        override fun onBluetoothStateChange(enabled: Boolean) {
+                            trySend(getModel())
+                        }
+
+                        override fun onBluetoothDevicesChanged() {
+                            trySend(getModel())
+                        }
+                    }
+                bluetoothController.addCallback(callback)
+                awaitClose { bluetoothController.removeCallback(callback) }
+            }
+            .flowOn(backgroundContext)
+            .distinctUntilChanged()
+
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        flowOf(Flags.hearingAidsQsTileDialog())
+
+    private fun getModel() =
+        HearingDevicesTileModel(
+            hearingDevicesChecker.isAnyActiveHearingDevice,
+            hearingDevicesChecker.isAnyPairedHearingDevice,
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt
new file mode 100644
index 0000000..5e7172e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.hearingdevices.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager
+import com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.Companion.LAUNCH_SOURCE_QS_TILE
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles hearing devices tile clicks. */
+class HearingDevicesTileUserActionInteractor
+@Inject
+constructor(
+    @Main private val mainContext: CoroutineContext,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val hearingDevicesDialogManager: HearingDevicesDialogManager,
+) : QSTileUserActionInteractor<HearingDevicesTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<HearingDevicesTileModel>) =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    withContext(mainContext) {
+                        hearingDevicesDialogManager.showDialog(
+                            action.expandable,
+                            LAUNCH_SOURCE_QS_TILE,
+                        )
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.expandable,
+                        Intent(Settings.ACTION_HEARING_DEVICES_SETTINGS),
+                    )
+                }
+                is QSTileUserAction.ToggleClick -> {}
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/model/HearingDevicesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/model/HearingDevicesTileModel.kt
new file mode 100644
index 0000000..4e37b77
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/model/HearingDevicesTileModel.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.hearingdevices.domain.model
+
+/** Hearing devices tile model */
+data class HearingDevicesTileModel(
+    val isAnyActiveHearingDevice: Boolean,
+    val isAnyPairedHearingDevice: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index 7b6b0f6..6097ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -20,7 +20,6 @@
 
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.Flags.sceneContainer
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -42,8 +41,7 @@
                 KeyguardWmStateRefactor.isEnabled &&
                 MigrateClocksToBlueprint.isEnabled &&
                 NotificationThrottleHun.isEnabled &&
-                PredictiveBackSysUiFlag.isEnabled &&
-                DeviceEntryUdfpsRefactor.isEnabled
+                PredictiveBackSysUiFlag.isEnabled
 
     // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
 
@@ -58,7 +56,6 @@
             MigrateClocksToBlueprint.token,
             NotificationThrottleHun.token,
             PredictiveBackSysUiFlag.token,
-            DeviceEntryUdfpsRefactor.token,
             // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
index acfcd13..2259b55 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
@@ -313,7 +313,9 @@
         setWindowFocusable(true);
         mViewProxy.requestFocus();
 
-        enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle());
+        if (screenshot.getType() != WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+            enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle());
+        }
 
         attachWindow();
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
index f5c6052..08214c4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
@@ -35,6 +35,7 @@
 import android.view.Display
 import android.view.ScrollCaptureResponse
 import android.view.ViewRootImpl.ActivityConfigCallback
+import android.view.WindowManager
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import android.widget.Toast
 import android.window.WindowContext
@@ -217,7 +218,9 @@
         window.setFocusable(true)
         viewProxy.requestFocus()
 
-        enqueueScrollCaptureRequest(requestId, screenshot.userHandle)
+        if (screenshot.type != WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+            enqueueScrollCaptureRequest(requestId, screenshot.userHandle)
+        }
 
         window.attachWindow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
new file mode 100644
index 0000000..b64a577
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 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.statusbar.core
+
+import android.view.Display
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
+import com.android.systemui.util.kotlin.pairwiseBy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+
+/**
+ * Responsible for creating and starting the status bar components for each display. Also does it
+ * for newly added displays.
+ */
+@SysUISingleton
+class MultiDisplayStatusBarStarter
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val displayScopeRepository: DisplayScopeRepository,
+    private val statusBarOrchestratorFactory: StatusBarOrchestrator.Factory,
+    private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore,
+    private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
+    private val displayRepository: DisplayRepository,
+    private val initializerStore: StatusBarInitializerStore,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+    private val statusBarInitializerStore: StatusBarInitializerStore,
+) : CoreStartable {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun start() {
+        applicationScope.launch {
+            displayRepository.displays
+                .pairwiseBy { previousDisplays, currentDisplays ->
+                    currentDisplays - previousDisplays
+                }
+                .onStart { emit(displayRepository.displays.value) }
+                .collect { newDisplays ->
+                    newDisplays.forEach { createAndStartComponentsForDisplay(it) }
+                }
+        }
+    }
+
+    private fun createAndStartComponentsForDisplay(display: Display) {
+        val displayId = display.displayId
+        createAndStartOrchestratorForDisplay(displayId)
+        createAndStartInitializerForDisplay(displayId)
+    }
+
+    private fun createAndStartOrchestratorForDisplay(displayId: Int) {
+        statusBarOrchestratorFactory
+            .create(
+                displayId,
+                displayScopeRepository.scopeForDisplay(displayId),
+                statusBarWindowStateRepositoryStore.forDisplay(displayId),
+                statusBarModeRepositoryStore.forDisplay(displayId),
+                initializerStore.forDisplay(displayId),
+                statusBarWindowControllerStore.forDisplay(displayId),
+            )
+            .start()
+    }
+
+    private fun createAndStartInitializerForDisplay(displayId: Int) {
+        statusBarInitializerStore.forDisplay(displayId).start()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 7eff812..2c94632 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
-import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import com.android.systemui.statusbar.window.StatusBarWindowController
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -37,7 +37,7 @@
  * Responsible for creating the status bar window and initializing the root components of that
  * window (see [CollapsedStatusBarFragment])
  */
-interface StatusBarInitializer {
+interface StatusBarInitializer : CoreStartable {
 
     var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener?
 
@@ -68,18 +68,17 @@
     }
 
     interface Factory {
-        fun create(displayId: Int): StatusBarInitializer
+        fun create(statusBarWindowController: StatusBarWindowController): StatusBarInitializer
     }
 }
 
 class StatusBarInitializerImpl
 @AssistedInject
 constructor(
-    @Assisted private val displayId: Int,
-    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+    @Assisted private val statusBarWindowController: StatusBarWindowController,
     private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
     private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
-) : CoreStartable, StatusBarInitializer {
+) : StatusBarInitializer {
     private var component: StatusBarFragmentComponent? = null
 
     @get:VisibleForTesting
@@ -111,7 +110,7 @@
 
     private fun doStart() {
         initialized = true
-        statusBarWindowControllerStore.defaultDisplay.fragmentHostManager
+        statusBarWindowController.fragmentHostManager
             .addTagListener(
                 CollapsedStatusBarFragment.TAG,
                 object : FragmentHostManager.FragmentListener {
@@ -145,6 +144,8 @@
 
     @AssistedFactory
     interface Factory : StatusBarInitializer.Factory {
-        override fun create(displayId: Int): StatusBarInitializerImpl
+        override fun create(
+            statusBarWindowController: StatusBarWindowController
+        ): StatusBarInitializerImpl
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
index 8d044bb..6c38026 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import java.util.concurrent.ConcurrentHashMap
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineName
@@ -53,6 +54,7 @@
     @Background private val backgroundApplicationScope: CoroutineScope,
     private val factory: StatusBarInitializer.Factory,
     private val displayRepository: DisplayRepository,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
 ) : StatusBarInitializerStore, CoreStartable {
 
     init {
@@ -68,7 +70,11 @@
         if (displayRepository.getDisplay(displayId) == null) {
             throw IllegalArgumentException("Display with id $displayId doesn't exist.")
         }
-        return perDisplayInitializers.computeIfAbsent(displayId) { factory.create(displayId) }
+        return perDisplayInitializers.computeIfAbsent(displayId) {
+            factory.create(
+                statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId)
+            )
+        }
     }
 
     override fun start() {
@@ -85,15 +91,13 @@
 @SysUISingleton
 class SingleDisplayStatusBarInitializerStore
 @Inject
-constructor(factory: StatusBarInitializerImpl.Factory) : StatusBarInitializerStore {
+constructor(private val defaultInstance: StatusBarInitializer) : StatusBarInitializerStore {
 
     init {
         StatusBarConnectedDisplays.assertInLegacyMode()
     }
 
-    private val defaultInstance = factory.create(Display.DEFAULT_DISPLAY)
-
     override val defaultDisplay: StatusBarInitializer = defaultInstance
 
-    override fun forDisplay(displayId: Int): StatusBarInitializer = defaultDisplay
+    override fun forDisplay(displayId: Int): StatusBarInitializer = defaultInstance
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
index d372eb2..47e6c57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -16,12 +16,12 @@
 
 package com.android.systemui.statusbar.core
 
+import android.view.Display
 import android.view.View
 import com.android.systemui.CoreStartable
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.plugins.PluginDependencyProvider
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -31,19 +31,21 @@
 import com.android.systemui.statusbar.AutoHideUiElement
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.data.model.StatusBarMode
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
 import com.android.systemui.statusbar.phone.AutoHideController
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
-import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
-import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStatePerDisplayRepository
 import com.android.wm.shell.bubbles.Bubbles
 import dagger.Lazy
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import java.io.PrintWriter
 import java.util.Optional
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
@@ -57,27 +59,35 @@
  * It is a temporary class, created to pull status bar related logic out of CentralSurfacesImpl. The
  * plan is break it out into individual classes.
  */
-@SysUISingleton
 class StatusBarOrchestrator
-@Inject
+@AssistedInject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
-    private val statusBarInitializer: StatusBarInitializer,
-    private val statusBarModeRepository: StatusBarModeRepositoryStore,
-    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+    @Assisted private val displayId: Int,
+    @Assisted private val coroutineScope: CoroutineScope,
+    @Assisted private val statusBarWindowStateRepository: StatusBarWindowStatePerDisplayRepository,
+    @Assisted private val statusBarModeRepository: StatusBarModePerDisplayRepository,
+    @Assisted private val statusBarInitializer: StatusBarInitializer,
+    @Assisted private val statusBarWindowController: StatusBarWindowController,
     private val demoModeController: DemoModeController,
     private val pluginDependencyProvider: PluginDependencyProvider,
     private val autoHideController: AutoHideController,
     private val remoteInputManager: NotificationRemoteInputManager,
     private val notificationShadeWindowViewControllerLazy:
-    Lazy<NotificationShadeWindowViewController>,
+        Lazy<NotificationShadeWindowViewController>,
     private val shadeSurface: ShadeSurface,
     private val bubblesOptional: Optional<Bubbles>,
-    private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore,
+    private val dumpManager: DumpManager,
     powerInteractor: PowerInteractor,
     primaryBouncerInteractor: PrimaryBouncerInteractor,
 ) : CoreStartable {
 
+    private val dumpableName: String =
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            javaClass.simpleName
+        } else {
+            "${javaClass.simpleName}$displayId"
+        }
+
     private val phoneStatusBarViewController =
         MutableStateFlow<PhoneStatusBarViewController?>(value = null)
 
@@ -86,9 +96,9 @@
 
     private val shouldAnimateNextBarModeChange =
         combine(
-            statusBarModeRepository.defaultDisplay.isTransientShown,
+            statusBarModeRepository.isTransientShown,
             powerInteractor.isAwake,
-            statusBarWindowStateRepositoryStore.defaultDisplay.windowState,
+            statusBarWindowStateRepository.windowState,
         ) { isTransientShown, isDeviceAwake, statusBarWindowState ->
             !isTransientShown &&
                 isDeviceAwake &&
@@ -107,8 +117,8 @@
 
     private val statusBarVisible =
         combine(
-            statusBarModeRepository.defaultDisplay.statusBarMode,
-            statusBarWindowStateRepositoryStore.defaultDisplay.windowState,
+            statusBarModeRepository.statusBarMode,
+            statusBarWindowStateRepository.windowState,
         ) { mode, statusBarWindowState ->
             mode != StatusBarMode.LIGHTS_OUT &&
                 mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT &&
@@ -119,7 +129,7 @@
         combine(
                 shouldAnimateNextBarModeChange,
                 phoneStatusBarTransitions.filterNotNull(),
-                statusBarModeRepository.defaultDisplay.statusBarMode,
+                statusBarModeRepository.statusBarMode,
                 ::Triple,
             )
             .distinctUntilChangedBy { (_, barTransitions, statusBarMode) ->
@@ -130,26 +140,29 @@
 
     override fun start() {
         StatusBarSimpleFragment.assertInNewMode()
-        applicationScope.launch {
-            launch {
-                controllerAndBouncerShowing.collect { (controller, bouncerShowing) ->
-                    setBouncerShowingForStatusBarComponents(controller, bouncerShowing)
+        coroutineScope
+            .launch {
+                dumpManager.registerCriticalDumpable(dumpableName, this@StatusBarOrchestrator)
+                launch {
+                    controllerAndBouncerShowing.collect { (controller, bouncerShowing) ->
+                        setBouncerShowingForStatusBarComponents(controller, bouncerShowing)
+                    }
                 }
-            }
-            launch {
-                barTransitionsAndDeviceAsleep.collect { (barTransitions, deviceAsleep) ->
-                    if (deviceAsleep) {
-                        barTransitions.finishAnimations()
+                launch {
+                    barTransitionsAndDeviceAsleep.collect { (barTransitions, deviceAsleep) ->
+                        if (deviceAsleep) {
+                            barTransitions.finishAnimations()
+                        }
+                    }
+                }
+                launch { statusBarVisible.collect { updateBubblesVisibility(it) } }
+                launch {
+                    barModeUpdate.collect { (animate, barTransitions, statusBarMode) ->
+                        updateBarMode(animate, barTransitions, statusBarMode)
                     }
                 }
             }
-            launch { statusBarVisible.collect { updateBubblesVisibility(it) } }
-            launch {
-                barModeUpdate.collect { (animate, barTransitions, statusBarMode) ->
-                    updateBarMode(animate, barTransitions, statusBarMode)
-                }
-            }
-        }
+            .invokeOnCompletion { dumpManager.unregisterDumpable(dumpableName) }
         createAndAddWindow()
         setupPluginDependencies()
         setUpAutoHide()
@@ -157,7 +170,7 @@
 
     private fun createAndAddWindow() {
         initializeStatusBarFragment()
-        statusBarWindowControllerStore.defaultDisplay.attach()
+        statusBarWindowController.attach()
     }
 
     private fun initializeStatusBarFragment() {
@@ -170,6 +183,10 @@
                     phoneStatusBarViewController.value = statusBarViewController
                     phoneStatusBarTransitions.value = statusBarTransitions
 
+                    if (displayId != Display.DEFAULT_DISPLAY) {
+                        return
+                    }
+                    // TODO(b/373310629): shade should be display id aware
                     notificationShadeWindowViewControllerLazy
                         .get()
                         .setStatusBarViewController(statusBarViewController)
@@ -189,6 +206,10 @@
     }
 
     private fun setUpAutoHide() {
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            return
+        }
+        // TODO(b/373309973): per display implementation of auto hide controller
         autoHideController.setStatusBar(
             object : AutoHideUiElement {
                 override fun synchronizeState() {}
@@ -198,13 +219,14 @@
                 }
 
                 override fun isVisible(): Boolean {
-                    return statusBarModeRepository.defaultDisplay.isTransientShown.value
+                    return statusBarModeRepository.isTransientShown.value
                 }
 
                 override fun hide() {
-                    statusBarModeRepository.defaultDisplay.clearTransient()
+                    statusBarModeRepository.clearTransient()
                 }
-            })
+            }
+        )
     }
 
     private fun updateBarMode(
@@ -215,11 +237,18 @@
         if (!demoModeController.isInDemoMode) {
             barTransitions.transitionTo(barMode.toTransitionModeInt(), animate)
         }
-        autoHideController.touchAutoHide()
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            // TODO(b/373309973): per display implementation of auto hide controller
+            autoHideController.touchAutoHide()
+        }
     }
 
     private fun updateBubblesVisibility(statusBarVisible: Boolean) {
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            return
+        }
         bubblesOptional.ifPresent { bubbles: Bubbles ->
+            // TODO(b/373311537): per display implementation of Bubbles
             bubbles.onStatusBarVisibilityChanged(statusBarVisible)
         }
     }
@@ -238,11 +267,23 @@
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println(statusBarWindowStateRepositoryStore.defaultDisplay.windowState.value)
+        pw.println(statusBarWindowStateRepository.windowState.value)
         CentralSurfaces.dumpBarTransitions(
             pw,
             "PhoneStatusBarTransitions",
             phoneStatusBarTransitions.value,
         )
     }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            displayId: Int,
+            displayScope: CoroutineScope,
+            statusBarWindowStateRepository: StatusBarWindowStatePerDisplayRepository,
+            statusBarModeRepository: StatusBarModePerDisplayRepository,
+            statusBarInitializer: StatusBarInitializer,
+            statusBarWindowController: StatusBarWindowController,
+        ): StatusBarOrchestrator
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
index 962cb095..154be1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -31,6 +31,7 @@
 
 interface StatusBarModeRepositoryStore {
     val defaultDisplay: StatusBarModePerDisplayRepository
+
     fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository
 }
 
@@ -39,7 +40,7 @@
 @Inject
 constructor(
     @DisplayId private val displayId: Int,
-    factory: StatusBarModePerDisplayRepositoryFactory
+    factory: StatusBarModePerDisplayRepositoryFactory,
 ) :
     StatusBarModeRepositoryStore,
     CoreStartable,
@@ -47,11 +48,9 @@
     override val defaultDisplay = factory.create(displayId)
 
     override fun forDisplay(displayId: Int) =
-        if (this.displayId == displayId) {
-            defaultDisplay
-        } else {
-            TODO("b/127878649 implement multi-display state management")
-        }
+        // TODO(b/369337087): implement per display status bar modes.
+        //  For now just use default display instance.
+        defaultDisplay
 
     override fun start() {
         defaultDisplay.start()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index f3437b5..00c5c40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -2098,13 +2098,8 @@
     }
 
     class TouchHandler implements Gefingerpoken {
-        private boolean mSwipeWantsIt = false;
-
         @Override
         public boolean onInterceptTouchEvent(MotionEvent ev) {
-            // Reset on each call to intercept, and share swipe state with onTouchEvent()
-            // below when this method returns true.
-            mSwipeWantsIt = false;
             mView.initDownStates(ev);
             mView.handleEmptySpaceClick(ev);
 
@@ -2131,16 +2126,17 @@
                     mView.startDraggingOnHun();
                 }
             }
+            boolean swipeWantsIt = false;
             if (mLongPressedView == null && !mView.isBeingDragged()
                     && !mView.isExpandingNotification()
                     && !mView.getExpandedInThisMotion()
                     && !mView.getOnlyScrollingInThisMotion()
                     && !mView.getDisallowDismissInThisMotion()) {
-                mSwipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
+                swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
             }
             // Check if we need to clear any snooze leavebehinds
             boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
-            if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !mSwipeWantsIt &&
+            if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt &&
                     !expandWantsIt && !scrollWantsIt) {
                 mView.setCheckForLeaveBehind(false);
                 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
@@ -2159,8 +2155,7 @@
                     && ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
                 mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
             }
-            return mSwipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt ||
-                    hunWantsIt;
+            return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt || hunWantsIt;
         }
 
         @Override
@@ -2197,7 +2192,7 @@
                     }
                 }
             }
-            boolean horizontalSwipeWantsIt = mSwipeWantsIt;
+            boolean horizontalSwipeWantsIt = false;
             boolean scrollerWantsIt = false;
             // NOTE: the order of these is important. If reversed, onScrollTouch will reset on an
             // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
@@ -2206,7 +2201,7 @@
                     && !mView.getExpandedInThisMotion()
                     && !onlyScrollingInThisMotion
                     && !mView.getDisallowDismissInThisMotion()) {
-                mSwipeHelper.onTouchEvent(ev);
+                horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
             }
             if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
                     && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 5f864e5..09e191d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -18,10 +18,12 @@
 import android.view.Display
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Default
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.core.CommandQueueInitializer
 import com.android.systemui.statusbar.core.MultiDisplayStatusBarInitializerStore
+import com.android.systemui.statusbar.core.MultiDisplayStatusBarStarter
 import com.android.systemui.statusbar.core.SingleDisplayStatusBarInitializerStore
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.core.StatusBarInitializer
@@ -29,7 +31,9 @@
 import com.android.systemui.statusbar.core.StatusBarInitializerStore
 import com.android.systemui.statusbar.core.StatusBarOrchestrator
 import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
 import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStoreImpl
 import dagger.Binds
@@ -38,6 +42,7 @@
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import kotlinx.coroutines.CoroutineScope
 
 /** Similar in purpose to [StatusBarModule], but scoped only to phones */
 @Module
@@ -58,24 +63,56 @@
         implFactory: StatusBarInitializerImpl.Factory
     ): StatusBarInitializer.Factory
 
-    /** Binds {@link StatusBarInitializer} as a {@link CoreStartable}. */
-    @Binds
-    @IntoMap
-    @ClassKey(StatusBarInitializerImpl::class)
-    fun bindStatusBarInitializer(@Default impl: StatusBarInitializerImpl): CoreStartable
-
     @Binds fun statusBarInitializer(@Default impl: StatusBarInitializerImpl): StatusBarInitializer
 
     companion object {
+        /** Binds {@link StatusBarInitializer} as a {@link CoreStartable}. */
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(StatusBarInitializer::class)
+        fun bindStatusBarInitializer(
+            @Default defaultInitializerLazy: Lazy<StatusBarInitializerImpl>
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                // Will be started through MultiDisplayStatusBarStarter
+                CoreStartable.NOP
+            } else {
+                defaultInitializerLazy.get()
+            }
+        }
+
         // Dagger doesn't support providing AssistedInject types, without a qualifier. Using the
         // Default qualifier for this reason.
         @Default
         @Provides
         @SysUISingleton
         fun statusBarInitializerImpl(
-            implFactory: StatusBarInitializerImpl.Factory
+            implFactory: StatusBarInitializerImpl.Factory,
+            statusBarWindowControllerStore: StatusBarWindowControllerStore,
         ): StatusBarInitializerImpl {
-            return implFactory.create(displayId = Display.DEFAULT_DISPLAY)
+            return implFactory.create(statusBarWindowControllerStore.defaultDisplay)
+        }
+
+        @Provides
+        @SysUISingleton
+        @Default // Dagger does not support providing @AssistedInject types without a qualifier
+        fun orchestrator(
+            @Background backgroundApplicationScope: CoroutineScope,
+            statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore,
+            statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
+            initializerStore: StatusBarInitializerStore,
+            statusBarWindowControllerStore: StatusBarWindowControllerStore,
+            statusBarOrchestratorFactory: StatusBarOrchestrator.Factory,
+        ): StatusBarOrchestrator {
+            return statusBarOrchestratorFactory.create(
+                Display.DEFAULT_DISPLAY,
+                backgroundApplicationScope,
+                statusBarWindowStateRepositoryStore.defaultDisplay,
+                statusBarModeRepositoryStore.defaultDisplay,
+                initializerStore.defaultDisplay,
+                statusBarWindowControllerStore.defaultDisplay,
+            )
         }
 
         @Provides
@@ -83,11 +120,29 @@
         @IntoMap
         @ClassKey(StatusBarOrchestrator::class)
         fun orchestratorCoreStartable(
-            orchestratorLazy: Lazy<StatusBarOrchestrator>
+            @Default orchestratorLazy: Lazy<StatusBarOrchestrator>
         ): CoreStartable {
-            return if (StatusBarSimpleFragment.isEnabled) {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                // Will be started through MultiDisplayStatusBarStarter
+                CoreStartable.NOP
+            } else if (StatusBarSimpleFragment.isEnabled) {
                 orchestratorLazy.get()
             } else {
+                // Will be started through CentralSurfacesImpl
+                CoreStartable.NOP
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(MultiDisplayStatusBarStarter::class)
+        fun multiDisplayStarter(
+            multiDisplayStatusBarStarterLazy: Lazy<MultiDisplayStatusBarStarter>
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayStatusBarStarterLazy.get()
+            } else {
                 CoreStartable.NOP
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 35e30b8..d868519 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -29,6 +29,7 @@
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
+import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -333,7 +334,7 @@
     @Override
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        mDumpManager.registerDumpable(getClass().getSimpleName(), this);
+        mDumpManager.registerDumpable(getDumpableName(), this);
         mStatusBarFragmentComponent = mStatusBarFragmentComponentFactory.create(
                 (PhoneStatusBarView) getView());
         mStatusBarFragmentComponent.init();
@@ -374,6 +375,14 @@
                 mStatusBar, mCollapsedStatusBarViewModel, mStatusBarVisibilityChangeListener);
     }
 
+    private String getDumpableName() {
+        if (getContext().getDisplayId() == Display.DEFAULT_DISPLAY) {
+            return getClass().getSimpleName();
+        } else {
+            return getClass().getSimpleName() + getContext().getDisplayId();
+        }
+    }
+
     @Override
     public void onCameraLaunchGestureDetected(int source) {
         mWaitingForWindowStateChangeAfterCameraLaunch = true;
@@ -470,7 +479,7 @@
             startable.stop();
             mStartableStates.put(startable, Startable.State.STOPPED);
         }
-        mDumpManager.unregisterDumpable(getClass().getSimpleName());
+        mDumpManager.unregisterDumpable(getDumpableName());
         if (mNicBindingDisposable != null) {
             mNicBindingDisposable.dispose();
             mNicBindingDisposable = null;
@@ -486,7 +495,12 @@
         NotificationIconContainer notificationIcons =
                 notificationIconArea.requireViewById(R.id.notificationIcons);
         mNotificationIconAreaInner = notificationIcons;
-        mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
+        if (getContext().getDisplayId() == Display.DEFAULT_DISPLAY) {
+            //TODO(b/369337701): implement notification icons for all displays.
+            // Currently if we try to bind for all displays, there is a crash, because the same
+            // notification icon view can't have multiple parents.
+            mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
+        }
 
         if (!StatusBarSimpleFragment.isEnabled()) {
             updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
deleted file mode 100644
index c51aa04..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
-import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
-import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.AnimatedStateListDrawable;
-import android.util.Pair;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.ImageView;
-
-import com.android.systemui.Flags;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.AuthRippleController;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
-import com.android.systemui.doze.util.BurnInHelperKt;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-public class LegacyLockIconViewControllerBaseTest extends SysuiTestCase {
-    protected static final String UNLOCKED_LABEL = "unlocked";
-    protected static final String LOCKED_LABEL = "locked";
-    protected static final int PADDING = 10;
-
-    protected MockitoSession mStaticMockSession;
-
-    protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-    protected @Mock DeviceEntryInteractor mDeviceEntryInteractor;
-    protected @Mock LockIconView mLockIconView;
-    protected @Mock ImageView mLockIcon;
-    protected @Mock AnimatedStateListDrawable mIconDrawable;
-    protected @Mock Context mContext;
-    protected @Mock Resources mResources;
-    protected @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager;
-    protected @Mock StatusBarStateController mStatusBarStateController;
-    protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    protected @Mock KeyguardViewController mKeyguardViewController;
-    protected @Mock KeyguardStateController mKeyguardStateController;
-    protected @Mock FalsingManager mFalsingManager;
-    protected @Mock AuthController mAuthController;
-    protected @Mock DumpManager mDumpManager;
-    protected @Mock AccessibilityManager mAccessibilityManager;
-    protected @Mock ConfigurationController mConfigurationController;
-    protected @Mock VibratorHelper mVibrator;
-    protected @Mock AuthRippleController mAuthRippleController;
-    protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
-    protected FakeFeatureFlags mFeatureFlags;
-
-    protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
-
-    protected LegacyLockIconViewController mUnderTest;
-
-    // Capture listeners so that they can be used to send events
-    @Captor protected ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
-            ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
-
-    @Captor protected ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor =
-            ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
-    protected KeyguardStateController.Callback mKeyguardStateCallback;
-
-    @Captor protected ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor =
-            ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
-    protected StatusBarStateController.StateListener mStatusBarStateListener;
-
-    @Captor protected ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
-    protected AuthController.Callback mAuthControllerCallback;
-
-    @Captor protected ArgumentCaptor<KeyguardUpdateMonitorCallback>
-            mKeyguardUpdateMonitorCallbackCaptor =
-            ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
-    protected KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
-
-    @Captor protected ArgumentCaptor<Point> mPointCaptor;
-
-    @Before
-    public void setUp() throws Exception {
-        mStaticMockSession = mockitoSession()
-                .mockStatic(BurnInHelperKt.class)
-                .strictness(Strictness.LENIENT)
-                .startMocking();
-        MockitoAnnotations.initMocks(this);
-
-        setupLockIconViewMocks();
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
-        Rect windowBounds = new Rect(0, 0, 800, 1200);
-        when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds);
-        when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
-        when(mResources.getString(R.string.accessibility_lock_icon)).thenReturn(LOCKED_LABEL);
-        when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
-        when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING);
-        when(mAuthController.getScaleFactor()).thenReturn(1f);
-
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-
-        if (!SceneContainerFlag.isEnabled()) {
-            mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
-            //TODO move this to use @DisableFlags annotation if needed
-            mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
-        }
-
-        mFeatureFlags = new FakeFeatureFlags();
-        mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
-        mFeatureFlags.set(LOCKSCREEN_ENABLE_LANDSCAPE, false);
-
-        mUnderTest = new LegacyLockIconViewController(
-                mStatusBarStateController,
-                mKeyguardUpdateMonitor,
-                mKeyguardViewController,
-                mKeyguardStateController,
-                mFalsingManager,
-                mAuthController,
-                mDumpManager,
-                mAccessibilityManager,
-                mConfigurationController,
-                mDelayableExecutor,
-                mVibrator,
-                mAuthRippleController,
-                mResources,
-                mKosmos.getKeyguardTransitionInteractor(),
-                KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
-                mFeatureFlags,
-                mPrimaryBouncerInteractor,
-                mContext,
-                () -> mDeviceEntryInteractor
-        );
-    }
-
-    @After
-    public void tearDown() {
-        mStaticMockSession.finishMocking();
-    }
-
-    protected Pair<Float, Point> setupUdfps() {
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
-        final Point udfpsLocation = new Point(50, 75);
-        final float radius = 33f;
-        when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
-        when(mAuthController.getUdfpsRadius()).thenReturn(radius);
-
-        return new Pair(radius, udfpsLocation);
-    }
-
-    protected void setupShowLockIcon() {
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
-    }
-
-    protected void captureAuthControllerCallback() {
-        verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
-        mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
-    }
-
-    protected void captureKeyguardStateCallback() {
-        verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture());
-        mKeyguardStateCallback = mKeyguardStateCaptor.getValue();
-    }
-
-    protected void captureStatusBarStateListener() {
-        verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture());
-        mStatusBarStateListener = mStatusBarStateCaptor.getValue();
-    }
-
-    protected void captureKeyguardUpdateMonitorCallback() {
-        verify(mKeyguardUpdateMonitor).registerCallback(
-                mKeyguardUpdateMonitorCallbackCaptor.capture());
-        mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
-    }
-
-    protected void setupLockIconViewMocks() {
-        when(mLockIconView.getResources()).thenReturn(mResources);
-        when(mLockIconView.getContext()).thenReturn(mContext);
-        when(mLockIconView.getLockIcon()).thenReturn(mLockIcon);
-    }
-
-    protected void resetLockIconView() {
-        reset(mLockIconView);
-        setupLockIconViewMocks();
-    }
-
-    protected void init(boolean useDozeMigrationFlag) {
-        mFeatureFlags.set(DOZING_MIGRATION_1, useDozeMigrationFlag);
-        mUnderTest.setLockIconView(mLockIconView);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
deleted file mode 100644
index c1ba39e..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2021 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.keyguard;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
-import static com.android.keyguard.LockIconView.ICON_LOCK;
-import static com.android.keyguard.LockIconView.ICON_UNLOCK;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.graphics.Point;
-import android.hardware.biometrics.BiometricSourceType;
-import android.testing.TestableLooper;
-import android.util.Pair;
-import android.view.HapticFeedbackConstants;
-import android.view.View;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.biometrics.UdfpsController;
-import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
-import com.android.systemui.doze.util.BurnInHelperKt;
-import com.android.systemui.flags.EnableSceneContainer;
-import com.android.systemui.statusbar.StatusBarState;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class LegacyLockIconViewControllerTest extends LegacyLockIconViewControllerBaseTest {
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        when(mLockIconView.isAttachedToWindow()).thenReturn(true);
-    }
-
-    @Test
-    public void testUpdateFingerprintLocationOnInit() {
-        // GIVEN fp sensor location is available pre-attached
-        Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
-
-        // WHEN lock icon view controller is initialized and attached
-        init(/* useMigrationFlag= */false);
-
-        // THEN lock icon view location is updated to the udfps location with UDFPS radius
-        verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
-                eq(PADDING));
-    }
-
-    @Test
-    public void testUpdatePaddingBasedOnResolutionScale() {
-        // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
-        Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
-        when(mAuthController.getScaleFactor()).thenReturn(5f);
-
-        // WHEN lock icon view controller is initialized and attached
-        init(/* useMigrationFlag= */false);
-
-        // THEN lock icon view location is updated with the scaled radius
-        verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
-                eq(PADDING * 5));
-    }
-
-    @Test
-    public void testUpdateLockIconLocationOnAuthenticatorsRegistered() {
-        // GIVEN fp sensor location is not available pre-init
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
-        when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
-        init(/* useMigrationFlag= */false);
-        resetLockIconView(); // reset any method call counts for when we verify method calls later
-
-        // GIVEN fp sensor location is available post-attached
-        captureAuthControllerCallback();
-        Pair<Float, Point> udfps = setupUdfps();
-
-        // WHEN all authenticators are registered
-        mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
-        mDelayableExecutor.runAllReady();
-
-        // THEN lock icon view location is updated with the same coordinates as auth controller vals
-        verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
-                eq(PADDING));
-    }
-
-    @Test
-    public void testUpdateLockIconLocationOnUdfpsLocationChanged() {
-        // GIVEN fp sensor location is not available pre-init
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
-        when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
-        init(/* useMigrationFlag= */false);
-        resetLockIconView(); // reset any method call counts for when we verify method calls later
-
-        // GIVEN fp sensor location is available post-attached
-        captureAuthControllerCallback();
-        Pair<Float, Point> udfps = setupUdfps();
-
-        // WHEN udfps location changes
-        mAuthControllerCallback.onUdfpsLocationChanged(new UdfpsOverlayParams());
-        mDelayableExecutor.runAllReady();
-
-        // THEN lock icon view location is updated with the same coordinates as auth controller vals
-        verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
-                eq(PADDING));
-    }
-
-    @Test
-    public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() {
-        // GIVEN Udpfs sensor location is available
-        setupUdfps();
-
-        // WHEN the view is attached
-        init(/* useMigrationFlag= */false);
-
-        // THEN the lock icon view background should be enabled
-        verify(mLockIconView).setUseBackground(true);
-    }
-
-    @Test
-    public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() {
-        // GIVEN Udfps sensor location is not supported
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
-
-        // WHEN the view is attached
-        init(/* useMigrationFlag= */false);
-
-        // THEN the lock icon view background should be disabled
-        verify(mLockIconView).setUseBackground(false);
-    }
-
-    @Test
-    public void testLockIconStartState() {
-        // GIVEN lock icon state
-        setupShowLockIcon();
-
-        // WHEN lock icon controller is initialized
-        init(/* useMigrationFlag= */false);
-
-        // THEN the lock icon should show
-        verify(mLockIconView).updateIcon(ICON_LOCK, false);
-    }
-
-    @Test
-    public void testLockIcon_updateToUnlock() {
-        // GIVEN starting state for the lock icon
-        setupShowLockIcon();
-
-        // GIVEN lock icon controller is initialized and view is attached
-        init(/* useMigrationFlag= */false);
-        captureKeyguardStateCallback();
-        reset(mLockIconView);
-
-        // WHEN the unlocked state changes to canDismissLockScreen=true
-        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
-        mKeyguardStateCallback.onUnlockedChanged();
-
-        // THEN the unlock should show
-        verify(mLockIconView).updateIcon(ICON_UNLOCK, false);
-    }
-
-    @Test
-    public void testLockIcon_clearsIconWhenUnlocked() {
-        // GIVEN udfps not enrolled
-        setupUdfps();
-        when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false);
-
-        // GIVEN starting state for the lock icon
-        setupShowLockIcon();
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
-
-        // GIVEN lock icon controller is initialized and view is attached
-        init(/* useMigrationFlag= */false);
-        captureStatusBarStateListener();
-        reset(mLockIconView);
-
-        // WHEN the dozing state changes
-        mStatusBarStateListener.onDozingChanged(false /* isDozing */);
-
-        // THEN the icon is cleared
-        verify(mLockIconView).clearIcon();
-    }
-
-    @Test
-    public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() {
-        // GIVEN udfps enrolled
-        setupUdfps();
-        when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
-
-        // GIVEN starting state for the lock icon
-        setupShowLockIcon();
-
-        // GIVEN lock icon controller is initialized and view is attached
-        init(/* useMigrationFlag= */false);
-        captureStatusBarStateListener();
-        reset(mLockIconView);
-
-        // WHEN the dozing state changes
-        mStatusBarStateListener.onDozingChanged(true /* isDozing */);
-
-        // THEN the AOD lock icon should show
-        verify(mLockIconView).updateIcon(ICON_LOCK, true);
-    }
-
-    @Test
-    public void testBurnInOffsetsUpdated_onDozeAmountChanged() {
-        // GIVEN udfps enrolled
-        setupUdfps();
-        when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
-
-        // GIVEN burn-in offset = 5
-        int burnInOffset = 5;
-        when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset);
-
-        // GIVEN starting state for the lock icon (keyguard)
-        setupShowLockIcon();
-        init(/* useMigrationFlag= */false);
-        captureStatusBarStateListener();
-        reset(mLockIconView);
-
-        // WHEN dozing updates
-        mStatusBarStateListener.onDozingChanged(true /* isDozing */);
-        mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
-
-        // THEN the view's translation is updated to use the AoD burn-in offsets
-        verify(mLockIconView).setTranslationY(burnInOffset);
-        verify(mLockIconView).setTranslationX(burnInOffset);
-        reset(mLockIconView);
-
-        // WHEN the device is no longer dozing
-        mStatusBarStateListener.onDozingChanged(false /* isDozing */);
-        mStatusBarStateListener.onDozeAmountChanged(0f, 0f);
-
-        // THEN the view is updated to NO translation (no burn-in offsets anymore)
-        verify(mLockIconView).setTranslationY(0);
-        verify(mLockIconView).setTranslationX(0);
-    }
-
-    @Test
-    public void lockIconShows_afterUnlockStateChanges() {
-        // GIVEN lock icon controller is initialized and view is attached
-        init(/* useMigrationFlag= */false);
-        captureKeyguardStateCallback();
-        captureKeyguardUpdateMonitorCallback();
-
-        // GIVEN user has unlocked with a biometric auth (ie: face auth)
-        // and biometric running state changes
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
-                BiometricSourceType.FACE);
-        reset(mLockIconView);
-
-        // WHEN the unlocked state changes
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
-        mKeyguardStateCallback.onUnlockedChanged();
-
-        // THEN the lock icon is shown
-        verify(mLockIconView).setContentDescription(LOCKED_LABEL);
-    }
-
-    @Test
-    public void lockIconAccessibility_notVisibleToUser() {
-        // GIVEN lock icon controller is initialized and view is attached
-        init(/* useMigrationFlag= */false);
-        captureKeyguardStateCallback();
-        captureKeyguardUpdateMonitorCallback();
-
-        // GIVEN user has unlocked with a biometric auth (ie: face auth)
-        // and biometric running state changes
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
-                BiometricSourceType.FACE);
-        reset(mLockIconView);
-        when(mLockIconView.isVisibleToUser()).thenReturn(false);
-
-        // WHEN the unlocked state changes
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
-        mKeyguardStateCallback.onUnlockedChanged();
-
-        // THEN the lock icon is shown
-        verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-    }
-
-    @Test
-    public void lockIconAccessibility_bouncerAnimatingAway() {
-        // GIVEN lock icon controller is initialized and view is attached
-        init(/* useMigrationFlag= */false);
-        captureKeyguardStateCallback();
-        captureKeyguardUpdateMonitorCallback();
-
-        // GIVEN user has unlocked with a biometric auth (ie: face auth)
-        // and biometric running state changes
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
-                BiometricSourceType.FACE);
-        reset(mLockIconView);
-        when(mLockIconView.isVisibleToUser()).thenReturn(true);
-        when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
-
-        // WHEN the unlocked state changes
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
-        mKeyguardStateCallback.onUnlockedChanged();
-
-        // THEN the lock icon is shown
-        verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-    }
-
-    @Test
-    public void lockIconAccessibility_bouncerNotAnimatingAway_viewVisible() {
-        // GIVEN lock icon controller is initialized and view is attached
-        init(/* useMigrationFlag= */false);
-        captureKeyguardStateCallback();
-        captureKeyguardUpdateMonitorCallback();
-
-        // GIVEN user has unlocked with a biometric auth (ie: face auth)
-        // and biometric running state changes
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
-                BiometricSourceType.FACE);
-        reset(mLockIconView);
-        when(mLockIconView.isVisibleToUser()).thenReturn(true);
-        when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(false);
-
-        // WHEN the unlocked state changes
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
-        mKeyguardStateCallback.onUnlockedChanged();
-
-        // THEN the lock icon is shown
-        verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-    }
-
-    @Test
-    public void playHaptic_onTouchExploration_performHapticFeedback() {
-       // WHEN request to vibrate on touch exploration
-        mUnderTest.vibrateOnTouchExploration();
-
-        // THEN performHapticFeedback is used
-        verify(mVibrator).performHapticFeedback(any(), eq(HapticFeedbackConstants.CONTEXT_CLICK));
-    }
-
-    @Test
-    public void playHaptic_onLongPress_performHapticFeedback() {
-        // WHEN request to vibrate on long press
-        mUnderTest.vibrateOnLongPress();
-
-        // THEN uses perform haptic feedback
-        verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
-    }
-
-    @Test
-    public void longPress_showBouncer_sceneContainerNotEnabled() {
-        init(/* useMigrationFlag= */ false);
-        when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
-
-        // WHEN longPress
-        mUnderTest.onLongPress();
-
-        // THEN show primary bouncer via keyguard view controller, not scene container
-        verify(mKeyguardViewController).showPrimaryBouncer(anyBoolean());
-        verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void longPress_showBouncer() {
-        init(/* useMigrationFlag= */ false);
-        when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
-
-        // WHEN longPress
-        mUnderTest.onLongPress();
-
-        // THEN show primary bouncer
-        verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean());
-        verify(mDeviceEntryInteractor).attemptDeviceEntry();
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void longPress_falsingTriggered_doesNotShowBouncer() {
-        init(/* useMigrationFlag= */ false);
-        when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
-
-        // WHEN longPress
-        mUnderTest.onLongPress();
-
-        // THEN don't show primary bouncer
-        verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
-        verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt
deleted file mode 100644
index 2fd3cb0..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard
-
-import android.view.View
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.LockIconView.ICON_LOCK
-import com.android.systemui.doze.util.getBurnInOffset
-import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.util.mockito.whenever
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class LegacyLockIconViewControllerWithCoroutinesTest : LegacyLockIconViewControllerBaseTest() {
-
-    /** After migration, replaces LockIconViewControllerTest version */
-    @Test
-    fun testLockIcon_clearsIconWhenUnlocked() =
-        runBlocking(IMMEDIATE) {
-            // GIVEN udfps not enrolled
-            setupUdfps()
-            whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false)
-
-            // GIVEN starting state for the lock icon
-            setupShowLockIcon()
-            whenever(mStatusBarStateController.state).thenReturn(StatusBarState.SHADE)
-
-            // GIVEN lock icon controller is initialized and view is attached
-            init(/* useMigrationFlag= */ true)
-            reset(mLockIconView)
-
-            // WHEN the dozing state changes
-            mUnderTest.mIsDozingCallback.accept(false)
-            // THEN the icon is cleared
-            verify(mLockIconView).clearIcon()
-        }
-
-    /** After migration, replaces LockIconViewControllerTest version */
-    @Test
-    fun testLockIcon_updateToAodLock_whenUdfpsEnrolled() =
-        runBlocking(IMMEDIATE) {
-            // GIVEN udfps enrolled
-            setupUdfps()
-            whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true)
-
-            // GIVEN starting state for the lock icon
-            setupShowLockIcon()
-
-            // GIVEN lock icon controller is initialized and view is attached
-            init(/* useMigrationFlag= */ true)
-            reset(mLockIconView)
-
-            // WHEN the dozing state changes
-            mUnderTest.mIsDozingCallback.accept(true)
-
-            // THEN the AOD lock icon should show
-            verify(mLockIconView).updateIcon(ICON_LOCK, true)
-        }
-
-    /** After migration, replaces LockIconViewControllerTest version */
-    @Test
-    fun testBurnInOffsetsUpdated_onDozeAmountChanged() =
-        runBlocking(IMMEDIATE) {
-            // GIVEN udfps enrolled
-            setupUdfps()
-            whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true)
-
-            // GIVEN burn-in offset = 5
-            val burnInOffset = 5
-            whenever(getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
-
-            // GIVEN starting state for the lock icon (keyguard)
-            setupShowLockIcon()
-            init(/* useMigrationFlag= */ true)
-            reset(mLockIconView)
-
-            // WHEN dozing updates
-            mUnderTest.mIsDozingCallback.accept(true)
-            mUnderTest.mDozeTransitionCallback.accept(1f)
-
-            // THEN the view's translation is updated to use the AoD burn-in offsets
-            verify(mLockIconView).setTranslationY(burnInOffset.toFloat())
-            verify(mLockIconView).setTranslationX(burnInOffset.toFloat())
-            reset(mLockIconView)
-
-            // WHEN the device is no longer dozing
-            mUnderTest.mIsDozingCallback.accept(false)
-            mUnderTest.mDozeTransitionCallback.accept(0f)
-
-            // THEN the view is updated to NO translation (no burn-in offsets anymore)
-            verify(mLockIconView).setTranslationY(0f)
-            verify(mLockIconView).setTranslationX(0f)
-        }
-
-    @Test
-    fun testHideLockIconView_onLockscreenHostedDreamStateChanged() =
-        runBlocking(IMMEDIATE) {
-            // GIVEN starting state for the lock icon (keyguard) and wallpaper dream enabled
-            mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, true)
-            setupShowLockIcon()
-            init(/* useMigrationFlag= */ true)
-            reset(mLockIconView)
-
-            // WHEN dream starts
-            mUnderTest.mIsActiveDreamLockscreenHostedCallback.accept(
-                true /* isActiveDreamLockscreenHosted */
-            )
-
-            // THEN the lock icon is hidden
-            verify(mLockIconView).visibility = View.INVISIBLE
-            reset(mLockIconView)
-
-            // WHEN the device is no longer dreaming
-            mUnderTest.mIsActiveDreamLockscreenHostedCallback.accept(
-                false /* isActiveDreamLockscreenHosted */
-            )
-
-            // THEN lock icon is visible
-            verify(mLockIconView).visibility = View.VISIBLE
-        }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
deleted file mode 100644
index 6dc4b10..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics
-
-import android.graphics.Point
-import android.hardware.biometrics.BiometricSourceType
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.testing.TestableLooper.RunWithLooper
-import android.util.DisplayMetrics
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.keyguard.logging.KeyguardLogger
-import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.leak.RotationUtils
-import com.android.systemui.util.mockito.any
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import org.junit.After
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-import org.mockito.MockitoSession
-import org.mockito.quality.Strictness
-import javax.inject.Provider
-
-
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AuthRippleControllerTest : SysuiTestCase() {
-    private lateinit var staticMockSession: MockitoSession
-
-    private lateinit var controller: AuthRippleController
-    @Mock private lateinit var rippleView: AuthRippleView
-    @Mock private lateinit var commandRegistry: CommandRegistry
-    @Mock private lateinit var configurationController: ConfigurationController
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var authController: AuthController
-    @Mock private lateinit var authRippleInteractor: AuthRippleInteractor
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock
-    private lateinit var notificationShadeWindowController: NotificationShadeWindowController
-    @Mock
-    private lateinit var biometricUnlockController: BiometricUnlockController
-    @Mock
-    private lateinit var udfpsControllerProvider: Provider<UdfpsController>
-    @Mock
-    private lateinit var udfpsController: UdfpsController
-    @Mock
-    private lateinit var statusBarStateController: StatusBarStateController
-    @Mock
-    private lateinit var lightRevealScrim: LightRevealScrim
-    @Mock
-    private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
-
-    private val facePropertyRepository = FakeFacePropertyRepository()
-    private val displayMetrics = DisplayMetrics()
-
-    @Captor
-    private lateinit var biometricUnlockListener:
-            ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>
-
-    @Before
-    fun setUp() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
-        MockitoAnnotations.initMocks(this)
-        staticMockSession = mockitoSession()
-                .mockStatic(RotationUtils::class.java)
-                .strictness(Strictness.LENIENT)
-                .startMocking()
-
-        `when`(RotationUtils.getRotation(context)).thenReturn(RotationUtils.ROTATION_NONE)
-        `when`(authController.udfpsProps).thenReturn(listOf(fpSensorProp))
-        `when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
-
-        controller = AuthRippleController(
-            context,
-            authController,
-            configurationController,
-            keyguardUpdateMonitor,
-            keyguardStateController,
-            wakefulnessLifecycle,
-            commandRegistry,
-            notificationShadeWindowController,
-            udfpsControllerProvider,
-            statusBarStateController,
-            displayMetrics,
-            KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
-            biometricUnlockController,
-            lightRevealScrim,
-            authRippleInteractor,
-            facePropertyRepository,
-            rippleView,
-        )
-        controller.init()
-    }
-
-    @After
-    fun tearDown() {
-        staticMockSession.finishMocking()
-    }
-
-    @Test
-    fun testFingerprintTrigger_KeyguardShowing_Ripple() {
-        // GIVEN fp exists, keyguard is showing, unlocking with fp allowed
-        val fpsLocation = Point(5, 5)
-        `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
-        controller.onViewAttached()
-        `when`(keyguardStateController.isShowing).thenReturn(true)
-        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
-
-        // WHEN fingerprint authenticated
-        verify(biometricUnlockController).addListener(biometricUnlockListener.capture())
-        biometricUnlockListener.value
-                .onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType.FINGERPRINT)
-
-        // THEN update sensor location and show ripple
-        verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
-        verify(rippleView).startUnlockedRipple(any())
-    }
-
-    @Test
-    fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
-        // GIVEN fp exists & unlocking with fp allowed
-        val fpsLocation = Point(5, 5)
-        `when`(authController.udfpsLocation).thenReturn(fpsLocation)
-        controller.onViewAttached()
-        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
-
-        // WHEN keyguard is NOT showing & fingerprint authenticated
-        `when`(keyguardStateController.isShowing).thenReturn(false)
-        val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
-        verify(keyguardUpdateMonitor).registerCallback(captor.capture())
-        captor.value.onBiometricAuthenticated(
-            0 /* userId */,
-            BiometricSourceType.FINGERPRINT /* type */,
-            false /* isStrongBiometric */)
-
-        // THEN no ripple
-        verify(rippleView, never()).startUnlockedRipple(any())
-    }
-
-    @Test
-    fun testFingerprintTrigger_biometricUnlockNotAllowed_NoRipple() {
-        // GIVEN fp exists & keyguard is showing
-        val fpsLocation = Point(5, 5)
-        `when`(authController.udfpsLocation).thenReturn(fpsLocation)
-        controller.onViewAttached()
-        `when`(keyguardStateController.isShowing).thenReturn(true)
-
-        // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated
-        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                eq(BiometricSourceType.FINGERPRINT))).thenReturn(false)
-        val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
-        verify(keyguardUpdateMonitor).registerCallback(captor.capture())
-        captor.value.onBiometricAuthenticated(
-            0 /* userId */,
-            BiometricSourceType.FINGERPRINT /* type */,
-            false /* isStrongBiometric */)
-
-        // THEN no ripple
-        verify(rippleView, never()).startUnlockedRipple(any())
-    }
-
-    @Test
-    fun testNullFaceSensorLocationDoesNothing() {
-        facePropertyRepository.setSensorLocation(null)
-        controller.onViewAttached()
-
-        val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
-        verify(keyguardUpdateMonitor).registerCallback(captor.capture())
-
-        captor.value.onBiometricAuthenticated(
-            0 /* userId */,
-            BiometricSourceType.FACE /* type */,
-            false /* isStrongBiometric */)
-        verify(rippleView, never()).startUnlockedRipple(any())
-    }
-
-    @Test
-    fun testNullFingerprintSensorLocationDoesNothing() {
-        `when`(authController.fingerprintSensorLocation).thenReturn(null)
-        controller.onViewAttached()
-
-        val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
-        verify(keyguardUpdateMonitor).registerCallback(captor.capture())
-
-        captor.value.onBiometricAuthenticated(
-            0 /* userId */,
-            BiometricSourceType.FINGERPRINT /* type */,
-            false /* isStrongBiometric */)
-        verify(rippleView, never()).startUnlockedRipple(any())
-    }
-
-    @Test
-    fun registersAndDeregisters() {
-        controller.onViewAttached()
-        val captor = ArgumentCaptor
-            .forClass(KeyguardStateController.Callback::class.java)
-        verify(keyguardStateController).addCallback(captor.capture())
-        val captor2 = ArgumentCaptor
-            .forClass(WakefulnessLifecycle.Observer::class.java)
-        verify(wakefulnessLifecycle).addObserver(captor2.capture())
-        controller.onViewDetached()
-        verify(keyguardStateController).removeCallback(any())
-        verify(wakefulnessLifecycle).removeObserver(any())
-    }
-
-    @Test
-    @RunWithLooper(setAsMainLooper = true)
-    fun testAnimatorRunWhenWakeAndUnlock_fingerprint() {
-        mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
-        val fpsLocation = Point(5, 5)
-        `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
-        controller.onViewAttached()
-        `when`(keyguardStateController.isShowing).thenReturn(true)
-        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                BiometricSourceType.FINGERPRINT)).thenReturn(true)
-        `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
-
-        controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
-        assertTrue("reveal didn't start on keyguardFadingAway",
-            controller.startLightRevealScrimOnKeyguardFadingAway)
-        `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
-        controller.onKeyguardFadingAwayChanged()
-        assertFalse("reveal triggers multiple times",
-            controller.startLightRevealScrimOnKeyguardFadingAway)
-    }
-
-    @Test
-    @RunWithLooper(setAsMainLooper = true)
-    fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() {
-        mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
-        val faceLocation = Point(5, 5)
-        facePropertyRepository.setSensorLocation(faceLocation)
-        controller.onViewAttached()
-        `when`(keyguardStateController.isShowing).thenReturn(true)
-        `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
-        `when`(authController.isUdfpsFingerDown).thenReturn(true)
-        `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                eq(BiometricSourceType.FACE))).thenReturn(true)
-
-        controller.showUnlockRipple(BiometricSourceType.FACE)
-        assertTrue("reveal didn't start on keyguardFadingAway",
-                controller.startLightRevealScrimOnKeyguardFadingAway)
-        `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
-        controller.onKeyguardFadingAwayChanged()
-        assertFalse("reveal triggers multiple times",
-                controller.startLightRevealScrimOnKeyguardFadingAway)
-    }
-
-    @Test
-    fun testUpdateRippleColor() {
-        controller.onViewAttached()
-        val captor = ArgumentCaptor
-            .forClass(ConfigurationController.ConfigurationListener::class.java)
-        verify(configurationController).addCallback(captor.capture())
-
-        reset(rippleView)
-        captor.value.onThemeChanged()
-        verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt())
-
-        reset(rippleView)
-        captor.value.onUiModeChanged()
-        verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt())
-    }
-
-    @Test
-    fun testUdfps_onFingerDown_runningForDeviceEntry_showDwellRipple() {
-        // GIVEN fingerprint detection is running on keyguard
-        `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true)
-
-        // GIVEN view is already attached
-        controller.onViewAttached()
-        val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java)
-        verify(udfpsController).addCallback(captor.capture())
-
-        // GIVEN fp is updated to Point(5, 5)
-        val fpsLocation = Point(5, 5)
-        `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
-
-        // WHEN finger is down
-        captor.value.onFingerDown()
-
-        // THEN update sensor location and show ripple
-        verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
-        verify(rippleView).startDwellRipple(false)
-    }
-
-    @Test
-    fun testUdfps_onFingerDown_notDeviceEntry_doesNotShowDwellRipple() {
-        // GIVEN fingerprint detection is NOT running on keyguard
-        `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(false)
-
-        // GIVEN view is already attached
-        controller.onViewAttached()
-        val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java)
-        verify(udfpsController).addCallback(captor.capture())
-
-        // WHEN finger is down
-        captor.value.onFingerDown()
-
-        // THEN doesn't show dwell ripple
-        verify(rippleView, never()).startDwellRipple(false)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
deleted file mode 100644
index 9fbe096..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics
-
-import android.hardware.biometrics.SensorLocationInternal
-import android.testing.TestableLooper
-import android.testing.ViewUtils
-import android.view.LayoutInflater
-import android.view.Surface
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.withArgCaptor
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.nullable
-import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
-
-private const val SENSOR_X = 50
-private const val SENSOR_Y = 250
-private const val SENSOR_RADIUS = 10
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
-class UdfpsViewTest : SysuiTestCase() {
-
-    @JvmField @Rule
-    var rule = MockitoJUnit.rule()
-
-    @Mock
-    lateinit var hbmProvider: UdfpsDisplayModeProvider
-    @Mock
-    lateinit var animationViewController: UdfpsAnimationViewController<UdfpsAnimationView>
-
-    private lateinit var view: UdfpsView
-
-    @Before
-    fun setup() {
-        context.setTheme(androidx.appcompat.R.style.Theme_AppCompat)
-        view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView
-        view.animationViewController = animationViewController
-        val sensorBounds = SensorLocationInternal("", SENSOR_X, SENSOR_Y, SENSOR_RADIUS).rect
-        view.overlayParams = UdfpsOverlayParams(sensorBounds, sensorBounds, 1920,
-            1080, 1f, Surface.ROTATION_0)
-        view.setUdfpsDisplayModeProvider(hbmProvider)
-        ViewUtils.attachView(view)
-    }
-
-    @After
-    fun cleanup() {
-        ViewUtils.detachView(view)
-    }
-
-    // TODO: Add test to verify view is size of screen
-
-    @Test
-    fun startAndStopIllumination() {
-        val onDone: Runnable = mock()
-        view.configureDisplay(onDone)
-
-        val illuminator = withArgCaptor<Runnable> {
-            verify(hbmProvider).enable(capture())
-        }
-
-        assertThat(view.isDisplayConfigured).isTrue()
-        verify(animationViewController).onDisplayConfiguring()
-        verify(animationViewController, never()).onDisplayUnconfigured()
-        verify(onDone, never()).run()
-
-        // fake illumination event
-        illuminator.run()
-        waitForLooper()
-        verify(onDone).run()
-        verify(hbmProvider, never()).disable(any())
-
-        view.unconfigureDisplay()
-        assertThat(view.isDisplayConfigured).isFalse()
-        verify(animationViewController).onDisplayUnconfigured()
-        verify(hbmProvider).disable(nullable(Runnable::class.java))
-    }
-
-    private fun waitForLooper() = TestableLooper.get(this).processAllMessages()
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 44d81a7..8f64287 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -512,10 +512,10 @@
                 mLightBarController,
                 mAutoHideController,
                 new StatusBarInitializerImpl(
-                        mContext.getDisplayId(),
-                        mStatusBarWindowControllerStore,
+                        mStatusBarWindowController,
                         mCollapsedStatusBarFragmentProvider,
-                        emptySet()),
+                        emptySet()
+                ),
                 mStatusBarWindowControllerStore,
                 mStatusBarWindowStateController,
                 new FakeStatusBarModeRepository(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index c0152b26..41402ba 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.flags
 
 import android.platform.test.annotations.EnableFlags
-import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
 import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
@@ -36,7 +35,6 @@
     FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN,
     FLAG_PREDICTIVE_BACK_SYSUI,
     FLAG_SCENE_CONTAINER,
-    FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
 )
 @Retention(AnnotationRetention.RUNTIME)
 @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/hearingdevices/HearingDevicesTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/hearingdevices/HearingDevicesTileKosmos.kt
new file mode 100644
index 0000000..e16756b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/hearingdevices/HearingDevicesTileKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.qs.tiles.impl.hearingdevices
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsHearingDevicesTileConfig by
+    Kosmos.Fixture { QSAccessibilityModule.provideHearingDevicesTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt
index edd6604..9fa3abf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt
@@ -19,11 +19,18 @@
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+import org.mockito.kotlin.mock
 
-class FakeStatusBarInitializer(
-    private val statusBarViewController: PhoneStatusBarViewController,
-    private val statusBarTransitions: PhoneStatusBarTransitions,
-) : StatusBarInitializer {
+class FakeStatusBarInitializer : StatusBarInitializer {
+
+    val statusBarViewController = mock<PhoneStatusBarViewController>()
+    val statusBarTransitions = mock<PhoneStatusBarTransitions>()
+
+    var startedByCoreStartable: Boolean = false
+        private set
+
+    var initializedByCentralSurfaces: Boolean = false
+        private set
 
     override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
         set(value) {
@@ -31,5 +38,11 @@
             value?.onStatusBarViewUpdated(statusBarViewController, statusBarTransitions)
         }
 
-    override fun initializeStatusBar() {}
+    override fun initializeStatusBar() {
+        initializedByCentralSurfaces = true
+    }
+
+    override fun start() {
+        startedByCoreStartable = true
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
index 73ed228..8c218be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
@@ -16,14 +16,11 @@
 
 package com.android.systemui.statusbar.core
 
-import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
-import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+import com.android.systemui.statusbar.window.StatusBarWindowController
 
-class FakeStatusBarInitializerFactory(
-    private val statusBarViewController: PhoneStatusBarViewController,
-    private val statusBarTransitions: PhoneStatusBarTransitions,
-) : StatusBarInitializer.Factory {
+class FakeStatusBarInitializerFactory() : StatusBarInitializer.Factory {
 
-    override fun create(displayId: Int): StatusBarInitializer =
-        FakeStatusBarInitializer(statusBarViewController, statusBarTransitions)
+    override fun create(
+        statusBarWindowController: StatusBarWindowController
+    ): StatusBarInitializer = FakeStatusBarInitializer()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerStore.kt
new file mode 100644
index 0000000..0c2cba9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerStore.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.statusbar.core
+
+import android.view.Display
+
+class FakeStatusBarInitializerStore : StatusBarInitializerStore {
+
+    private val initializers = mutableMapOf<Int, FakeStatusBarInitializer>()
+
+    override val defaultDisplay: FakeStatusBarInitializer
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): FakeStatusBarInitializer {
+        return initializers.computeIfAbsent(displayId) { FakeStatusBarInitializer() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt
new file mode 100644
index 0000000..9197dcd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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.statusbar.core
+
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStatePerDisplayRepository
+import kotlinx.coroutines.CoroutineScope
+import org.mockito.kotlin.mock
+
+class FakeStatusBarOrchestratorFactory : StatusBarOrchestrator.Factory {
+
+    private val createdOrchestrators = mutableMapOf<Int, StatusBarOrchestrator>()
+
+    fun createdOrchestratorForDisplay(displayId: Int): StatusBarOrchestrator? =
+        createdOrchestrators[displayId]
+
+    override fun create(
+        displayId: Int,
+        displayScope: CoroutineScope,
+        statusBarWindowStateRepository: StatusBarWindowStatePerDisplayRepository,
+        statusBarModeRepository: StatusBarModePerDisplayRepository,
+        statusBarInitializer: StatusBarInitializer,
+        statusBarWindowController: StatusBarWindowController,
+    ): StatusBarOrchestrator =
+        mock<StatusBarOrchestrator>().also { createdOrchestrators[displayId] = it }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
index 7ad715b..8066b91 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -19,20 +19,13 @@
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.statusbar.phone.phoneStatusBarTransitions
-import com.android.systemui.statusbar.phone.phoneStatusBarViewController
+import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
 
-val Kosmos.fakeStatusBarInitializer by
-    Kosmos.Fixture {
-        FakeStatusBarInitializer(phoneStatusBarViewController, phoneStatusBarTransitions)
-    }
+val Kosmos.fakeStatusBarInitializer by Kosmos.Fixture { FakeStatusBarInitializer() }
 
 var Kosmos.statusBarInitializer by Kosmos.Fixture { fakeStatusBarInitializer }
 
-val Kosmos.fakeStatusBarInitializerFactory by
-    Kosmos.Fixture {
-        FakeStatusBarInitializerFactory(phoneStatusBarViewController, phoneStatusBarTransitions)
-    }
+val Kosmos.fakeStatusBarInitializerFactory by Kosmos.Fixture { FakeStatusBarInitializerFactory() }
 
 var Kosmos.statusBarInitializerFactory: StatusBarInitializer.Factory by
     Kosmos.Fixture { fakeStatusBarInitializerFactory }
@@ -43,5 +36,11 @@
             applicationCoroutineScope,
             fakeStatusBarInitializerFactory,
             displayRepository,
+            fakeStatusBarWindowControllerStore,
         )
     }
+
+val Kosmos.fakeStatusBarInitializerStore by Kosmos.Fixture { FakeStatusBarInitializerStore() }
+
+var Kosmos.statusBarInitializerStore: StatusBarInitializerStore by
+    Kosmos.Fixture { fakeStatusBarInitializerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 54de293..87f7142 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -16,7 +16,11 @@
 
 package com.android.systemui.statusbar.core
 
+import android.content.testableContext
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayScopeRepository
+import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.mockDemoModeController
@@ -24,20 +28,25 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.shade.mockNotificationShadeWindowViewController
 import com.android.systemui.shade.mockShadeSurface
-import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.data.repository.statusBarModeRepository
 import com.android.systemui.statusbar.mockNotificationRemoteInputManager
 import com.android.systemui.statusbar.phone.mockAutoHideController
+import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStatePerDisplayRepository
 import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
-import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
+import com.android.systemui.statusbar.window.fakeStatusBarWindowController
+import com.android.systemui.statusbar.window.statusBarWindowControllerStore
 import com.android.wm.shell.bubbles.bubblesOptional
 
 val Kosmos.statusBarOrchestrator by
     Kosmos.Fixture {
         StatusBarOrchestrator(
+            testableContext.displayId,
             applicationCoroutineScope,
+            fakeStatusBarWindowStatePerDisplayRepository,
+            fakeStatusBarModePerDisplayRepository,
             fakeStatusBarInitializer,
-            fakeStatusBarModeRepository,
-            fakeStatusBarWindowControllerStore,
+            fakeStatusBarWindowController,
             mockDemoModeController,
             mockPluginDependencyProvider,
             mockAutoHideController,
@@ -45,8 +54,28 @@
             { mockNotificationShadeWindowViewController },
             mockShadeSurface,
             bubblesOptional,
-            statusBarWindowStateRepositoryStore,
+            dumpManager,
             powerInteractor,
             primaryBouncerInteractor,
         )
     }
+
+val Kosmos.fakeStatusBarOrchestratorFactory by Kosmos.Fixture { FakeStatusBarOrchestratorFactory() }
+
+var Kosmos.statusBarOrchestratorFactory: StatusBarOrchestrator.Factory by
+    Kosmos.Fixture { fakeStatusBarOrchestratorFactory }
+
+val Kosmos.multiDisplayStatusBarStarter by
+    Kosmos.Fixture {
+        MultiDisplayStatusBarStarter(
+            applicationCoroutineScope,
+            displayScopeRepository,
+            statusBarOrchestratorFactory,
+            statusBarWindowStateRepositoryStore,
+            statusBarModeRepository,
+            displayRepository,
+            statusBarInitializerStore,
+            statusBarWindowControllerStore,
+            statusBarInitializerStore,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 6069083..285cebb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.data.model.StatusBarAppearance
 import com.android.systemui.statusbar.data.model.StatusBarMode
-import com.google.common.truth.Truth.assertThat
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
@@ -37,7 +36,6 @@
         FakeStatusBarModePerDisplayRepository()
 
     override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository {
-        assertThat(displayId).isEqualTo(DISPLAY_ID)
         return defaultDisplay
     }
 }
@@ -51,6 +49,7 @@
     override fun showTransient() {
         isTransientShown.value = true
     }
+
     override fun clearTransient() {
         isTransientShown.value = false
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
index 0f2b477..12db2f741 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
@@ -18,6 +18,9 @@
 
 import com.android.systemui.kosmos.Kosmos
 
+val Kosmos.fakeStatusBarModePerDisplayRepository by
+    Kosmos.Fixture { FakeStatusBarModePerDisplayRepository() }
+
 val Kosmos.statusBarModeRepository: StatusBarModeRepositoryStore by
     Kosmos.Fixture { fakeStatusBarModeRepository }
 val Kosmos.fakeStatusBarModeRepository by Kosmos.Fixture { FakeStatusBarModeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/BluetoothControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/BluetoothControllerKosmos.kt
new file mode 100644
index 0000000..14f4d75
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/BluetoothControllerKosmos.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 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.statusbar.policy
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeBluetoothController by Kosmos.Fixture { FakeBluetoothController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeBluetoothController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeBluetoothController.kt
new file mode 100644
index 0000000..4876cd8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeBluetoothController.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 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.statusbar.policy
+
+import android.bluetooth.BluetoothAdapter
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.statusbar.policy.BluetoothController.Callback
+import java.io.PrintWriter
+import java.util.Collections
+import java.util.concurrent.Executor
+
+class FakeBluetoothController : BluetoothController {
+
+    private var callbacks = mutableListOf<Callback>()
+    private var enabled = false
+
+    override fun addCallback(listener: Callback) {
+        callbacks += listener
+        listener.onBluetoothStateChange(isBluetoothEnabled)
+    }
+
+    override fun removeCallback(listener: Callback) {
+        callbacks -= listener
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {}
+
+    override fun isBluetoothSupported(): Boolean = false
+
+    override fun isBluetoothEnabled(): Boolean = enabled
+
+    override fun getBluetoothState(): Int = 0
+
+    override fun isBluetoothConnected(): Boolean = false
+
+    override fun isBluetoothConnecting(): Boolean = false
+
+    override fun isBluetoothAudioProfileOnly(): Boolean = false
+
+    override fun isBluetoothAudioActive(): Boolean = false
+
+    override fun getConnectedDeviceName(): String? = null
+
+    override fun setBluetoothEnabled(enabled: Boolean) {
+        this.enabled = enabled
+        callbacks.forEach { it.onBluetoothStateChange(enabled) }
+    }
+
+    override fun canConfigBluetooth(): Boolean = false
+
+    override fun getConnectedDevices(): MutableList<CachedBluetoothDevice> = Collections.emptyList()
+
+    override fun addOnMetadataChangedListener(
+        device: CachedBluetoothDevice?,
+        executor: Executor?,
+        listener: BluetoothAdapter.OnMetadataChangedListener?,
+    ) {}
+
+    override fun removeOnMetadataChangedListener(
+        device: CachedBluetoothDevice?,
+        listener: BluetoothAdapter.OnMetadataChangedListener?,
+    ) {}
+
+    /** Trigger the [Callback.onBluetoothDevicesChanged] method for all registered callbacks. */
+    @VisibleForTesting
+    fun onBluetoothDevicesChanged() {
+        callbacks.forEach { it.onBluetoothDevicesChanged() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
index 2205a3b..cbaf2bd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
@@ -21,6 +21,9 @@
 import com.android.systemui.settings.displayTracker
 import com.android.systemui.statusbar.commandQueue
 
+val Kosmos.fakeStatusBarWindowStatePerDisplayRepository by
+    Kosmos.Fixture { FakeStatusBarWindowStatePerDisplayRepository() }
+
 val Kosmos.fakeStatusBarWindowStateRepositoryStore by
     Kosmos.Fixture { FakeStatusBarWindowStateRepositoryStore() }
 
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index ab00bfd..1f16776 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -71,6 +71,7 @@
         "CtsSurfaceValidatorLib",
         "service-sdksandbox.impl",
         "com.android.window.flags.window-aconfig-java",
+        "android.view.inputmethod.flags-aconfig-java",
         "flag-junit",
     ],
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index d2cf03d..ee56210 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -41,11 +41,13 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.StatusBarManager;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
@@ -525,6 +527,59 @@
         assertTrue(win1.getWindowFrames().hasInsetsChanged());
     }
 
+    /**
+     * This test verifies that after setting {@link WindowContainer#mExcludeInsetsTypes}, the IME
+     * insets have a height of zero (applied in {@link InsetsPolicy#adjustVisibilityForIme}).
+     */
+    @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testExcludeImeInsets() {
+        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+        final InsetsSource imeSource = new InsetsSource(ID_IME, ime());
+        imeSource.setVisible(true);
+        mImeWindow.mHasSurface = true;
+
+        final WindowState win = addWindow(TYPE_APPLICATION, "win1");
+        win.setRequestedVisibleTypes(0, ime());
+
+        win.mAboveInsetsState.addSource(imeSource);
+        win.mHasSurface = true;
+
+        DisplayContentTests.performLayout(mDisplayContent);
+        // IME should cover half of the app's window
+        final var winFrame = win.getFrame();
+        imeSource.setFrame(winFrame.left, winFrame.bottom / 2, winFrame.right, winFrame.bottom);
+        imeSource.setVisibleFrame(imeSource.getFrame());
+        DisplayContentTests.performLayout(mDisplayContent);
+
+        assertTrue(mImeWindow.isVisible());
+        assertTrue(win.isVisible());
+
+        displayPolicy.beginPostLayoutPolicyLw();
+        displayPolicy.applyPostLayoutPolicyLw(win, win.mAttrs, null, null);
+        displayPolicy.finishPostLayoutPolicyLw();
+
+        final var imeInsetsShown = win.getInsetsState().calculateInsets(win.getFrame(), ime(),
+                true);
+        assertEquals(new Rect(0, 0, 0, winFrame.bottom / 2), imeInsetsShown.toRect());
+
+
+        // Now we're setting the excludedInsetsTypes for the IME. The IME is still showing, but
+        // in this case, InsetsPolicy#adjustVisibilityForIme will override and dispatch IME
+        // insets with zero height.
+        win.setExcludeInsetsTypes(ime());
+
+        displayPolicy.beginPostLayoutPolicyLw();
+        displayPolicy.applyPostLayoutPolicyLw(win, win.mAttrs, null, null);
+        displayPolicy.finishPostLayoutPolicyLw();
+
+        final var imeInsetsHidden = win.getInsetsState().calculateInsets(win.getFrame(), ime(),
+                true);
+        assertEquals(Insets.NONE, imeInsetsHidden);
+    }
+
+
     private WindowState addNavigationBar() {
         final Binder owner = new Binder();
         final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");