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

Commit 19cefefa authored by Yuyang Huang's avatar Yuyang Huang Committed by Gerrit Code Review
Browse files

Merge "Keep HFP device active during HFP to LE Audio handover." into main

parents bee80915 e31fbc81
Loading
Loading
Loading
Loading
+40 −1
Original line number 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
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() {
        return mSystemInterface.isInCall() || (mSystemInterface.isRinging()
                && isInbandRingingEnabled());
@@ -2191,12 +2212,30 @@ public class HeadsetService extends ProfileService {
                // co-existence see {@link LeAudioService#setInactiveForHfpHandover}
                if (Flags.leaudioResumeActiveAfterHfpHandover()) {
                    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
                                && !leAudioService.getConnectedDevices().isEmpty()
                                && leAudioService.getActiveDevices().get(0) == null) {
                            leAudioService.setActiveAfterHfpHandover();
                        }
                    }
                }

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

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

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

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

        // Connect HF
        connectTestDevice(device);
@@ -1599,12 +1600,56 @@ public class HeadsetServiceAndStateMachineTest {
        assertThat(mHeadsetService.getActiveDevice()).isEqualTo(device);
        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(
                new HeadsetStackEvent(
                        HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
                        HeadsetHalConstants.AUDIO_STATE_CONNECTED,
                        device));

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