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

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

Merge NotificationsSoundPolicyRepository and ZenModeRepository

These were essentially doing the same thing in slightly different ways.
NotificationsSoundPolicyRepository is the one we're keeping under a new
name: it uses BroadcastReceiver to listen for policy changes.
Meanwhile, the other ZenModeRepository was using ZenModeController, which
in turn uses ContentObserver to listen for changes. In the long run, we
want to replace the callback-based ZenModeController with flows, so this
is one step towards that goal.

Note that the notificationPolicy flow in the old
NotificationSoundPolicyRepository was actually returning the
consolidatedNotificationPolicy. To make it more explicit, I included
both in the new repository, but changed the existing users of
notificationPolicy to use consolidatedNotificationPolicy to avoid a
behavior change.

Also replaced the ZenMode class with a simple int to avoid confusion
with the ZenMode class we're introducing from settings in a follow-up
CL. The only thing it was doing was validation, but that's not strictly
necessary since every consumer of this value (aka the interactors) has
to decide what to do with an "unknown" value anyway.

Bug: 308591859
Bug: 346519570
Test: ZenModeRepositoryTest
Flag: android.app.modes_api
Flag: com.android.systemui.new_volume_panel
Flag: com.android.systemui.notifications_footer_view_refactor
Change-Id: Ie02480dec05055ad3d7c24bd16c76f91bc64c58c
parent 183e7f3c
Loading
Loading
Loading
Loading
+0 −39
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.settingslib.statusbar.notification.data.model

import android.provider.Settings.Global

/** Validating wrapper for [android.app.NotificationManager.getZenMode] values. */
@JvmInline
value class ZenMode(val zenMode: Int) {

    init {
        require(zenMode in supportedModes) { "Unsupported zenMode=$zenMode" }
    }

    private companion object {

        val supportedModes =
            listOf(
                Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                Global.ZEN_MODE_NO_INTERRUPTIONS,
                Global.ZEN_MODE_ALARMS,
                Global.ZEN_MODE_OFF,
            )
    }
}
+6 −7
Original line number Diff line number Diff line
@@ -18,19 +18,18 @@ package com.android.settingslib.statusbar.notification.data.repository

import android.app.NotificationManager
import android.provider.Settings
import com.android.settingslib.statusbar.notification.data.model.ZenMode
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

class FakeNotificationsSoundPolicyRepository : NotificationsSoundPolicyRepository {
class FakeZenModeRepository : ZenModeRepository {

    private val mutableNotificationPolicy = MutableStateFlow<NotificationManager.Policy?>(null)
    override val notificationPolicy: StateFlow<NotificationManager.Policy?>
    override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?>
        get() = mutableNotificationPolicy.asStateFlow()

    private val mutableZenMode = MutableStateFlow<ZenMode?>(ZenMode(Settings.Global.ZEN_MODE_OFF))
    override val zenMode: StateFlow<ZenMode?>
    private val mutableZenMode = MutableStateFlow(Settings.Global.ZEN_MODE_OFF)
    override val globalZenMode: StateFlow<Int>
        get() = mutableZenMode.asStateFlow()

    init {
@@ -41,12 +40,12 @@ class FakeNotificationsSoundPolicyRepository : NotificationsSoundPolicyRepositor
        mutableNotificationPolicy.value = policy
    }

    fun updateZenMode(zenMode: ZenMode?) {
    fun updateZenMode(zenMode: Int) {
        mutableZenMode.value = zenMode
    }
}

fun FakeNotificationsSoundPolicyRepository.updateNotificationPolicy(
fun FakeZenModeRepository.updateNotificationPolicy(
    priorityCategories: Int = 0,
    priorityCallSenders: Int = NotificationManager.Policy.PRIORITY_SENDERS_ANY,
    priorityMessageSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
+24 −23
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.android.settingslib.statusbar.notification.data.model.ZenMode
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
@@ -37,21 +36,20 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/** Provides state of volume policy and restrictions imposed by notifications. */
interface NotificationsSoundPolicyRepository {

    /** @see NotificationManager.getNotificationPolicy */
    val notificationPolicy: StateFlow<NotificationManager.Policy?>
interface ZenModeRepository {
    /** @see NotificationManager.getConsolidatedNotificationPolicy */
    val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?>

    /** @see NotificationManager.getZenMode */
    val zenMode: StateFlow<ZenMode?>
    val globalZenMode: StateFlow<Int?>
}

class NotificationsSoundPolicyRepositoryImpl(
class ZenModeRepositoryImpl(
    private val context: Context,
    private val notificationManager: NotificationManager,
    scope: CoroutineScope,
    backgroundCoroutineContext: CoroutineContext,
) : NotificationsSoundPolicyRepository {
    val scope: CoroutineScope,
    val backgroundCoroutineContext: CoroutineContext,
) : ZenModeRepository {

    private val notificationBroadcasts =
        callbackFlow {
@@ -67,8 +65,7 @@ class NotificationsSoundPolicyRepositoryImpl(
                    IntentFilter().apply {
                        addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)
                        addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
                    }
                )
                    })

                awaitClose { context.unregisterReceiver(receiver) }
            }
@@ -77,19 +74,23 @@ class NotificationsSoundPolicyRepositoryImpl(
                scope = scope,
            )

    override val notificationPolicy: StateFlow<NotificationManager.Policy?> =
        notificationBroadcasts
            .filter { NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED == it }
            .map { notificationManager.consolidatedNotificationPolicy }
            .onStart { emit(notificationManager.consolidatedNotificationPolicy) }
            .flowOn(backgroundCoroutineContext)
            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
    override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> =
        // TODO(b/347707024): This should use ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED
        // instead.
        flowFromBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) {
            notificationManager.consolidatedNotificationPolicy
        }

    override val globalZenMode: StateFlow<Int?> =
        flowFromBroadcast(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED) {
            notificationManager.zenMode
        }

    override val zenMode: StateFlow<ZenMode?> =
    private fun <T> flowFromBroadcast(intentAction: String, mapper: () -> T) =
        notificationBroadcasts
            .filter { NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED == it }
            .map { ZenMode(notificationManager.zenMode) }
            .onStart { emit(ZenMode(notificationManager.zenMode)) }
            .filter { intentAction == it }
            .map { mapper() }
            .onStart { emit(mapper()) }
            .flowOn(backgroundCoroutineContext)
            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
}
+7 −10
Original line number Diff line number Diff line
@@ -20,8 +20,7 @@ import android.app.NotificationManager
import android.media.AudioManager
import android.provider.Settings
import android.service.notification.ZenModeConfig
import com.android.settingslib.statusbar.notification.data.model.ZenMode
import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository
import com.android.settingslib.statusbar.notification.data.repository.ZenModeRepository
import com.android.settingslib.volume.shared.model.AudioStream
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -30,17 +29,15 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map

/** Determines notification sounds state and limitations. */
class NotificationsSoundPolicyInteractor(
    private val repository: NotificationsSoundPolicyRepository
) {
class NotificationsSoundPolicyInteractor(private val repository: ZenModeRepository) {

    /** @see NotificationManager.getNotificationPolicy */
    val notificationPolicy: StateFlow<NotificationManager.Policy?>
        get() = repository.notificationPolicy
    private val notificationPolicy: StateFlow<NotificationManager.Policy?>
        get() = repository.consolidatedNotificationPolicy

    /** @see NotificationManager.getZenMode */
    val zenMode: StateFlow<ZenMode?>
        get() = repository.zenMode
    val zenMode: StateFlow<Int?>
        get() = repository.globalZenMode

    /** Checks if [notificationPolicy] allows alarms. */
    val areAlarmsAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowAlarms() }
@@ -67,7 +64,7 @@ class NotificationsSoundPolicyInteractor(
            isRingerAllowed.filterNotNull(),
            isSystemAllowed.filterNotNull(),
        ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed, isSystemAllowed ->
            when (zenMode.zenMode) {
            when (zenMode) {
                // Everything is muted
                Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> return@combine true
                Settings.Global.ZEN_MODE_ALARMS ->
+24 −19
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import android.content.Intent
import android.provider.Settings.Global
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.statusbar.notification.data.model.ZenMode
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -45,13 +44,18 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
class NotificationsSoundPolicyRepositoryTest {
class ZenModeRepositoryTest {

    @Mock private lateinit var context: Context
    @Mock private lateinit var notificationManager: NotificationManager
    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
    @Mock
    private lateinit var context: Context

    private lateinit var underTest: NotificationsSoundPolicyRepository
    @Mock
    private lateinit var notificationManager: NotificationManager

    @Captor
    private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>

    private lateinit var underTest: ZenModeRepository

    private val testScope: TestScope = TestScope()

@@ -60,7 +64,7 @@ class NotificationsSoundPolicyRepositoryTest {
        MockitoAnnotations.initMocks(this)

        underTest =
            NotificationsSoundPolicyRepositoryImpl(
            ZenModeRepositoryImpl(
                context,
                notificationManager,
                testScope.backgroundScope,
@@ -69,14 +73,15 @@ class NotificationsSoundPolicyRepositoryTest {
    }

    @Test
    fun policyChanges_repositoryEmits() {
    fun consolidatedPolicyChanges_repositoryEmits() {
        testScope.runTest {
            val values = mutableListOf<NotificationManager.Policy?>()
            `when`(notificationManager.notificationPolicy).thenReturn(testPolicy1)
            underTest.notificationPolicy.onEach { values.add(it) }.launchIn(backgroundScope)
            `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy1)
            underTest.consolidatedNotificationPolicy.onEach { values.add(it) }
                    .launchIn(backgroundScope)
            runCurrent()

            `when`(notificationManager.notificationPolicy).thenReturn(testPolicy2)
            `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy2)
            triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
            runCurrent()

@@ -89,9 +94,9 @@ class NotificationsSoundPolicyRepositoryTest {
    @Test
    fun zenModeChanges_repositoryEmits() {
        testScope.runTest {
            val values = mutableListOf<ZenMode?>()
            val values = mutableListOf<Int?>()
            `when`(notificationManager.zenMode).thenReturn(Global.ZEN_MODE_OFF)
            underTest.zenMode.onEach { values.add(it) }.launchIn(backgroundScope)
            underTest.globalZenMode.onEach { values.add(it) }.launchIn(backgroundScope)
            runCurrent()

            `when`(notificationManager.zenMode).thenReturn(Global.ZEN_MODE_ALARMS)
@@ -100,7 +105,7 @@ class NotificationsSoundPolicyRepositoryTest {

            assertThat(values)
                    .containsExactlyElementsIn(
                    listOf(null, ZenMode(Global.ZEN_MODE_OFF), ZenMode(Global.ZEN_MODE_ALARMS))
                        listOf(null, Global.ZEN_MODE_OFF, Global.ZEN_MODE_ALARMS)
                    )
                    .inOrder()
        }
Loading