From 4296b30dcf879701a9bf614b37b45d5f1d886fe4 Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Wed, 12 Jun 2024 13:14:46 +0800 Subject: [PATCH] Determine Spatial Audio AudioDeviceAttributes by BT profile state Test: atest BluetoothDetailsSpatialAudioControllerTest Bug: 341005211 Flag: com.android.settingslib.flags.enable_determining_spatial_audio_attributes_by_profile Change-Id: I1436019d239414c3855d506dcf35d736c8428e0a --- ...luetoothDetailsSpatialAudioController.java | 96 ++++++++++++++++++- ...oothDetailsSpatialAudioControllerTest.java | 67 ++++++++++++- 2 files changed, 158 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java index 30e86fe9b2f..4ff71360a49 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java @@ -19,13 +19,16 @@ package com.android.settings.bluetooth; import static android.media.Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; +import android.media.AudioManager; import android.media.Spatializer; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; @@ -37,9 +40,14 @@ import androidx.preference.TwoStatePreference; import com.android.settings.R; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.flags.Flags; import com.android.settingslib.utils.ThreadUtils; +import com.google.common.collect.ImmutableSet; + +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -53,22 +61,27 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont private static final String KEY_SPATIAL_AUDIO = "spatial_audio"; private static final String KEY_HEAD_TRACKING = "head_tracking"; + private final AudioManager mAudioManager; private final Spatializer mSpatializer; @VisibleForTesting PreferenceCategory mProfilesContainer; - @VisibleForTesting - AudioDeviceAttributes mAudioDevice = null; + @VisibleForTesting @Nullable AudioDeviceAttributes mAudioDevice = null; AtomicBoolean mHasHeadTracker = new AtomicBoolean(false); AtomicBoolean mInitialRefresh = new AtomicBoolean(true); + public static final Set SA_PROFILES = + ImmutableSet.of( + BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID); + public BluetoothDetailsSpatialAudioController( Context context, PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) { super(context, fragment, device, lifecycle); + mAudioManager = context.getSystemService(AudioManager.class); mSpatializer = FeatureFactory.getFeatureFactory().getBluetoothFeatureProvider() .getSpatializer(context); } @@ -142,8 +155,12 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont @Override protected void refresh() { - if (mAudioDevice == null) { - getAvailableDevice(); + if (Flags.enableDeterminingSpatialAudioAttributesByProfile()) { + getAvailableDeviceByProfileState(); + } else { + if (mAudioDevice == null) { + getAvailableDevice(); + } } ThreadUtils.postOnBackgroundThread( () -> { @@ -274,6 +291,77 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont + ", type : " + (mAudioDevice == null ? "no type" : mAudioDevice.getType())); } + private void getAvailableDeviceByProfileState() { + Log.i( + TAG, + "getAvailableDevice() mCachedDevice: " + + mCachedDevice + + " profiles: " + + mCachedDevice.getProfiles()); + + AudioDeviceAttributes saDevice = null; + for (LocalBluetoothProfile profile : mCachedDevice.getProfiles()) { + // pick first enabled profile that is compatible with spatial audio + if (SA_PROFILES.contains(profile.getProfileId()) + && profile.isEnabled(mCachedDevice.getDevice())) { + switch (profile.getProfileId()) { + case BluetoothProfile.A2DP: + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + mCachedDevice.getAddress()); + break; + case BluetoothProfile.LE_AUDIO: + if (mAudioManager.getBluetoothAudioDeviceCategory( + mCachedDevice.getAddress()) + == AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER) { + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + mCachedDevice.getAddress()); + } else { + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_HEADSET, + mCachedDevice.getAddress()); + } + + break; + case BluetoothProfile.HEARING_AID: + saDevice = + new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HEARING_AID, + mCachedDevice.getAddress()); + break; + default: + Log.i( + TAG, + "unrecognized profile for spatial audio: " + + profile.getProfileId()); + break; + } + break; + } + } + mAudioDevice = null; + if (saDevice != null && mSpatializer.isAvailableForDevice(saDevice)) { + mAudioDevice = saDevice; + } + + Log.d( + TAG, + "getAvailableDevice() device : " + + mCachedDevice.getDevice().getAnonymizedAddress() + + ", is available : " + + (mAudioDevice != null) + + ", type : " + + (mAudioDevice == null ? "no type" : mAudioDevice.getType())); + } + @VisibleForTesting void setAvailableDevice(AudioDeviceAttributes audioDevice) { mAudioDevice = audioDevice; diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java index d9a917b0cdd..24528ae4dcc 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioControllerTest.java @@ -21,26 +21,35 @@ import static android.media.Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.Spatializer; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.preference.PreferenceCategory; import androidx.preference.TwoStatePreference; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.bluetooth.A2dpProfile; +import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.flags.Flags; import com.google.common.collect.ImmutableList; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -54,7 +63,8 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetailsControllerTestBase { - + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C"; private static final String KEY_SPATIAL_AUDIO = "spatial_audio"; private static final String KEY_HEAD_TRACKING = "head_tracking"; @@ -64,6 +74,9 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails @Mock private Lifecycle mSpatialAudioLifecycle; @Mock private PreferenceCategory mProfilesContainer; @Mock private BluetoothDevice mBluetoothDevice; + @Mock private A2dpProfile mA2dpProfile; + @Mock private LeAudioProfile mLeAudioProfile; + @Mock private HearingAidProfile mHearingAidProfile; private AudioDeviceAttributes mAvailableDevice; @@ -83,6 +96,12 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails when(mAudioManager.getSpatializer()).thenReturn(mSpatializer); when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS); when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedDevice.getProfiles()) + .thenReturn(List.of(mA2dpProfile, mLeAudioProfile, mHearingAidProfile)); + when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(true); + when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP); + when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO); + when(mHearingAidProfile.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID); when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(MAC_ADDRESS); when(mFeatureFactory.getBluetoothFeatureProvider().getSpatializer(mContext)) .thenReturn(mSpatializer); @@ -272,6 +291,52 @@ public class BluetoothDetailsSpatialAudioControllerTest extends BluetoothDetails false); } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DETERMINING_SPATIAL_AUDIO_ATTRIBUTES_BY_PROFILE) + public void refresh_leAudioProfileEnabledForHeadset_useLeAudioHeadsetAttributes() { + when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true); + when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(false); + when(mHearingAidProfile.isEnabled(mBluetoothDevice)).thenReturn(false); + when(mAudioManager.getBluetoothAudioDeviceCategory(MAC_ADDRESS)) + .thenReturn(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES); + when(mSpatializer.isAvailableForDevice(any())).thenReturn(true); + + mController.refresh(); + ShadowLooper.idleMainLooper(); + + assertThat(mController.mAudioDevice.getType()).isEqualTo(AudioDeviceInfo.TYPE_BLE_HEADSET); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DETERMINING_SPATIAL_AUDIO_ATTRIBUTES_BY_PROFILE) + public void refresh_leAudioProfileEnabledForSpeaker_useLeAudioSpeakerAttributes() { + when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true); + when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(false); + when(mHearingAidProfile.isEnabled(mBluetoothDevice)).thenReturn(false); + when(mAudioManager.getBluetoothAudioDeviceCategory(MAC_ADDRESS)) + .thenReturn(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER); + when(mSpatializer.isAvailableForDevice(any())).thenReturn(true); + + mController.refresh(); + ShadowLooper.idleMainLooper(); + + assertThat(mController.mAudioDevice.getType()).isEqualTo(AudioDeviceInfo.TYPE_BLE_SPEAKER); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DETERMINING_SPATIAL_AUDIO_ATTRIBUTES_BY_PROFILE) + public void refresh_hearingAidProfileEnabled_useHearingAidAttributes() { + when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(false); + when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(false); + when(mHearingAidProfile.isEnabled(mBluetoothDevice)).thenReturn(true); + when(mSpatializer.isAvailableForDevice(any())).thenReturn(true); + + mController.refresh(); + ShadowLooper.idleMainLooper(); + + assertThat(mController.mAudioDevice.getType()).isEqualTo(AudioDeviceInfo.TYPE_HEARING_AID); + } + @Test public void turnedOnSpatialAudio_invokesAddCompatibleAudioDevice() { mController.setAvailableDevice(mAvailableDevice);