diff options
| author | 2023-11-07 17:07:10 +0100 | |
|---|---|---|
| committer | 2023-11-14 15:59:04 +0100 | |
| commit | 810d2e3155e1bdfaed67b491b0c29ec2b58b66d8 (patch) | |
| tree | 10f07c1d449b125c159bade0dc100f3a956a8432 | |
| parent | 5cfe5d02a59a0c6ecd0b906a346b77c3dbf0a114 (diff) | |
Introduce ZenModeRepository & Interactor.
This code isn't used yet, but usages will be flagged.
Interactor tests are based on ZenModeControllerImplTest.
Bug: 293167744
Bug: 308591859
Test: atest ZenModeRepositoryImplTest ZenModeInteractorTest
Flag: NONE
Change-Id: Ie714e049d4d0cfe1968e757460a78def9df00f4d
7 files changed, 448 insertions, 2 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 818ba9512e15..3304b9827fd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository; import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl; import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepositoryModule; +import com.android.systemui.statusbar.policy.data.repository.ZenModeRepositoryModule; import dagger.Binds; import dagger.Module; @@ -81,7 +82,7 @@ import java.util.concurrent.Executor; import javax.inject.Named; /** Dagger Module for code in the statusbar.policy package. */ -@Module(includes = { DeviceProvisioningRepositoryModule.class }) +@Module(includes = { DeviceProvisioningRepositoryModule.class, ZenModeRepositoryModule.class }) public interface StatusBarPolicyModule { String DEVICE_STATE_ROTATION_LOCK_DEFAULTS = "DEVICE_STATE_ROTATION_LOCK_DEFAULTS"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt new file mode 100644 index 000000000000..94ab58ae5a3d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.data.repository + +import android.app.NotificationManager +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.statusbar.policy.ZenModeController +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** + * A repository that holds information about the status and configuration of Zen Mode (or Do Not + * Disturb/DND Mode). + */ +interface ZenModeRepository { + val zenMode: Flow<Int> + val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?> +} + +class ZenModeRepositoryImpl +@Inject +constructor( + private val zenModeController: ZenModeController, +) : ZenModeRepository { + // TODO(b/308591859): ZenModeController should use flows instead of callbacks. The + // conflatedCallbackFlows here should be replaced eventually, see: + // https://docs.google.com/document/d/1gAiuYupwUAFdbxkDXa29A4aFNu7XoCd7sCIk31WTnHU/edit?resourcekey=0-J4ZBiUhLhhQnNobAcI2vIw + + override val zenMode: Flow<Int> = conflatedCallbackFlow { + val callback = + object : ZenModeController.Callback { + override fun onZenChanged(zen: Int) { + trySend(zen) + } + } + zenModeController.addCallback(callback) + trySend(zenModeController.zen) + + awaitClose { zenModeController.removeCallback(callback) } + } + + override val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?> = + conflatedCallbackFlow { + val callback = + object : ZenModeController.Callback { + override fun onConsolidatedPolicyChanged(policy: NotificationManager.Policy?) { + trySend(policy) + } + } + zenModeController.addCallback(callback) + trySend(zenModeController.consolidatedPolicy) + + awaitClose { zenModeController.removeCallback(callback) } + } +} + +@Module +interface ZenModeRepositoryModule { + @Binds fun bindImpl(impl: ZenModeRepositoryImpl): ZenModeRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt new file mode 100644 index 000000000000..ae31851cb8f5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.domain.interactor + +import android.provider.Settings +import com.android.systemui.statusbar.policy.data.repository.ZenModeRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +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(repository: ZenModeRepository) { + val isZenModeEnabled: Flow<Boolean> = + repository.zenMode + .map { + when (it) { + Settings.Global.ZEN_MODE_ALARMS -> true + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> true + Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> true + Settings.Global.ZEN_MODE_OFF -> false + else -> false + } + } + .distinctUntilChanged() + + val areNotificationsHiddenInShade: Flow<Boolean> = + combine(isZenModeEnabled, repository.consolidatedNotificationPolicy) { dndEnabled, policy -> + if (!dndEnabled) { + false + } else { + val showInNotificationList = policy?.showInNotificationList() ?: true + !showInNotificationList + } + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt new file mode 100644 index 000000000000..004f67934e86 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.policy.data.repository + +import android.app.NotificationManager +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.policy.ZenModeController +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor +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 +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ZenModeRepositoryImplTest : SysuiTestCase() { + @Mock lateinit var zenModeController: ZenModeController + + lateinit var underTest: ZenModeRepositoryImpl + + private val testPolicy = NotificationManager.Policy(0, 1, 0) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + underTest = ZenModeRepositoryImpl(zenModeController) + } + + @Test + fun zenMode_reflectsCurrentControllerState() = runTest { + whenever(zenModeController.zen).thenReturn(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) + val zenMode by collectLastValue(underTest.zenMode) + assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) + } + + @Test + fun zenMode_updatesWhenControllerStateChanges() = runTest { + val zenMode by collectLastValue(underTest.zenMode) + runCurrent() + whenever(zenModeController.zen).thenReturn(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + withArgCaptor { Mockito.verify(zenModeController).addCallback(capture()) } + .onZenChanged(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + } + + @Test + fun policy_reflectsCurrentControllerState() { + runTest { + whenever(zenModeController.consolidatedPolicy).thenReturn(testPolicy) + val policy by collectLastValue(underTest.consolidatedNotificationPolicy) + assertThat(policy).isEqualTo(testPolicy) + } + } + + @Test + fun policy_updatesWhenControllerStateChanges() = runTest { + val policy by collectLastValue(underTest.consolidatedNotificationPolicy) + runCurrent() + whenever(zenModeController.consolidatedPolicy).thenReturn(testPolicy) + withArgCaptor { Mockito.verify(zenModeController).addCallback(capture()) } + .onConsolidatedPolicyChanged(testPolicy) + assertThat(policy).isEqualTo(testPolicy) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt new file mode 100644 index 000000000000..78e79718e166 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.domain.interactor + +import android.app.NotificationManager.Policy +import android.provider.Settings +import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.collectLastValue +import com.android.runCurrent +import com.android.runTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import org.junit.Test + +@SmallTest +class ZenModeInteractorTest : SysuiTestCase() { + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<ZenModeInteractor> { + + val repository: FakeZenModeRepository + + @Component.Factory + interface Factory { + fun create(@BindsInstance test: SysuiTestCase): TestComponent + } + } + + private val testComponent: TestComponent = + DaggerZenModeInteractorTest_TestComponent.factory().create(test = this) + + @Test + fun testIsZenModeEnabled_off() = + testComponent.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.zenMode.value = Settings.Global.ZEN_MODE_OFF + runCurrent() + + assertThat(enabled).isFalse() + } + + @Test + fun testIsZenModeEnabled_alarms() = + testComponent.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.zenMode.value = Settings.Global.ZEN_MODE_ALARMS + runCurrent() + + assertThat(enabled).isTrue() + } + + @Test + fun testIsZenModeEnabled_importantInterruptions() = + testComponent.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + runCurrent() + + assertThat(enabled).isTrue() + } + + @Test + fun testIsZenModeEnabled_noInterruptions() = + testComponent.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.zenMode.value = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS + runCurrent() + + assertThat(enabled).isTrue() + } + + @Test + fun testIsZenModeEnabled_unknown() = + testComponent.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.zenMode.value = 4 // this should fail if we ever add another zen mode type + runCurrent() + + assertThat(enabled).isFalse() + } + + @Test + fun testAreNotificationsHiddenInShade_noPolicy() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.consolidatedNotificationPolicy.value = null + repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun testAreNotificationsHiddenInShade_zenOffShadeSuppressed() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.consolidatedNotificationPolicy.value = + policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + repository.zenMode.value = Settings.Global.ZEN_MODE_OFF + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun testAreNotificationsHiddenInShade_zenOnShadeNotSuppressed() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.consolidatedNotificationPolicy.value = + policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR) + repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun testAreNotificationsHiddenInShade_zenOnShadeSuppressed() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.consolidatedNotificationPolicy.value = + policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + runCurrent() + + assertThat(hidden).isTrue() + } +} + +fun policyWithSuppressedVisualEffects(suppressedVisualEffects: Int) = + Policy( + /* priorityCategories = */ 0, + /* priorityCallSenders = */ 0, + /* priorityMessageSenders = */ 0, + /* suppressedVisualEffects = */ suppressedVisualEffects + ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt index 5aece1bbbd31..16dab40d6edc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt @@ -16,7 +16,14 @@ package com.android.systemui.statusbar.policy.data import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepositoryModule +import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepositoryModule import dagger.Module -@Module(includes = [FakeDeviceProvisioningRepositoryModule::class]) +@Module( + includes = + [ + FakeDeviceProvisioningRepositoryModule::class, + FakeZenModeRepositoryModule::class, + ] +) object FakeStatusBarPolicyDataLayerModule diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt new file mode 100644 index 000000000000..405993073b68 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.data.repository + +import android.app.NotificationManager +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +@SysUISingleton +class FakeZenModeRepository @Inject constructor() : ZenModeRepository { + override val zenMode: MutableStateFlow<Int> = MutableStateFlow(Settings.Global.ZEN_MODE_OFF) + override val consolidatedNotificationPolicy: MutableStateFlow<NotificationManager.Policy?> = + MutableStateFlow( + NotificationManager.Policy( + /* priorityCategories = */ 0, + /* priorityCallSenders = */ 0, + /* priorityMessageSenders = */ 0, + ) + ) +} + +@Module +interface FakeZenModeRepositoryModule { + @Binds fun bindFake(fake: FakeZenModeRepository): ZenModeRepository +} |