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

Commit aea35bca authored by Anton Potapov's avatar Anton Potapov
Browse files

Make AudioRepository#getAudioStream listen to only corresponding

stream events when possible.

Flag: aconfig new_volume_panel DISABLED
Test: atest AudioRepositoryTest
Test: atest LocalMediaRepositoryImplTest
Test: atest MediaControllerRepositoryImplTest
Test: atest AudioManagerEventsReceiverTest
Fixes: 325959032
Change-Id: Ifb1ae6e5dd5b897e12ce98296a6fd6991b9bcf94
parent f942b9ad
Loading
Loading
Loading
Loading
+15 −5
Original line number Diff line number Diff line
@@ -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)
    }
+8 −7
Original line number Diff line number Diff line
@@ -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(),
+8 −15
Original line number Diff line number Diff line
@@ -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)
    }
}
+39 −6
Original line number Diff line number Diff line
@@ -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
        }
    }
}
+37 −0
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.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
}
Loading