Loading packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt +17 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ class FakeZenModeRepository : ZenModeRepository { override val modes: Flow<List<ZenMode>> get() = mutableModesFlow.asStateFlow() private val activeModesDurations = mutableMapOf<String, Duration?>() init { updateNotificationPolicy() } Loading @@ -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) { Loading @@ -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 Loading packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt +17 −6 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -51,9 +52,7 @@ class NotificationSettingsRepository( ) .map { it == 1 } .flowOn(backgroundDispatcher) .stateIn( scope = scope, ) .stateIn(scope = backgroundScope) suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) { withContext(backgroundDispatcher) { Loading @@ -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, ) } packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt +80 −14 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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)) } } packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt +46 −7 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 { Loading @@ -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" } } packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt +29 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) } ) Loading Loading @@ -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
packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt +17 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ class FakeZenModeRepository : ZenModeRepository { override val modes: Flow<List<ZenMode>> get() = mutableModesFlow.asStateFlow() private val activeModesDurations = mutableMapOf<String, Duration?>() init { updateNotificationPolicy() } Loading @@ -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) { Loading @@ -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 Loading
packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt +17 −6 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -51,9 +52,7 @@ class NotificationSettingsRepository( ) .map { it == 1 } .flowOn(backgroundDispatcher) .stateIn( scope = scope, ) .stateIn(scope = backgroundScope) suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) { withContext(backgroundDispatcher) { Loading @@ -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, ) }
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt +80 −14 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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() Loading @@ -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)) } }
packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt +46 −7 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 { Loading @@ -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" } }
packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt +29 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) } ) Loading Loading @@ -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 } }