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

Commit a933bbc2 authored by Rongxuan Liu's avatar Rongxuan Liu Committed by Gerrit Code Review
Browse files

Merge "[le audio] Resync device to its peer active broadcast source" into main

parents a4d8b874 23d827db
Loading
Loading
Loading
Loading
+59 −0
Original line number Diff line number Diff line
@@ -3378,6 +3378,34 @@ public class BassClientService extends ProfileService {
        }
    }

    /** Handle device newly connected and its peer device still has active source */
    private void checkAndResumeBroadcast(BluetoothDevice sink) {
        BluetoothLeBroadcastMetadata metadata = mBroadcastMetadataMap.get(sink);
        if (metadata == null) {
            Log.d(TAG, "checkAndResumeBroadcast: no metadata available");
            return;
        }
        for (BluetoothDevice groupDevice : getTargetDeviceList(sink, true)) {
            if (groupDevice.equals(sink)) {
                continue;
            }
            // Check peer device
            Optional<BluetoothLeBroadcastReceiveState> receiver =
                    getOrCreateStateMachine(groupDevice).getAllSources().stream()
                            .filter(e -> e.getBroadcastId() == metadata.getBroadcastId())
                            .findAny();
            if (receiver.isPresent()
                    && !getAllSources(sink).stream()
                            .anyMatch(
                                    rs ->
                                            (rs.getBroadcastId()
                                                    == receiver.get().getBroadcastId()))) {
                Log.d(TAG, "checkAndResumeBroadcast: restore the source for device: " + sink);
                addSource(sink, metadata, false);
            }
        }
    }

    private void logPausedBroadcastsAndSinks() {
        log(
                "mPausedBroadcastIds: "
@@ -3788,6 +3816,7 @@ public class BassClientService extends ProfileService {
        private static final int MSG_SOURCE_REMOVED_FAILED = 11;
        private static final int MSG_RECEIVESTATE_CHANGED = 12;
        private static final int MSG_SOURCE_LOST = 13;
        private static final int MSG_BASS_STATE_READY = 14;

        @GuardedBy("mCallbacksList")
        private final RemoteCallbackList<IBluetoothLeBroadcastAssistantCallback> mCallbacksList =
@@ -3840,8 +3869,33 @@ public class BassClientService extends ProfileService {
            }
        }

        private boolean handleServiceInternalMessage(Message msg) {
            boolean isMsgHandled = false;
            if (sService == null) {
                Log.e(TAG, "Service is null");
                return isMsgHandled;
            }
            BluetoothDevice sink;

            switch (msg.what) {
                case MSG_BASS_STATE_READY:
                    sink = (BluetoothDevice) msg.obj;
                    sService.checkAndResumeBroadcast(sink);
                    isMsgHandled = true;
                    break;
                default:
                    break;
            }
            return isMsgHandled;
        }

        @Override
        public void handleMessage(Message msg) {
            if (handleServiceInternalMessage(msg)) {
                log("Handled internal message: " + msg.what);
                return;
            }

            checkForPendingGroupOpRequest(msg);

            synchronized (mCallbacksList) {
@@ -4100,6 +4154,11 @@ public class BassClientService extends ProfileService {
            sEventLogger.logd(TAG, "notifySourceLost: broadcastId: " + broadcastId);
            obtainMessage(MSG_SOURCE_LOST, 0, broadcastId).sendToTarget();
        }

        void notifyBassStateReady(BluetoothDevice sink) {
            sEventLogger.logd(TAG, "notifyBassStateReady: sink: " + sink);
            obtainMessage(MSG_BASS_STATE_READY, sink).sendToTarget();
        }
    }

    @Override
+16 −7
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;

import static com.android.bluetooth.flags.Flags.leaudioBigDependsOnAudioState;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastResyncHelper;

import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -968,6 +969,11 @@ public class BassClientStateMachine extends StateMachine {
        mBroadcastSyncStats.clear();
    }

    private boolean isSourceAbsent(BluetoothLeBroadcastReceiveState recvState) {
        return recvState.getSourceDevice() == null
                || recvState.getSourceDevice().getAddress().equals("00:00:00:00:00:00");
    }

    private void checkAndUpdateBroadcastCode(BluetoothLeBroadcastReceiveState recvState) {
        log("checkAndUpdateBroadcastCode");
        // Whenever receive state indicated code requested, assistant should set the broadcast code
@@ -1149,14 +1155,18 @@ public class BassClientStateMachine extends StateMachine {
                return;
            }
            mBluetoothLeBroadcastReceiveStates.put(characteristic.getInstanceId(), recvState);
            if (!isSourceAbsent(recvState)) {
                checkAndUpdateBroadcastCode(recvState);
                processPASyncState(recvState);
            }
            if (leaudioBroadcastResyncHelper()) {
                // Notify service BASS state ready for operations
                mService.getCallbacks().notifyBassStateReady(mDevice);
            }
        } else {
            log("Updated receiver state: " + recvState);
            mBluetoothLeBroadcastReceiveStates.replace(characteristic.getInstanceId(), recvState);
            String emptyBluetoothDevice = "00:00:00:00:00:00";
            if (oldRecvState.getSourceDevice() == null
                    || oldRecvState.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) {
            if (isSourceAbsent(oldRecvState)) {
                log("New Source Addition");
                removeMessages(CANCEL_PENDING_SOURCE_OPERATION);
                mService.getCallbacks()
@@ -1170,8 +1180,7 @@ public class BassClientStateMachine extends StateMachine {
                processPASyncState(recvState);
                processSyncStateChangeStats(recvState);
            } else {
                if (recvState.getSourceDevice() == null
                        || recvState.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) {
                if (isSourceAbsent(recvState)) {
                    BluetoothDevice removedDevice = oldRecvState.getSourceDevice();
                    log("sourceInfo removal " + removedDevice);
                    int prevSourceId = oldRecvState.getSourceId();
+54 −0
Original line number Diff line number Diff line
@@ -6659,4 +6659,58 @@ public class BassClientServiceTest {
                .periodicAdvertisingManagerRegisterSync(
                        any(), any(), anyInt(), anyInt(), any(), any());
    }

    /**
     * Test add source will be triggered if new device connected and its peer is synced to broadcast
     * source
     */
    @Test
    @EnableFlags({
        Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER,
        Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE
    })
    public void sinkBassStateReady_addSourceIfPeerDeviceSynced() {
        // Imitate broadcast being active
        doReturn(true).when(mLeAudioService).isPlaying(TEST_BROADCAST_ID);
        prepareTwoSynchronizedDevicesForLocalBroadcast();

        mBassClientService.getCallbacks().notifyBassStateReady(mCurrentDevice);
        TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper());

        assertThat(mStateMachines.size()).isEqualTo(2);
        for (BassClientStateMachine sm : mStateMachines.values()) {
            // No adding source if device remain synced
            verify(sm, never()).sendMessage(any());
        }

        // Remove source on the mCurrentDevice
        for (BassClientStateMachine sm : mStateMachines.values()) {
            if (sm.getDevice().equals(mCurrentDevice)) {
                injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID);
            }
        }

        mBassClientService.getCallbacks().notifyBassStateReady(mCurrentDevice);
        TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper());

        for (BassClientStateMachine sm : mStateMachines.values()) {
            // Verify mCurrentDevice is resuming the broadcast
            if (sm.getDevice().equals(mCurrentDevice1)) {
                verify(sm, never()).sendMessage(any());
            } else if (sm.getDevice().equals(mCurrentDevice)) {
                ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
                verify(sm, atLeast(1)).sendMessage(messageCaptor.capture());

                Message msg =
                        messageCaptor.getAllValues().stream()
                                .filter(m -> (m.what == BassClientStateMachine.ADD_BCAST_SOURCE))
                                .findFirst()
                                .orElse(null);
                assertThat(msg).isNotNull();
                clearInvocations(sm);
            } else {
                throw new AssertionError("Unexpected device");
            }
        }
    }
}
+34 −0
Original line number Diff line number Diff line
@@ -2619,6 +2619,39 @@ public class BassClientStateMachineTest {
                        eq(0x3)); // STATS_SYNC_AUDIO_SYNC_SUCCESS
    }

    @Test
    @EnableFlags({
        Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER,
        Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE
    })
    public void sinkConnected_queueAddingSourceForReceiveStateReady() {
        mBassClientStateMachine.connectGatt(true);
        BluetoothGattCallback cb = mBassClientStateMachine.mGattCallback;
        cb.onMtuChanged(null, 23, GATT_SUCCESS);
        initToConnectedState();

        mBassClientStateMachine.mNumOfBroadcastReceiverStates = 1;
        BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
        when(mBassClientService.getCallbacks()).thenReturn(callbacks);

        BassClientStateMachine.BluetoothGattTestableWrapper btGatt =
                Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class);
        mBassClientStateMachine.mBluetoothGatt = btGatt;
        BluetoothGattCharacteristic scanControlPoint =
                Mockito.mock(BluetoothGattCharacteristic.class);
        mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint;

        // Initial receive state with empty source device
        generateBroadcastReceiveStatesAndVerify(
                mEmptyTestDevice,
                TEST_SOURCE_ID,
                BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE,
                BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                0x0L);
        // Verify notifyBassStateReady is called
        verify(callbacks).notifyBassStateReady(eq(mTestDevice));
    }

    private void initToConnectingState() {
        allowConnection(true);
        allowConnectGatt(true);
@@ -2727,6 +2760,7 @@ public class BassClientStateMachineTest {
    private void prepareInitialReceiveStateForGatt() {
        initToConnectedState();
        mBassClientStateMachine.connectGatt(true);

        mBassClientStateMachine.mNumOfBroadcastReceiverStates = 2;
        BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
        when(mBassClientService.getCallbacks()).thenReturn(callbacks);