Merge "Moving CrashRecoveryModuleTests to presubmits" into main
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index d4d9d00..fdb4523 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -372,6 +372,7 @@
         //   ==> activity view
         //   ==> manage button
         bringChildToFront(mManageButton);
+        setManageClickListener();
 
         applyThemeAttrs();
 
@@ -502,6 +503,7 @@
                 R.layout.bubble_manage_button, this /* parent */, false /* attach */);
         addView(mManageButton);
         mManageButton.setVisibility(visibility);
+        setManageClickListener();
         post(() -> {
             int touchAreaHeight =
                     getResources().getDimensionPixelSize(
@@ -646,9 +648,8 @@
         }
     }
 
-    // TODO: Could listener be passed when we pass StackView / can we avoid setting this like this
-    void setManageClickListener(OnClickListener manageClickListener) {
-        mManageButton.setOnClickListener(manageClickListener);
+    private void setManageClickListener() {
+        mManageButton.setOnClickListener(v -> mStackView.onManageBubbleClicked());
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index f93f19d..8f8b77b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1374,7 +1374,6 @@
         // The menu itself should respect locale direction so the icons are on the correct side.
         mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
         addView(mManageMenu);
-        updateManageButtonListener();
     }
 
     /**
@@ -3375,14 +3374,6 @@
             mExpandedViewContainer.setAlpha(0f);
             mExpandedViewContainer.addView(bev);
 
-            postDelayed(() -> {
-                // Set the Manage button click handler from postDelayed. This appears to resolve
-                // a race condition with adding the BubbleExpandedView view to the expanded view
-                // container. Due to the race condition the click handler sometimes is not set up
-                // correctly and is never called.
-                updateManageButtonListener();
-            }, 0);
-
             if (!mIsExpansionAnimating) {
                 mIsBubbleSwitchAnimating = true;
                 mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
@@ -3392,13 +3383,8 @@
         }
     }
 
-    private void updateManageButtonListener() {
-        BubbleExpandedView bev = getExpandedView();
-        if (mIsExpanded && bev != null) {
-            bev.setManageClickListener((view) -> {
-                showManageMenu(true /* show */);
-            });
-        }
+    void onManageBubbleClicked() {
+        showManageMenu(true /* show */);
     }
 
     /**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index d5c9102..a67e7c6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -21,64 +21,68 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.animation.Expandable
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
-import com.google.common.truth.Truth
-import kotlin.coroutines.EmptyCoroutineContext
+import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @EnableFlags(android.app.Flags.FLAG_MODES_UI)
 class ModesTileUserActionInteractorTest : SysuiTestCase() {
-    private val inputHandler = FakeQSTileIntentUserInputHandler()
+    private val kosmos = testKosmos()
+    private val inputHandler = kosmos.qsTileIntentUserInputHandler
+    private val mockDialogDelegate = kosmos.mockModesDialogDelegate
 
-    @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
-    @Mock private lateinit var dialogDelegate: ModesDialogDelegate
-    @Mock private lateinit var mockDialog: SystemUIDialog
+    private val underTest =
+        ModesTileUserActionInteractor(
+            inputHandler,
+            mockDialogDelegate,
+        )
 
-    private lateinit var underTest: ModesTileUserActionInteractor
+    @Test
+    fun handleClick_active() = runTest {
+        val expandable = mock<Expandable>()
+        underTest.handleInput(
+            QSTileInputTestKtx.click(data = ModesTileModel(true), expandable = expandable))
 
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-
-        whenever(dialogDelegate.createDialog()).thenReturn(mockDialog)
-
-        underTest =
-            ModesTileUserActionInteractor(
-                EmptyCoroutineContext,
-                inputHandler,
-                dialogTransitionAnimator,
-                dialogDelegate,
-            )
+        verify(mockDialogDelegate).showDialog(eq(expandable))
     }
 
     @Test
-    fun handleClick() = runTest {
-        underTest.handleInput(QSTileInputTestKtx.click(ModesTileModel(false)))
+    fun handleClick_inactive() = runTest {
+        val expandable = mock<Expandable>()
+        underTest.handleInput(
+            QSTileInputTestKtx.click(data = ModesTileModel(false), expandable = expandable))
 
-        verify(mockDialog).show()
+        verify(mockDialogDelegate).showDialog(eq(expandable))
     }
 
     @Test
-    fun handleLongClick() = runTest {
+    fun handleLongClick_active() = runTest {
+        underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(true)))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+        }
+    }
+
+    @Test
+    fun handleLongClick_inactive() = runTest {
         underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(false)))
 
         QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
-            Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
new file mode 100644
index 0000000..bf0a39b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.policy.ui.dialog
+
+import android.app.Dialog
+import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.mockActivityTransitionAnimatorController
+import com.android.systemui.animation.mockDialogTransitionAnimator
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.mainCoroutineContext
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.runOnMainThreadAndWaitForIdleSync
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
+import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModesDialogDelegateTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val activityStarter = kosmos.activityStarter
+    private val mockDialogTransitionAnimator = kosmos.mockDialogTransitionAnimator
+    private val mockAnimationController = kosmos.mockActivityTransitionAnimatorController
+    private lateinit var underTest: ModesDialogDelegate
+
+    @Before
+    fun setup() {
+        whenever(
+                mockDialogTransitionAnimator.createActivityTransitionController(
+                    any<SystemUIDialog>(),
+                    eq(null)
+                )
+            )
+            .thenReturn(mockAnimationController)
+
+        underTest =
+            ModesDialogDelegate(
+                kosmos.systemUIDialogFactory,
+                mockDialogTransitionAnimator,
+                activityStarter,
+                { kosmos.modesDialogViewModel },
+                kosmos.mainCoroutineContext,
+            )
+    }
+
+    @Test
+    fun launchFromDialog_whenDialogNotOpen() {
+        val intent: Intent = mock()
+
+        runOnMainThreadAndWaitForIdleSync { underTest.launchFromDialog(intent) }
+
+        verify(activityStarter)
+            .startActivity(eq(intent), eq(true), eq<ActivityTransitionAnimator.Controller?>(null))
+    }
+
+    @Test
+    fun launchFromDialog_whenDialogOpen() =
+        testScope.runTest {
+            val intent: Intent = mock()
+            lateinit var dialog: Dialog
+
+            runOnMainThreadAndWaitForIdleSync {
+                kosmos.applicationCoroutineScope.launch { dialog = underTest.showDialog() }
+                runCurrent()
+                underTest.launchFromDialog(intent)
+            }
+
+            verify(mockDialogTransitionAnimator)
+                .createActivityTransitionController(any<Dialog>(), eq(null))
+            verify(activityStarter).startActivity(eq(intent), eq(true), eq(mockAnimationController))
+
+            runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
+        }
+
+    @Test
+    fun dismiss_clearsDialogReference() {
+        val dialog = runOnMainThreadAndWaitForIdleSync { underTest.createDialog() }
+
+        assertThat(underTest.currentDialog).isEqualTo(dialog)
+
+        runOnMainThreadAndWaitForIdleSync {
+            dialog.show()
+            dialog.dismiss()
+        }
+
+        assertThat(underTest.currentDialog).isNull()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index fdfc7f1..349b62e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -18,6 +18,8 @@
 
 package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
 
+import android.content.Intent
+import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.modes.TestModeBuilder
@@ -27,6 +29,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,16 +37,21 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ModesDialogViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    val repository = kosmos.fakeZenModeRepository
-    val interactor = kosmos.zenModeInteractor
+    private val repository = kosmos.fakeZenModeRepository
+    private val interactor = kosmos.zenModeInteractor
+    private val mockDialogDelegate = kosmos.mockModesDialogDelegate
 
-    val underTest = ModesDialogViewModel(context, interactor, kosmos.testDispatcher)
+    private val underTest =
+        ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate)
 
     @Test
     fun tiles_filtersOutDisabledModes() =
@@ -64,7 +72,8 @@
                         .setEnabled(false)
                         .setManualInvocationAllowed(true)
                         .build(),
-                ))
+                )
+            )
             runCurrent()
 
             assertThat(tiles?.size).isEqualTo(2)
@@ -108,7 +117,8 @@
                         .setActive(false)
                         .setManualInvocationAllowed(false)
                         .build(),
-                ))
+                )
+            )
             runCurrent()
 
             assertThat(tiles?.size).isEqualTo(3)
@@ -161,4 +171,56 @@
 
             assertThat(tiles?.first()?.enabled).isFalse()
         }
+
+    @Test
+    fun onLongClick_launchesIntent() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles)
+            val intentCaptor = argumentCaptor<Intent>()
+
+            val modeId = "id"
+            repository.addModes(
+                listOf(
+                    TestModeBuilder()
+                        .setId(modeId)
+                        .setId("A")
+                        .setActive(true)
+                        .setManualInvocationAllowed(true)
+                        .build(),
+                    TestModeBuilder()
+                        .setId(modeId)
+                        .setId("B")
+                        .setActive(false)
+                        .setManualInvocationAllowed(true)
+                        .build(),
+                )
+            )
+            runCurrent()
+
+            assertThat(tiles?.size).isEqualTo(2)
+
+            // Trigger onLongClick for A
+            tiles?.first()?.onLongClick?.let { it() }
+            runCurrent()
+
+            // Check that it launched the correct intent
+            verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
+            var intent = intentCaptor.lastValue
+            assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+            assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
+                .isEqualTo("A")
+
+            clearInvocations(mockDialogDelegate)
+
+            // Trigger onLongClick for B
+            tiles?.last()?.onLongClick?.let { it() }
+            runCurrent()
+
+            // Check that it launched the correct intent
+            verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
+            intent = intentCaptor.lastValue
+            assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+            assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
+                .isEqualTo("B")
+        }
 }
diff --git a/packages/SystemUI/res/color/disconnected_network_primary_color.xml b/packages/SystemUI/res/color/disconnected_network_primary_color.xml
new file mode 100644
index 0000000..536bf78
--- /dev/null
+++ b/packages/SystemUI/res/color/disconnected_network_primary_color.xml
@@ -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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:color="?androidprv:attr/materialColorPrimaryContainer" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 22d156d..0029180 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -170,8 +170,7 @@
                         android:layout_height="28dp"
                         android:layout_marginStart="7dp"
                         android:layout_marginEnd="16dp"
-                        android:layout_gravity="center_vertical"
-                        android:background="?android:attr/textColorSecondary"/>
+                        android:layout_gravity="center_vertical"/>
 
                     <FrameLayout
                         android:layout_width="@dimen/settingslib_switch_track_width"
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 36912ac..c428705d 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1386,9 +1386,13 @@
         <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
     </style>
 
-    <style name="TextAppearance.InternetDialog.Active"/>
+    <style name="TextAppearance.InternetDialog.Active">
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
+    </style>
 
-    <style name="TextAppearance.InternetDialog.Secondary.Active"/>
+    <style name="TextAppearance.InternetDialog.Secondary.Active">
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
+    </style>
 
     <style name="FgsManagerDialogTitle">
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
@@ -1424,17 +1428,32 @@
         <item name="android:orientation">horizontal</item>
         <item name="android:focusable">true</item>
         <item name="android:clickable">true</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+    </style>
+
+    <style name="BluetoothTileDialog.Device.Active">
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
     </style>
 
     <style name="BluetoothTileDialog.DeviceName">
         <item name="android:textSize">14sp</item>
         <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="BluetoothTileDialog.DeviceSummary">
         <item name="android:ellipsize">end</item>
         <item name="android:maxLines">2</item>
         <item name="android:textAppearance">@style/TextAppearance.Dialog.Body.Message</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
+    </style>
+
+    <style name="BluetoothTileDialog.DeviceName.Active">
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
+    </style>
+
+    <style name="BluetoothTileDialog.DeviceSummary.Active">
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
     </style>
 
     <style name="BroadcastDialog">
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index 911145b..f5b9a05 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -37,6 +37,7 @@
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.android.internal.R as InternalR
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
@@ -367,6 +368,7 @@
             private val nameView = view.requireViewById<TextView>(R.id.bluetooth_device_name)
             private val summaryView = view.requireViewById<TextView>(R.id.bluetooth_device_summary)
             private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon)
+            private val iconGear = view.requireViewById<ImageView>(R.id.gear_icon_image)
             private val gearView = view.requireViewById<View>(R.id.gear_icon)
 
             internal fun bind(
@@ -380,6 +382,36 @@
                         mutableDeviceItemClick.tryEmit(item)
                         uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
                     }
+
+                    // updating icon colors
+                    val tintColor =
+                        com.android.settingslib.Utils.getColorAttr(
+                                context,
+                                if (item.isActive) InternalR.attr.materialColorOnPrimaryContainer
+                                else InternalR.attr.materialColorOnSurface
+                            )
+                            .defaultColor
+
+                    // update icons
+                    iconView.apply {
+                        item.iconWithDescription?.let {
+                            setImageDrawable(it.first.apply { mutate()?.setTint(tintColor) })
+                            contentDescription = it.second
+                        }
+                    }
+
+                    iconGear.apply { drawable?.let { it.mutate()?.setTint(tintColor) } }
+
+                    // update text styles
+                    nameView.setTextAppearance(
+                        if (item.isActive) R.style.BluetoothTileDialog_DeviceName_Active
+                        else R.style.BluetoothTileDialog_DeviceName
+                    )
+                    summaryView.setTextAppearance(
+                        if (item.isActive) R.style.BluetoothTileDialog_DeviceSummary_Active
+                        else R.style.BluetoothTileDialog_DeviceSummary
+                    )
+
                     accessibilityDelegate =
                         object : AccessibilityDelegate() {
                             override fun onInitializeAccessibilityNodeInfo(
@@ -398,12 +430,7 @@
                 }
                 nameView.text = item.deviceName
                 summaryView.text = item.connectionSummary
-                iconView.apply {
-                    item.iconWithDescription?.let {
-                        setImageDrawable(it.first)
-                        contentDescription = it.second
-                    }
-                }
+
                 gearView.setOnClickListener {
                     deviceItemOnClickCallback.onDeviceItemGearClicked(item, it)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index 0ea98d1..a78130f 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -52,4 +52,5 @@
     val background: Int? = null,
     var isEnabled: Boolean = true,
     var actionAccessibilityLabel: String = "",
+    var isActive: Boolean = false
 )
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 51b2280..d7893db 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -55,7 +55,8 @@
             type: DeviceItemType,
             connectionSummary: String,
             background: Int,
-            actionAccessibilityLabel: String
+            actionAccessibilityLabel: String,
+            isActive: Boolean
         ): DeviceItem {
             return DeviceItem(
                 type = type,
@@ -68,7 +69,8 @@
                     },
                 background = background,
                 isEnabled = !cachedDevice.isBusy,
-                actionAccessibilityLabel = actionAccessibilityLabel
+                actionAccessibilityLabel = actionAccessibilityLabel,
+                isActive = isActive
             )
         }
     }
@@ -91,7 +93,8 @@
             DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary ?: "",
             backgroundOn,
-            context.getString(actionAccessibilityLabelDisconnect)
+            context.getString(actionAccessibilityLabelDisconnect),
+            isActive = true
         )
     }
 }
@@ -116,7 +119,8 @@
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
                 ?: context.getString(audioSharing),
             if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
-            ""
+            "",
+            isActive = !cachedDevice.isBusy
         )
     }
 }
@@ -150,7 +154,8 @@
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
                 ?: context.getString(connected),
             if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
-            context.getString(actionAccessibilityLabelActivate)
+            context.getString(actionAccessibilityLabelActivate),
+            isActive = false
         )
     }
 }
@@ -188,7 +193,8 @@
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
                 ?: context.getString(connected),
             if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
-            context.getString(actionAccessibilityLabelDisconnect)
+            context.getString(actionAccessibilityLabelDisconnect),
+            isActive = false
         )
     }
 }
@@ -216,7 +222,8 @@
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
                 ?: context.getString(saved),
             if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
-            context.getString(actionAccessibilityLabelActivate)
+            context.getString(actionAccessibilityLabelActivate),
+            isActive = false
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index bd0e729..04c6fa9 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -301,8 +301,8 @@
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
                     setExpandedView(this::animateIn);
                 }
-                mView.announceForAccessibility(
-                        getAccessibilityAnnouncement(mClipboardModel.getType()));
+                mWindow.withWindowAttached(() -> mView.announceForAccessibility(
+                        getAccessibilityAnnouncement(mClipboardModel.getType())));
             } else if (!mIsMinimized) {
                 setExpandedView(() -> {
                 });
@@ -320,8 +320,8 @@
                     setExpandedView();
                 }
                 animateIn();
-                mView.announceForAccessibility(
-                        getAccessibilityAnnouncement(mClipboardModel.getType()));
+                mWindow.withWindowAttached(() -> mView.announceForAccessibility(
+                        getAccessibilityAnnouncement(mClipboardModel.getType())));
             } else if (!mIsMinimized) {
                 setExpandedView();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index edc49cac2..f018336 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -489,6 +489,10 @@
             mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
             mMobileToggleDivider.setVisibility(
                     mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
+            int primaryColor = isNetworkConnected
+                    ? R.color.connected_network_primary_color
+                    : R.color.disconnected_network_primary_color;
+            mMobileToggleDivider.setBackgroundColor(dialog.getContext().getColor(primaryColor));
 
             // Display the info for the non-DDS if it's actively being used
             int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index 4c6563d..083bf05 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -16,14 +16,10 @@
 
 package com.android.systemui.qs.tiles.impl.modes.domain.interactor
 
-//noinspection CleanArchitectureDependencyViolation: dialog needs to be opened on click
 import android.content.Intent
 import android.provider.Settings
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.DialogCuj
-import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.SysUISingleton
 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
@@ -31,15 +27,13 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
 import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.withContext
 
+@SysUISingleton
 class ModesTileUserActionInteractor
 @Inject
 constructor(
-    @Main private val coroutineContext: CoroutineContext,
-    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
-    private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
+    // TODO(b/353896370): The domain layer should not have to depend on the UI layer.
     private val dialogDelegate: ModesDialogDelegate,
 ) : QSTileUserActionInteractor<ModesTileModel> {
     val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
@@ -51,29 +45,14 @@
                     handleClick(action.expandable)
                 }
                 is QSTileUserAction.LongClick -> {
-                    qsTileIntentUserActionHandler.handle(action.expandable, longClickIntent)
+                    qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent)
                 }
             }
         }
     }
 
     suspend fun handleClick(expandable: Expandable?) {
-        // Show a dialog with the list of modes to configure. Dialogs shown by the
-        // DialogTransitionAnimator must be created and shown on the main thread, so we post it to
-        // the UI handler.
-        withContext(coroutineContext) {
-            val dialog = dialogDelegate.createDialog()
-
-            expandable
-                ?.dialogTransitionController(
-                    DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
-                )
-                ?.let { controller -> dialogTransitionAnimator.show(dialog, controller) }
-                ?: dialog.show()
-        }
-    }
-
-    companion object {
-        private const val INTERACTION_JANK_TAG = "configure_priority_modes"
+        // Show a dialog with the list of modes to configure.
+        dialogDelegate.showDialog(expandable)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 2b094d6..8aa989f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -18,67 +18,155 @@
 
 import android.content.Intent
 import android.provider.Settings
+import android.util.Log
 import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
 import com.android.compose.PlatformButton
 import com.android.compose.PlatformOutlinedButton
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dialog.ui.composable.AlertDialogContent
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.statusbar.phone.create
 import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid
 import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
+import com.android.systemui.util.Assert
 import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
 
+@SysUISingleton
 class ModesDialogDelegate
 @Inject
 constructor(
     private val sysuiDialogFactory: SystemUIDialogFactory,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val activityStarter: ActivityStarter,
-    private val viewModel: ModesDialogViewModel,
+    // Using a provider to avoid a circular dependency.
+    private val viewModel: Provider<ModesDialogViewModel>,
+    @Main private val mainCoroutineContext: CoroutineContext,
 ) : SystemUIDialog.Delegate {
+    // NOTE: This should only be accessed/written from the main thread.
+    @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null
+
     override fun createDialog(): SystemUIDialog {
-        return sysuiDialogFactory.create { dialog ->
-            AlertDialogContent(
-                title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
-                content = { ModeTileGrid(viewModel) },
-                neutralButton = {
-                    PlatformOutlinedButton(
-                        onClick = {
-                            val animationController =
-                                dialogTransitionAnimator.createActivityTransitionController(
-                                    dialog.getButton(SystemUIDialog.BUTTON_NEUTRAL)
-                                )
-                            if (animationController == null) {
-                                // The controller will take care of dismissing for us after the
-                                // animation, but let's make sure we dismiss the dialog if we don't
-                                // animate it.
-                                dialog.dismiss()
-                            }
-                            activityStarter.startActivity(
-                                ZEN_MODE_SETTINGS_INTENT,
-                                true /* dismissShade */,
-                                animationController
-                            )
-                        }
-                    ) {
-                        Text(stringResource(R.string.zen_modes_dialog_settings))
+        Assert.isMainThread()
+        if (currentDialog != null) {
+            Log.w(TAG, "Dialog is already open, dismissing it and creating a new one.")
+            currentDialog?.dismiss()
+        }
+
+        currentDialog = sysuiDialogFactory.create() { ModesDialogContent(it) }
+        currentDialog
+            ?.lifecycle
+            ?.addObserver(
+                object : DefaultLifecycleObserver {
+                    override fun onStop(owner: LifecycleOwner) {
+                        Assert.isMainThread()
+                        currentDialog = null
                     }
-                },
-                positiveButton = {
-                    PlatformButton(onClick = { dialog.dismiss() }) {
-                        Text(stringResource(R.string.zen_modes_dialog_done))
-                    }
-                },
+                }
+            )
+
+        return currentDialog!!
+    }
+
+    @Composable
+    private fun ModesDialogContent(dialog: SystemUIDialog) {
+        AlertDialogContent(
+            title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
+            content = { ModeTileGrid(viewModel.get()) },
+            neutralButton = {
+                PlatformOutlinedButton(onClick = { openSettings(dialog) }) {
+                    Text(stringResource(R.string.zen_modes_dialog_settings))
+                }
+            },
+            positiveButton = {
+                PlatformButton(onClick = { dialog.dismiss() }) {
+                    Text(stringResource(R.string.zen_modes_dialog_done))
+                }
+            },
+        )
+    }
+
+    private fun openSettings(dialog: SystemUIDialog) {
+        val animationController =
+            dialogTransitionAnimator.createActivityTransitionController(dialog)
+        if (animationController == null) {
+            // The controller will take care of dismissing for us after
+            // the animation, but let's make sure we dismiss the dialog
+            // if we don't animate it.
+            dialog.dismiss()
+        }
+        activityStarter.startActivity(
+            ZEN_MODE_SETTINGS_INTENT,
+            true /* dismissShade */,
+            animationController
+        )
+    }
+
+    suspend fun showDialog(expandable: Expandable? = null): SystemUIDialog {
+        // Dialogs shown by the DialogTransitionAnimator must be created and shown on the main
+        // thread, so we post it to the UI handler.
+        withContext(mainCoroutineContext) {
+            // Create the dialog if necessary
+            if (currentDialog == null) {
+                createDialog()
+            }
+
+            expandable
+                ?.dialogTransitionController(
+                    DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
+                )
+                ?.let { controller -> dialogTransitionAnimator.show(currentDialog!!, controller) }
+                ?: currentDialog!!.show()
+        }
+
+        return currentDialog!!
+    }
+
+    /**
+     * Launches the [intent] by animating from the dialog. If the dialog is not showing, just
+     * launches it normally without animating.
+     */
+    fun launchFromDialog(intent: Intent) {
+        Assert.isMainThread()
+        if (currentDialog == null) {
+            Log.w(
+                TAG,
+                "Cannot launch from dialog, the dialog is not present. " +
+                    "Will launch activity without animating."
             )
         }
+
+        val animationController =
+            currentDialog?.let { dialogTransitionAnimator.createActivityTransitionController(it) }
+        if (animationController == null) {
+            currentDialog?.dismiss()
+        }
+        activityStarter.startActivity(
+            intent,
+            true, /* dismissShade */
+            animationController,
+        )
     }
 
     companion object {
+        private const val TAG = "ModesDialogDelegate"
         private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
+        private const val INTERACTION_JANK_TAG = "configure_priority_modes"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index e84c8b6..7e64090 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -17,11 +17,15 @@
 package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
 
 import android.content.Context
+import android.content.Intent
+import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
+import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
 import com.android.settingslib.notification.modes.ZenMode
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -39,6 +43,7 @@
     val context: Context,
     zenModeInteractor: ZenModeInteractor,
     @Background val bgDispatcher: CoroutineDispatcher,
+    private val dialogDelegate: ModesDialogDelegate,
 ) {
     // Modes that should be displayed in the dialog
     // TODO(b/346519570): Include modes that have not been set up yet.
@@ -71,7 +76,10 @@
                             }
                         },
                         onLongClick = {
-                            // TODO(b/346519570): Open settings page for mode.
+                            val intent: Intent =
+                                Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+                                    .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)
+                            dialogDelegate.launchFromDialog(intent)
                         }
                     )
                 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 27b6ea6..74d9692 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -27,7 +27,6 @@
 import com.android.internal.logging.MetricsLogger
 import com.android.settingslib.notification.data.repository.FakeZenModeRepository
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -47,7 +46,6 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
-import kotlin.coroutines.EmptyCoroutineContext
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -82,8 +80,6 @@
 
     @Mock private lateinit var qsTileConfigProvider: QSTileConfigProvider
 
-    @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
-
     @Mock private lateinit var dialogDelegate: ModesDialogDelegate
 
     private val inputHandler = FakeQSTileIntentUserInputHandler()
@@ -131,9 +127,7 @@
 
         userActionInteractor =
             ModesTileUserActionInteractor(
-                EmptyCoroutineContext,
                 inputHandler,
-                dialogTransitionAnimator,
                 dialogDelegate,
             )
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index b23767e..5ac41ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -18,6 +18,10 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testCase
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mockActivityTransitionAnimatorController by
+    Kosmos.Fixture { mock<ActivityTransitionAnimator.Controller>() }
 
 val Kosmos.activityTransitionAnimator by
     Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
new file mode 100644
index 0000000..2ecfb45
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.modes.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
+import javax.inject.Provider
+
+val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by
+    Kosmos.Fixture {
+        ModesTileUserActionInteractor(
+            qsTileIntentUserInputHandler,
+            Provider { modesDialogDelegate }.get(),
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
new file mode 100644
index 0000000..99bb479
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.ui.dialog
+
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.mainCoroutineContext
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
+import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mockModesDialogDelegate by Kosmos.Fixture { mock<ModesDialogDelegate>() }
+
+var Kosmos.modesDialogDelegate: ModesDialogDelegate by
+    Kosmos.Fixture {
+        ModesDialogDelegate(
+            systemUIDialogFactory,
+            dialogTransitionAnimator,
+            activityStarter,
+            { modesDialogViewModel },
+            mainCoroutineContext,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
new file mode 100644
index 0000000..00020f8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.ui.dialog.viewmodel
+
+import android.content.mockedContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
+import javax.inject.Provider
+
+val Kosmos.modesDialogViewModel: ModesDialogViewModel by
+    Kosmos.Fixture {
+        ModesDialogViewModel(
+            mockedContext,
+            zenModeInteractor,
+            testDispatcher,
+            Provider { modesDialogDelegate }.get(),
+        )
+    }