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

Commit 75292900 authored by Michal Belusiak's avatar Michal Belusiak Committed by Gerrit Code Review
Browse files

Merge "BassClientService: Add sync timeout on new sync when searching is stopped" into main

parents 3ba1e2e7 cb9e67c9
Loading
Loading
Loading
Loading
+41 −23
Original line number Diff line number Diff line
@@ -76,6 +76,7 @@ import com.android.bluetooth.le_audio.LeAudioService;
import com.android.internal.annotations.VisibleForTesting;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@@ -112,9 +113,14 @@ public class BassClientService extends ProfileService {
    private static final int BROADCAST_STATE_STOPPING = 3;
    private static final int BROADCAST_STATE_STREAMING = 4;

    private static final int MESSAGE_SYNC_TIMEOUT = 1;

    /* 1 minute timeout for primary device reconnection in Private Broadcast case */
    private static final int DIALING_OUT_TIMEOUT_MS = 60000;

    // 30 secs timeout for keeping PSYNC active when searching is stopped
    @VisibleForTesting static Duration sSyncActiveTimeout = Duration.ofSeconds(30);

    private static BassClientService sService;

    private final Map<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>();
@@ -142,7 +148,6 @@ public class BassClientService extends ProfileService {

    private HandlerThread mStateMachinesThread;
    private HandlerThread mCallbackHandlerThread;
    private Handler mHandler = null;
    private AdapterService mAdapterService;
    private DatabaseManager mDatabaseManager;
    private BluetoothAdapter mBluetoothAdapter = null;
@@ -178,6 +183,19 @@ public class BassClientService extends ProfileService {
    @VisibleForTesting
    ServiceFactory mServiceFactory = new ServiceFactory();

    private final Handler mHandler =
            new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case MESSAGE_SYNC_TIMEOUT:
                            log("MESSAGE_SYNC_TIMEOUT: clear all sync data");
                            clearAllSyncData();
                            break;
                    }
                }
            };

    public BassClientService(Context ctx) {
        super(ctx);
    }
@@ -462,9 +480,6 @@ public class BassClientService extends ProfileService {
                "DatabaseManager cannot be null when BassClientService starts");
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        // Setup Handler to handle local broadcast use cases.
        mHandler = new Handler(Looper.getMainLooper());

        mStateMachines.clear();
        mStateMachinesThread = new HandlerThread("BassClientService.StateMachines");
        mStateMachinesThread.start();
@@ -530,11 +545,7 @@ public class BassClientService extends ProfileService {
            mStateMachinesThread = null;
        }

        // Unregister Handler and stop all queued messages.
        if (mHandler != null) {
        mHandler.removeCallbacksAndMessages(null);
            mHandler = null;
        }

        setBassClientService(null);
        if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
@@ -571,12 +582,12 @@ public class BassClientService extends ProfileService {
            }
        } else {
            synchronized (mSearchScanCallbackLock) {
                if (mBluetoothLeScannerWrapper != null) {
                if (mBluetoothLeScannerWrapper != null && mSearchScanCallback != null) {
                    mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback);
                    mBluetoothLeScannerWrapper = null;
                }
                mBluetoothLeScannerWrapper = null;
                mSearchScanCallback = null;
                cleanAllSyncData();
                clearAllSyncData();
            }

            mLocalBroadcastReceivers.clear();
@@ -1551,6 +1562,7 @@ public class BassClientService extends ProfileService {
                            informConnectedDeviceAboutScanOffloadStop();
                        }
                    };
            mHandler.removeMessages(MESSAGE_SYNC_TIMEOUT);
            // when starting scan, clear the previously cached broadcast scan results
            mCachedBroadcasts.clear();
            if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
@@ -1626,11 +1638,7 @@ public class BassClientService extends ProfileService {
            }
        } else {
            synchronized (mSearchScanCallbackLock) {
                if (mBluetoothLeScannerWrapper == null) {
                    Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
                    return;
                }
                if (mSearchScanCallback == null) {
                if (mBluetoothLeScannerWrapper == null || mSearchScanCallback == null) {
                    Log.e(TAG, "Scan not started yet");
                    mCallbacks.notifySearchStopFailed(BluetoothStatusCodes.ERROR_UNKNOWN);
                    return;
@@ -1638,7 +1646,7 @@ public class BassClientService extends ProfileService {
                mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback);
                mBluetoothLeScannerWrapper = null;
                mSearchScanCallback = null;
                cleanAllSyncData();
                clearAllSyncData();
                informConnectedDeviceAboutScanOffloadStop();
                sEventLogger.logd(TAG, "stopSearchingForSources");
                mCallbacks.notifySearchStopped(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
@@ -1646,7 +1654,7 @@ public class BassClientService extends ProfileService {
        }
    }

    private void cleanAllSyncData() {
    private void clearAllSyncData() {
        mSourceSyncRequestsQueue.clear();
        mPendingSourcesToAdd.clear();

@@ -1708,6 +1716,16 @@ public class BassClientService extends ProfileService {
                        null);
                addActiveSyncedSource(syncHandle);

                synchronized (mSearchScanCallbackLock) {
                    // when searching is stopped then start timer to stop active syncs
                    if (mSearchScanCallback == null) {
                        mHandler.removeMessages(MESSAGE_SYNC_TIMEOUT);
                        log("onSyncEstablished started timeout for canceling syncs");
                        mHandler.sendEmptyMessageDelayed(
                                MESSAGE_SYNC_TIMEOUT, sSyncActiveTimeout.toMillis());
                    }
                }

                // update valid sync handle in mPeriodicAdvCallbacksMap
                synchronized (mPeriodicAdvCallbacksMap) {
                    if (mPeriodicAdvCallbacksMap.containsKey(BassConstants.INVALID_SYNC_HANDLE)) {
@@ -1766,8 +1784,8 @@ public class BassClientService extends ProfileService {
                }
            }
            int broadcastId = getBroadcastIdForSyncHandle(syncHandle);
            cleanAllDataForSyncHandle(syncHandle);
            // Clean from cache to make possible sync again
            clearAllDataForSyncHandle(syncHandle);
            // Clear from cache to make possible sync again
            mCachedBroadcasts.remove(broadcastId);
        }

@@ -1811,7 +1829,7 @@ public class BassClientService extends ProfileService {
        }
    }

    private void cleanAllDataForSyncHandle(Integer syncHandle) {
    private void clearAllDataForSyncHandle(Integer syncHandle) {
        removeActiveSyncedSource(syncHandle);
        mPeriodicAdvCallbacksMap.remove(syncHandle);
        mSyncHandleToBaseDataMap.remove(syncHandle);
@@ -1965,7 +1983,7 @@ public class BassClientService extends ProfileService {
        } else {
            log("calling unregisterSync, not found syncHandle: " + syncHandle);
        }
        cleanAllDataForSyncHandle(syncHandle);
        clearAllDataForSyncHandle(syncHandle);
        return true;
    }

+302 −0
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.LinkedBlockingQueue;
@@ -538,6 +539,307 @@ public class BassClientServiceTest {
        }
    }

    @Test
    public void testStopSearchingForSources() {
        mSetFlagsRule.enableFlags(
                Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE);

        prepareConnectedDeviceGroup();
        startSearchingForSources();

        // Scan and sync 1
        clearInvocations(mMethodProxy);
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        verify(mMethodProxy, timeout(TIMEOUT_MS).times(1))
                .periodicAdvertisingManagerRegisterSync(
                        any(), any(), anyInt(), anyInt(), any(), any());
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);
        assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1);
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(mSourceDevice);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(TEST_BROADCAST_ID);

        // Stop searching
        clearInvocations(mMethodProxy);
        clearInvocations(mBluetoothLeScannerWrapper);
        mBassClientService.stopSearchingForSources();

        verify(mBluetoothLeScannerWrapper).stopScan(mCallbackCaptor.getValue());
        for (BassClientStateMachine sm : mStateMachines.values()) {
            verify(sm).sendMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD);
        }

        // Check if unsyced
        verify(mMethodProxy, timeout(TIMEOUT_MS).times(1))
                .periodicAdvertisingManagerUnregisterSync(any(), any());
        assertThat(mBassClientService.getActiveSyncedSources()).isEmpty();
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(BassConstants.INVALID_BROADCAST_ID);
    }

    @Test
    public void testStop() {
        mSetFlagsRule.enableFlags(
                Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE);

        prepareConnectedDeviceGroup();
        startSearchingForSources();

        // Scan and sync 1
        clearInvocations(mMethodProxy);
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        verify(mMethodProxy, timeout(TIMEOUT_MS).times(1))
                .periodicAdvertisingManagerRegisterSync(
                        any(), any(), anyInt(), anyInt(), any(), any());
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);
        assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1);
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(mSourceDevice);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(TEST_BROADCAST_ID);

        // Stop
        clearInvocations(mMethodProxy);
        clearInvocations(mBluetoothLeScannerWrapper);
        mBassClientService.stop();

        // Check if unsyced
        verify(mMethodProxy, timeout(TIMEOUT_MS).times(1))
                .periodicAdvertisingManagerUnregisterSync(any(), any());
        assertThat(mBassClientService.getActiveSyncedSources()).isEmpty();
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(BassConstants.INVALID_BROADCAST_ID);
    }

    @Test
    public void testStopSearchingForSources_startAndSyncAgain() {
        mSetFlagsRule.enableFlags(
                Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE);

        prepareConnectedDeviceGroup();
        startSearchingForSources();

        // Scan and sync 1
        clearInvocations(mMethodProxy);
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);

        // Stop searching
        mBassClientService.stopSearchingForSources();

        // Start searching again
        clearInvocations(mBluetoothLeScannerWrapper);
        startSearchingForSources();

        // Sync the same device again
        clearInvocations(mMethodProxy);
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        verify(mMethodProxy, timeout(TIMEOUT_MS).times(1))
                .periodicAdvertisingManagerRegisterSync(
                        any(), any(), anyInt(), anyInt(), any(), any());
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);
        assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1);
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(mSourceDevice);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(TEST_BROADCAST_ID);
    }

    @Test
    public void testStop_startAndSyncAgain() {
        mSetFlagsRule.enableFlags(
                Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE);

        prepareConnectedDeviceGroup();
        startSearchingForSources();

        // Scan and sync 1
        clearInvocations(mMethodProxy);
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);

        // Stop
        mBassClientService.stop();

        // Start again
        mBassClientService.start();

        // Start searching again
        clearInvocations(mBluetoothLeScannerWrapper);
        prepareConnectedDeviceGroup();
        startSearchingForSources();

        // Sync the same device again
        clearInvocations(mMethodProxy);
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        verify(mMethodProxy, timeout(TIMEOUT_MS).times(1))
                .periodicAdvertisingManagerRegisterSync(
                        any(), any(), anyInt(), anyInt(), any(), any());
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);
        assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1);
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(mSourceDevice);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(TEST_BROADCAST_ID);
    }

    @Test
    public void testStopSearchingForSources_addSourceCauseSyncEvenWithoutScanning() {
        mSetFlagsRule.enableFlags(
                Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE);

        prepareConnectedDeviceGroup();
        startSearchingForSources();

        // Scan and sync 1
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);

        // Stop searching
        mBassClientService.stopSearchingForSources();

        // Add source to unsynced broadcast, causes synchronization first
        BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID);
        clearInvocations(mMethodProxy);
        mBassClientService.addSource(mCurrentDevice, meta, true);
        handleHandoverSupport();
        verify(mMethodProxy, timeout(TIMEOUT_MS).times(1))
                .periodicAdvertisingManagerRegisterSync(
                        any(), any(), anyInt(), anyInt(), any(), any());

        // Verify not getting ADD_BCAST_SOURCE message before source sync
        assertThat(mStateMachines.size()).isEqualTo(2);
        for (BassClientStateMachine sm : mStateMachines.values()) {
            ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
            verify(sm, never()).sendMessage(messageCaptor.capture());

            Message msg =
                    messageCaptor.getAllValues().stream()
                            .filter(
                                    m ->
                                            (m.what == BassClientStateMachine.ADD_BCAST_SOURCE)
                                                    && (m.obj == meta))
                            .findFirst()
                            .orElse(null);
            assertThat(msg).isNull();

            clearInvocations(sm);
        }

        // Source synced which cause execute pending add source
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);
        assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1);
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(mSourceDevice);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(TEST_BROADCAST_ID);

        // Verify all group members getting ADD_BCAST_SOURCE message
        assertThat(mStateMachines.size()).isEqualTo(2);
        for (BassClientStateMachine sm : mStateMachines.values()) {
            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)
                                                    && (m.obj == meta))
                            .findFirst()
                            .orElse(null);
            assertThat(msg).isNotNull();
        }
    }

    @Test
    public void testStopSearchingForSources_timeoutForActiveSync() {
        mSetFlagsRule.enableFlags(
                Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE);

        prepareConnectedDeviceGroup();
        startSearchingForSources();

        // Scan and sync 1
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);

        // Stop searching
        mBassClientService.stopSearchingForSources();

        // Add source to unsynced broadcast, causes synchronization first
        BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID);
        mBassClientService.addSource(mCurrentDevice, meta, true);
        handleHandoverSupport();

        // Source synced which cause start timeout event
        mBassClientService.sSyncActiveTimeout = Duration.ofSeconds(1);
        clearInvocations(mMethodProxy);
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);

        assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1);
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(mSourceDevice);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(TEST_BROADCAST_ID);

        // Check if unsyced
        verify(mMethodProxy, timeout(2000).times(1))
                .periodicAdvertisingManagerUnregisterSync(any(), any());
        assertThat(mBassClientService.getActiveSyncedSources()).isEmpty();
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(BassConstants.INVALID_BROADCAST_ID);
    }

    @Test
    public void testStopSearchingForSources_clearTimeoutForActiveSync() {
        mSetFlagsRule.enableFlags(
                Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE);

        prepareConnectedDeviceGroup();
        startSearchingForSources();

        // Scan and sync 1
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);

        // Stop searching
        mBassClientService.stopSearchingForSources();

        // Add source to unsynced broadcast, causes synchronization first
        BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID);
        mBassClientService.addSource(mCurrentDevice, meta, true);
        handleHandoverSupport();

        // Source synced which cause start timeout event
        mBassClientService.sSyncActiveTimeout = Duration.ofSeconds(1);
        clearInvocations(mMethodProxy);
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);

        assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1);
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(mSourceDevice);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(TEST_BROADCAST_ID);

        // Start searching again should clear timeout
        clearInvocations(mBluetoothLeScannerWrapper);
        clearInvocations(mMethodProxy);
        startSearchingForSources();

        verify(mMethodProxy, timeout(2000).times(0))
                .periodicAdvertisingManagerUnregisterSync(any(), any());
        assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1);
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(mSourceDevice);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(TEST_BROADCAST_ID);
    }

    private byte[] getScanRecord(int broadcastId) {
        return new byte[] {
            0x02,