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

Commit 6126cbf4 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Fix switching between LeAudio headsets

When switching between LeAudio Headsets, Java is interested only in
getting ACTIVE groups status with new group.

This is because, LeAudioService is aware about active group and when
switching to new active group, it can update AudioManager in a single
call which improves user experiance when switching during the streaming.

Bug: 369599302
Test: atest LeAudioServiceTest bluetooth_le_audio_client_test
Flag: Exempt, regression tested with unit tests, new test added
Change-Id: Ifc6564e973c1255ad04d5221249b3313fa60d6af
parent f7aa9fc3
Loading
Loading
Loading
Loading
+17 −4
Original line number Diff line number Diff line
@@ -2108,7 +2108,8 @@ public class LeAudioService extends ProfileService {
                        + (", isSink: " + isSink)
                        + (" isSource: " + isSource)
                        + (", mActiveAudioInDevice: " + mActiveAudioInDevice)
                        + (", mActiveAudioOutDevice: " + mActiveAudioOutDevice));
                        + (", mActiveAudioOutDevice: " + mActiveAudioOutDevice)
                        + (", mExposedActiveDevice: " + mExposedActiveDevice));

        if (!device.equals(mExposedActiveDevice)) {
            return;
@@ -2123,9 +2124,21 @@ public class LeAudioService extends ProfileService {
            return;
        }

        if (device.equals(mActiveAudioInDevice) || device.equals(mActiveAudioOutDevice)) {
            Log.i(TAG, "Audio manager disactivate LeAudio device " + mExposedActiveDevice);
            mExposedActiveDevice = null;
            setActiveDevice(null);
            return;
        }

        Log.i(
                TAG,
                ("LeAudio active device switch: "
                        + mExposedActiveDevice
                        + " -> "
                        + (mActiveAudioInDevice != null
                                ? mActiveAudioInDevice
                                : mActiveAudioOutDevice)));
    }

    /* Notifications of audio device connection/disconn events. */
+68 −19
Original line number Diff line number Diff line
@@ -1643,8 +1643,6 @@ public class LeAudioServiceTest {
        ArgumentCaptor<BluetoothProfileConnectionInfo> connectionInfoArgumentCaptor =
                ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class);

        /* Expect 2 calles to Audio Manager - one for output  as this is
         * Ringtone use case */
        verify(mAudioManager)
                .handleBluetoothActiveDeviceChanged(
                        eq(mSingleDevice), eq(null), connectionInfoArgumentCaptor.capture());
@@ -1660,39 +1658,90 @@ public class LeAudioServiceTest {
        assertThat(mService.setActiveDevice(mSingleDevice_2)).isTrue();
        verify(mNativeInterface).groupSetActive(groupId_2);

        // First wait for INACTIVE state will be sent from native
        LeAudioStackEvent inactiveGroupState =

        // First wait for ACTIVE state will be sent from native
        LeAudioStackEvent activeGroupState =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
        inactiveGroupState.valueInt1 = groupId_1;
        inactiveGroupState.valueInt2 = LeAudioStackEvent.GROUP_STATUS_INACTIVE;
        mService.messageFromNative(inactiveGroupState);
        activeGroupState.valueInt1 = groupId_2;
        activeGroupState.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE;
        activeGroupState.valueInt3 = groupId_1;
        mService.messageFromNative(activeGroupState);

        // Make sure suppressNoisyIntent is set to true. Soon new group will be active
        verify(mAudioManager)
                .handleBluetoothActiveDeviceChanged(
                        eq(null), eq(mSingleDevice), connectionInfoArgumentCaptor.capture());
                        eq(mSingleDevice_2),
                        eq(mSingleDevice),
                        connectionInfoArgumentCaptor.capture());
        connInfo = connectionInfoArgumentCaptor.getValue();
        assertThat(connInfo.isSuppressNoisyIntent()).isTrue();

        injectAudioDeviceRemoved(
                mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, false);
        injectAudioDeviceAdded(
                mSingleDevice_2, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, true);
        verify(mNativeInterface, times(0)).groupSetActive(-1);
    }

        reset(mAudioManager);
    /** Test switching active groups */
    @Test
    public void testAudioFrameworkAutonomousDeviceRemoval() {
        int groupId_1 = 1;

        // First wait for ACTIVE state will be sent from native
        LeAudioStackEvent activeGroupState =
        /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */
        int direction = 1;
        int snkAudioLocation = 3;
        int srcAudioLocation = 4;
        int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE;

        // Not connected device
        assertThat(mService.setActiveDevice(mSingleDevice)).isFalse();

        // Define some return values needed in test
        doReturn(-1).when(mVolumeControlService).getAudioDeviceGroupVolume(anyInt());
        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));

        // Connect both
        connectTestDevice(mSingleDevice, groupId_1);

        // Add location support
        LeAudioStackEvent audioConfChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED);
        audioConfChangedEvent.device = mSingleDevice;
        audioConfChangedEvent.valueInt1 = direction;
        audioConfChangedEvent.valueInt2 = groupId_1;
        audioConfChangedEvent.valueInt3 = snkAudioLocation;
        audioConfChangedEvent.valueInt4 = srcAudioLocation;
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

        assertThat(mService.setActiveDevice(mSingleDevice)).isTrue();
        verify(mNativeInterface).groupSetActive(groupId_1);

        // Set group and device as active
        LeAudioStackEvent groupStatusChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
        activeGroupState.valueInt1 = groupId_2;
        activeGroupState.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE;
        mService.messageFromNative(activeGroupState);
        groupStatusChangedEvent.valueInt1 = groupId_1;
        groupStatusChangedEvent.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE;
        mService.messageFromNative(groupStatusChangedEvent);

        verify(mTbsService).setInbandRingtoneSupport(mSingleDevice);

        ArgumentCaptor<BluetoothProfileConnectionInfo> connectionInfoArgumentCaptor =
                ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class);

        verify(mAudioManager)
                .handleBluetoothActiveDeviceChanged(
                        eq(mSingleDevice_2), eq(null), connectionInfoArgumentCaptor.capture());
        connInfo = connectionInfoArgumentCaptor.getValue();
                        eq(mSingleDevice), eq(null), connectionInfoArgumentCaptor.capture());

        injectAudioDeviceAdded(mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, true);

        BluetoothProfileConnectionInfo connInfo = connectionInfoArgumentCaptor.getValue();
        assertThat(connInfo.isSuppressNoisyIntent()).isTrue();

        injectAudioDeviceAdded(
                mSingleDevice_2, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, true);
        // AudioManager removes audio device
        injectAudioDeviceRemoved(
                mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, false);
        verify(mNativeInterface, times(1)).groupSetActive(-1);
    }

    /** Test setting active device group without Ringtone context */
+5 −1
Original line number Diff line number Diff line
@@ -1434,8 +1434,12 @@ public:
       * the new group so the group change is correctly handled in OnStateMachineStatusReportCb
       */
      active_group_id_ = group_id;
      SuspendedForReconfiguration();
      GroupStop(previous_active_group);
      callbacks_->OnGroupStatus(previous_active_group, GroupStatus::INACTIVE);
      /* Note: On purpose we are not sending INACTIVE status up to Java, because previous active
       * group will be provided in ACTIVE status. This is in order to have single call to audio
       * framework
       */
    }

    /* Reset sink listener notified status */