Merge "Revert^2 "Add view models for volume dialog ringer drawer"" into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
new file mode 100644
index 0000000..faf01ed
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.volume.dialog.ringer.ui.viewmodel
+
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.media.AudioManager.STREAM_RING
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.fakeVibratorHelper
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class VolumeDialogRingerDrawerViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val controller = kosmos.fakeVolumeDialogController
+    private val vibratorHelper = kosmos.fakeVibratorHelper
+
+    private lateinit var underTest: VolumeDialogRingerDrawerViewModel
+
+    @Before
+    fun setUp() {
+        underTest = kosmos.volumeDialogRingerDrawerViewModel
+    }
+
+    @Test
+    fun onSelectedRingerNormalModeButtonClicked_openDrawer() =
+        testScope.runTest {
+            val ringerViewModel by collectLastValue(underTest.ringerViewModel)
+            val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
+
+            setUpRingerModeAndOpenDrawer(normalRingerMode)
+
+            assertThat(ringerViewModel).isNotNull()
+            assertThat(ringerViewModel?.drawerState)
+                .isEqualTo(RingerDrawerState.Open(normalRingerMode))
+        }
+
+    @Test
+    fun onSelectedRingerButtonClicked_drawerOpened_closeDrawer() =
+        testScope.runTest {
+            val ringerViewModel by collectLastValue(underTest.ringerViewModel)
+            val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
+
+            setUpRingerModeAndOpenDrawer(normalRingerMode)
+            underTest.onRingerButtonClicked(normalRingerMode)
+            controller.getState()
+
+            assertThat(ringerViewModel).isNotNull()
+            assertThat(ringerViewModel?.drawerState)
+                .isEqualTo(RingerDrawerState.Closed(normalRingerMode))
+        }
+
+    @Test
+    fun onNewRingerButtonClicked_drawerOpened_updateRingerMode_closeDrawer() =
+        testScope.runTest {
+            val ringerViewModel by collectLastValue(underTest.ringerViewModel)
+            val vibrateRingerMode = RingerMode(RINGER_MODE_VIBRATE)
+
+            setUpRingerModeAndOpenDrawer(RingerMode(RINGER_MODE_NORMAL))
+            // Select vibrate ringer mode.
+            underTest.onRingerButtonClicked(vibrateRingerMode)
+            controller.getState()
+            runCurrent()
+
+            assertThat(ringerViewModel).isNotNull()
+            assertThat(
+                    ringerViewModel
+                        ?.availableButtons
+                        ?.get(ringerViewModel!!.currentButtonIndex)
+                        ?.ringerMode
+                )
+                .isEqualTo(vibrateRingerMode)
+            assertThat(ringerViewModel?.drawerState)
+                .isEqualTo(RingerDrawerState.Closed(vibrateRingerMode))
+
+            val silentRingerMode = RingerMode(RINGER_MODE_SILENT)
+            // Open drawer
+            underTest.onRingerButtonClicked(vibrateRingerMode)
+            controller.getState()
+
+            // Select silent ringer mode.
+            underTest.onRingerButtonClicked(silentRingerMode)
+            controller.getState()
+            runCurrent()
+
+            assertThat(ringerViewModel).isNotNull()
+            assertThat(
+                    ringerViewModel
+                        ?.availableButtons
+                        ?.get(ringerViewModel!!.currentButtonIndex)
+                        ?.ringerMode
+                )
+                .isEqualTo(silentRingerMode)
+            assertThat(ringerViewModel?.drawerState)
+                .isEqualTo(RingerDrawerState.Closed(silentRingerMode))
+            assertThat(controller.hasScheduledTouchFeedback).isFalse()
+            assertThat(vibratorHelper.totalVibrations).isEqualTo(2)
+        }
+
+    private fun TestScope.setUpRingerModeAndOpenDrawer(selectedRingerMode: RingerMode) {
+        controller.setStreamVolume(STREAM_RING, 50)
+        controller.setRingerMode(selectedRingerMode.value, false)
+        runCurrent()
+
+        underTest.onRingerButtonClicked(RingerMode(selectedRingerMode.value))
+        controller.getState()
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt
new file mode 100644
index 0000000..78d2d16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.volume.dialog.ringer.ui.viewmodel
+
+import android.annotation.DrawableRes
+import android.annotation.StringRes
+import com.android.settingslib.volume.shared.model.RingerMode
+
+/** Models ringer button that corresponds to each ringer mode. */
+data class RingerButtonViewModel(
+    /** Image resource id for the image button. */
+    @DrawableRes val imageResId: Int,
+    /** Content description for a11y. */
+    @StringRes val contentDescriptionResId: Int,
+    /** Hint label for accessibility use. */
+    @StringRes val hintLabelResId: Int,
+    /** Used to notify view model when button is clicked. */
+    val ringerMode: RingerMode,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
new file mode 100644
index 0000000..f321837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.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.volume.dialog.ringer.ui.viewmodel
+
+import com.android.settingslib.volume.shared.model.RingerMode
+
+/** Models volume dialog ringer drawer state */
+sealed interface RingerDrawerState {
+
+    /** When clicked to open drawer */
+    data class Open(val mode: RingerMode) : RingerDrawerState
+
+    /** When clicked to close drawer */
+    data class Closed(val mode: RingerMode) : RingerDrawerState
+
+    /** Initial state when volume dialog is shown with a closed drawer. */
+    interface Initial : RingerDrawerState {
+        companion object : Initial
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
new file mode 100644
index 0000000..a09bfeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.volume.dialog.ringer.ui.viewmodel
+
+/** Models volume dialog ringer */
+data class RingerViewModel(
+    /** List of the available buttons according to the available modes */
+    val availableButtons: List<RingerButtonViewModel?>,
+    /** The index of the currently selected button */
+    val currentButtonIndex: Int,
+    /** For open and close animations */
+    val drawerState: RingerDrawerState,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
new file mode 100644
index 0000000..ac82ae3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.volume.dialog.ringer.ui.viewmodel
+
+import android.media.AudioAttributes
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.os.VibrationEffect
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.ringer.domain.VolumeDialogRingerInteractor
+import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
+import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+private const val TAG = "VolumeDialogRingerDrawerViewModel"
+
+class VolumeDialogRingerDrawerViewModel
+@AssistedInject
+constructor(
+    @VolumeDialog private val coroutineScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val interactor: VolumeDialogRingerInteractor,
+    private val vibrator: VibratorHelper,
+    private val volumeDialogLogger: VolumeDialogLogger,
+) {
+
+    private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial)
+
+    val ringerViewModel: Flow<RingerViewModel> =
+        combine(interactor.ringerModel, drawerState) { ringerModel, state ->
+                ringerModel.toViewModel(state)
+            }
+            .flowOn(backgroundDispatcher)
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+            .filterNotNull()
+
+    // Vibration attributes.
+    private val sonificiationVibrationAttributes =
+        AudioAttributes.Builder()
+            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+            .build()
+
+    fun onRingerButtonClicked(ringerMode: RingerMode) {
+        if (drawerState.value is RingerDrawerState.Open) {
+            Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value)
+            provideTouchFeedback(ringerMode)
+            interactor.setRingerMode(ringerMode)
+        }
+        drawerState.value =
+            when (drawerState.value) {
+                is RingerDrawerState.Initial -> {
+                    RingerDrawerState.Open(ringerMode)
+                }
+                is RingerDrawerState.Open -> {
+                    RingerDrawerState.Closed(ringerMode)
+                }
+                is RingerDrawerState.Closed -> {
+                    RingerDrawerState.Open(ringerMode)
+                }
+            }
+    }
+
+    private fun provideTouchFeedback(ringerMode: RingerMode) {
+        when (ringerMode.value) {
+            RINGER_MODE_NORMAL -> {
+                interactor.scheduleTouchFeedback()
+                null
+            }
+            RINGER_MODE_SILENT -> VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
+            RINGER_MODE_VIBRATE -> VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)
+            else -> VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)
+        }?.let { vibrator.vibrate(it, sonificiationVibrationAttributes) }
+    }
+
+    private fun VolumeDialogRingerModel.toViewModel(
+        drawerState: RingerDrawerState
+    ): RingerViewModel {
+        val currentIndex = availableModes.indexOf(currentRingerMode)
+        if (currentIndex == -1) {
+            volumeDialogLogger.onCurrentRingerModeIsUnsupported(currentRingerMode)
+        }
+        return RingerViewModel(
+            availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
+            currentButtonIndex = currentIndex,
+            drawerState = drawerState,
+        )
+    }
+
+    private fun VolumeDialogRingerModel.toButtonViewModel(
+        ringerMode: RingerMode
+    ): RingerButtonViewModel? {
+        return when (ringerMode.value) {
+            RINGER_MODE_SILENT ->
+                RingerButtonViewModel(
+                    imageResId = R.drawable.ic_speaker_mute,
+                    contentDescriptionResId = R.string.volume_ringer_status_silent,
+                    hintLabelResId = R.string.volume_ringer_hint_unmute,
+                    ringerMode = ringerMode,
+                )
+            RINGER_MODE_VIBRATE ->
+                RingerButtonViewModel(
+                    imageResId = R.drawable.ic_volume_ringer_vibrate,
+                    contentDescriptionResId = R.string.volume_ringer_status_vibrate,
+                    hintLabelResId = R.string.volume_ringer_hint_vibrate,
+                    ringerMode = ringerMode,
+                )
+            RINGER_MODE_NORMAL ->
+                when {
+                    isMuted && isEnabled ->
+                        RingerButtonViewModel(
+                            imageResId = R.drawable.ic_speaker_mute,
+                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            hintLabelResId = R.string.volume_ringer_hint_unmute,
+                            ringerMode = ringerMode,
+                        )
+
+                    availableModes.contains(RingerMode(RINGER_MODE_VIBRATE)) ->
+                        RingerButtonViewModel(
+                            imageResId = R.drawable.ic_speaker_on,
+                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            hintLabelResId = R.string.volume_ringer_hint_vibrate,
+                            ringerMode = ringerMode,
+                        )
+
+                    else ->
+                        RingerButtonViewModel(
+                            imageResId = R.drawable.ic_speaker_on,
+                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            hintLabelResId = R.string.volume_ringer_hint_mute,
+                            ringerMode = ringerMode,
+                        )
+                }
+            else -> null
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): VolumeDialogRingerDrawerViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
index 59c38c0..9a3aa7e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.volume.dialog.shared
 
+import com.android.settingslib.volume.shared.model.RingerMode
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.VolumeLog
@@ -43,4 +44,13 @@
             { "Dismiss: ${Events.DISMISS_REASONS[int1]}" },
         )
     }
+
+    fun onCurrentRingerModeIsUnsupported(ringerMode: RingerMode) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = ringerMode.value },
+            { "Current ringer mode: $int1, ringer mode is unsupported in ringer drawer options" },
+        )
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
new file mode 100644
index 0000000..db1c01a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.volume.dialog.ringer.ui.viewmodel
+
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor
+import com.android.systemui.volume.dialog.shared.volumeDialogLogger
+
+val Kosmos.volumeDialogRingerDrawerViewModel by
+    Kosmos.Fixture {
+        VolumeDialogRingerDrawerViewModel(
+            backgroundDispatcher = testDispatcher,
+            coroutineScope = applicationCoroutineScope,
+            interactor = volumeDialogRingerInteractor,
+            vibrator = vibratorHelper,
+            volumeDialogLogger = volumeDialogLogger,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt
new file mode 100644
index 0000000..f9d4a99
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.volume.dialog.shared
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.volumeDialogLogger by Kosmos.Fixture { VolumeDialogLogger(logcatLogBuffer()) }