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

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

Add spatial audio button when builtin speaker supports it

Flag: aconfig new_volume_panel TEAMFOOD
Test: atest SpatialAudioComponentInteractor
Test: atest SpatialAudioAvailabilityCriteriaTest
Fixes: 329194622
Change-Id: I3dd049cfe313b61ce9e23621666975f10167f2bd
parent 3312c97d
Loading
Loading
Loading
Loading
+32 −23
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.volume.panel.component.spatial.domain

import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.testing.TestableLooper.RunWithLooper
@@ -49,26 +51,27 @@ import org.junit.runner.RunWith
class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val cachedBluetoothDevice: CachedBluetoothDevice = mock {
        whenever(address).thenReturn("test_address")
    }
    private val bluetoothMediaDevice: BluetoothMediaDevice = mock {
        whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
    }

    private lateinit var underTest: SpatialAudioAvailabilityCriteria

    @Before
    fun setup() {
        with(kosmos) {
            mediaControllerRepository.setActiveLocalMediaController(
                mediaController.apply {
                    whenever(packageName).thenReturn("test.pkg")
                    whenever(sessionToken).thenReturn(MediaSession.Token(0, mock {}))
                    whenever(playbackState).thenReturn(PlaybackState.Builder().build())
            val cachedBluetoothDevice: CachedBluetoothDevice = mock {
                whenever(address).thenReturn("test_address")
            }
            localMediaRepository.updateCurrentConnectedDevice(
                mock<BluetoothMediaDevice> {
                    whenever(name).thenReturn("test_device")
                    whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
                }
            )

            whenever(mediaController.packageName).thenReturn("test.pkg")
            whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
            whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())

            mediaControllerRepository.setActiveLocalMediaController(mediaController)

            underTest = SpatialAudioAvailabilityCriteria(spatialAudioComponentInteractor)
        }
    }
@@ -77,9 +80,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
    fun noSpatialAudio_noHeadTracking_unavailable() {
        with(kosmos) {
            testScope.runTest {
                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
                spatializerRepository.defaultHeadTrackingAvailable = false
                spatializerRepository.defaultSpatialAudioAvailable = false
                spatializerRepository.setIsSpatialAudioAvailable(headset, false)
                spatializerRepository.setIsHeadTrackingAvailable(headset, false)

                val isAvailable by collectLastValue(underTest.isAvailable())
                runCurrent()
@@ -93,9 +95,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
    fun spatialAudio_noHeadTracking_available() {
        with(kosmos) {
            testScope.runTest {
                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
                spatializerRepository.defaultHeadTrackingAvailable = false
                spatializerRepository.defaultSpatialAudioAvailable = true
                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
                spatializerRepository.setIsHeadTrackingAvailable(headset, false)

                val isAvailable by collectLastValue(underTest.isAvailable())
                runCurrent()
@@ -109,9 +110,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
    fun spatialAudio_headTracking_available() {
        with(kosmos) {
            testScope.runTest {
                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
                spatializerRepository.defaultHeadTrackingAvailable = true
                spatializerRepository.defaultSpatialAudioAvailable = true
                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
                spatializerRepository.setIsHeadTrackingAvailable(headset, true)

                val isAvailable by collectLastValue(underTest.isAvailable())
                runCurrent()
@@ -125,8 +125,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
    fun spatialAudio_headTracking_noDevice_unavailable() {
        with(kosmos) {
            testScope.runTest {
                spatializerRepository.defaultHeadTrackingAvailable = true
                spatializerRepository.defaultSpatialAudioAvailable = true
                localMediaRepository.updateCurrentConnectedDevice(null)
                spatializerRepository.setIsSpatialAudioAvailable(headset, false)

                val isAvailable by collectLastValue(underTest.isAvailable())
                runCurrent()
@@ -135,4 +135,13 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
            }
        }
    }

    private companion object {
        val headset =
            AudioDeviceAttributes(
                AudioDeviceAttributes.ROLE_OUTPUT,
                AudioDeviceInfo.TYPE_BLE_HEADSET,
                "test_address"
            )
    }
}
+91 −10
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.spatializerInteractor
@@ -37,6 +38,7 @@ import com.android.systemui.volume.localMediaRepository
import com.android.systemui.volume.mediaController
import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.mediaOutputInteractor
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -74,16 +76,6 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {

            mediaControllerRepository.setActiveLocalMediaController(mediaController)

            spatializerRepository.setIsSpatialAudioAvailable(
                AudioDeviceAttributes(
                    AudioDeviceAttributes.ROLE_OUTPUT,
                    AudioDeviceInfo.TYPE_BLE_HEADSET,
                    "test_address"
                ),
                true
            )
            spatializerRepository.defaultHeadTrackingAvailable = true

            underTest =
                SpatialAudioComponentInteractor(
                    mediaOutputInteractor,
@@ -97,6 +89,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
    fun setEnabled_changesIsEnabled() {
        with(kosmos) {
            testScope.runTest {
                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
                val values by collectValues(underTest.isEnabled)

                underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
@@ -116,4 +109,92 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
            }
        }
    }

    @Test
    fun connectedDeviceSupports_isAvailable_SpatialAudio() {
        with(kosmos) {
            testScope.runTest {
                spatializerRepository.setIsSpatialAudioAvailable(headset, true)

                val isAvailable by collectLastValue(underTest.isAvailable)

                assertThat(isAvailable)
                    .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java)
            }
        }
    }

    @Test
    fun connectedDeviceSupportsHeadTracking_isAvailable_HeadTracking() {
        with(kosmos) {
            testScope.runTest {
                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
                spatializerRepository.setIsHeadTrackingAvailable(headset, true)

                val isAvailable by collectLastValue(underTest.isAvailable)

                assertThat(isAvailable)
                    .isInstanceOf(SpatialAudioAvailabilityModel.HeadTracking::class.java)
            }
        }
    }

    @Test
    fun connectedDeviceDoesntSupport_isAvailable_Unavailable() {
        with(kosmos) {
            testScope.runTest {
                spatializerRepository.setIsSpatialAudioAvailable(headset, false)

                val isAvailable by collectLastValue(underTest.isAvailable)

                assertThat(isAvailable)
                    .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java)
            }
        }
    }

    @Test
    fun noConnectedDeviceBuiltinSupports_isAvailable_SpatialAudio() {
        with(kosmos) {
            testScope.runTest {
                localMediaRepository.updateCurrentConnectedDevice(null)
                spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, true)

                val isAvailable by collectLastValue(underTest.isAvailable)

                assertThat(isAvailable)
                    .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java)
            }
        }
    }

    @Test
    fun noConnectedDeviceBuiltinDoesntSupport_isAvailable_Unavailable() {
        with(kosmos) {
            testScope.runTest {
                localMediaRepository.updateCurrentConnectedDevice(null)
                spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, false)

                val isAvailable by collectLastValue(underTest.isAvailable)

                assertThat(isAvailable)
                    .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java)
            }
        }
    }

    private companion object {
        val headset =
            AudioDeviceAttributes(
                AudioDeviceAttributes.ROLE_OUTPUT,
                AudioDeviceInfo.TYPE_BLE_HEADSET,
                "test_address"
            )
        val builtinSpeaker =
            AudioDeviceAttributes(
                AudioDeviceAttributes.ROLE_OUTPUT,
                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
                ""
            )
    }
}
+48 −34
Original line number Diff line number Diff line
@@ -18,8 +18,9 @@ package com.android.systemui.volume.panel.component.spatial.domain.interactor

import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.media.PhoneMediaDevice
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
@@ -54,12 +55,9 @@ constructor(
    private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> =
        mediaOutputInteractor.currentConnectedDevice
            .map { mediaDevice ->
                mediaDevice ?: return@map null
                val btDevice: CachedBluetoothDevice =
                    (mediaDevice as? BluetoothMediaDevice)?.cachedDevice ?: return@map null
                btDevice.getAudioDeviceAttributes()
                if (mediaDevice == null) builtinSpeaker else mediaDevice.getAudioDeviceAttributes()
            }
            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), builtinSpeaker)

    /**
     * Returns spatial audio availability model. It can be:
@@ -137,34 +135,50 @@ constructor(
        changes.emit(Unit)
    }

    private suspend fun CachedBluetoothDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
    private suspend fun MediaDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
        when (this) {
            is PhoneMediaDevice -> return builtinSpeaker
            is BluetoothMediaDevice -> {
                val device = cachedDevice ?: return null
                return listOf(
                        AudioDeviceAttributes(
                            AudioDeviceAttributes.ROLE_OUTPUT,
                            AudioDeviceInfo.TYPE_BLE_HEADSET,
                    address
                            device.address,
                        ),
                        AudioDeviceAttributes(
                            AudioDeviceAttributes.ROLE_OUTPUT,
                            AudioDeviceInfo.TYPE_BLE_SPEAKER,
                    address
                            device.address,
                        ),
                        AudioDeviceAttributes(
                            AudioDeviceAttributes.ROLE_OUTPUT,
                            AudioDeviceInfo.TYPE_BLE_BROADCAST,
                    address
                            device.address,
                        ),
                        AudioDeviceAttributes(
                            AudioDeviceAttributes.ROLE_OUTPUT,
                            AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
                    address
                            device.address,
                        ),
                        AudioDeviceAttributes(
                            AudioDeviceAttributes.ROLE_OUTPUT,
                            AudioDeviceInfo.TYPE_HEARING_AID,
                    address
                            device.address,
                        )
                    )
                    .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
            }
            else -> return null
        }
    }

    private companion object {
        val builtinSpeaker =
            AudioDeviceAttributes(
                AudioDeviceAttributes.ROLE_OUTPUT,
                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
                ""
            )
    }
}