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

Commit 1dfbc3da authored by Ioana Alexandru's avatar Ioana Alexandru Committed by Matías Hernández
Browse files

Update DND quick affordance to work better with modes

Also (flaggedly) refactor DoNotDisturbQuickAffordanceConfig to use ZenModeInteractor instead of the obsolete ZenModeController.

Previously, onTriggered was calling setInterruptionFilter(), which under MODES_UI controls only the DND mode. Thus pressing it when some other mode was active had no effect (tried to turn off DND, which wasn't actually active). Ideally, there should be a quick affordance for modes, however these cannot be dynamic at this point -- so this one still controls only DND, but now works correctly.

Fixes: 365759676
Test: atest DoNotDisturbQuickAffordanceConfigTest
Flag: android.app.modes_ui

Change-Id: I0a306e2e1905f3caf8451f75710c317ec245fe32
parent 45f28caa
Loading
Loading
Loading
Loading
+205 −14
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@
 */
package com.android.systemui.keyguard.data.quickaffordance

import android.app.Flags
import android.net.Uri
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_OFF
@@ -25,6 +28,7 @@ import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.EnableZenModeDialog
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
@@ -35,7 +39,11 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
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.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
@@ -43,6 +51,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import java.time.Duration
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -66,8 +75,13 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testDispatcher = kosmos.testDispatcher
    private val testScope = kosmos.testScope

    private val settings = kosmos.fakeSettings

    private val zenModeRepository = kosmos.fakeZenModeRepository
    private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
    private val secureSettingsRepository = kosmos.secureSettingsRepository

    @Mock private lateinit var zenModeController: ZenModeController
    @Mock private lateinit var userTracker: UserTracker
    @Mock private lateinit var conditionUri: Uri
@@ -85,16 +99,35 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
            DoNotDisturbQuickAffordanceConfig(
                context,
                zenModeController,
                kosmos.zenModeInteractor,
                settings,
                userTracker,
                testDispatcher,
                testScope.backgroundScope,
                conditionUri,
                enableZenModeDialog,
            )
    }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun dndNotAvailable_pickerStateHidden() =
        testScope.runTest {
            deviceProvisioningRepository.setDeviceProvisioned(false)
            runCurrent()

            val result = underTest.getPickerScreenState()
            runCurrent()

            assertEquals(
                KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
                result,
            )
        }

    @Test
    @DisableFlags(Flags.FLAG_MODES_UI)
    fun controllerDndNotAvailable_pickerStateHidden() =
        testScope.runTest {
            // given
            whenever(zenModeController.isZenAvailable).thenReturn(false)
@@ -105,12 +138,32 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
            // then
            assertEquals(
                KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
                result
                result,
            )
        }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun dndAvailable_pickerStateVisible() =
        testScope.runTest {
            deviceProvisioningRepository.setDeviceProvisioned(true)
            runCurrent()

            val result = underTest.getPickerScreenState()
            runCurrent()

            assertThat(result)
                .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
            val defaultPickerState =
                result as KeyguardQuickAffordanceConfig.PickerScreenState.Default
            assertThat(defaultPickerState.configureIntent).isNotNull()
            assertThat(defaultPickerState.configureIntent?.action)
                .isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
        }

    @Test
    @DisableFlags(Flags.FLAG_MODES_UI)
    fun controllerDndAvailable_pickerStateVisible() =
        testScope.runTest {
            // given
            whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -129,7 +182,27 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
        }

    @Test
    fun onTriggered_dndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() =
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun onTriggered_dndModeIsNotOff_setToOff() =
        testScope.runTest {
            val currentModes by collectLastValue(zenModeRepository.modes)

            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
            secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -2)
            collectLastValue(underTest.lockScreenState)
            runCurrent()

            val result = underTest.onTriggered(null)
            runCurrent()

            val dndMode = currentModes!!.single()
            assertThat(dndMode.isActive).isFalse()
            assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
        }

    @Test
    @DisableFlags(Flags.FLAG_MODES_UI)
    fun onTriggered_controllerDndModeIsNotZEN_MODE_OFF_setToZEN_MODE_OFF() =
        testScope.runTest {
            // given
            whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -140,11 +213,12 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {

            // when
            val result = underTest.onTriggered(null)

            verify(zenModeController)
                .setZen(
                    spyZenMode.capture(),
                    spyConditionId.capture(),
                    eq(DoNotDisturbQuickAffordanceConfig.TAG)
                    eq(DoNotDisturbQuickAffordanceConfig.TAG),
                )

            // then
@@ -154,7 +228,28 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
        }

    @Test
    fun onTriggered_dndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() =
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun onTriggered_dndModeIsOff_settingFOREVER_setZenWithoutCondition() =
        testScope.runTest {
            val currentModes by collectLastValue(zenModeRepository.modes)

            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
            secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_FOREVER)
            collectLastValue(underTest.lockScreenState)
            runCurrent()

            val result = underTest.onTriggered(null)
            runCurrent()

            val dndMode = currentModes!!.single()
            assertThat(dndMode.isActive).isTrue()
            assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)).isNull()
            assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
        }

    @Test
    @DisableFlags(Flags.FLAG_MODES_UI)
    fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingFOREVER_setZenWithoutCondition() =
        testScope.runTest {
            // given
            whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -169,7 +264,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
                .setZen(
                    spyZenMode.capture(),
                    spyConditionId.capture(),
                    eq(DoNotDisturbQuickAffordanceConfig.TAG)
                    eq(DoNotDisturbQuickAffordanceConfig.TAG),
                )

            // then
@@ -179,7 +274,27 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
        }

    @Test
    fun onTriggered_dndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() =
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun onTriggered_dndModeIsOff_settingNotFOREVERorPROMPT_dndWithDuration() =
        testScope.runTest {
            val currentModes by collectLastValue(zenModeRepository.modes)
            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
            secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, -900)
            runCurrent()

            val result = underTest.onTriggered(null)
            runCurrent()

            assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
            val dndMode = currentModes!!.single()
            assertThat(dndMode.isActive).isTrue()
            assertThat(zenModeRepository.getModeActiveDuration(dndMode.id))
                .isEqualTo(Duration.ofMinutes(-900))
        }

    @Test
    @DisableFlags(Flags.FLAG_MODES_UI)
    fun onTriggered_controllerDndZEN_MODE_OFF_settingNotFOREVERorPROMPT_zenWithCondition() =
        testScope.runTest {
            // given
            whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -194,7 +309,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
                .setZen(
                    spyZenMode.capture(),
                    spyConditionId.capture(),
                    eq(DoNotDisturbQuickAffordanceConfig.TAG)
                    eq(DoNotDisturbQuickAffordanceConfig.TAG),
                )

            // then
@@ -204,7 +319,28 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
        }

    @Test
    fun onTriggered_dndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() =
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun onTriggered_dndModeIsOff_settingIsPROMPT_showDialog() =
        testScope.runTest {
            val expandable: Expandable = mock()
            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
            secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
            whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
            collectLastValue(underTest.lockScreenState)
            runCurrent()

            val result = underTest.onTriggered(expandable)

            assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog)
            assertEquals(
                expandable,
                (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable,
            )
        }

    @Test
    @DisableFlags(Flags.FLAG_MODES_UI)
    fun onTriggered_controllerDndModeIsZEN_MODE_OFF_settingIsPROMPT_showDialog() =
        testScope.runTest {
            // given
            val expandable: Expandable = mock()
@@ -222,12 +358,30 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
            assertTrue(result is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog)
            assertEquals(
                expandable,
                (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable
                (result as KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog).expandable,
            )
        }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun lockScreenState_dndAvailableStartsAsTrue_changeToFalse_StateIsHidden() =
        testScope.runTest {
            deviceProvisioningRepository.setDeviceProvisioned(true)
            val valueSnapshot = collectLastValue(underTest.lockScreenState)
            val secondLastValue = valueSnapshot()
            runCurrent()

            deviceProvisioningRepository.setDeviceProvisioned(false)
            runCurrent()
            val lastValue = valueSnapshot()

            assertTrue(secondLastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
            assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
        }

    @Test
    @DisableFlags(Flags.FLAG_MODES_UI)
    fun lockScreenState_controllerDndAvailableStartsAsTrue_changeToFalse_StateIsHidden() =
        testScope.runTest {
            // given
            whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -246,7 +400,44 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
        }

    @Test
    fun lockScreenState_dndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() =
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun lockScreenState_dndModeStartsAsOff_changeToOn_StateVisible() =
        testScope.runTest {
            val lockScreenState by collectLastValue(underTest.lockScreenState)

            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
            runCurrent()

            assertThat(lockScreenState)
                .isEqualTo(
                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                        Icon.Resource(
                            R.drawable.qs_dnd_icon_off,
                            ContentDescription.Resource(R.string.dnd_is_off),
                        ),
                        ActivationState.Inactive,
                    )
                )

            zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
            runCurrent()

            assertThat(lockScreenState)
                .isEqualTo(
                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                        Icon.Resource(
                            R.drawable.qs_dnd_icon_on,
                            ContentDescription.Resource(R.string.dnd_is_on),
                        ),
                        ActivationState.Active,
                    )
                )
        }

    @Test
    @DisableFlags(Flags.FLAG_MODES_UI)
    fun lockScreenState_controllerDndModeStartsAsZEN_MODE_OFF_changeToNotOFF_StateVisible() =
        testScope.runTest {
            // given
            whenever(zenModeController.isZenAvailable).thenReturn(true)
@@ -265,9 +456,9 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                    Icon.Resource(
                        R.drawable.qs_dnd_icon_off,
                        ContentDescription.Resource(R.string.dnd_is_off)
                        ContentDescription.Resource(R.string.dnd_is_off),
                    ),
                    ActivationState.Inactive
                    ActivationState.Inactive,
                ),
                secondLastValue,
            )
@@ -275,9 +466,9 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                    Icon.Resource(
                        R.drawable.qs_dnd_icon_on,
                        ContentDescription.Resource(R.string.dnd_is_on)
                        ContentDescription.Resource(R.string.dnd_is_on),
                    ),
                    ActivationState.Active
                    ActivationState.Active,
                ),
                lastValue,
            )
+42 −0
Original line number Diff line number Diff line
@@ -17,7 +17,9 @@
package com.android.systemui.statusbar.policy.domain.interactor

import android.app.AutomaticZenRule
import android.app.Flags
import android.app.NotificationManager.Policy
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
@@ -32,6 +34,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -50,9 +53,30 @@ class ZenModeInteractorTest : SysuiTestCase() {
    private val testScope = kosmos.testScope
    private val zenModeRepository = kosmos.fakeZenModeRepository
    private val settingsRepository = kosmos.secureSettingsRepository
    private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository

    private val underTest = kosmos.zenModeInteractor

    @Test
    fun isZenAvailable_off() =
        testScope.runTest {
            val isZenAvailable by collectLastValue(underTest.isZenAvailable)
            deviceProvisioningRepository.setDeviceProvisioned(false)
            runCurrent()

            assertThat(isZenAvailable).isFalse()
        }

    @Test
    fun isZenAvailable_on() =
        testScope.runTest {
            val isZenAvailable by collectLastValue(underTest.isZenAvailable)
            deviceProvisioningRepository.setDeviceProvisioned(true)
            runCurrent()

            assertThat(isZenAvailable).isTrue()
        }

    @Test
    fun isZenModeEnabled_off() =
        testScope.runTest {
@@ -337,4 +361,22 @@ class ZenModeInteractorTest : SysuiTestCase() {
            runCurrent()
            assertThat(mainActiveMode).isNull()
        }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    fun dndMode_flows() =
        testScope.runTest {
            val dndMode by collectLastValue(underTest.dndMode)

            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
            runCurrent()

            assertThat(dndMode!!.isActive).isFalse()

            zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
            runCurrent()

            assertThat(dndMode!!.isActive).isTrue()
        }
}
+148 −51

File changed.

Preview size limit exceeded, changes collapsed.

+27 −1
Original line number Diff line number Diff line
@@ -27,7 +27,10 @@ import com.android.settingslib.notification.modes.ZenIcon
import com.android.settingslib.notification.modes.ZenIconLoader
import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
import java.time.Duration
@@ -51,7 +54,17 @@ constructor(
    private val notificationSettingsRepository: NotificationSettingsRepository,
    @Background private val bgDispatcher: CoroutineDispatcher,
    private val iconLoader: ZenIconLoader,
    private val deviceProvisioningRepository: DeviceProvisioningRepository,
    private val userSetupRepository: UserSetupRepository,
) {
    val isZenAvailable: Flow<Boolean> =
        combine(
            deviceProvisioningRepository.isDeviceProvisioned,
            userSetupRepository.isUserSetUp,
        ) { isDeviceProvisioned, isUserSetUp ->
            isDeviceProvisioned && isUserSetUp
        }

    val isZenModeEnabled: Flow<Boolean> =
        zenModeRepository.globalZenMode
            .map {
@@ -80,6 +93,18 @@ constructor(

    val modes: Flow<List<ZenMode>> = zenModeRepository.modes

    /**
     * Returns the special "manual DND" mode.
     *
     * This is only meant as a temporary solution for "legacy" UI pieces that handle DND
     * specifically; any new or migrated features should use modes more generally, through [modes]
     * or [activeModes].
     */
    val dndMode: Flow<ZenMode?> by lazy {
        ModesUi.assertInNewMode()
        zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } }
    }

    /** Flow returning the currently active mode(s), if any. */
    val activeModes: Flow<ActiveZenModes> =
        modes
@@ -113,10 +138,11 @@ constructor(
                        Log.e(
                            TAG,
                            "Interactor cannot handle showing the zen duration prompt. " +
                                "Please use EnableZenModeDialog when this setting is active."
                                "Please use EnableZenModeDialog when this setting is active.",
                        )
                        null
                    }

                    ZEN_DURATION_FOREVER -> null
                    else -> Duration.ofMinutes(zenDuration.toLong())
                }
+4 −0
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository

val Kosmos.zenModeInteractor by Fixture {
@@ -31,5 +33,7 @@ val Kosmos.zenModeInteractor by Fixture {
        notificationSettingsRepository = notificationSettingsRepository,
        bgDispatcher = testDispatcher,
        iconLoader = zenIconLoader,
        deviceProvisioningRepository = deviceProvisioningRepository,
        userSetupRepository = userSetupRepository,
    )
}