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

Commit c6b49e7d authored by Yuyang Huang's avatar Yuyang Huang Committed by Automerger Merge Worker
Browse files

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

Merge "Keep HFP device active during HFP to LE Audio handover." into main am: 19cefefa am: 657e3a15

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/3080246



Change-Id: Ie0e14c588b0d0dd7cf3ffe754a9ec9437d0c3c76
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 149316a2 657e3a15
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(