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

Commit b41b1922 authored by Grzegorz Kołodziejczyk's avatar Grzegorz Kołodziejczyk
Browse files

bass_client: Don't remove source on stream suspension

This change allows sink devices to take responsibility over maintaining
source synchronization while requesting unicast streaming.

Tag: #bug
Bug: 330690507
Bug: 333050419
Test: atest BassClientServiceTest
Test: atest BassClientStateMachineTest
Change-Id: I0d5a01c72aac22a88e3623b6bd2e96d42eabdfd2
parent e7ceccfb
Loading
Loading
Loading
Loading
+202 −46
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@ package com.android.bluetooth.bass_client;

import static android.Manifest.permission.BLUETOOTH_CONNECT;

import static com.android.bluetooth.flags.Flags.leaudioBroadcastAudioHandoverPolicies;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastFeatureSupport;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastAssistantPeripheralEntrustment;
import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;

import android.bluetooth.BluetoothAdapter;
@@ -43,6 +45,7 @@ import android.os.Message;
import android.os.ParcelUuid;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.provider.DeviceConfig;
import android.sysprop.BluetoothProperties;
import android.util.Log;
import android.util.Pair;
@@ -54,7 +57,6 @@ import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
import com.android.bluetooth.flags.Flags;
import com.android.bluetooth.le_audio.LeAudioService;
import com.android.internal.annotations.VisibleForTesting;

@@ -975,6 +977,44 @@ public class BassClientService extends ProfileService {
        }
    }

    private int areValidParametersToModifySource(
            BluetoothLeBroadcastMetadata updatedMetadata,
            BassClientStateMachine stateMachine,
            Integer deviceSourceId,
            BluetoothDevice device) {
        if (updatedMetadata == null || stateMachine == null) {
            log(
                    "areValidParametersToModifySource: Error bad parameters: sourceId = "
                            + deviceSourceId
                            + " updatedMetadata = "
                            + updatedMetadata);
            return BluetoothStatusCodes.ERROR_BAD_PARAMETERS;
        }
        if (deviceSourceId == BassConstants.INVALID_SOURCE_ID) {
            log("areValidParametersToModifySource: no such sourceId for device: " + device);
            return BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_INVALID_SOURCE_ID;
        }
        if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
            log("areValidParametersToModifySource: device is not connected");
            return BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR;
        }
        byte[] code = updatedMetadata.getBroadcastCode();
        if ((code != null) && (code.length != 0)) {
            if ((code.length > 16) || (code.length < 4)) {
                log(
                        "areValidParametersToModifySource: Invalid broadcast code length: "
                                + code.length
                                + ", should be between 4 and 16 octets");
                return BluetoothStatusCodes.ERROR_BAD_PARAMETERS;
            }
        }
        if (stateMachine.hasPendingSourceOperation()) {
            throw new IllegalStateException("modifySource: source operation already pending");
        }

        return BluetoothStatusCodes.SUCCESS;
    }

    void handleConnectionStateChanged(BluetoothDevice device, int fromState, int toState) {
        mHandler.post(() -> connectionStateChanged(device, fromState, toState));
    }
@@ -1483,12 +1523,28 @@ public class BassClientService extends ProfileService {
            return;
        }

        if (leaudioBroadcastAssistantPeripheralEntrustment()) {
            if (isLocalBroadcast(sourceMetadata)) {
                LeAudioService leAudioService = mServiceFactory.getLeAudioService();
                if (leAudioService == null
                        || !leAudioService.isPlaying(sourceMetadata.getBroadcastId())) {
                    Log.w(TAG, "addSource: Local source can't be add");

                    mCallbacks.notifySourceAddFailed(
                            sink,
                            sourceMetadata,
                            BluetoothStatusCodes.ERROR_LOCAL_NOT_ENOUGH_RESOURCES);
                }
            }
        } else {
            if (!isAllowedToAddSource()) {
                Log.d(TAG, "Add source to pending list");
                mPendingAddSources.push(new AddSourceData(sink, sourceMetadata, isGroupOp));

                return;
            }
        }


        byte[] code = sourceMetadata.getBroadcastCode();
        for (BluetoothDevice device : devices) {
@@ -1628,37 +1684,14 @@ public class BassClientService extends ProfileService {
            BluetoothDevice device = deviceSourceIdPair.getKey();
            Integer deviceSourceId = deviceSourceIdPair.getValue();
            BassClientStateMachine stateMachine = getOrCreateStateMachine(device);
            if (updatedMetadata == null || stateMachine == null) {
                log("modifySource: Error bad parameters: sourceId = " + deviceSourceId
                        + " updatedMetadata = " + updatedMetadata);
                mCallbacks.notifySourceModifyFailed(device, sourceId,
                        BluetoothStatusCodes.ERROR_BAD_PARAMETERS);
                continue;
            }
            if (deviceSourceId == BassConstants.INVALID_SOURCE_ID) {
                log("modifySource: no such sourceId for device: " + device);
                mCallbacks.notifySourceModifyFailed(device, sourceId,
                        BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_INVALID_SOURCE_ID);
                continue;
            }
            if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
                log("modifySource: device is not connected");
                mCallbacks.notifySourceModifyFailed(device, sourceId,
                        BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
                continue;
            }
            if ((code != null) && (code.length != 0)) {
                if ((code.length > 16) || (code.length < 4)) {
                    log("Invalid broadcast code length: " + code.length
                            + ", should be between 4 and 16 octets");
                    mCallbacks.notifySourceModifyFailed(device, sourceId,
                            BluetoothStatusCodes.ERROR_BAD_PARAMETERS);

            int statusCode =
                    areValidParametersToModifySource(
                            updatedMetadata, stateMachine, deviceSourceId, device);
            if (statusCode != BluetoothStatusCodes.SUCCESS) {
                mCallbacks.notifySourceModifyFailed(device, sourceId, statusCode);
                continue;
            }
            }
            if (stateMachine.hasPendingSourceOperation()) {
                throw new IllegalStateException("modifySource: source operation already pending");
            }

            sEventLogger.logd(
                    TAG,
@@ -1848,6 +1881,38 @@ public class BassClientService extends ProfileService {
        Log.d(TAG, msg);
    }

    private List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>>
            getReceiveStateDevicePairs(int broadcastId) {
        List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> list = new ArrayList<>();

        for (BluetoothDevice device : getConnectedDevices()) {
            for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
                /* Check if local/last broadcast is the synced one. Invalid broadcast ID means
                 * that all receivers should be considered.
                 */
                if ((broadcastId != BassConstants.INVALID_BROADCAST_ID)
                        && (receiveState.getBroadcastId() != broadcastId)) {
                    continue;
                }

                list.add(
                        new Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>(
                                receiveState, device));
            }
        }

        return list;
    }

    private void stopSourceReceivers(int broadcastId) {
        List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToRemove =
                getReceiveStateDevicePairs(broadcastId);

        for (Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice> pair : sourcesToRemove) {
            removeSource(pair.second, pair.first.getSourceId());
        }
    }

    private void stopSourceReceivers(int broadcastId, boolean store) {
        Log.d(TAG, "stopSourceReceivers(), broadcastId: " + broadcastId + ", store: " + store);

@@ -1884,7 +1949,7 @@ public class BassClientService extends ProfileService {
    }

    private boolean isAllowedToAddSource() {
        if (Flags.leaudioBroadcastAudioHandoverPolicies()) {
        if (leaudioBroadcastAudioHandoverPolicies()) {
            /* Check if should wait for status update */
            if (mUnicastSourceStreamStatus.isEmpty()) {
                /* Assistant was not active, inform about activation */
@@ -2012,6 +2077,23 @@ public class BassClientService extends ProfileService {
        }
    }

    /** Cache suspending sources */
    public void cacheSuspendingSources(int broadcastId) {
        sEventLogger.logd(TAG, "Cache suspending sources: " + broadcastId);
        List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToCache =
                getReceiveStateDevicePairs(broadcastId);

        if (!mPausedBroadcastSinks.isEmpty()) {
            Log.w(TAG, "cacheSuspendingSources(), paused broadcast sinks are replaced");
            sEventLogger.logd(TAG, "Clear broadcast sinks paused cache");
            mPausedBroadcastSinks.clear();
        }

        for (Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice> pair : sourcesToCache) {
            mPausedBroadcastSinks.add(pair.second);
        }
    }

    /** Request receivers to suspend broadcast sources synchronization */
    public void suspendReceiversSourceSynchronization(int broadcastId) {
        sEventLogger.logd(TAG, "Suspend receivers source synchronization: " + broadcastId);
@@ -2027,8 +2109,12 @@ public class BassClientService extends ProfileService {
    /** Request receivers to stop broadcast sources synchronization and remove them */
    public void stopReceiversSourceSynchronization(int broadcastId) {
        sEventLogger.logd(TAG, "Stop receivers source synchronization: " + broadcastId);
        if (leaudioBroadcastAssistantPeripheralEntrustment()) {
            stopSourceReceivers(broadcastId);
        } else {
            stopSourceReceivers(broadcastId, false);
        }
    }

    /** Request receivers to resume broadcast source synchronization */
    public void resumeReceiversSourceSynchronization() {
@@ -2039,8 +2125,72 @@ public class BassClientService extends ProfileService {
            sEventLogger.logd(TAG, "Remove broadcast sink from paused cache: " + sink);
            BluetoothLeBroadcastMetadata metadata = mBroadcastMetadataMap.get(sink);

            if (leaudioBroadcastAssistantPeripheralEntrustment()) {
                if (metadata == null) {
                    Log.w(
                            TAG,
                            "resumeReceiversSourceSynchronization: failed to get metadata to resume"
                                    + " sink: "
                                    + sink);
                    continue;
                }

                // For each device, find the source ID having this broadcast ID
                BassClientStateMachine stateMachine = getOrCreateStateMachine(sink);
                List<BluetoothLeBroadcastReceiveState> sources = stateMachine.getAllSources();
                Optional<BluetoothLeBroadcastReceiveState> receiveState =
                        sources.stream()
                                .filter(e -> e.getBroadcastId() == metadata.getBroadcastId())
                                .findAny();

                if (receiveState.isPresent()) {
                    /* Update metadata for sink device */
                    mBroadcastMetadataMap.put(sink, metadata);

                    int sourceId = receiveState.get().getSourceId();
                    int statusCode =
                            areValidParametersToModifySource(
                                    metadata, stateMachine, sourceId, sink);

                    if (statusCode != BluetoothStatusCodes.SUCCESS) {
                        mCallbacks.notifySourceModifyFailed(sink, sourceId, statusCode);
                        continue;
                    }

                    sEventLogger.logd(
                            TAG,
                            "Modify Broadcast Source (resume): device: "
                                    + sink
                                    + ", sourceId: "
                                    + sourceId
                                    + ", updatedMetadata: "
                                    + metadata);

                    Message message =
                            stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE);
                    message.arg1 = sourceId;
                    message.arg2 =
                            DeviceConfig.getBoolean(
                                            DeviceConfig.NAMESPACE_BLUETOOTH,
                                            "persist.vendor.service.bt.defNoPAS",
                                            true)
                                    ? BassConstants.PA_SYNC_PAST_AVAILABLE
                                    : BassConstants.PA_SYNC_PAST_NOT_AVAILABLE;
                    message.obj = metadata;
                    stateMachine.sendMessage(message);
                } else {
                    addSource(sink, metadata, false);
                }
            } else {
                if (metadata != null) {
                    addSource(sink, metadata, false);
                } else {
                    Log.w(
                            TAG,
                            "resumeReceiversSourceSynchronization: failed to get metadata to resume"
                                    + " sink: "
                                    + sink);
                }
            }
        }
    }
@@ -2051,14 +2201,19 @@ public class BassClientService extends ProfileService {

        if (status == STATUS_LOCAL_STREAM_REQUESTED) {
            if (isAnyReceiverReceivingBroadcast(getConnectedDevices())) {
                if (leaudioBroadcastAssistantPeripheralEntrustment()) {
                    cacheSuspendingSources(BassConstants.INVALID_BROADCAST_ID);
                } else {
                    suspendAllReceiversSourceSynchronization();
                }
            }
        } else if (status == STATUS_LOCAL_STREAM_SUSPENDED) {
            /* Resume paused receivers if there are some */
            if (!mPausedBroadcastSinks.isEmpty()) {
                resumeReceiversSourceSynchronization();
            }

            if (!leaudioBroadcastAssistantPeripheralEntrustment()) {
                /* Add pending sources if there are some */
                while (!mPendingAddSources.isEmpty()) {
                    AddSourceData addSourceData = mPendingAddSources.pop();
@@ -2068,6 +2223,7 @@ public class BassClientService extends ProfileService {
                            addSourceData.mSourceMetadata,
                            addSourceData.mIsGroupOp);
                }
            }
        } else if (status == STATUS_LOCAL_STREAM_STREAMING) {
            Log.d(TAG, "Ignore STREAMING source status");
        }
+17 −2
Original line number Diff line number Diff line
@@ -1204,6 +1204,19 @@ public class LeAudioService extends ProfileService {
            return;
        }

        if (Flags.leaudioBroadcastAssistantPeripheralEntrustment()) {
            if (!isPlaying(broadcastId)) {
                Log.d(TAG, "pauseBroadcast: Broadcast is not playing, skip pause request");
                return;
            }

            // Due to broadcast pause sinks may lose synchronization
            BassClientService bassClientService = getBassClientService();
            if (bassClientService != null) {
                bassClientService.cacheSuspendingSources(broadcastId);
            }
        }

        Log.d(TAG, "pauseBroadcast");
        mLeAudioBroadcasterNativeInterface.pauseBroadcast(broadcastId);
    }
@@ -3054,9 +3067,11 @@ public class LeAudioService extends ProfileService {
                    notifyPlaybackStopped(broadcastId,
                            BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST);

                    if (!Flags.leaudioBroadcastAssistantPeripheralEntrustment()) {
                        if (bassClientService != null) {
                            bassClientService.suspendReceiversSourceSynchronization(broadcastId);
                        }
                    }

                    transitionFromBroadcastToUnicast();
                    break;
+225 −43

File changed.

Preview size limit exceeded, changes collapsed.