Loading packages/SettingsLib/aconfig/settingslib.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -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 } } packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt +12 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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( Loading Loading @@ -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) Loading packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt +13 −0 Original line number Diff line number Diff line Loading @@ -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( Loading packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt +4 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -27,6 +29,8 @@ val Kosmos.spatialAudioComponentInteractor by SpatialAudioComponentInteractor( audioOutputInteractor, spatializerInteractor, audioRepository, backgroundCoroutineContext, testScope.backgroundScope ) } packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt +64 −6 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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> { Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading Loading @@ -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 Loading
packages/SettingsLib/aconfig/settingslib.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -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 } }
packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt +12 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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( Loading Loading @@ -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) Loading
packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt +13 −0 Original line number Diff line number Diff line Loading @@ -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( Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt +4 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -27,6 +29,8 @@ val Kosmos.spatialAudioComponentInteractor by SpatialAudioComponentInteractor( audioOutputInteractor, spatializerInteractor, audioRepository, backgroundCoroutineContext, testScope.backgroundScope ) }
packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt +64 −6 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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> { Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading Loading @@ -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