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

Commit e31fbc81 authored by Yuyang Huang's avatar Yuyang Huang
Browse files

Keep HFP device active during HFP to LE Audio handover.

This targets to fix flakiness when controller does not allow SCO and CIS coexistence, SCO's state goes from CONNECTING to DISCONNECTED that mis-triggers HFP to LE Audio handover.

Bug: 339079855
Bug: 339528451
Test: atest HeadsetServiceAndStateMachineTest
Change-Id: Ib3ef963daefdfcaf20aceb1e6bb0155dd9235746
parent a23f9b68
Loading
Loading
Loading
Loading
+40 −1
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@ import static com.android.modules.utils.build.SdkLevel.isAtLeastU;


import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNull;


import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothClass;
@@ -2130,6 +2131,26 @@ public class HeadsetService extends ProfileService {
        }
        }
    }
    }


    /**
     * Check if the device only allows HFP profile as audio profile
     *
     * @param device Bluetooth device
     * @return true if it is a BluetoothDevice with only HFP profile connectable
     */
    private boolean isHFPAudioOnly(@NonNull BluetoothDevice device) {
        int hfpPolicy =
                mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET);
        int a2dpPolicy = mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP);
        int leAudioPolicy =
                mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO);
        int ashaPolicy =
                mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID);
        return hfpPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED
                && a2dpPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED
                && leAudioPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED
                && ashaPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED;
    }

    private boolean shouldCallAudioBeActive() {
    private boolean shouldCallAudioBeActive() {
        return mSystemInterface.isInCall() || (mSystemInterface.isRinging()
        return mSystemInterface.isInCall() || (mSystemInterface.isRinging()
                && isInbandRingingEnabled());
                && isInbandRingingEnabled());
@@ -2191,12 +2212,30 @@ public class HeadsetService extends ProfileService {
                // co-existence see {@link LeAudioService#setInactiveForHfpHandover}
                // co-existence see {@link LeAudioService#setInactiveForHfpHandover}
                if (Flags.leaudioResumeActiveAfterHfpHandover()) {
                if (Flags.leaudioResumeActiveAfterHfpHandover()) {
                    LeAudioService leAudioService = mFactory.getLeAudioService();
                    LeAudioService leAudioService = mFactory.getLeAudioService();
                    if (!Flags.keepHfpActiveDuringLeaudioHandover()
                            && leAudioService != null
                            && !leAudioService.getConnectedDevices().isEmpty()
                            && leAudioService.getActiveDevices().get(0) == null) {
                        leAudioService.setActiveAfterHfpHandover();
                    }

                    // usually controller limitation cause CONNECTING -> DISCONNECTED, so only
                    // resume LE audio active device if it is HFP audio only and SCO disconnected
                    Log.v(
                            TAG,
                            "keep HFP active during handover: isHFPAudioOnly="
                                    + isHFPAudioOnly(device));
                    if (Flags.keepHfpActiveDuringLeaudioHandover()
                            && fromState != BluetoothHeadset.STATE_AUDIO_CONNECTING
                            && isHFPAudioOnly(device)) {

                        if (leAudioService != null
                        if (leAudioService != null
                                && !leAudioService.getConnectedDevices().isEmpty()
                                && !leAudioService.getConnectedDevices().isEmpty()
                                && leAudioService.getActiveDevices().get(0) == null) {
                                && leAudioService.getActiveDevices().get(0) == null) {
                            leAudioService.setActiveAfterHfpHandover();
                            leAudioService.setActiveAfterHfpHandover();
                        }
                        }
                    }
                    }
                }


                // Unsuspend A2DP when SCO connection is gone and call state is idle
                // Unsuspend A2DP when SCO connection is gone and call state is idle
                if (mSystemInterface.isCallIdle()) {
                if (mSystemInterface.isCallIdle()) {
+48 −3
Original line number Original line Diff line number Diff line
@@ -1577,18 +1577,19 @@ public class HeadsetServiceAndStateMachineTest {
    }
    }


    @Test
    @Test
    public void testHfpHandoverToLeAudioAfterScoDisconnect() {
    public void testHfpOnlyHandoverToLeAudioAfterScoDisconnect() {
        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_RESUME_ACTIVE_AFTER_HFP_HANDOVER);
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_RESUME_ACTIVE_AFTER_HFP_HANDOVER);
        mSetFlagsRule.enableFlags(Flags.FLAG_KEEP_HFP_ACTIVE_DURING_LEAUDIO_HANDOVER);


        assertThat(mHeadsetService.mFactory).isNotNull();
        assertThat(mHeadsetService.mFactory).isNotNull();
        mHeadsetService.mFactory = mServiceFactory;

        doReturn(mLeAudioService).when(mServiceFactory).getLeAudioService();
        doReturn(mLeAudioService).when(mServiceFactory).getLeAudioService();
        doReturn(List.of(device)).when(mLeAudioService).getConnectedDevices();
        doReturn(List.of(device)).when(mLeAudioService).getConnectedDevices();
        List<BluetoothDevice> activeDeviceList = new ArrayList<>();
        List<BluetoothDevice> activeDeviceList = new ArrayList<>();
        activeDeviceList.add(null);
        activeDeviceList.add(null);
        doReturn(activeDeviceList).when(mLeAudioService).getActiveDevices();
        doReturn(activeDeviceList).when(mLeAudioService).getActiveDevices();
        mHeadsetService.mFactory = mServiceFactory;
        doReturn(true).when(mSystemInterface).isCallIdle();


        // Connect HF
        // Connect HF
        connectTestDevice(device);
        connectTestDevice(device);
@@ -1599,12 +1600,56 @@ public class HeadsetServiceAndStateMachineTest {
        assertThat(mHeadsetService.getActiveDevice()).isEqualTo(device);
        assertThat(mHeadsetService.getActiveDevice()).isEqualTo(device);
        verify(mNativeInterface).sendBsir(eq(device), eq(true));
        verify(mNativeInterface).sendBsir(eq(device), eq(true));


        // this device is a HFP only device
        doReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED)
                .when(mDatabaseManager)
                .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET);
        doReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN)
                .when(mDatabaseManager)
                .getProfileConnectionPolicy(device, BluetoothProfile.A2DP);
        doReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN)
                .when(mDatabaseManager)
                .getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID);
        doReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN)
                .when(mDatabaseManager)
                .getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO);

        doReturn(true).when(mSystemInterface).isInCall();

        mHeadsetService.messageFromNative(
                new HeadsetStackEvent(
                        HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
                        HeadsetHalConstants.AUDIO_STATE_CONNECTING,
                        device));
        mTestLooper.dispatchAll();

        // simulate controller cannot handle SCO and CIS coexistence,
        // and SCO is failed to connect initially,
        mHeadsetService.messageFromNative(
                new HeadsetStackEvent(
                        HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
                        HeadsetHalConstants.AUDIO_STATE_DISCONNECTED,
                        device));
        mTestLooper.dispatchAll();
        // at this moment, should not resume LE Audio active device
        verify(mLeAudioService, never()).setActiveAfterHfpHandover();

        mHeadsetService.messageFromNative(
                new HeadsetStackEvent(
                        HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
                        HeadsetHalConstants.AUDIO_STATE_CONNECTING,
                        device));
        mTestLooper.dispatchAll();

        // then SCO is connected
        mHeadsetService.messageFromNative(
        mHeadsetService.messageFromNative(
                new HeadsetStackEvent(
                new HeadsetStackEvent(
                        HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
                        HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
                        HeadsetHalConstants.AUDIO_STATE_CONNECTED,
                        HeadsetHalConstants.AUDIO_STATE_CONNECTED,
                        device));
                        device));


        doReturn(false).when(mSystemInterface).isInCall();
        doReturn(true).when(mSystemInterface).isCallIdle();
        // Audio disconnected
        // Audio disconnected
        mHeadsetService.messageFromNative(
        mHeadsetService.messageFromNative(
                new HeadsetStackEvent(
                new HeadsetStackEvent(