diff options
| author | 2025-01-08 17:10:01 +0000 | |
|---|---|---|
| committer | 2025-01-09 15:50:19 +0000 | |
| commit | 0986cd433357c8a0511d1b3a49922ead1eeef86c (patch) | |
| tree | 679278257f78e3c1e74e5104c6d1e971ea56a9ee | |
| parent | d1f16038471078f636d76f6d09ee5fa8d2babb70 (diff) | |
Check if the stream can be disabled by the zen mode
This change reworks onStart `{ volumeDialogController.getState() }`
invocation because in this case the new state is emitted before the
`VolumeDialogCallbacksInteractor` is ready to process the upcoming values.
Flag: com.android.systemui.volume_redesign
Fixes: 388510417
Fixes: 388508798
Fixes: 388455787
Fixes: 388454510
Test: atest VolumeDialogSliderInteractorTest
Test: atest initialEvent_isSubscribedToEvents
Change-Id: Id92a54a75f0f95fa01ea9df32bf1e16c0df17393
8 files changed, 164 insertions, 48 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt new file mode 100644 index 000000000000..fec186e862be --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2025 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.volume.dialog.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class VolumeDialogCallbacksInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos().apply { useUnconfinedTestDispatcher() } + + private val underTest: VolumeDialogCallbacksInteractor by lazy { + kosmos.volumeDialogCallbacksInteractor + } + + @Test + fun initialEvent_isSubscribedToEvents() = + kosmos.runTest { + val event by collectLastValue(underTest.event) + assertThat(event).isInstanceOf(VolumeDialogEventModel.SubscribedToEvents::class.java) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt index bfafdab003aa..1001c245cc95 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt @@ -16,75 +16,115 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor +import android.media.AudioManager +import android.service.notification.ZenPolicy import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +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.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.plugins.VolumeDialogController import com.android.systemui.plugins.fakeVolumeDialogController +import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.testKosmos +import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType 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 -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class VolumeDialogSliderInteractorTest : SysuiTestCase() { - private val kosmos = testKosmos() - - private lateinit var underTest: VolumeDialogSliderInteractor + private val kosmos = + testKosmos().apply { + useUnconfinedTestDispatcher() + zenModeRepository.addMode( + TestModeBuilder() + .setName("Blocks media, Active") + .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build()) + .setActive(true) + .build() + ) + } - @Before - fun setUp() { - underTest = kosmos.volumeDialogSliderInteractor + private val underTest: VolumeDialogSliderInteractor by lazy { + kosmos.volumeDialogSliderInteractor } @Test fun settingStreamVolume_setsActiveStream() = - with(kosmos) { - testScope.runTest { - runCurrent() - // initialize the stream model - fakeVolumeDialogController.setStreamVolume(volumeDialogSliderType.audioStream, 0) + kosmos.runTest { + // initialize the stream model + fakeVolumeDialogController.setStreamVolume(volumeDialogSliderType.audioStream, 0) - val sliderModel by collectLastValue(underTest.slider) - underTest.setStreamVolume(1) - runCurrent() + val sliderModel by collectLastValue(underTest.slider) + underTest.setStreamVolume(1) - assertThat(sliderModel!!.isActive).isTrue() - } + assertThat(sliderModel!!.isActive).isTrue() } @Test fun streamVolumeIs_minMaxAreEnforced() = - with(kosmos) { - testScope.runTest { - runCurrent() - fakeVolumeDialogController.updateState { - states.put( - volumeDialogSliderType.audioStream, - VolumeDialogController.StreamState().apply { - levelMin = 0 - level = 2 - levelMax = 1 - }, - ) - } - - val sliderModel by collectLastValue(underTest.slider) - runCurrent() - - assertThat(sliderModel!!.level).isEqualTo(1) + kosmos.runTest { + fakeVolumeDialogController.updateState { + states.put( + volumeDialogSliderType.audioStream, + VolumeDialogController.StreamState().apply { + levelMin = 0 + level = 2 + levelMax = 1 + }, + ) } + + val sliderModel by collectLastValue(underTest.slider) + + assertThat(sliderModel!!.level).isEqualTo(1) + } + + @Test + fun streamCantBeBlockedByZenMode_isDisabledByZenMode_false() = + kosmos.runTest { + volumeDialogSliderType = VolumeDialogSliderType.Stream(AudioManager.STREAM_VOICE_CALL) + + val isDisabledByZenMode by collectLastValue(underTest.isDisabledByZenMode) + + assertThat(isDisabledByZenMode).isFalse() + } + + @Test + fun remoteMediaStream_zenModeRestrictive_IsNotDisabledByZenMode() = + kosmos.runTest { + volumeDialogSliderType = VolumeDialogSliderType.RemoteMediaStream(0) + + val isDisabledByZenMode by collectLastValue(underTest.isDisabledByZenMode) + + assertThat(isDisabledByZenMode).isFalse() + } + + @Test + fun audioSharingStream_zenModeRestrictive_IsNotDisabledByZenMode() = + kosmos.runTest { + volumeDialogSliderType = VolumeDialogSliderType.AudioSharingStream(0) + + val isDisabledByZenMode by collectLastValue(underTest.isDisabledByZenMode) + + assertThat(isDisabledByZenMode).isFalse() + } + + @Test + fun streamBlockedByZenMode_isDisabledByZenMode_true() = + kosmos.runTest { + volumeDialogSliderType = VolumeDialogSliderType.Stream(AudioManager.STREAM_MUSIC) + + val isDisabledByZenMode by collectLastValue(underTest.isDisabledByZenMode) + + assertThat(isDisabledByZenMode).isTrue() } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt index 3b0c8a6b46f8..b52c1075e9f0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt @@ -25,6 +25,7 @@ import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -54,9 +55,10 @@ constructor( callbackFlow { val producer = VolumeDialogEventModelProducer(this) volumeDialogController.addCallback(producer, bgHandler) + send(VolumeDialogEventModel.SubscribedToEvents) awaitClose { volumeDialogController.removeCallback(producer) } } - .buffer(BUFFER_CAPACITY) + .buffer(capacity = BUFFER_CAPACITY, onBufferOverflow = BufferOverflow.DROP_OLDEST) .shareIn(replay = 0, scope = coroutineScope, started = SharingStarted.WhileSubscribed()) private class VolumeDialogEventModelProducer( diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt index 51e79242daaf..26d2414acec1 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart /** * Exposes [VolumeDialogController.getState] in the [volumeDialogState]. @@ -65,12 +64,14 @@ constructor( is VolumeDialogEventModel.ShowSafetyWarning -> { setSafetyWarning(VolumeDialogSafetyWarningModel.Visible(event.flags)) } + is VolumeDialogEventModel.SubscribedToEvents -> { + volumeDialogController.getState() + } else -> { // do nothing } } } - .onStart { volumeDialogController.getState() } .launchIn(coroutineScope) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt index 80e423838251..9793d2be6b98 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt @@ -52,4 +52,11 @@ sealed interface VolumeDialogEventModel { VolumeDialogEventModel data object VolumeChangedFromKey : VolumeDialogEventModel + + /** + * Signals that the + * [com.android.systemui.volume.dialog.domain.interactor.VolumeDialogCallbacksInteractor] is + * ready to process the events. + */ + data object SubscribedToEvents : VolumeDialogEventModel } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt index 3988acbea7c2..b86252ddd1b7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt @@ -47,7 +47,7 @@ constructor( ) { val isDisabledByZenMode: Flow<Boolean> = - if (sliderType is VolumeDialogSliderType.Stream) { + if (zenModeInteractor.canBeBlockedByZenMode(sliderType)) { zenModeInteractor.activeModesBlockingStream(AudioStream(sliderType.audioStream)).map { it.mainMode != null } @@ -75,3 +75,8 @@ constructor( } } } + +private fun ZenModeInteractor.canBeBlockedByZenMode(sliderType: VolumeDialogSliderType): Boolean { + return sliderType is VolumeDialogSliderType.Stream && + canBeBlockedByZenMode(AudioStream(sliderType.audioStream)) +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt index daf4c8275d20..71fe22ba4b01 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt @@ -26,6 +26,7 @@ import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.res.R import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor +import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -49,10 +50,10 @@ constructor( isRoutedToBluetooth: Boolean, ): Flow<Drawable> { return combine( - zenModeInteractor.activeModesBlockingStream(AudioStream(stream)), + zenModeInteractor.activeModesBlockingStream(stream), ringerModeForStream(stream), ) { activeModesBlockingStream, ringerMode -> - if (activeModesBlockingStream.mainMode?.icon != null) { + if (activeModesBlockingStream?.mainMode?.icon != null) { return@combine activeModesBlockingStream.mainMode.icon.drawable } else { context.getDrawable( @@ -141,3 +142,16 @@ constructor( } } } + +private fun ZenModeInteractor.activeModesBlockingStream(stream: Int): Flow<ActiveZenModes?> { + return if (AudioStream.supportedStreamTypes.contains(stream)) { + val audioStream = AudioStream(stream) + if (canBeBlockedByZenMode(audioStream)) { + activeModesBlockingStream(audioStream) + } else { + flowOf(null) + } + } else { + flowOf(null) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt index 43eb93e4dd53..9d73ae3f176f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt @@ -197,7 +197,7 @@ class FakeVolumeDialogController(private val audioManager: AudioManager) : Volum } } -private inline fun CopyOnWriteArraySet<VolumeDialogController.Callbacks>.sendEvent( +private inline fun Collection<VolumeDialogController.Callbacks>.sendEvent( event: (callback: VolumeDialogController.Callbacks) -> Unit ) { for (callback in this) { |