Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4790b0fa authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge changes Ie20dc71c,I45f1e485 into main

* changes:
  Use currently active ZenMode icon in the Volume Dialog
  Update ZenModeInteractor to return a ZenMode that blocks an audio stream
parents e8666b55 804db350
Loading
Loading
Loading
Loading
+50 −41
Original line number Diff line number Diff line
@@ -18,9 +18,8 @@ package com.android.systemui.statusbar.policy.domain.interactor

import android.app.AutomaticZenRule
import android.app.Flags
import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
import android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY
import android.app.NotificationManager.Policy
import android.media.AudioManager
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION
@@ -34,6 +33,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.settingslib.notification.data.repository.updateNotificationPolicy
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -402,115 +402,124 @@ class ZenModeInteractorTest : SysuiTestCase() {

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun activeModesBlockingEverything_hasModesWithFilterNone() =
    fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() =
        testScope.runTest {
            val blockingEverything by collectLastValue(underTest.activeModesBlockingEverything)
            val blockingMedia by
                collectLastValue(
                    underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_MUSIC))
                )

            zenModeRepository.addModes(
                listOf(
                    TestModeBuilder()
                        .setName("Filter=None, Not active")
                        .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
                        .setName("Blocks media, Not active")
                        .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
                        .setActive(false)
                        .build(),
                    TestModeBuilder()
                        .setName("Filter=Priority, Active")
                        .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                        .setName("Allows media, Active")
                        .setZenPolicy(ZenPolicy.Builder().allowMedia(true).build())
                        .setActive(true)
                        .build(),
                    TestModeBuilder()
                        .setName("Filter=None, Active")
                        .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
                        .setName("Blocks media, Active")
                        .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
                        .setActive(true)
                        .build(),
                    TestModeBuilder()
                        .setName("Filter=None, Active Too")
                        .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
                        .setName("Blocks media, Active Too")
                        .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
                        .setActive(true)
                        .build(),
                )
            )
            runCurrent()

            assertThat(blockingEverything!!.mainMode!!.name).isEqualTo("Filter=None, Active")
            assertThat(blockingEverything!!.modeNames)
                .containsExactly("Filter=None, Active", "Filter=None, Active Too")
            assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active")
            assertThat(blockingMedia!!.modeNames)
                .containsExactly("Blocks media, Active", "Blocks media, Active Too")
                .inOrder()
        }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() =
    fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() =
        testScope.runTest {
            val blockingMedia by collectLastValue(underTest.activeModesBlockingMedia)
            val blockingAlarms by
                collectLastValue(
                    underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_ALARM))
                )

            zenModeRepository.addModes(
                listOf(
                    TestModeBuilder()
                        .setName("Blocks media, Not active")
                        .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
                        .setName("Blocks alarms, Not active")
                        .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
                        .setActive(false)
                        .build(),
                    TestModeBuilder()
                        .setName("Allows media, Active")
                        .setZenPolicy(ZenPolicy.Builder().allowMedia(true).build())
                        .setName("Allows alarms, Active")
                        .setZenPolicy(ZenPolicy.Builder().allowAlarms(true).build())
                        .setActive(true)
                        .build(),
                    TestModeBuilder()
                        .setName("Blocks media, Active")
                        .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
                        .setName("Blocks alarms, Active")
                        .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
                        .setActive(true)
                        .build(),
                    TestModeBuilder()
                        .setName("Blocks media, Active Too")
                        .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
                        .setName("Blocks alarms, Active Too")
                        .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
                        .setActive(true)
                        .build(),
                )
            )
            runCurrent()

            assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active")
            assertThat(blockingMedia!!.modeNames)
                .containsExactly("Blocks media, Active", "Blocks media, Active Too")
            assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active")
            assertThat(blockingAlarms!!.modeNames)
                .containsExactly("Blocks alarms, Active", "Blocks alarms, Active Too")
                .inOrder()
        }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() =
    fun activeModesBlockingAlarms_hasModesWithPolicyBlockingSystem() =
        testScope.runTest {
            val blockingAlarms by collectLastValue(underTest.activeModesBlockingAlarms)
            val blockingSystem by
                collectLastValue(
                    underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_SYSTEM))
                )

            zenModeRepository.addModes(
                listOf(
                    TestModeBuilder()
                        .setName("Blocks alarms, Not active")
                        .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
                        .setName("Blocks system, Not active")
                        .setZenPolicy(ZenPolicy.Builder().allowSystem(false).build())
                        .setActive(false)
                        .build(),
                    TestModeBuilder()
                        .setName("Allows alarms, Active")
                        .setZenPolicy(ZenPolicy.Builder().allowAlarms(true).build())
                        .setName("Allows system, Active")
                        .setZenPolicy(ZenPolicy.Builder().allowSystem(true).build())
                        .setActive(true)
                        .build(),
                    TestModeBuilder()
                        .setName("Blocks alarms, Active")
                        .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
                        .setName("Blocks system, Active")
                        .setZenPolicy(ZenPolicy.Builder().allowSystem(false).build())
                        .setActive(true)
                        .build(),
                    TestModeBuilder()
                        .setName("Blocks alarms, Active Too")
                        .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
                        .setName("Blocks system, Active Too")
                        .setZenPolicy(ZenPolicy.Builder().allowSystem(false).build())
                        .setActive(true)
                        .build(),
                )
            )
            runCurrent()

            assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active")
            assertThat(blockingAlarms!!.modeNames)
                .containsExactly("Blocks alarms, Active", "Blocks alarms, Active Too")
            assertThat(blockingSystem!!.mainMode!!.name).isEqualTo("Blocks system, Active")
            assertThat(blockingSystem!!.modeNames)
                .containsExactly("Blocks system, Active", "Blocks system, Active Too")
                .inOrder()
        }

+23 −50
Original line number Diff line number Diff line
@@ -23,66 +23,40 @@ import android.platform.test.annotations.EnableFlags
import android.service.notification.ZenPolicy
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.uiEventLogger
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.testKosmos
import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
import com.android.systemui.volume.shared.volumePanelLogger
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
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)
class AudioStreamSliderViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val zenModeRepository = kosmos.fakeZenModeRepository

    private lateinit var mediaStream: AudioStreamSliderViewModel
    private lateinit var alarmsStream: AudioStreamSliderViewModel
    private lateinit var notificationStream: AudioStreamSliderViewModel
    private lateinit var otherStream: AudioStreamSliderViewModel

    @Before
    fun setUp() {
        mediaStream = audioStreamSliderViewModel(AudioManager.STREAM_MUSIC)
        alarmsStream = audioStreamSliderViewModel(AudioManager.STREAM_ALARM)
        notificationStream = audioStreamSliderViewModel(AudioManager.STREAM_NOTIFICATION)
        otherStream = audioStreamSliderViewModel(AudioManager.STREAM_VOICE_CALL)
    }

    private fun audioStreamSliderViewModel(stream: Int): AudioStreamSliderViewModel {
        return AudioStreamSliderViewModel(
    private fun Kosmos.audioStreamSliderViewModel(stream: Int): AudioStreamSliderViewModel {
        return audioStreamSliderViewModelFactory.create(
            AudioStreamSliderViewModel.FactoryAudioStreamWrapper(AudioStream(stream)),
            testScope.backgroundScope,
            context,
            kosmos.audioVolumeInteractor,
            kosmos.zenModeInteractor,
            kosmos.uiEventLogger,
            kosmos.volumePanelLogger,
            kosmos.sliderHapticsViewModelFactory,
            applicationCoroutineScope,
        )
    }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
    fun slider_media_hasDisabledByModesText() =
        testScope.runTest {
            val mediaSlider by collectLastValue(mediaStream.slider)
        kosmos.runTest {
            val mediaSlider by
                collectLastValue(audioStreamSliderViewModel(AudioManager.STREAM_MUSIC).slider)

            zenModeRepository.addMode(
                TestModeBuilder()
@@ -112,8 +86,9 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() {
    @Test
    @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
    fun slider_alarms_hasDisabledByModesText() =
        testScope.runTest {
            val alarmsSlider by collectLastValue(alarmsStream.slider)
        kosmos.runTest {
            val alarmsSlider by
                collectLastValue(audioStreamSliderViewModel(AudioManager.STREAM_ALARM).slider)

            zenModeRepository.addMode(
                TestModeBuilder()
@@ -141,9 +116,10 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() {

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
    fun slider_other_hasDisabledByModesText() =
        testScope.runTest {
            val otherSlider by collectLastValue(otherStream.slider)
    fun slider_other_hasDisabledText() =
        kosmos.runTest {
            val otherSlider by
                collectLastValue(audioStreamSliderViewModel(AudioManager.STREAM_VOICE_CALL).slider)

            zenModeRepository.addMode(
                TestModeBuilder()
@@ -154,20 +130,17 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() {
            )
            runCurrent()

            assertThat(otherSlider!!.disabledMessage)
                .isEqualTo("Unavailable because Everything blocked is on")

            zenModeRepository.clearModes()
            runCurrent()

            assertThat(otherSlider!!.disabledMessage).isEqualTo("Unavailable")
        }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
    fun slider_notification_hasSpecialDisabledText() =
        testScope.runTest {
            val notificationSlider by collectLastValue(notificationStream.slider)
        kosmos.runTest {
            val notificationSlider by
                collectLastValue(
                    audioStreamSliderViewModel(AudioManager.STREAM_NOTIFICATION).slider
                )
            runCurrent()

            assertThat(notificationSlider!!.disabledMessage)
+21 −15
Original line number Diff line number Diff line
@@ -16,8 +16,8 @@

package com.android.systemui.statusbar.policy.domain.interactor

import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
import android.content.Context
import android.media.AudioManager
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
@@ -29,6 +29,7 @@ import com.android.settingslib.notification.data.repository.ZenModeRepository
import com.android.settingslib.notification.modes.ZenIcon
import com.android.settingslib.notification.modes.ZenIconLoader
import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
@@ -67,6 +68,17 @@ constructor(
    deviceProvisioningRepository: DeviceProvisioningRepository,
    userSetupRepository: UserSetupRepository,
) {
    /**
     * List of predicates to determine if the [ZenMode] blocks an audio stream. Typical use case
     * would be: `zenModeByStreamPredicates[stream](zenMode)`
     */
    private val zenModeByStreamPredicates =
        mapOf<Int, (ZenMode) -> Boolean>(
            AudioManager.STREAM_MUSIC to { it.policy.priorityCategoryMedia == STATE_DISALLOW },
            AudioManager.STREAM_ALARM to { it.policy.priorityCategoryAlarms == STATE_DISALLOW },
            AudioManager.STREAM_SYSTEM to { it.policy.priorityCategorySystem == STATE_DISALLOW },
        )

    val isZenAvailable: Flow<Boolean> =
        combine(
            deviceProvisioningRepository.isDeviceProvisioned,
@@ -125,21 +137,16 @@ constructor(
            .flowOn(bgDispatcher)
            .distinctUntilChanged()

    val activeModesBlockingEverything: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode ->
        mode.interruptionFilter == INTERRUPTION_FILTER_NONE
    }
    fun canBeBlockedByZenMode(stream: AudioStream): Boolean =
        zenModeByStreamPredicates.containsKey(stream.value)

    val activeModesBlockingMedia: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode ->
        mode.policy.priorityCategoryMedia == STATE_DISALLOW
    fun activeModesBlockingStream(stream: AudioStream): Flow<ActiveZenModes> {
        val isBlockingStream = zenModeByStreamPredicates[stream.value]
        require(isBlockingStream != null) {
            "$stream is unsupported. Use canBeBlockedByZenMode to check if the stream can be affected by the Zen Mode."
        }

    val activeModesBlockingAlarms: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode ->
        mode.policy.priorityCategoryAlarms == STATE_DISALLOW
    }

    private fun getFilteredActiveModesFlow(predicate: (ZenMode) -> Boolean): Flow<ActiveZenModes> {
        return modes
            .map { modes -> modes.filter { mode -> predicate(mode) } }
            .map { modes -> modes.filter { isBlockingStream(it) } }
            .map { modes -> buildActiveZenModes(modes) }
            .flowOn(bgDispatcher)
            .distinctUntilChanged()
@@ -194,7 +201,6 @@ constructor(
                        )
                        null
                    }

                    ZEN_DURATION_FOREVER -> null
                    else -> Duration.ofMinutes(zenDuration.toLong())
                }
+1 −1
Original line number Diff line number Diff line
@@ -82,7 +82,7 @@ constructor(private val viewModel: VolumeDialogSliderViewModel) {
        // coerce the current value to the new value range before animating it. This prevents
        // animating from the value that is outside of current [valueFrom, valueTo].
        value = value.coerceIn(valueFrom, valueTo)
        setTrackIconActiveStart(model.iconRes)
        trackIconActiveStart = model.icon
        if (isInitialUpdate) {
            value = model.value
        } else {
+62 −41
Original line number Diff line number Diff line
@@ -16,13 +16,16 @@

package com.android.systemui.volume.dialog.sliders.ui.viewmodel

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.media.AudioManager
import androidx.annotation.DrawableRes
import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -31,11 +34,12 @@ import kotlinx.coroutines.flow.flowOf
class VolumeDialogSliderIconProvider
@Inject
constructor(
    private val notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
    private val context: Context,
    private val zenModeInteractor: ZenModeInteractor,
    private val audioVolumeInteractor: AudioVolumeInteractor,
) {

    @DrawableRes
    @SuppressLint("UseCompatLoadingForDrawables")
    fun getStreamIcon(
        stream: Int,
        level: Int,
@@ -43,23 +47,46 @@ constructor(
        levelMax: Int,
        isMuted: Boolean,
        isRoutedToBluetooth: Boolean,
    ): Flow<Int> {
    ): Flow<Drawable> {
        return combine(
            notificationsSoundPolicyInteractor.isZenMuted(AudioStream(stream)),
            zenModeInteractor.activeModesBlockingStream(AudioStream(stream)),
            ringerModeForStream(stream),
        ) { isZenMuted, ringerMode ->
            val isStreamOffline = level == 0 || isMuted
            if (isZenMuted) {
                // TODO(b/372466264) use icon for the corresponding zenmode
                return@combine com.android.internal.R.drawable.ic_qs_dnd
        ) { activeModesBlockingStream, ringerMode ->
            if (activeModesBlockingStream.mainMode?.icon != null) {
                return@combine activeModesBlockingStream.mainMode.icon.drawable
            } else {
                context.getDrawable(
                    getIconRes(
                        stream,
                        level,
                        levelMin,
                        levelMax,
                        isMuted,
                        isRoutedToBluetooth,
                        ringerMode,
                    )
                )!!
            }
        }
    }

    @DrawableRes
    private fun getIconRes(
        stream: Int,
        level: Int,
        levelMin: Int,
        levelMax: Int,
        isMuted: Boolean,
        isRoutedToBluetooth: Boolean,
        ringerMode: RingerMode?,
    ): Int {
        val isStreamOffline = level == 0 || isMuted
        when (ringerMode?.value) {
                AudioManager.RINGER_MODE_VIBRATE ->
                    return@combine R.drawable.ic_volume_ringer_vibrate
                AudioManager.RINGER_MODE_SILENT -> return@combine R.drawable.ic_ring_volume_off
            AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate
            AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off
        }
        if (isRoutedToBluetooth) {
                return@combine if (stream == AudioManager.STREAM_VOICE_CALL) {
            return if (stream == AudioManager.STREAM_VOICE_CALL) {
                R.drawable.ic_volume_bt_sco
            } else {
                if (isStreamOffline) {
@@ -70,8 +97,14 @@ constructor(
            }
        }

            return@combine if (isStreamOffline) {
                getMutedIconForStream(stream) ?: getIconForStream(stream)
        return if (isStreamOffline) {
            when (stream) {
                AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute
                AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute
                AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute
                AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute
                else -> null
            } ?: getIconForStream(stream)
        } else {
            if (level < (levelMax + levelMin) / 2) {
                // This icon is different on TV
@@ -81,18 +114,6 @@ constructor(
            }
        }
    }
    }

    @DrawableRes
    private fun getMutedIconForStream(stream: Int): Int? {
        return when (stream) {
            AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute
            AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute
            AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute
            AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute
            else -> null
        }
    }

    @DrawableRes
    private fun getIconForStream(stream: Int): Int {
Loading