diff options
13 files changed, 283 insertions, 72 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt index f729c04fb849..6851997ac323 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt @@ -21,10 +21,12 @@ import android.media.AudioManager import android.media.AudioManager.OnCommunicationDeviceChangedListener import androidx.concurrent.futures.DirectExecutor import com.android.internal.util.ConcurrentUtils -import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver +import com.android.settingslib.volume.shared.AudioManagerEventsReceiver +import com.android.settingslib.volume.shared.model.AudioManagerEvent import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.settingslib.volume.shared.model.RingerMode +import com.android.settingslib.volume.shared.model.StreamAudioManagerEvent import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose @@ -33,6 +35,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -72,7 +75,7 @@ interface AudioRepository { } class AudioRepositoryImpl( - private val audioManagerIntentsReceiver: AudioManagerIntentsReceiver, + private val audioManagerEventsReceiver: AudioManagerEventsReceiver, private val audioManager: AudioManager, private val backgroundCoroutineContext: CoroutineContext, private val coroutineScope: CoroutineScope, @@ -89,8 +92,8 @@ class AudioRepositoryImpl( .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode) override val ringerMode: StateFlow<RingerMode> = - audioManagerIntentsReceiver.intents - .filter { AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION == it.action } + audioManagerEventsReceiver.events + .filterIsInstance(AudioManagerEvent.InternalRingerModeChanged::class) .map { RingerMode(audioManager.ringerModeInternal) } .flowOn(backgroundCoroutineContext) .stateIn( @@ -120,7 +123,14 @@ class AudioRepositoryImpl( ) override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> { - return audioManagerIntentsReceiver.intents + return audioManagerEventsReceiver.events + .filter { + if (it is StreamAudioManagerEvent) { + it.audioStream == audioStream + } else { + true + } + } .map { getCurrentAudioStream(audioStream) } .flowOn(backgroundCoroutineContext) } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt index aa9ae76c66c4..298dd71e555e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt @@ -15,13 +15,13 @@ */ package com.android.settingslib.volume.data.repository -import android.media.AudioManager import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.model.RoutingSession -import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver +import com.android.settingslib.volume.shared.AudioManagerEventsReceiver +import com.android.settingslib.volume.shared.model.AudioManagerEvent import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose @@ -29,7 +29,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -54,7 +54,7 @@ interface LocalMediaRepository { } class LocalMediaRepositoryImpl( - audioManagerIntentsReceiver: AudioManagerIntentsReceiver, + audioManagerEventsReceiver: AudioManagerEventsReceiver, private val localMediaManager: LocalMediaManager, private val mediaRouter2Manager: MediaRouter2Manager, coroutineScope: CoroutineScope, @@ -62,9 +62,9 @@ class LocalMediaRepositoryImpl( ) : LocalMediaRepository { private val devicesChanges = - audioManagerIntentsReceiver.intents.filter { - AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action - } + audioManagerEventsReceiver.events.filterIsInstance( + AudioManagerEvent.StreamDevicesChanged::class + ) private val mediaDevicesUpdates: Flow<DevicesUpdate> = callbackFlow { val callback = @@ -109,6 +109,7 @@ class LocalMediaRepositoryImpl( override val currentConnectedDevice: StateFlow<MediaDevice?> = merge(devicesChanges, mediaDevicesUpdates) .map { localMediaManager.currentConnectedDevice } + .onStart { emit(localMediaManager.currentConnectedDevice) } .stateIn( coroutineScope, SharingStarted.WhileSubscribed(), diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt index 6925c71fc68f..7c231d1fad4e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt @@ -16,21 +16,19 @@ package com.android.settingslib.volume.data.repository -import android.content.Intent -import android.media.AudioManager import android.media.session.MediaController import android.media.session.MediaSessionManager -import android.media.session.PlaybackState import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.headsetAudioModeChanges import com.android.settingslib.media.session.activeMediaChanges -import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver +import com.android.settingslib.volume.shared.AudioManagerEventsReceiver +import com.android.settingslib.volume.shared.model.AudioManagerEvent import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onStart @@ -44,7 +42,7 @@ interface MediaControllerRepository { } class MediaControllerRepositoryImpl( - audioManagerIntentsReceiver: AudioManagerIntentsReceiver, + audioManagerEventsReceiver: AudioManagerEventsReceiver, private val mediaSessionManager: MediaSessionManager, localBluetoothManager: LocalBluetoothManager?, coroutineScope: CoroutineScope, @@ -52,9 +50,9 @@ class MediaControllerRepositoryImpl( ) : MediaControllerRepository { private val devicesChanges = - audioManagerIntentsReceiver.intents.filter { - AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action - } + audioManagerEventsReceiver.events.filterIsInstance( + AudioManagerEvent.StreamDevicesChanged::class + ) override val activeLocalMediaController: StateFlow<MediaController?> = combine( @@ -63,7 +61,7 @@ class MediaControllerRepositoryImpl( }, localBluetoothManager?.headsetAudioModeChanges?.onStart { emit(Unit) } ?: flowOf(null), - devicesChanges.onStart { emit(Intent()) }, + devicesChanges.onStart { emit(AudioManagerEvent.StreamDevicesChanged) }, ) { controllers, _, _ -> controllers?.let(::findLocalMediaController) } @@ -98,9 +96,4 @@ class MediaControllerRepositoryImpl( } return localController } - - private companion object { - val inactivePlaybackStates = - setOf(PlaybackState.STATE_STOPPED, PlaybackState.STATE_NONE, PlaybackState.STATE_ERROR) - } } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt index 9fa4c86cdea1..13ed9a802318 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt @@ -21,6 +21,9 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.media.AudioManager +import android.util.Log +import com.android.settingslib.volume.shared.model.AudioManagerEvent +import com.android.settingslib.volume.shared.model.AudioStream import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharedFlow @@ -28,19 +31,20 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch -/** Exposes [AudioManager] intents as a observable shared flow. */ -interface AudioManagerIntentsReceiver { +/** Exposes [AudioManager] events as a observable shared flow. */ +interface AudioManagerEventsReceiver { - val intents: SharedFlow<Intent> + val events: SharedFlow<AudioManagerEvent> } -class AudioManagerIntentsReceiverImpl( +class AudioManagerEventsReceiverImpl( private val context: Context, coroutineScope: CoroutineScope, -) : AudioManagerIntentsReceiver { +) : AudioManagerEventsReceiver { private val allActions: Collection<String> get() = @@ -52,7 +56,7 @@ class AudioManagerIntentsReceiverImpl( AudioManager.STREAM_DEVICES_CHANGED_ACTION, ) - override val intents: SharedFlow<Intent> = + override val events: SharedFlow<AudioManagerEvent> = callbackFlow { val receiver = object : BroadcastReceiver() { @@ -73,5 +77,34 @@ class AudioManagerIntentsReceiverImpl( } .filterNotNull() .filter { intent -> allActions.contains(intent.action) } + .mapNotNull { it.toAudioManagerEvent() } .shareIn(coroutineScope, SharingStarted.WhileSubscribed()) + + private fun Intent.toAudioManagerEvent(): AudioManagerEvent? { + when (action) { + AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION -> + return AudioManagerEvent.InternalRingerModeChanged + AudioManager.STREAM_DEVICES_CHANGED_ACTION -> + return AudioManagerEvent.StreamDevicesChanged + AudioManager.MASTER_MUTE_CHANGED_ACTION -> + return AudioManagerEvent.StreamMasterMuteChanged + } + + val audioStreamType: Int = + getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.ERROR) + if (audioStreamType == AudioManager.ERROR) { + Log.e( + "AudioManagerIntentsReceiver", + "Intent doesn't have AudioManager.EXTRA_VOLUME_STREAM_TYPE extra", + ) + return null + } + val audioStream = AudioStream(audioStreamType) + return when (action) { + AudioManager.STREAM_MUTE_CHANGED_ACTION -> + AudioManagerEvent.StreamMuteChanged(audioStream) + AudioManager.VOLUME_CHANGED_ACTION -> AudioManagerEvent.StreamVolumeChanged(audioStream) + else -> null + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioManagerEvent.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioManagerEvent.kt new file mode 100644 index 000000000000..e19896bc5e87 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioManagerEvent.kt @@ -0,0 +1,37 @@ +/* + * 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.volume.shared.model + +/** Model events happening with the [android.media.AudioManager]. */ +sealed interface AudioManagerEvent { + + data class StreamMuteChanged(override val audioStream: AudioStream) : StreamAudioManagerEvent + + data class StreamVolumeChanged(override val audioStream: AudioStream) : StreamAudioManagerEvent + + data object StreamMasterMuteChanged : AudioManagerEvent + + data object InternalRingerModeChanged : AudioManagerEvent + + data object StreamDevicesChanged : AudioManagerEvent +} + +/** [AudioManagerEvent] that happens for a specific [AudioStream]. */ +sealed interface StreamAudioManagerEvent : AudioManagerEvent { + + val audioStream: AudioStream +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt index 48b04db5b50b..9ddf876be68e 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt @@ -20,7 +20,8 @@ import android.media.AudioDeviceInfo import android.media.AudioManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver +import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver +import com.android.settingslib.volume.shared.model.AudioManagerEvent import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.settingslib.volume.shared.model.RingerMode @@ -46,7 +47,6 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@Suppress("UnspecifiedRegisterReceiverFlag") @RunWith(AndroidJUnit4::class) class AudioRepositoryTest { @@ -59,7 +59,7 @@ class AudioRepositoryTest { @Mock private lateinit var audioManager: AudioManager @Mock private lateinit var communicationDevice: AudioDeviceInfo - private val intentsReceiver = FakeAudioManagerIntentsReceiver() + private val eventsReceiver = FakeAudioManagerEventsReceiver() private val volumeByStream: MutableMap<Int, Int> = mutableMapOf() private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf() private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf() @@ -77,12 +77,14 @@ class AudioRepositoryTest { `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME) `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL) `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then { - volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int - triggerIntent(AudioManager.ACTION_VOLUME_CHANGED) + val streamType = it.arguments[1] as Int + volumeByStream[it.arguments[0] as Int] = streamType + triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType))) } `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then { - isMuteByStream[it.arguments[0] as Int] = it.arguments[2] == AudioManager.ADJUST_MUTE - triggerIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION) + val streamType = it.arguments[0] as Int + isMuteByStream[streamType] = it.arguments[2] == AudioManager.ADJUST_MUTE + triggerEvent(AudioManagerEvent.StreamMuteChanged(AudioStream(streamType))) } `when`(audioManager.getStreamVolume(anyInt())).thenAnswer { volumeByStream.getOrDefault(it.arguments[0] as Int, 0) @@ -96,7 +98,7 @@ class AudioRepositoryTest { underTest = AudioRepositoryImpl( - intentsReceiver, + eventsReceiver, audioManager, testScope.testScheduler, testScope.backgroundScope, @@ -125,7 +127,7 @@ class AudioRepositoryTest { runCurrent() `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT) - triggerIntent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION) + triggerEvent(AudioManagerEvent.InternalRingerModeChanged) runCurrent() assertThat(modes) @@ -267,8 +269,8 @@ class AudioRepositoryTest { modeListenerCaptor.value.onModeChanged(mode) } - private fun triggerIntent(action: String) { - testScope.launch { intentsReceiver.triggerIntent(action) } + private fun triggerEvent(event: AudioManagerEvent) { + testScope.launch { eventsReceiver.triggerEvent(event) } } private companion object { diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt index dc9ea10a1074..2d12dae36ff1 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt @@ -23,7 +23,7 @@ import androidx.test.filters.SmallTest import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.model.RoutingSession -import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver +import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn @@ -58,7 +58,7 @@ class LocalMediaRepositoryImplTest { @Captor private lateinit var deviceCallbackCaptor: ArgumentCaptor<LocalMediaManager.DeviceCallback> - private val intentsReceiver = FakeAudioManagerIntentsReceiver() + private val eventsReceiver = FakeAudioManagerEventsReceiver() private val testScope = TestScope() private lateinit var underTest: LocalMediaRepository @@ -69,7 +69,7 @@ class LocalMediaRepositoryImplTest { underTest = LocalMediaRepositoryImpl( - intentsReceiver, + eventsReceiver, localMediaManager, mediaRouter2Manager, testScope.backgroundScope, diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt index 7bd43d2cf8ab..f3d17141334e 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt @@ -16,7 +16,6 @@ package com.android.settingslib.volume.data.repository -import android.media.AudioManager import android.media.session.MediaController import android.media.session.MediaController.PlaybackInfo import android.media.session.MediaSessionManager @@ -26,7 +25,8 @@ import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.BluetoothCallback import com.android.settingslib.bluetooth.BluetoothEventManager import com.android.settingslib.bluetooth.LocalBluetoothManager -import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver +import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver +import com.android.settingslib.volume.shared.model.AudioManagerEvent import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn @@ -66,7 +66,7 @@ class MediaControllerRepositoryImplTest { @Mock private lateinit var localPlaybackInfo: PlaybackInfo private val testScope = TestScope() - private val intentsReceiver = FakeAudioManagerIntentsReceiver() + private val eventsReceiver = FakeAudioManagerEventsReceiver() private lateinit var underTest: MediaControllerRepository @@ -94,7 +94,7 @@ class MediaControllerRepositoryImplTest { underTest = MediaControllerRepositoryImpl( - intentsReceiver, + eventsReceiver, mediaSessionManager, localBluetoothManager, testScope.backgroundScope, @@ -121,7 +121,7 @@ class MediaControllerRepositoryImplTest { .launchIn(backgroundScope) runCurrent() - intentsReceiver.triggerIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION) + eventsReceiver.triggerEvent(AudioManagerEvent.StreamDevicesChanged) triggerOnAudioModeChanged() runCurrent() @@ -146,7 +146,7 @@ class MediaControllerRepositoryImplTest { .launchIn(backgroundScope) runCurrent() - intentsReceiver.triggerIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION) + eventsReceiver.triggerEvent(AudioManagerEvent.StreamDevicesChanged) triggerOnAudioModeChanged() runCurrent() diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt new file mode 100644 index 000000000000..35ee8287d52f --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt @@ -0,0 +1,139 @@ +/* + * 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.volume.shared + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.media.AudioManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.volume.shared.model.AudioManagerEvent +import com.android.settingslib.volume.shared.model.AudioStream +import com.google.common.truth.Expect +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.Rule +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.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@Suppress("UnspecifiedRegisterReceiverFlag") +@RunWith(AndroidJUnit4::class) +class AudioManagerEventsReceiverTest { + + @JvmField @Rule val expect = Expect.create() + private val testScope = TestScope() + + @Mock private lateinit var context: Context + @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver> + + private lateinit var underTest: AudioManagerEventsReceiver + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + underTest = AudioManagerEventsReceiverImpl(context, testScope.backgroundScope) + } + + @Test + fun validIntent_translatedToEvent() { + testScope.runTest { + val events = mutableListOf<AudioManagerEvent>() + underTest.events.onEach { events.add(it) }.launchIn(backgroundScope) + runCurrent() + + triggerIntent( + Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION).apply { + putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.STREAM_SYSTEM) + } + ) + triggerIntent( + Intent(AudioManager.VOLUME_CHANGED_ACTION).apply { + putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.STREAM_SYSTEM) + } + ) + triggerIntent(Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION)) + triggerIntent(Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) + triggerIntent(Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) + runCurrent() + + expect + .that(events) + .containsExactly( + AudioManagerEvent.StreamMuteChanged( + AudioStream(AudioManager.STREAM_SYSTEM), + ), + AudioManagerEvent.StreamVolumeChanged( + AudioStream(AudioManager.STREAM_SYSTEM), + ), + AudioManagerEvent.StreamMasterMuteChanged, + AudioManagerEvent.InternalRingerModeChanged, + AudioManagerEvent.StreamDevicesChanged, + ) + } + } + + @Test + fun streamAudioManagerEvent_withoutAudioStream_areSkipped() { + testScope.runTest { + val events = mutableListOf<AudioManagerEvent>() + underTest.events.onEach { events.add(it) }.launchIn(backgroundScope) + runCurrent() + + triggerIntent(Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION)) + triggerIntent(Intent(AudioManager.VOLUME_CHANGED_ACTION)) + runCurrent() + + expect.that(events).isEmpty() + } + } + + @Test + fun invalidIntents_areSkipped() { + testScope.runTest { + val events = mutableListOf<AudioManagerEvent>() + underTest.events.onEach { events.add(it) }.launchIn(backgroundScope) + runCurrent() + + triggerIntent(null) + triggerIntent(Intent()) + triggerIntent(Intent("invalid_action")) + runCurrent() + + expect.that(events).isEmpty() + } + } + + private fun triggerIntent(intent: Intent?) { + verify(context).registerReceiver(receiverCaptor.capture(), any()) + receiverCaptor.value.onReceive(context, intent) + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerEventsReceiver.kt index 530690a5faa9..b742df7afc46 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerEventsReceiver.kt @@ -16,21 +16,17 @@ package com.android.settingslib.volume.shared -import android.content.Intent +import com.android.settingslib.volume.shared.model.AudioManagerEvent import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow -class FakeAudioManagerIntentsReceiver : AudioManagerIntentsReceiver { +class FakeAudioManagerEventsReceiver : AudioManagerEventsReceiver { - private val mutableIntents = MutableSharedFlow<Intent>() - override val intents: SharedFlow<Intent> = mutableIntents.asSharedFlow() + private val mutableIntents = MutableSharedFlow<AudioManagerEvent>() + override val events: SharedFlow<AudioManagerEvent> = mutableIntents.asSharedFlow() - suspend fun triggerIntent(intent: Intent) { - mutableIntents.emit(intent) - } - - suspend fun triggerIntent(action: String) { - triggerIntent(Intent(action)) + suspend fun triggerEvent(event: AudioManagerEvent) { + mutableIntents.emit(event) } } 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 67d6a7f5d735..f6fd519ed723 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt @@ -27,8 +27,8 @@ import com.android.settingslib.statusbar.notification.data.repository.Notificati import com.android.settingslib.volume.data.repository.AudioRepository import com.android.settingslib.volume.data.repository.AudioRepositoryImpl import com.android.settingslib.volume.domain.interactor.AudioModeInteractor -import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver -import com.android.settingslib.volume.shared.AudioManagerIntentsReceiverImpl +import com.android.settingslib.volume.shared.AudioManagerEventsReceiver +import com.android.settingslib.volume.shared.AudioManagerEventsReceiverImpl import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import dagger.Module @@ -46,11 +46,11 @@ interface AudioModule { fun provideAudioManagerIntentsReceiver( @Application context: Context, @Application coroutineScope: CoroutineScope, - ): AudioManagerIntentsReceiver = AudioManagerIntentsReceiverImpl(context, coroutineScope) + ): AudioManagerEventsReceiver = AudioManagerEventsReceiverImpl(context, coroutineScope) @Provides fun provideAudioRepository( - intentsReceiver: AudioManagerIntentsReceiver, + intentsReceiver: AudioManagerEventsReceiver, audioManager: AudioManager, @Background coroutineContext: CoroutineContext, @Application coroutineScope: CoroutineScope, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt index 9f99e9778ef2..bf9963d13959 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt @@ -20,7 +20,7 @@ import android.media.session.MediaSessionManager import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.volume.data.repository.MediaControllerRepository import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl -import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver +import com.android.settingslib.volume.shared.AudioManagerEventsReceiver import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -45,7 +45,7 @@ interface MediaDevicesModule { @Provides @SysUISingleton fun provideMediaDeviceSessionRepository( - intentsReceiver: AudioManagerIntentsReceiver, + intentsReceiver: AudioManagerEventsReceiver, mediaSessionManager: MediaSessionManager, localBluetoothManager: LocalBluetoothManager?, @Application coroutineScope: CoroutineScope, diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt index d8cd128af0c5..11b4690e59ee 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt @@ -18,7 +18,7 @@ package com.android.systemui.volume.panel.component.mediaoutput.data.repository import android.media.MediaRouter2Manager import com.android.settingslib.volume.data.repository.LocalMediaRepository import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl -import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver +import com.android.settingslib.volume.shared.AudioManagerEventsReceiver import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.media.controls.util.LocalMediaManagerFactory @@ -34,7 +34,7 @@ interface LocalMediaRepositoryFactory { class LocalMediaRepositoryFactoryImpl @Inject constructor( - private val intentsReceiver: AudioManagerIntentsReceiver, + private val eventsReceiver: AudioManagerEventsReceiver, private val mediaRouter2Manager: MediaRouter2Manager, private val localMediaManagerFactory: LocalMediaManagerFactory, @Application private val coroutineScope: CoroutineScope, @@ -43,7 +43,7 @@ constructor( override fun create(packageName: String?): LocalMediaRepository = LocalMediaRepositoryImpl( - intentsReceiver, + eventsReceiver, localMediaManagerFactory.create(packageName), mediaRouter2Manager, coroutineScope, |