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

Commit 78a06622 authored by Haijie Hong's avatar Haijie Hong Committed by Android (Google) Code Review
Browse files

Merge "Determine Spatial Audio AudioDeviceAttributes by BT profile state" into main

parents 45c36458 4296b30d
Loading
Loading
Loading
Loading
+92 −4
Original line number Diff line number Diff line
@@ -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<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,9 +155,13 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont

    @Override
    protected void refresh() {
        if (Flags.enableDeterminingSpatialAudioAttributesByProfile()) {
            getAvailableDeviceByProfileState();
        } else {
            if (mAudioDevice == null) {
                getAvailableDevice();
            }
        }
        ThreadUtils.postOnBackgroundThread(
                () -> {
                    mHasHeadTracker.set(
@@ -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;
+66 −1
Original line number Diff line number Diff line
@@ -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);