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

Commit 588c7b6a authored by Michal Belusiak's avatar Michal Belusiak
Browse files

Bass: Do not notify about source lost immediately

Start 5s broadcast sync lost timer, instead of immediate
notify about sync lost.
Start 5s broadcast sync lost timer when sync removed because of
max active synced sources limit reached.
Restart timer on each new advertising from that broadcaster when
timer is started.
Notify about lost and clear cache if needed, when timer fire out.
Additionally, handle all timeouts per broadcast id.

Bug: 372390557
Bug: 363168099
Test: atest BassClientServiceTest
Change-Id: I48c929496ee7b1a1bc477938d10188f00e51631e
parent 687f2b2a
Loading
Loading
Loading
Loading
+178 −59
Original line number Diff line number Diff line
@@ -124,6 +124,7 @@ public class BassClientService extends ProfileService {
    @VisibleForTesting static final int MESSAGE_SYNC_TIMEOUT = 1;
    @VisibleForTesting static final int MESSAGE_BIG_MONITOR_TIMEOUT = 2;
    @VisibleForTesting static final int MESSAGE_BROADCAST_MONITOR_TIMEOUT = 3;
    @VisibleForTesting static final int MESSAGE_SYNC_LOST_TIMEOUT = 4;

    /* 1 minute timeout for primary device reconnection in Private Broadcast case */
    private static final int DIALING_OUT_TIMEOUT_MS = 60000;
@@ -137,6 +138,9 @@ public class BassClientService extends ProfileService {
    // 5 minutes timeout for monitoring broadcaster
    private static final Duration sBroadcasterMonitorTimeout = Duration.ofMinutes(5);

    // 5 seconds timeout for sync Lost notification
    private static final Duration sSyncLostTimeout = Duration.ofSeconds(5);

    private enum PauseType {
        HOST_INTENTIONAL,
        SINK_UNINTENTIONAL
@@ -216,27 +220,65 @@ public class BassClientService extends ProfileService {
                                clearAllSyncData();
                                break;
                            }
                        default:
                            break;
                    }
                }
            };

    @VisibleForTesting public final TimeoutHandler mTimeoutHandler = new TimeoutHandler();

    @VisibleForTesting
    public final class TimeoutHandler {
        private final Map<Integer, Handler> mHandlers = new HashMap<>();

        @VisibleForTesting
        public Handler getOrCreateHandler(int broadcastId) {
            return mHandlers.computeIfAbsent(
                    broadcastId,
                    key ->
                            new Handler(Looper.getMainLooper()) {
                                @Override
                                public void handleMessage(Message msg) {
                                    switch (msg.what) {
                                        case MESSAGE_SYNC_LOST_TIMEOUT:
                                            {
                                                log("MESSAGE_SYNC_LOST_TIMEOUT");
                                                // fall through
                                            }
                                        case MESSAGE_BROADCAST_MONITOR_TIMEOUT:
                                            {
                                                log("MESSAGE_BROADCAST_MONITOR_TIMEOUT");
                                int broadcastId = msg.arg1;
                                                List<Integer> activeSyncedSrc =
                                                        new ArrayList<>(getActiveSyncedSources());
                                                if (activeSyncedSrc.contains(
                                                        getSyncHandleForBroadcastId(broadcastId))) {
                                                    break;
                                                }
                                log("Notify broadcast source lost, broadcast id: " + broadcastId);
                                                // Clear from cache to make possible sync again
                                                // (only during active searching)
                                                synchronized (mSearchScanCallbackLock) {
                                                    if (mSearchScanCallback != null) {
                                                        mCachedBroadcasts.remove(broadcastId);
                                                    }
                                                }
                                                log(
                                                        "Notify broadcast source lost, broadcast"
                                                                + " id: "
                                                                + broadcastId);
                                                mCallbacks.notifySourceLost(broadcastId);
                                                if (!isSinkUnintentionalPauseType(broadcastId)) {
                                                    break;
                                                }
                                                // fall through
                                            }
                                        case MESSAGE_BIG_MONITOR_TIMEOUT:
                                            {
                                                log("MESSAGE_BIG_MONITOR_TIMEOUT");
                                int broadcastId = msg.arg1;
                                                stopSourceReceivers(broadcastId);
                                                synchronized (mSearchScanCallbackLock) {
                                    // when searching is stopped then clear all sync data
                                                    // when searching is stopped then clear all sync
                                                    // data
                                                    if (mSearchScanCallback == null) {
                                                        clearAllSyncData();
                                                    }
@@ -246,8 +288,62 @@ public class BassClientService extends ProfileService {
                                        default:
                                            break;
                                    }
                                    Handler handler = getOrCreateHandler(broadcastId);
                                    if (!handler.hasMessagesOrCallbacks()) {
                                        mHandlers.remove(broadcastId);
                                    }
                                }
                            });
        }

        void start(int broadcastId, int msg, Duration duration) {
            Handler handler = getOrCreateHandler(broadcastId);
            log(
                    "Started timeout: "
                            + ("broadcastId: " + broadcastId)
                            + (", msg: " + msg)
                            + (", duration: " + duration));
            handler.sendEmptyMessageDelayed(msg, duration.toMillis());
        }

        void stop(int broadcastId, int msg) {
            if (!mHandlers.containsKey(broadcastId)) {
                return;
            }
            Handler handler = getOrCreateHandler(broadcastId);
            handler.removeMessages(msg);
            if (!handler.hasMessagesOrCallbacks()) {
                mHandlers.remove(broadcastId);
            }
        }

        void stopAll() {
            for (Handler handler : mHandlers.values()) {
                handler.removeCallbacksAndMessages(null);
            }
            mHandlers.clear();
        }

        void stopAll(int msg) {
            Iterator<Map.Entry<Integer, Handler>> iterator = mHandlers.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<Integer, Handler> entry = iterator.next();
                Handler handler = entry.getValue();
                handler.removeMessages(msg);
                if (!handler.hasMessagesOrCallbacks()) {
                    iterator.remove();
                }
            }
        }

        boolean isStarted(int broadcastId, int msg) {
            if (!mHandlers.containsKey(broadcastId)) {
                return false;
            }
            Handler handler = getOrCreateHandler(broadcastId);
            return handler.hasMessages(msg);
        }
    }
            };

    public BassClientService(Context ctx) {
        super(ctx);
@@ -710,6 +806,7 @@ public class BassClientService extends ProfileService {
        }

        mHandler.removeCallbacksAndMessages(null);
        mTimeoutHandler.stopAll();

        setBassClientService(null);
        if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
@@ -1041,11 +1138,9 @@ public class BassClientService extends ProfileService {
                    mPausedBroadcastIds.put(broadcastId, PauseType.SINK_UNINTENTIONAL);
                    cacheSuspendingSources(broadcastId);

                    mHandler.removeMessages(MESSAGE_BIG_MONITOR_TIMEOUT);
                    Message newMsg = mHandler.obtainMessage(MESSAGE_BIG_MONITOR_TIMEOUT);
                    newMsg.arg1 = broadcastId;
                    log("Started MESSAGE_BIG_MONITOR_TIMEOUT");
                    mHandler.sendMessageDelayed(newMsg, sBigMonitorTimeout.toMillis());
                    mTimeoutHandler.stop(broadcastId, MESSAGE_BIG_MONITOR_TIMEOUT);
                    mTimeoutHandler.start(
                            broadcastId, MESSAGE_BIG_MONITOR_TIMEOUT, sBigMonitorTimeout);
                }
            }
        } else if (isEmptyBluetoothDevice(receiveState.getSourceDevice())) {
@@ -1832,8 +1927,10 @@ public class BassClientService extends ProfileService {
                                        TAG,
                                        "Broadcast Source Found: Broadcast ID: " + broadcastId);

                                if (broadcastId != BassConstants.INVALID_BROADCAST_ID
                                        && !mCachedBroadcasts.containsKey(broadcastId)) {
                                if (broadcastId == BassConstants.INVALID_BROADCAST_ID) {
                                    return;
                                }
                                if (!mCachedBroadcasts.containsKey(broadcastId)) {
                                    log("selectBroadcastSource: broadcastId " + broadcastId);
                                    mCachedBroadcasts.put(broadcastId, result);
                                    if (leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
@@ -1848,6 +1945,14 @@ public class BassClientService extends ProfileService {
                                            }
                                        }
                                    }
                                } else if (leaudioBroadcastResyncHelper()
                                        && mTimeoutHandler.isStarted(
                                                broadcastId, MESSAGE_SYNC_LOST_TIMEOUT)) {
                                    mTimeoutHandler.stop(broadcastId, MESSAGE_SYNC_LOST_TIMEOUT);
                                    mTimeoutHandler.start(
                                            broadcastId,
                                            MESSAGE_SYNC_LOST_TIMEOUT,
                                            sSyncLostTimeout);
                                }
                            }
                        }
@@ -1970,6 +2075,7 @@ public class BassClientService extends ProfileService {

    private void clearAllSyncData() {
        log("clearAllSyncData");
        mTimeoutHandler.stopAll(MESSAGE_SYNC_LOST_TIMEOUT);
        mSourceSyncRequestsQueue.clear();
        mSyncFailureCounter.clear();
        mPendingSourcesToAdd.clear();
@@ -2047,7 +2153,7 @@ public class BassClientService extends ProfileService {
                        }
                    }
                } else {
                    mHandler.removeMessages(MESSAGE_BROADCAST_MONITOR_TIMEOUT);
                    mTimeoutHandler.stop(broadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT);
                }

                // update valid sync handle in mPeriodicAdvCallbacksMap
@@ -2100,11 +2206,12 @@ public class BassClientService extends ProfileService {
                    mSyncFailureCounter.put(broadcastId, failsCounter);
                }
                if (isSinkUnintentionalPauseType(broadcastId)) {
                    if (!mHandler.hasMessages(MESSAGE_BROADCAST_MONITOR_TIMEOUT)) {
                        Message newMsg = mHandler.obtainMessage(MESSAGE_BROADCAST_MONITOR_TIMEOUT);
                        newMsg.arg1 = broadcastId;
                        log("Started MESSAGE_BROADCAST_MONITOR_TIMEOUT");
                        mHandler.sendMessageDelayed(newMsg, sBroadcasterMonitorTimeout.toMillis());
                    if (!mTimeoutHandler.isStarted(
                            broadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT)) {
                        mTimeoutHandler.start(
                                broadcastId,
                                MESSAGE_BROADCAST_MONITOR_TIMEOUT,
                                sBroadcasterMonitorTimeout);
                    }
                    addSelectSourceRequest(broadcastId, true);
                } else {
@@ -2183,16 +2290,23 @@ public class BassClientService extends ProfileService {
                    int failsCounter = mSyncFailureCounter.getOrDefault(broadcastId, 0) + 1;
                    mSyncFailureCounter.put(broadcastId, failsCounter);
                }
                mTimeoutHandler.stop(broadcastId, MESSAGE_SYNC_LOST_TIMEOUT);
                if (isSinkUnintentionalPauseType(broadcastId)) {
                    if (!mHandler.hasMessages(MESSAGE_BROADCAST_MONITOR_TIMEOUT)) {
                        Message newMsg = mHandler.obtainMessage(MESSAGE_BROADCAST_MONITOR_TIMEOUT);
                        newMsg.arg1 = broadcastId;
                        log("Started MESSAGE_BROADCAST_MONITOR_TIMEOUT");
                        mHandler.sendMessageDelayed(newMsg, sBroadcasterMonitorTimeout.toMillis());
                    if (!mTimeoutHandler.isStarted(
                            broadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT)) {
                        mTimeoutHandler.start(
                                broadcastId,
                                MESSAGE_BROADCAST_MONITOR_TIMEOUT,
                                sBroadcasterMonitorTimeout);
                    }
                    addSelectSourceRequest(broadcastId, true);
                } else {
                    // Clear from cache to make possible sync again (only during active searching)
                    if (leaudioBroadcastResyncHelper()) {
                        mTimeoutHandler.start(
                                broadcastId, MESSAGE_SYNC_LOST_TIMEOUT, sSyncLostTimeout);
                    } else {
                        // Clear from cache to make possible sync again (only during active
                        // searching)
                        synchronized (mSearchScanCallbackLock) {
                            if (mSearchScanCallback != null) {
                                mCachedBroadcasts.remove(broadcastId);
@@ -2203,6 +2317,7 @@ public class BassClientService extends ProfileService {
                    }
                }
            }
        }

        @Override
        public void onBigInfoAdvertisingReport(int syncHandle, boolean encrypted) {
@@ -2480,7 +2595,7 @@ public class BassClientService extends ProfileService {
                        + broadcastId
                        + ", hasPriority: "
                        + hasPriority);

        mTimeoutHandler.stop(broadcastId, MESSAGE_SYNC_LOST_TIMEOUT);
        ScanResult scanRes = getCachedBroadcast(broadcastId);
        if (scanRes != null) {
            ScanRecord scanRecord = scanRes.getScanRecord();
@@ -2572,9 +2687,11 @@ public class BassClientService extends ProfileService {
                cancelActiveSync(syncHandle);
            } else {
                Boolean canceledActiveSync = false;
                int broadcstIdToLostMonitoring = BassConstants.INVALID_BROADCAST_ID;
                for (int syncHandle : activeSyncedSrc) {
                    if (!isAnyReceiverSyncedToBroadcast(getBroadcastIdForSyncHandle(syncHandle))) {
                        canceledActiveSync = true;
                        broadcstIdToLostMonitoring = getBroadcastIdForSyncHandle(syncHandle);
                        cancelActiveSync(syncHandle);
                        break;
                    }
@@ -2582,8 +2699,11 @@ public class BassClientService extends ProfileService {
                if (!canceledActiveSync) {
                    int syncHandle = activeSyncedSrc.get(0);
                    // removing the 1st synced source before proceeding to add new
                    broadcstIdToLostMonitoring = getBroadcastIdForSyncHandle(syncHandle);
                    cancelActiveSync(syncHandle);
                }
                mTimeoutHandler.start(
                        broadcstIdToLostMonitoring, MESSAGE_SYNC_LOST_TIMEOUT, sSyncLostTimeout);
            }
        }

@@ -3447,14 +3567,13 @@ public class BassClientService extends ProfileService {
            return;
        }
        log("stopBigMonitoring");
        mHandler.removeMessages(MESSAGE_BIG_MONITOR_TIMEOUT);
        mHandler.removeMessages(MESSAGE_BROADCAST_MONITOR_TIMEOUT);

        mPausedBroadcastSinks.clear();

        Iterator<Integer> iterator = mPausedBroadcastIds.keySet().iterator();
        while (iterator.hasNext()) {
            int pausedBroadcastId = iterator.next();
            mTimeoutHandler.stop(pausedBroadcastId, MESSAGE_BIG_MONITOR_TIMEOUT);
            mTimeoutHandler.stop(pausedBroadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT);
            iterator.remove();
            synchronized (mSearchScanCallbackLock) {
                // when searching is stopped then stop active sync
@@ -3475,8 +3594,8 @@ public class BassClientService extends ProfileService {
        while (iterator.hasNext()) {
            int pausedBroadcastId = iterator.next();
            if (!isAnyReceiverSyncedToBroadcast(pausedBroadcastId)) {
                mHandler.removeMessages(MESSAGE_BIG_MONITOR_TIMEOUT);
                mHandler.removeMessages(MESSAGE_BROADCAST_MONITOR_TIMEOUT);
                mTimeoutHandler.stop(pausedBroadcastId, MESSAGE_BIG_MONITOR_TIMEOUT);
                mTimeoutHandler.stop(pausedBroadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT);
                iterator.remove();
                synchronized (mSearchScanCallbackLock) {
                    // when searching is stopped then stop active sync
@@ -3494,8 +3613,8 @@ public class BassClientService extends ProfileService {
            return;
        }
        log("stopBigMonitoring broadcastId: " + broadcastId + ", hostInitiated: " + hostInitiated);
        mHandler.removeMessages(MESSAGE_BIG_MONITOR_TIMEOUT);
        mHandler.removeMessages(MESSAGE_BROADCAST_MONITOR_TIMEOUT);
        mTimeoutHandler.stop(broadcastId, MESSAGE_BIG_MONITOR_TIMEOUT);
        mTimeoutHandler.stop(broadcastId, MESSAGE_BROADCAST_MONITOR_TIMEOUT);
        if (hostInitiated) {
            mPausedBroadcastIds.put(broadcastId, PauseType.HOST_INTENTIONAL);
        } else {
+270 −50

File changed.

Preview size limit exceeded, changes collapsed.

+3 −2
Original line number Diff line number Diff line
@@ -316,6 +316,8 @@ public final class BluetoothLeBroadcastAssistant implements BluetoothProfile, Au
         * Callback invoked when a new Broadcast Source is found together with the {@link
         * BluetoothLeBroadcastMetadata}.
         *
         * <p>Broadcast is found when it is available for user to synchronize with it.
         *
         * @param source {@link BluetoothLeBroadcastMetadata} representing a Broadcast Source
         * @hide
         */
@@ -443,8 +445,7 @@ public final class BluetoothLeBroadcastAssistant implements BluetoothProfile, Au
        /**
         * Callback invoked when the Broadcast Source is lost together with source broadcast id.
         *
         * <p>This callback is to notify source lost due to periodic advertising sync lost. Callback
         * client can know that the source notified by {@link
         * <p>Callback client can know that the source notified by {@link
         * Callback#onSourceFound(BluetoothLeBroadcastMetadata)} before is not available any more
         * after this callback.
         *