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

Commit 36333310 authored by Ioana Alexandru's avatar Ioana Alexandru
Browse files

Show duration prompt from modes dialog

I'm using the exact same way to create the dialog as we do in DndTile.
No transition for now, because it's already a bit much that we have a
dialog being opened from another dialog.

I can't unit test the viewmodel without changing the design of
EnableZenModeDialog (because I can't verify that the dialog was opened),
so we have to rely on testing all the other pieces.

Bug: 346519570
Flag: android.app.modes_ui
Test: unit tested + tested manually with the different settings
Change-Id: I0ef7d91e5e7b920b62db533e81de498f617f7e31
parent cf7ea371
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -40,6 +40,8 @@ class FakeZenModeRepository : ZenModeRepository {
    override val modes: Flow<List<ZenMode>>
        get() = mutableModesFlow.asStateFlow()

    private val activeModesDurations = mutableMapOf<String, Duration?>()

    init {
        updateNotificationPolicy()
    }
@@ -64,8 +66,22 @@ class FakeZenModeRepository : ZenModeRepository {
        mutableModesFlow.value = mutableModesFlow.value.filter { it.id != id }
    }

    fun getMode(id: String): ZenMode? {
        return mutableModesFlow.value.find { it.id == id }
    }

    override fun activateMode(zenMode: ZenMode, duration: Duration?) {
        activateMode(zenMode.id)
        activeModesDurations[zenMode.id] = duration
    }

    fun getModeActiveDuration(id: String): Duration? {
        if (!activeModesDurations.containsKey(id)) {
            throw IllegalArgumentException(
                "mode $id not manually activated, you need to call activateMode"
            )
        }
        return activeModesDurations[id]
    }

    override fun deactivateMode(zenMode: ZenMode) {
@@ -78,6 +94,7 @@ class FakeZenModeRepository : ZenModeRepository {

    fun deactivateMode(id: String) {
        updateModeActiveState(id = id, isActive = false)
        activeModesDurations.remove(id)
    }

    // Update the active state while maintaining the mode's position in the list
+17 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.shared.notifications.data.repository

import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
@@ -32,10 +33,10 @@ import kotlinx.coroutines.withContext

/** Provides access to state related to notification settings. */
class NotificationSettingsRepository(
    private val scope: CoroutineScope,
    private val backgroundScope: CoroutineScope,
    private val backgroundDispatcher: CoroutineDispatcher,
    private val secureSettingsRepository: SecureSettingsRepository,
    private val systemSettingsRepository: SystemSettingsRepository,
    systemSettingsRepository: SystemSettingsRepository,
) {
    val isNotificationHistoryEnabled: Flow<Boolean> =
        secureSettingsRepository
@@ -51,9 +52,7 @@ class NotificationSettingsRepository(
            )
            .map { it == 1 }
            .flowOn(backgroundDispatcher)
            .stateIn(
                scope = scope,
            )
            .stateIn(scope = backgroundScope)

    suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
        withContext(backgroundDispatcher) {
@@ -70,8 +69,20 @@ class NotificationSettingsRepository(
            .map { it == 1 }
            .flowOn(backgroundDispatcher)
            .stateIn(
                scope = scope,
                scope = backgroundScope,
                started = SharingStarted.Eagerly,
                initialValue = false,
            )

    /** The default duration for DND mode when enabled. See [Settings.Secure.ZEN_DURATION]. */
    val zenDuration: StateFlow<Int> =
        secureSettingsRepository
            .intSetting(name = Settings.Secure.ZEN_DURATION)
            .distinctUntilChanged()
            .flowOn(backgroundDispatcher)
            .stateIn(
                backgroundScope,
                started = SharingStarted.Eagerly,
                initialValue = ZEN_DURATION_PROMPT,
            )
}
+80 −14
Original line number Diff line number Diff line
@@ -18,15 +18,21 @@ package com.android.systemui.statusbar.policy.domain.interactor

import android.app.NotificationManager.Policy
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
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.data.repository.updateNotificationPolicy
import com.android.settingslib.notification.modes.TestModeBuilder
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.fakeZenModeRepository
import com.android.systemui.testKosmos
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
@@ -39,7 +45,8 @@ import org.junit.runner.RunWith
class ZenModeInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val repository = kosmos.fakeZenModeRepository
    private val zenModeRepository = kosmos.fakeZenModeRepository
    private val settingsRepository = kosmos.secureSettingsRepository

    private val underTest = kosmos.zenModeInteractor

@@ -48,7 +55,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
        testScope.runTest {
            val enabled by collectLastValue(underTest.isZenModeEnabled)

            repository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
            runCurrent()

            assertThat(enabled).isFalse()
@@ -59,7 +66,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
        testScope.runTest {
            val enabled by collectLastValue(underTest.isZenModeEnabled)

            repository.updateZenMode(Settings.Global.ZEN_MODE_ALARMS)
            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_ALARMS)
            runCurrent()

            assertThat(enabled).isTrue()
@@ -70,7 +77,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
        testScope.runTest {
            val enabled by collectLastValue(underTest.isZenModeEnabled)

            repository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
            runCurrent()

            assertThat(enabled).isTrue()
@@ -81,7 +88,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
        testScope.runTest {
            val enabled by collectLastValue(underTest.isZenModeEnabled)

            repository.updateZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
            runCurrent()

            assertThat(enabled).isTrue()
@@ -92,7 +99,8 @@ class ZenModeInteractorTest : SysuiTestCase() {
        testScope.runTest {
            val enabled by collectLastValue(underTest.isZenModeEnabled)

            repository.updateZenMode(4) // this should fail if we ever add another zen mode type
            // this should fail if we ever add another zen mode type
            zenModeRepository.updateZenMode(4)
            runCurrent()

            assertThat(enabled).isFalse()
@@ -103,8 +111,8 @@ class ZenModeInteractorTest : SysuiTestCase() {
        testScope.runTest {
            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)

            repository.updateNotificationPolicy(null)
            repository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
            zenModeRepository.updateNotificationPolicy(null)
            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
            runCurrent()

            assertThat(hidden).isFalse()
@@ -115,10 +123,10 @@ class ZenModeInteractorTest : SysuiTestCase() {
        testScope.runTest {
            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)

            repository.updateNotificationPolicy(
            zenModeRepository.updateNotificationPolicy(
                suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
            )
            repository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
            runCurrent()

            assertThat(hidden).isFalse()
@@ -129,10 +137,10 @@ class ZenModeInteractorTest : SysuiTestCase() {
        testScope.runTest {
            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)

            repository.updateNotificationPolicy(
            zenModeRepository.updateNotificationPolicy(
                suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_STATUS_BAR
            )
            repository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
            runCurrent()

            assertThat(hidden).isFalse()
@@ -143,12 +151,70 @@ class ZenModeInteractorTest : SysuiTestCase() {
        testScope.runTest {
            val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)

            repository.updateNotificationPolicy(
            zenModeRepository.updateNotificationPolicy(
                suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
            )
            repository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
            zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
            runCurrent()

            assertThat(hidden).isTrue()
        }

    @Test
    fun shouldAskForZenDuration_falseForNonManualDnd() =
        testScope.runTest {
            settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_PROMPT)
            runCurrent()

            assertThat(underTest.shouldAskForZenDuration(TestModeBuilder.EXAMPLE)).isFalse()
        }

    @Test
    fun shouldAskForZenDuration_changesWithSetting() =
        testScope.runTest {
            val manualDnd = TestModeBuilder.MANUAL_DND

            settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
            runCurrent()

            assertThat(underTest.shouldAskForZenDuration(manualDnd)).isFalse()

            settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_PROMPT)
            runCurrent()

            assertThat(underTest.shouldAskForZenDuration(manualDnd)).isTrue()
        }

    @Test
    fun activateMode_nonManualDnd() =
        testScope.runTest {
            val mode = TestModeBuilder().setActive(false).build()
            zenModeRepository.addModes(listOf(mode))
            settingsRepository.setInt(ZEN_DURATION, 60)
            runCurrent()

            underTest.activateMode(mode)
            assertThat(zenModeRepository.getMode(mode.id)?.isActive).isTrue()
            assertThat(zenModeRepository.getModeActiveDuration(mode.id)).isNull()
        }

    @Test
    fun activateMode_usesCorrectDuration() =
        testScope.runTest {
            val manualDnd = TestModeBuilder.MANUAL_DND
            zenModeRepository.addModes(listOf(manualDnd))
            settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
            runCurrent()

            underTest.activateMode(manualDnd)
            assertThat(zenModeRepository.getModeActiveDuration(manualDnd.id)).isNull()

            zenModeRepository.deactivateMode(manualDnd.id)
            settingsRepository.setInt(ZEN_DURATION, 60)
            runCurrent()

            underTest.activateMode(manualDnd)
            assertThat(zenModeRepository.getModeActiveDuration(manualDnd.id))
                .isEqualTo(Duration.ofMinutes(60))
        }
}
+46 −7
Original line number Diff line number Diff line
@@ -18,11 +18,15 @@ package com.android.systemui.statusbar.policy.domain.interactor

import android.content.Context
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import android.util.Log
import androidx.concurrent.futures.await
import com.android.settingslib.notification.data.repository.ZenModeRepository
import com.android.settingslib.notification.modes.ZenIconLoader
import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
import java.time.Duration
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -34,11 +38,16 @@ import kotlinx.coroutines.flow.map
 * An interactor that performs business logic related to the status and configuration of Zen Mode
 * (or Do Not Disturb/DND Mode).
 */
class ZenModeInteractor @Inject constructor(private val repository: ZenModeRepository) {
class ZenModeInteractor
@Inject
constructor(
    private val zenModeRepository: ZenModeRepository,
    private val notificationSettingsRepository: NotificationSettingsRepository,
) {
    private val iconLoader: ZenIconLoader = ZenIconLoader.getInstance()

    val isZenModeEnabled: Flow<Boolean> =
        repository.globalZenMode
        zenModeRepository.globalZenMode
            .map {
                when (it ?: Settings.Global.ZEN_MODE_OFF) {
                    Settings.Global.ZEN_MODE_ALARMS -> true
@@ -51,7 +60,9 @@ class ZenModeInteractor @Inject constructor(private val repository: ZenModeRepos
            .distinctUntilChanged()

    val areNotificationsHiddenInShade: Flow<Boolean> =
        combine(isZenModeEnabled, repository.consolidatedNotificationPolicy) { dndEnabled, policy ->
        combine(isZenModeEnabled, zenModeRepository.consolidatedNotificationPolicy) {
                dndEnabled,
                policy ->
                if (!dndEnabled) {
                    false
                } else {
@@ -61,17 +72,45 @@ class ZenModeInteractor @Inject constructor(private val repository: ZenModeRepos
            }
            .distinctUntilChanged()

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

    suspend fun getModeIcon(mode: ZenMode, context: Context): Icon {
        return Icon.Loaded(mode.getIcon(context, iconLoader).await(), contentDescription = null)
    }

    fun activateMode(zenMode: ZenMode, duration: Duration? = null) {
        repository.activateMode(zenMode, duration)
    fun activateMode(zenMode: ZenMode) {
        if (zenMode.isManualDnd) {
            val duration =
                when (zenDuration) {
                    ZEN_DURATION_PROMPT -> {
                        Log.e(
                            TAG,
                            "Interactor cannot handle showing the zen duration prompt. " +
                                "Please use EnableZenModeDialog when this setting is active."
                        )
                        null
                    }
                    ZEN_DURATION_FOREVER -> null
                    else -> Duration.ofMinutes(zenDuration.toLong())
                }

            zenModeRepository.activateMode(zenMode, duration)
        } else {
            zenModeRepository.activateMode(zenMode)
        }
    }

    fun deactivateMode(zenMode: ZenMode) {
        repository.deactivateMode(zenMode)
        zenModeRepository.deactivateMode(zenMode)
    }

    private val zenDuration
        get() = notificationSettingsRepository.zenDuration.value

    fun shouldAskForZenDuration(mode: ZenMode): Boolean =
        mode.isManualDnd && (zenDuration == ZEN_DURATION_PROMPT)

    companion object {
        private const val TAG = "ZenModeInteractor"
    }
}
+29 −2
Original line number Diff line number Diff line
@@ -16,14 +16,18 @@

package com.android.systemui.statusbar.policy.ui.dialog.viewmodel

import android.app.Dialog
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.EnableZenModeDialog
import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import javax.inject.Inject
@@ -46,6 +50,8 @@ constructor(
    @Background val bgDispatcher: CoroutineDispatcher,
    private val dialogDelegate: ModesDialogDelegate,
) {
    private val zenDialogMetricsLogger = ZenModeDialogMetricsLogger(context)

    // Modes that should be displayed in the dialog
    private val visibleModes: Flow<List<ZenMode>> =
        zenModeInteractor.modes
@@ -91,10 +97,15 @@ constructor(
                                zenModeInteractor.deactivateMode(mode)
                            } else {
                                if (mode.rule.isManualInvocationAllowed) {
                                    // TODO(b/346519570): Handle duration for DND mode.
                                    if (zenModeInteractor.shouldAskForZenDuration(mode)) {
                                        // NOTE: The dialog handles turning on the mode itself.
                                        val dialog = makeZenModeDialog()
                                        dialog.show()
                                    } else {
                                        zenModeInteractor.activateMode(mode)
                                    }
                                }
                            }
                        },
                        onLongClick = { openSettings(mode) }
                    )
@@ -122,4 +133,20 @@ constructor(
        val off = context.resources.getString(R.string.zen_mode_off)
        return mode.rule.triggerDescription ?: if (mode.isActive) on else off
    }

    private fun makeZenModeDialog(): Dialog {
        val dialog =
            EnableZenModeDialog(
                    context,
                    R.style.Theme_SystemUI_Dialog,
                    /* cancelIsNeutral= */ true,
                    zenDialogMetricsLogger
                )
                .createDialog()
        SystemUIDialog.applyFlags(dialog)
        SystemUIDialog.setShowForAllUsers(dialog, true)
        SystemUIDialog.registerDismissListener(dialog)
        SystemUIDialog.setDialogSize(dialog)
        return dialog
    }
}
Loading