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

Commit 7ea933a3 authored by Rongxuan Liu's avatar Rongxuan Liu
Browse files

[le audio] Avoid inactivating the device while receiving broadcast

When device updated available context types once syncing to BIS, the
assistant set device to be inactive. This caused calling handover won't
happen when the sinks are receiving broadcast.
We're skipping the inactivating action if the remote devices are
receiving broadcast.

This change should only apply for assistant standalone case, for local
broadcast, we are doing the handover to stop unicast stream first so
this logic won't be triggered.

Bug: 330691253
Bug: 329188955
Bug: 308171251
Test: atest LeAudioServiceTest BassClientServiceTest
Test: manual test with broadcast handover with calling
Change-Id: I1b9921a03a667d8527306e5fe6ebdc012b13161b
parent 8c1a26ea
Loading
Loading
Loading
Loading
+21 −21
Original line number Diff line number Diff line
@@ -636,7 +636,7 @@ public class BassClientService extends ProfileService {
            return;
        }

        boolean isAssistantActive = isAnyReceiverReceivingBroadcast();
        boolean isAssistantActive = isAnyReceiverReceivingBroadcast(getConnectedDevices());

        if (isAssistantActive) {
            /* Assistant become active */
@@ -1908,25 +1908,6 @@ public class BassClientService extends ProfileService {
        return true;
    }

    private boolean isAnyReceiverReceivingBroadcast() {
        for (BluetoothDevice device : getConnectedDevices()) {
            for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
                for (int i = 0; i < receiveState.getNumSubgroups(); i++) {
                    Long syncState = receiveState.getBisSyncState().get(i);

                    /* Not synced to BIS of failed to sync to BIG */
                    if (syncState == 0x00000000 || syncState == 0xFFFFFFFF) {
                        continue;
                    }

                    return true;
                }
            }
        }

        return false;
    }

    /** Return true if there is any non primary device receiving broadcast */
    private boolean isAudioSharingModeOn(Integer broadcastId) {
        if (mLocalBroadcastReceivers == null) {
@@ -2069,7 +2050,7 @@ public class BassClientService extends ProfileService {
        mUnicastSourceStreamStatus = Optional.of(status);

        if (status == STATUS_LOCAL_STREAM_REQUESTED) {
            if (isAnyReceiverReceivingBroadcast()) {
            if (isAnyReceiverReceivingBroadcast(getConnectedDevices())) {
                suspendAllReceiversSourceSynchronization();
            }
        } else if (status == STATUS_LOCAL_STREAM_SUSPENDED) {
@@ -2092,6 +2073,25 @@ public class BassClientService extends ProfileService {
        }
    }

    /** Check if any sink receivers are receiving broadcast stream */
    public boolean isAnyReceiverReceivingBroadcast(List<BluetoothDevice> devices) {
        for (BluetoothDevice device : devices) {
            for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
                for (int i = 0; i < receiveState.getNumSubgroups(); i++) {
                    Long syncState = receiveState.getBisSyncState().get(i);
                    /* Not synced to BIS of failed to sync to BIG */
                    if (syncState == 0x00000000 || syncState == 0xFFFFFFFF) {
                        continue;
                    }

                    return true;
                }
            }
        }

        return false;
    }

    /** Handle broadcast state changed */
    public void notifyBroadcastStateChanged(int state, int broadcastId) {
        switch (state) {
+16 −1
Original line number Diff line number Diff line
@@ -2196,7 +2196,9 @@ public class LeAudioService extends ProfileService {
            case LeAudioStackEvent.HEALTH_RECOMMENDATION_ACTION_INACTIVATE_GROUP:
                if (Flags.leaudioUnicastInactivateDeviceBasedOnContext()) {
                    LeAudioGroupDescriptor groupDescriptor = getGroupDescriptor(groupId);
                    if (groupDescriptor != null && groupDescriptor.mIsActive) {
                    if (groupDescriptor != null
                            && groupDescriptor.mIsActive
                            && !isGroupReceivingBroadcast(groupId)) {
                        Log.i(
                                TAG,
                                "Group "
@@ -2332,6 +2334,19 @@ public class LeAudioService extends ProfileService {
        }
    }

    private boolean isGroupReceivingBroadcast(int groupId) {
        if (!Flags.leaudioBroadcastAudioHandoverPolicies()) {
            return false;
        }

        BassClientService bassClientService = getBassClientService();
        if (bassClientService == null) {
            return false;
        }

        return bassClientService.isAnyReceiverReceivingBroadcast(getGroupDevices(groupId));
    }

    private void notifyGroupStreamStatusChanged(int groupId, int groupStreamStatus) {
        if (mLeAudioCallbacks != null) {
            int n = mLeAudioCallbacks.beginBroadcast();
+93 −0
Original line number Diff line number Diff line
@@ -1588,6 +1588,99 @@ public class BassClientServiceTest {
        }
    }

    @Test
    public void testIsAnyReceiverReceivingBroadcast() {
        prepareConnectedDeviceGroup();
        BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID);
        verifyAddSourceForGroup(meta);
        for (BassClientStateMachine sm : mStateMachines.values()) {
            if (sm.getDevice().equals(mCurrentDevice)) {
                injectRemoteSourceStateSourceAdded(
                        sm,
                        meta,
                        TEST_SOURCE_ID,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null);
                injectRemoteSourceStateChanged(
                        sm,
                        meta,
                        TEST_SOURCE_ID,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null,
                        (long) 0x00000000);
            } else if (sm.getDevice().equals(mCurrentDevice1)) {
                injectRemoteSourceStateSourceAdded(
                        sm,
                        meta,
                        TEST_SOURCE_ID + 1,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null);
                injectRemoteSourceStateChanged(
                        sm,
                        meta,
                        TEST_SOURCE_ID + 1,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null,
                        (long) 0x00000000);
            }
        }

        List<BluetoothDevice> devices = mBassClientService.getConnectedDevices();
        // Verify isAnyReceiverReceivingBroadcast returns false if no BIS synced
        assertThat(mBassClientService.isAnyReceiverReceivingBroadcast(devices)).isFalse();

        // Update receiver state with lost BIS sync
        for (BassClientStateMachine sm : mStateMachines.values()) {
            if (sm.getDevice().equals(mCurrentDevice)) {
                injectRemoteSourceStateChanged(
                        sm,
                        meta,
                        TEST_SOURCE_ID,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null,
                        (long) 0x00000001);
            } else if (sm.getDevice().equals(mCurrentDevice1)) {
                injectRemoteSourceStateChanged(
                        sm,
                        meta,
                        TEST_SOURCE_ID + 1,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null,
                        (long) 0x00000002);
            }
        }
        BluetoothDevice invalidDevice = TestUtils.getTestDevice(mBluetoothAdapter, 2);
        // Verify isAnyReceiverReceivingBroadcast returns false if invalid device
        assertThat(mBassClientService.isAnyReceiverReceivingBroadcast(List.of(invalidDevice)))
                .isFalse();
        // Verify isAnyReceiverReceivingBroadcast returns true if BIS synced
        assertThat(mBassClientService.isAnyReceiverReceivingBroadcast(devices)).isTrue();
    }

    private void prepareTwoSynchronizedDevices() {
        BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID);

+48 −0
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.TestUtils;
import com.android.bluetooth.bass_client.BassClientService;
import com.android.bluetooth.btservice.ActiveDeviceManager;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ServiceFactory;
@@ -132,6 +133,7 @@ public class LeAudioServiceTest {
    @Mock private VolumeControlService mVolumeControlService;
    @Mock private HapClientService mHapClientService;
    @Mock private CsipSetCoordinatorService mCsipSetCoordinatorService;
    @Mock private BassClientService mBassClientService;
    @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance();
    @Spy private ServiceFactory mServiceFactory = new ServiceFactory();

@@ -202,10 +204,12 @@ public class LeAudioServiceTest {
        mService.mMcpService = mMcpService;
        mService.mTbsService = mTbsService;
        mService.mHapClientService = mHapClientService;
        mService.mBassClientService = mBassClientService;
        mService.mServiceFactory = mServiceFactory;
        when(mServiceFactory.getVolumeControlService()).thenReturn(mVolumeControlService);
        when(mServiceFactory.getHapClientService()).thenReturn(mHapClientService);
        when(mServiceFactory.getCsipSetCoordinatorService()).thenReturn(mCsipSetCoordinatorService);
        when(mServiceFactory.getBassClientService()).thenReturn(mBassClientService);

        LeAudioStackEvent stackEvent =
        new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED);
@@ -1767,6 +1771,50 @@ public class LeAudioServiceTest {
                        eq(mSingleDevice), any(), any(BluetoothProfileConnectionInfo.class));
    }

    @Test
    public void testMediaContextUnavailableWhileReceivingBroadcast() {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_UNICAST_INACTIVATE_DEVICE_BASED_ON_CONTEXT);
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION);
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES);

        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mSingleDevice, testGroupId);

        Integer contexts = BluetoothLeAudio.CONTEXT_TYPE_MEDIA;
        injectAudioConfChanged(testGroupId, contexts, 1);

        // Set group and device as active.
        injectGroupStatusChange(testGroupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);

        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(mSingleDevice), any(), any(BluetoothProfileConnectionInfo.class));

        doReturn(true)
                .when(mBassClientService)
                .isAnyReceiverReceivingBroadcast(mService.getGroupDevices(testGroupId));
        LeAudioStackEvent healthBasedGroupAction =
                new LeAudioStackEvent(
                        LeAudioStackEvent.EVENT_TYPE_HEALTH_BASED_GROUP_RECOMMENDATION);
        healthBasedGroupAction.valueInt1 = testGroupId;
        healthBasedGroupAction.valueInt2 =
                LeAudioStackEvent.HEALTH_RECOMMENDATION_ACTION_INACTIVATE_GROUP;
        mService.messageFromNative(healthBasedGroupAction);
        // Verify skip setting device inactive if group is receiving broadcast
        verify(mAudioManager, times(0))
                .handleBluetoothActiveDeviceChanged(
                        eq(null), any(), any(BluetoothProfileConnectionInfo.class));

        doReturn(false)
                .when(mBassClientService)
                .isAnyReceiverReceivingBroadcast(mService.getGroupDevices(testGroupId));
        mService.messageFromNative(healthBasedGroupAction);
        // Verify setting device inactive if group is not receiving broadcast
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(null), any(), any(BluetoothProfileConnectionInfo.class));
    }

    private void sendEventAndVerifyIntentForGroupStatusChanged(int groupId, int groupStatus) {

        onGroupStatusCallbackCalled = false;