Merge "Modes tile: open simple dialog" into main
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 9b9e584..d5c9102 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,14 +21,23 @@
 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.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
 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 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.verify
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -36,7 +45,33 @@
 class ModesTileUserActionInteractorTest : SysuiTestCase() {
     private val inputHandler = FakeQSTileIntentUserInputHandler()
 
-    val underTest = ModesTileUserActionInteractor(inputHandler)
+    @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
+    @Mock private lateinit var dialogDelegate: ModesDialogDelegate
+    @Mock private lateinit var mockDialog: SystemUIDialog
+
+    private lateinit var underTest: ModesTileUserActionInteractor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(dialogDelegate.createDialog()).thenReturn(mockDialog)
+
+        underTest =
+            ModesTileUserActionInteractor(
+                EmptyCoroutineContext,
+                inputHandler,
+                dialogTransitionAnimator,
+                dialogDelegate,
+            )
+    }
+
+    @Test
+    fun handleClick() = runTest {
+        underTest.handleInput(QSTileInputTestKtx.click(ModesTileModel(false)))
+
+        verify(mockDialog).show()
+    }
 
     @Test
     fun handleLongClick() = runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
new file mode 100644
index 0000000..3baf2f4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.ui
+
+import android.graphics.drawable.TestStubDrawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModesTileMapperTest : SysuiTestCase() {
+    val config =
+        QSTileConfigTestBuilder.build {
+            uiConfig =
+                QSTileUIConfig.Resource(
+                    iconRes = R.drawable.qs_dnd_icon_off,
+                    labelRes = R.string.quick_settings_modes_label,
+                )
+        }
+
+    val underTest =
+        ModesTileMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.qs_dnd_icon_on, TestStubDrawable())
+                    addOverride(R.drawable.qs_dnd_icon_off, TestStubDrawable())
+                }
+                .resources,
+            context.theme,
+        )
+
+    @Test
+    fun inactiveState() {
+        val model = ModesTileModel(isActivated = false)
+
+        val state = underTest.map(config, model)
+
+        assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE)
+        assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_off)
+    }
+
+    @Test
+    fun activeState() {
+        val model = ModesTileModel(isActivated = true)
+
+        val state = underTest.map(config, model)
+
+        assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
+        assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_on)
+    }
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2bd97d9..7caa2c6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1084,6 +1084,15 @@
     <!-- QuickStep: Accessibility to toggle overview [CHAR LIMIT=40] -->
     <string name="quick_step_accessibility_toggle_overview">Toggle Overview</string>
 
+    <!-- Priority modes dialog title [CHAR LIMIT=35] -->
+    <string name="zen_modes_dialog_title">Priority modes</string>
+
+    <!-- Priority modes dialog confirmation button [CHAR LIMIT=15] -->
+    <string name="zen_modes_dialog_done">Done</string>
+
+    <!-- Priority modes dialog settings shortcut button [CHAR LIMIT=15] -->
+    <string name="zen_modes_dialog_settings">Settings</string>
+
     <!-- Zen mode: Priority only introduction message on first use -->
     <string name="zen_priority_introduction">You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events, and callers you specify. You\'ll still hear anything you choose to play including music, videos, and games.</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index b91891c..a300031 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 
 class ModesTile
 @Inject
@@ -91,8 +92,8 @@
 
     override fun newTileState() = BooleanState()
 
-    override fun handleClick(expandable: Expandable?) {
-        // TODO(b/346519570) open dialog
+    override fun handleClick(expandable: Expandable?) = runBlocking {
+        userActionInteractor.handleClick(expandable)
     }
 
     override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
@@ -107,6 +108,7 @@
                 label = tileLabel
                 secondaryLabel = tileState.secondaryLabel
                 contentDescription = tileState.contentDescription
+                forceExpandIcon = true
             }
         }
     }
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 fd1f3d8..4c6563d 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,19 +16,31 @@
 
 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.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.modes.domain.model.ModesTileModel
 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
 
 class ModesTileUserActionInteractor
 @Inject
 constructor(
+    @Main private val coroutineContext: CoroutineContext,
     private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val dialogDelegate: ModesDialogDelegate,
 ) : QSTileUserActionInteractor<ModesTileModel> {
     val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
 
@@ -36,7 +48,7 @@
         with(input) {
             when (action) {
                 is QSTileUserAction.Click -> {
-                    // TODO(b/346519570) open dialog
+                    handleClick(action.expandable)
                 }
                 is QSTileUserAction.LongClick -> {
                     qsTileIntentUserActionHandler.handle(action.expandable, longClickIntent)
@@ -44,4 +56,24 @@
             }
         }
     }
+
+    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"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 26b9a4c..7048ada 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -59,5 +59,6 @@
                     QSTileState.UserAction.CLICK,
                     QSTileState.UserAction.LONG_CLICK,
                 )
+            sideViewIcon = QSTileState.SideViewIcon.Chevron
         }
 }
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
new file mode 100644
index 0000000..6db1eac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -0,0 +1,81 @@
+/*
+ * 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 android.content.Intent
+import android.provider.Settings
+import androidx.compose.material3.Text
+import androidx.compose.ui.res.stringResource
+import com.android.compose.PlatformButton
+import com.android.compose.PlatformOutlinedButton
+import com.android.systemui.animation.DialogTransitionAnimator
+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.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+import javax.inject.Inject
+
+class ModesDialogDelegate
+@Inject
+constructor(
+    private val sysuiDialogFactory: SystemUIDialogFactory,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val activityStarter: ActivityStarter,
+) : SystemUIDialog.Delegate {
+    override fun createDialog(): SystemUIDialog {
+        return sysuiDialogFactory.create { dialog ->
+            AlertDialogContent(
+                title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
+                content = { Text("Under construction") },
+                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))
+                    }
+                },
+                positiveButton = {
+                    PlatformButton(onClick = { dialog.dismiss() }) {
+                        Text(stringResource(R.string.zen_modes_dialog_done))
+                    }
+                },
+            )
+        }
+    }
+
+    companion object {
+        private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
+    }
+}
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 4c77fb8..27b6ea6 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,6 +27,7 @@
 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
@@ -41,10 +42,12 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
 import com.android.systemui.util.mockito.any
 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
@@ -79,6 +82,10 @@
 
     @Mock private lateinit var qsTileConfigProvider: QSTileConfigProvider
 
+    @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
+
+    @Mock private lateinit var dialogDelegate: ModesDialogDelegate
+
     private val inputHandler = FakeQSTileIntentUserInputHandler()
     private val zenModeRepository = FakeZenModeRepository()
     private val tileDataInteractor = ModesTileDataInteractor(zenModeRepository)
@@ -122,7 +129,13 @@
                 }
             )
 
-        userActionInteractor = ModesTileUserActionInteractor(inputHandler)
+        userActionInteractor =
+            ModesTileUserActionInteractor(
+                EmptyCoroutineContext,
+                inputHandler,
+                dialogTransitionAnimator,
+                dialogDelegate,
+            )
 
         underTest =
             ModesTile(