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

Commit e48bd373 authored by Haijie Hong's avatar Haijie Hong Committed by Android Build Coastguard Worker
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
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:765df2d809e5f5800ac7dfd253704032d266c2bb)
Merged-In: Ia9dc2463c412963b15631e82f1f6dd08dcf7c133
Change-Id: Ia9dc2463c412963b15631e82f1f6dd08dcf7c133
parent 47284bb4
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