Merge "Determine Spatial Audio AudioDeviceAttributes by BT profile state" into main
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
index 30e86fe..4ff7136 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
@@ -19,13 +19,16 @@
 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 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 @@
     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<Integer> 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 @@
 
     @Override
     protected void refresh() {
-        if (mAudioDevice == null) {
-            getAvailableDevice();
+        if (Flags.enableDeterminingSpatialAudioAttributesByProfile()) {
+            getAvailableDeviceByProfileState();
+        } else {
+            if (mAudioDevice == null) {
+                getAvailableDevice();
+            }
         }
         ThreadUtils.postOnBackgroundThread(
                 () -> {
@@ -274,6 +291,77 @@
                 + ", 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 d9a917b..24528ae 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 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 @@
 
 @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 @@
     @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 @@
         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);
@@ -273,6 +292,52 @@
     }
 
     @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);
         mSpatialAudioPref.setChecked(true);