diff options
6 files changed, 344 insertions, 0 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/model/ZenMode.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/model/ZenMode.kt new file mode 100644 index 000000000000..a696f8c9c9c9 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/model/ZenMode.kt @@ -0,0 +1,39 @@ +/* + * 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, + ) + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt new file mode 100644 index 000000000000..60983070b1cf --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt @@ -0,0 +1,42 @@ +/* + * 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.repository + +import android.app.NotificationManager +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 { + + private val mutableNotificationPolicy = MutableStateFlow<NotificationManager.Policy?>(null) + override val notificationPolicy: StateFlow<NotificationManager.Policy?> + get() = mutableNotificationPolicy.asStateFlow() + + private val mutableZenMode = MutableStateFlow<ZenMode?>(null) + override val zenMode: StateFlow<ZenMode?> + get() = mutableZenMode.asStateFlow() + + fun updateNotificationPolicy(policy: NotificationManager.Policy?) { + mutableNotificationPolicy.value = policy + } + + fun updateZenMode(zenMode: ZenMode?) { + mutableZenMode.value = zenMode + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepository.kt new file mode 100644 index 000000000000..0fb8c3f8bc6d --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepository.kt @@ -0,0 +1,95 @@ +/* + * 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.repository + +import android.app.NotificationManager +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 +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn +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?> + + /** @see NotificationManager.getZenMode */ + val zenMode: StateFlow<ZenMode?> +} + +class NotificationsSoundPolicyRepositoryImpl( + private val context: Context, + private val notificationManager: NotificationManager, + scope: CoroutineScope, + backgroundCoroutineContext: CoroutineContext, +) : NotificationsSoundPolicyRepository { + + private val notificationBroadcasts = + callbackFlow { + val receiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + intent?.action?.let { action -> launch { send(action) } } + } + } + + context.registerReceiver( + receiver, + IntentFilter().apply { + addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED) + addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) + } + ) + + awaitClose { context.unregisterReceiver(receiver) } + } + .shareIn( + started = SharingStarted.WhileSubscribed(), + 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 zenMode: StateFlow<ZenMode?> = + notificationBroadcasts + .filter { NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED == it } + .map { ZenMode(notificationManager.zenMode) } + .onStart { emit(ZenMode(notificationManager.zenMode)) } + .flowOn(backgroundCoroutineContext) + .stateIn(scope, SharingStarted.WhileSubscribed(), null) +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepositoryTest.kt new file mode 100644 index 000000000000..dfc4c0a847c0 --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/statusbar/notification/data/repository/NotificationsSoundPolicyRepositoryTest.kt @@ -0,0 +1,128 @@ +/* + * 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.repository + +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +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 +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.TestScope +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.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +@SmallTest +class NotificationsSoundPolicyRepositoryTest { + + @Mock private lateinit var context: Context + @Mock private lateinit var notificationManager: NotificationManager + @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver> + + private lateinit var underTest: NotificationsSoundPolicyRepository + + private val testScope: TestScope = TestScope() + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + underTest = + NotificationsSoundPolicyRepositoryImpl( + context, + notificationManager, + testScope.backgroundScope, + testScope.testScheduler, + ) + } + + @Test + fun policyChanges_repositoryEmits() { + testScope.runTest { + val values = mutableListOf<NotificationManager.Policy?>() + `when`(notificationManager.notificationPolicy).thenReturn(testPolicy1) + underTest.notificationPolicy.onEach { values.add(it) }.launchIn(backgroundScope) + runCurrent() + + `when`(notificationManager.notificationPolicy).thenReturn(testPolicy2) + triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) + runCurrent() + + assertThat(values) + .containsExactlyElementsIn(listOf(null, testPolicy1, testPolicy2)) + .inOrder() + } + } + + @Test + fun zenModeChanges_repositoryEmits() { + testScope.runTest { + val values = mutableListOf<ZenMode?>() + `when`(notificationManager.zenMode).thenReturn(Global.ZEN_MODE_OFF) + underTest.zenMode.onEach { values.add(it) }.launchIn(backgroundScope) + runCurrent() + + `when`(notificationManager.zenMode).thenReturn(Global.ZEN_MODE_ALARMS) + triggerIntent(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED) + runCurrent() + + assertThat(values) + .containsExactlyElementsIn( + listOf(null, ZenMode(Global.ZEN_MODE_OFF), ZenMode(Global.ZEN_MODE_ALARMS)) + ) + .inOrder() + } + } + + private fun triggerIntent(action: String) { + verify(context).registerReceiver(receiverCaptor.capture(), any()) + receiverCaptor.value.onReceive(context, Intent(action)) + } + + private companion object { + val testPolicy1 = + NotificationManager.Policy( + /* priorityCategories = */ 1, + /* priorityCallSenders =*/ 1, + /* priorityMessageSenders = */ 1, + ) + val testPolicy2 = + NotificationManager.Policy( + /* priorityCategories = */ 2, + /* priorityCallSenders =*/ 2, + /* priorityMessageSenders = */ 2, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt index 1af5c46cd14b..67d6a7f5d735 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt @@ -16,11 +16,14 @@ package com.android.systemui.volume.dagger +import android.app.NotificationManager import android.content.Context import android.media.AudioManager import com.android.settingslib.media.data.repository.SpatializerRepository import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl import com.android.settingslib.media.domain.interactor.SpatializerInteractor +import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepository +import com.android.settingslib.statusbar.notification.data.repository.NotificationsSoundPolicyRepositoryImpl import com.android.settingslib.volume.data.repository.AudioRepository import com.android.settingslib.volume.data.repository.AudioRepositoryImpl import com.android.settingslib.volume.domain.interactor.AudioModeInteractor @@ -68,5 +71,19 @@ interface AudioModule { @Provides fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor = SpatializerInteractor(repository) + + @Provides + fun provideNotificationsSoundPolicyRepository( + context: Context, + notificationManager: NotificationManager, + @Background coroutineContext: CoroutineContext, + @Application coroutineScope: CoroutineScope, + ): NotificationsSoundPolicyRepository = + NotificationsSoundPolicyRepositoryImpl( + context, + notificationManager, + coroutineScope, + coroutineContext, + ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/NotificationsDataKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/NotificationsDataKosmos.kt new file mode 100644 index 000000000000..a61f7ececc69 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/NotificationsDataKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.systemui.statusbar.notification.data + +import com.android.settingslib.statusbar.notification.data.repository.FakeNotificationsSoundPolicyRepository +import com.android.systemui.kosmos.Kosmos + +val Kosmos.notificationsSoundPolicyRepository by + Kosmos.Fixture { FakeNotificationsSoundPolicyRepository() } |