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

Commit 765df2d8 authored by Haijie Hong's avatar Haijie Hong
Browse files

Determine Spatial Audio AudioDeviceAttributes by BT profile state

Test: atest SpatialAudioComponentInteractorTest
Flag: com.android.settingslib.flags.enable_determining_spatial_audio_attributes_by_profile
Bug: 341005211
Change-Id: Ia9dc2463c412963b15631e82f1f6dd08dcf7c133
parent 98264c6c
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -79,3 +79,13 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "enable_determining_spatial_audio_attributes_by_profile"
    namespace: "cross_device_experiences"
    description: "Use bluetooth profile connection policy to determine spatial audio attributes"
    bug: "341005211"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.ContentResolver
import android.database.ContentObserver
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.media.AudioManager.AudioDeviceCategory
import android.media.AudioManager.OnCommunicationDeviceChangedListener
import android.provider.Settings
import androidx.concurrent.futures.DirectExecutor
@@ -85,6 +86,10 @@ interface AudioRepository {
    suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean

    suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)

    /** Gets audio device category. */
    @AudioDeviceCategory
    suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int
}

class AudioRepositoryImpl(
@@ -211,6 +216,13 @@ class AudioRepositoryImpl(
        withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
    }

    @AudioDeviceCategory
    override suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int {
        return withContext(backgroundCoroutineContext) {
            audioManager.getBluetoothAudioDeviceCategory(bluetoothAddress)
        }
    }

    private fun getMinVolume(stream: AudioStream): Int =
        try {
            audioManager.getStreamMinVolume(stream.value)
+13 −0
Original line number Diff line number Diff line
@@ -247,6 +247,19 @@ class AudioRepositoryTest {
        }
    }

    @Test
    fun getBluetoothAudioDeviceCategory() {
        testScope.runTest {
            `when`(audioManager.getBluetoothAudioDeviceCategory("12:34:56:78")).thenReturn(
                AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES)

            val category = underTest.getBluetoothAudioDeviceCategory("12:34:56:78")
            runCurrent()

            assertThat(category).isEqualTo(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES)
        }
    }

    private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
        verify(audioManager)
            .addOnCommunicationDeviceChangedListener(
+4 −0
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@
package com.android.systemui.volume.panel.component.spatial

import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.spatializerInteractor
import com.android.systemui.volume.data.repository.audioRepository
import com.android.systemui.volume.domain.interactor.audioOutputInteractor
import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor

@@ -27,6 +29,8 @@ val Kosmos.spatialAudioComponentInteractor by
        SpatialAudioComponentInteractor(
            audioOutputInteractor,
            spatializerInteractor,
            audioRepository,
            backgroundCoroutineContext,
            testScope.backgroundScope
        )
    }
+64 −6
Original line number Diff line number Diff line
@@ -16,14 +16,22 @@

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

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothProfile
import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.A2dpProfile
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.HearingAidProfile
import com.android.settingslib.bluetooth.LeAudioProfile
import com.android.settingslib.flags.Flags
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -44,6 +52,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
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

@@ -52,15 +61,29 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class SpatialAudioComponentInteractorTest : SysuiTestCase() {
    @get:Rule val setFlagsRule = SetFlagsRule()

    private val kosmos = testKosmos()
    private lateinit var underTest: SpatialAudioComponentInteractor
    private val a2dpProfile: A2dpProfile = mock {
        whenever(profileId).thenReturn(BluetoothProfile.A2DP)
    }
    private val leAudioProfile: LeAudioProfile = mock {
        whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO)
    }
    private val hearingAidProfile: HearingAidProfile = mock {
        whenever(profileId).thenReturn(BluetoothProfile.HEARING_AID)
    }
    private val bluetoothDevice: BluetoothDevice = mock {}

    @Before
    fun setup() {
        with(kosmos) {
            val cachedBluetoothDevice: CachedBluetoothDevice = mock {
                whenever(address).thenReturn("test_address")
                whenever(device).thenReturn(bluetoothDevice)
                whenever(profiles)
                    .thenReturn(listOf(a2dpProfile, leAudioProfile, hearingAidProfile))
            }
            localMediaRepository.updateCurrentConnectedDevice(
                mock<BluetoothMediaDevice> {
@@ -83,7 +106,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
    fun setEnabled_changesIsEnabled() {
        with(kosmos) {
            testScope.runTest {
                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
                spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true)
                val values by collectValues(underTest.isEnabled)

                underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
@@ -105,11 +128,40 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
        }
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DETERMINING_SPATIAL_AUDIO_ATTRIBUTES_BY_PROFILE)
    fun setEnabled_determinedByBluetoothProfile_a2dpProfileEnabled() {
        with(kosmos) {
            testScope.runTest {
                whenever(a2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true)
                whenever(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(false)
                whenever(hearingAidProfile.isEnabled(bluetoothDevice)).thenReturn(false)
                spatializerRepository.setIsSpatialAudioAvailable(a2dpAttributes, true)
                val values by collectValues(underTest.isEnabled)

                underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
                runCurrent()
                underTest.setEnabled(SpatialAudioEnabledModel.SpatialAudioEnabled)
                runCurrent()

                assertThat(values)
                    .containsExactly(
                        SpatialAudioEnabledModel.Unknown,
                        SpatialAudioEnabledModel.Disabled,
                        SpatialAudioEnabledModel.SpatialAudioEnabled,
                    )
                    .inOrder()
                assertThat(spatializerRepository.getSpatialAudioCompatibleDevices())
                    .containsExactly(a2dpAttributes)
            }
        }
    }

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

                val isAvailable by collectLastValue(underTest.isAvailable)

@@ -123,8 +175,8 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
    fun connectedDeviceSupportsHeadTracking_isAvailable_HeadTracking() {
        with(kosmos) {
            testScope.runTest {
                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
                spatializerRepository.setIsHeadTrackingAvailable(headset, true)
                spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true)
                spatializerRepository.setIsHeadTrackingAvailable(bleHeadsetAttributes, true)

                val isAvailable by collectLastValue(underTest.isAvailable)

@@ -138,7 +190,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
    fun connectedDeviceDoesntSupport_isAvailable_Unavailable() {
        with(kosmos) {
            testScope.runTest {
                spatializerRepository.setIsSpatialAudioAvailable(headset, false)
                spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, false)

                val isAvailable by collectLastValue(underTest.isAvailable)

@@ -179,7 +231,13 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
    }

    private companion object {
        val headset =
        val a2dpAttributes =
            AudioDeviceAttributes(
                AudioDeviceAttributes.ROLE_OUTPUT,
                AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
                "test_address"
            )
        val bleHeadsetAttributes =
            AudioDeviceAttributes(
                AudioDeviceAttributes.ROLE_OUTPUT,
                AudioDeviceInfo.TYPE_BLE_HEADSET,
Loading