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

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

Merge changes I351f6592,I555e8a80,I6d2bb776 into main

* changes:
  le_audio: Suspend not allowed updated stream
  bass: Monitor LE Audio unicast source when managed
  bass: Add support for multiple sources per sink device
parents bf46d66f 6b4cc68d
Loading
Loading
Loading
Loading
+204 −82
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static com.android.bluetooth.flags.Flags.leaudioBroadcastApiGetLocalMetad
import static com.android.bluetooth.flags.Flags.leaudioBroadcastAssistantPeripheralEntrustment;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastExtractPeriodicScannerFromStateMachine;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastResyncHelper;
import static com.android.bluetooth.flags.Flags.leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator;
import static com.android.bluetooth.flags.Flags.leaudioSortScansToSyncByFails;

import android.annotation.RequiresPermission;
@@ -169,8 +170,8 @@ public class BassClientService extends ProfileService {
    private final Map<BluetoothDevice, List<Integer>> mGroupManagedSources =
            new ConcurrentHashMap<>();
    private final Map<BluetoothDevice, List<Integer>> mActiveSourceMap = new ConcurrentHashMap<>();
    private final Map<BluetoothDevice, BluetoothLeBroadcastMetadata> mBroadcastMetadataMap =
            new ConcurrentHashMap<>();
    private final Map<BluetoothDevice, Map<Integer, BluetoothLeBroadcastMetadata>>
            mBroadcastMetadataMap = new ConcurrentHashMap<>();
    private final HashSet<BluetoothDevice> mPausedBroadcastSinks = new HashSet<>();
    private final Map<BluetoothDevice, Pair<Integer, Integer>> mSinksWaitingForPast =
            new HashMap<>();
@@ -1173,8 +1174,12 @@ public class BassClientService extends ProfileService {
            return;
        }

        boolean isAssistantActive =
                areReceiversReceivingOnlyExternalBroadcast(getConnectedDevices());
        boolean isAssistantActive;
        if (leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator()) {
            isAssistantActive = hasPrimaryDeviceManagedExternalBroadcast();
        } else {
            isAssistantActive = areReceiversReceivingOnlyExternalBroadcast(getConnectedDevices());
        }

        if (isAssistantActive) {
            /* Assistant become active */
@@ -2879,6 +2884,77 @@ public class BassClientService extends ProfileService {
        }
    }

    private void storeSinkMetadata(
            BluetoothDevice device, int broadcastId, BluetoothLeBroadcastMetadata metadata) {
        if (device == null
                || broadcastId == BassConstants.INVALID_BROADCAST_ID
                || metadata == null) {
            Log.e(
                    TAG,
                    "Failed to store Sink Metadata, invalid parameters (device: "
                            + device
                            + ", broadcastId: "
                            + broadcastId
                            + ", metadata: "
                            + metadata
                            + ")");
            return;
        }

        mBroadcastMetadataMap.compute(
                device,
                (key, existingMap) -> {
                    if (existingMap == null) {
                        existingMap = new ConcurrentHashMap<>();
                    }
                    existingMap.put(broadcastId, metadata);
                    return existingMap;
                });
    }

    private void removeSinkMetadata(BluetoothDevice device, int broadcastId) {
        if (device == null || broadcastId == BassConstants.INVALID_BROADCAST_ID) {
            Log.e(
                    TAG,
                    "Failed to remove Sink Metadata, invalid parameters (device: "
                            + device
                            + ", broadcastId: "
                            + broadcastId
                            + ")");
            return;
        }

        mBroadcastMetadataMap.compute(
                device,
                (key, existingMap) -> {
                    if (existingMap != null) {
                        existingMap.remove(broadcastId);
                        if (existingMap.isEmpty()) {
                            return null;
                        }
                    } else {
                        Log.w(
                                TAG,
                                "There is no metadata related to sink (device: "
                                        + device
                                        + ", broadcastId: "
                                        + broadcastId);
                    }
                    return existingMap;
                });
    }

    private void removeSinkMetadata(BluetoothDevice device) {
        if (device == null) {
            Log.e(
                    TAG,
                    "Failed to remove Sink Metadata, invalid parameters (device: " + device + ")");
            return;
        }

        mBroadcastMetadataMap.remove(device);
    }

    /**
     * Add a Broadcast Source to the Broadcast Sink
     *
@@ -3048,7 +3124,7 @@ public class BassClientService extends ProfileService {
                    }

                    /* Store metadata for sink device */
                    mBroadcastMetadataMap.put(device, sourceMetadata);
                    storeSinkMetadata(device, sourceMetadata.getBroadcastId(), sourceMetadata);

                    Message message =
                            stateMachine.obtainMessage(BassClientStateMachine.SWITCH_BCAST_SOURCE);
@@ -3076,7 +3152,7 @@ public class BassClientService extends ProfileService {
            }

            /* Store metadata for sink device */
            mBroadcastMetadataMap.put(device, sourceMetadata);
            storeSinkMetadata(device, sourceMetadata.getBroadcastId(), sourceMetadata);

            if (isGroupOp) {
                enqueueSourceGroupOp(
@@ -3140,7 +3216,7 @@ public class BassClientService extends ProfileService {
        }

        /* Update metadata for sink device */
        mBroadcastMetadataMap.put(sink, updatedMetadata);
        storeSinkMetadata(sink, updatedMetadata.getBroadcastId(), updatedMetadata);

        byte[] code = updatedMetadata.getBroadcastCode();
        for (Map.Entry<BluetoothDevice, Integer> deviceSourceIdPair : devices.entrySet()) {
@@ -3209,16 +3285,13 @@ public class BassClientService extends ProfileService {
        Map<BluetoothDevice, Integer> devices = getGroupManagedDeviceSources(sink, sourceId).second;
        for (Map.Entry<BluetoothDevice, Integer> deviceSourceIdPair : devices.entrySet()) {
            BluetoothDevice device = deviceSourceIdPair.getKey();
            /* Removes metadata for sink device if not paused */
            if (!mPausedBroadcastSinks.contains(device)) {
                mBroadcastMetadataMap.remove(device);
            }

            Integer deviceSourceId = deviceSourceIdPair.getValue();
            BassClientStateMachine stateMachine = getOrCreateStateMachine(device);
            int statusCode =
                    validateParametersForSourceOperation(stateMachine, device, deviceSourceId);
            if (statusCode != BluetoothStatusCodes.SUCCESS) {
                removeSinkMetadata(device);
                mCallbacks.notifySourceRemoveFailed(device, sourceId, statusCode);
                continue;
            }
@@ -3226,6 +3299,15 @@ public class BassClientService extends ProfileService {
            BluetoothLeBroadcastMetadata metaData =
                    stateMachine.getCurrentBroadcastMetadata(sourceId);

            /* Removes metadata for sink device if not paused */
            if (!mPausedBroadcastSinks.contains(device)) {
                if (metaData != null) {
                    removeSinkMetadata(device, metaData.getBroadcastId());
                } else {
                    removeSinkMetadata(device);
                }
            }

            if (metaData != null) {
                stopBigMonitoring(metaData.getBroadcastId(), true);
            }
@@ -3574,7 +3656,7 @@ public class BassClientService extends ProfileService {
                    continue;
                }

                mBroadcastMetadataMap.remove(sink);
                removeSinkMetadata(sink);

                /* Check if there is any other primary device receiving this broadcast */
                if (devices.stream()
@@ -3629,10 +3711,18 @@ 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);
        Map<Integer, BluetoothLeBroadcastMetadata> entry = mBroadcastMetadataMap.get(sink);

        if (entry == null) {
            Log.d(TAG, "checkAndResumeBroadcast: no entry for device: " + sink + ", available");
            return;
        }

        for (Map.Entry<Integer, BluetoothLeBroadcastMetadata> idMetadataIdPair : entry.entrySet()) {
            BluetoothLeBroadcastMetadata metadata = idMetadataIdPair.getValue();
            if (metadata == null) {
                Log.d(TAG, "checkAndResumeBroadcast: no metadata available");
            return;
                continue;
            }
            for (BluetoothDevice groupDevice : getTargetDeviceList(sink, true)) {
                if (groupDevice.equals(sink)) {
@@ -3654,6 +3744,7 @@ public class BassClientService extends ProfileService {
                }
            }
        }
    }

    private void logPausedBroadcastsAndSinks() {
        log(
@@ -3784,7 +3875,10 @@ public class BassClientService extends ProfileService {
        while (iterator.hasNext()) {
            BluetoothDevice sink = iterator.next();
            sEventLogger.logd(TAG, "Remove broadcast sink from paused cache: " + sink);
            BluetoothLeBroadcastMetadata metadata = mBroadcastMetadataMap.get(sink);
            Map<Integer, BluetoothLeBroadcastMetadata> entry =
                    mBroadcastMetadataMap.getOrDefault(sink, Collections.emptyMap());

            for (BluetoothLeBroadcastMetadata metadata : entry.values()) {

                if (leaudioBroadcastAssistantPeripheralEntrustment()
                        || leaudioBroadcastResyncHelper()) {
@@ -3794,8 +3888,6 @@ public class BassClientService extends ProfileService {
                                "resumeReceiversSourceSynchronization: failed to get metadata to"
                                        + " resume sink: "
                                        + sink);
                    // remove the device from mPausedBroadcastSinks
                    iterator.remove();
                        continue;
                    }

@@ -3817,7 +3909,6 @@ public class BassClientService extends ProfileService {
                                    || receiveState.get().getPaSyncState()
                                            == BluetoothLeBroadcastReceiveState
                                                    .PA_SYNC_STATE_SYNCHRONIZED)) {
                    iterator.remove();
                        continue;
                    }

@@ -3827,7 +3918,8 @@ public class BassClientService extends ProfileService {
                            && (!leaudioBroadcastResyncHelper()
                                    || isLocalBroadcast(metadata)
                                    || activeSyncedSrc.contains(
                                        getSyncHandleForBroadcastId(metadata.getBroadcastId())))) {
                                            getSyncHandleForBroadcastId(
                                                    metadata.getBroadcastId())))) {
                        int sourceId = receiveState.get().getSourceId();
                        updateSourceToResumeBroadcast(sink, sourceId, metadata);
                    } else {
@@ -3845,6 +3937,7 @@ public class BassClientService extends ProfileService {
                                        + sink);
                    }
                }
            }
            // remove the device from mPausedBroadcastSinks
            iterator.remove();
        }
@@ -3954,6 +4047,35 @@ public class BassClientService extends ProfileService {
        return false;
    }

    public boolean hasPrimaryDeviceManagedExternalBroadcast() {
        LeAudioService leAudioService = mServiceFactory.getLeAudioService();

        if (leAudioService == null) {
            Log.e(TAG, "no LeAudioService");
            return false;
        }

        for (BluetoothDevice device : getConnectedDevices()) {
            if (!leAudioService.isPrimaryDevice(device)) {
                continue;
            }

            Map<Integer, BluetoothLeBroadcastMetadata> entry = mBroadcastMetadataMap.get(device);

            /* null means that this source was not added or modified by assistant */
            if (entry == null) {
                continue;
            }

            /* Assistant manages some external broadcast */
            if (entry.values().stream().anyMatch(e -> !isLocalBroadcast(e))) {
                return true;
            }
        }

        return false;
    }

    /** Check if any sink receivers are receiving broadcast stream */
    public boolean areReceiversReceivingOnlyExternalBroadcast(List<BluetoothDevice> devices) {
        boolean isReceivingExternalBroadcast = false;
+8 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import static com.android.bluetooth.flags.Flags.leaudioAllowedContextMask;
import static com.android.bluetooth.flags.Flags.leaudioBigDependsOnAudioState;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastApiManagePrimaryGroup;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastAssistantPeripheralEntrustment;
import static com.android.bluetooth.flags.Flags.leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator;
import static com.android.bluetooth.flags.Flags.leaudioUseAudioModeListener;
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;

@@ -1570,8 +1571,14 @@ public class LeAudioService extends ProfileService {
            return false;
        }

        if (leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator()) {
            return (descriptor.mGroupId == mUnicastGroupIdDeactivatedForBroadcastTransition)
                    || device.equals(mActiveAudioInDevice)
                    || device.equals(mActiveAudioOutDevice);
        } else {
            return descriptor.mGroupId == mUnicastGroupIdDeactivatedForBroadcastTransition;
        }
    }

    /** Return true if group is primary - is active or was active before switch to broadcast */
    public boolean isPrimaryGroup(int groupId) {
+8 −1
Original line number Diff line number Diff line
@@ -559,6 +559,9 @@ public class BassClientServiceTest {
                injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID + 1);
            }
        }

        doReturn(true).when(mLeAudioService).isPrimaryDevice(mCurrentDevice);
        doReturn(true).when(mLeAudioService).isPrimaryDevice(mCurrentDevice1);
    }

    private void startSearchingForSources() {
@@ -1576,6 +1579,9 @@ public class BassClientServiceTest {
                .getCallbacks()
                .notifySourceAdded(
                        sm.getDevice(), recvState, BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
        mBassClientService
                .getCallbacks()
                .notifyReceiveStateChanged(sm.getDevice(), recvState.getSourceId(), recvState);
        TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper());

        return recvState;
@@ -1945,7 +1951,6 @@ public class BassClientServiceTest {
                            : BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                    null,
                    (long) 0x00000001);
            verify(mLeAudioService).activeBroadcastAssistantNotification(eq(true));
        }

        // Remove broadcast source
@@ -1971,6 +1976,8 @@ public class BassClientServiceTest {
        for (BassClientStateMachine sm : mStateMachines.values()) {
            injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID);
        }

        verify(mLeAudioService).activeBroadcastAssistantNotification(eq(false));
    }

    private void verifyRemoveMessageAndInjectSourceRemoval() {
+83 −4
Original line number Diff line number Diff line
@@ -4402,10 +4402,21 @@ public:
      return;
    }

    /* Group should not be resumed if:
     * - configured context type is not allowed
     * - updated metadata contains only not allowed context types
     */
    if (!group->GetAllowedContextMask(bluetooth::le_audio::types::kLeAudioDirectionSink)
                 .test_all(local_metadata_context_types_.source) ||
        !group->GetAllowedContextMask(bluetooth::le_audio::types::kLeAudioDirectionSink)
                 .test(configuration_context_type_)) {
      log::warn("Block source resume request context type: {}",
                ToHexString(configuration_context_type_));
      log::warn(
              "Block source resume request context types: {}, allowed context mask: {}, "
              "configured: {}",
              ToString(local_metadata_context_types_.source),
              ToString(group->GetAllowedContextMask(
                      bluetooth::le_audio::types::kLeAudioDirectionSink)),
              ToString(configuration_context_type_));
      CancelLocalAudioSourceStreamingRequest();
      return;
    }
@@ -4679,10 +4690,21 @@ public:
      return;
    }

    /* Group should not be resumed if:
     * - configured context type is not allowed
     * - updated metadata contains only not allowed context types
     */
    if (!group->GetAllowedContextMask(bluetooth::le_audio::types::kLeAudioDirectionSource)
                 .test_all(local_metadata_context_types_.sink) ||
        !group->GetAllowedContextMask(bluetooth::le_audio::types::kLeAudioDirectionSource)
                 .test(configuration_context_type_)) {
      log::warn("Block sink resume request context type: {}",
                ToHexString(configuration_context_type_));
      log::warn(
              "Block sink resume request context types: {} vs allowed context mask: {}, "
              "configured: {}",
              ToString(local_metadata_context_types_.sink),
              ToString(group->GetAllowedContextMask(
                      bluetooth::le_audio::types::kLeAudioDirectionSource)),
              ToString(configuration_context_type_));
      CancelLocalAudioSourceStreamingRequest();
      return;
    }
@@ -4911,6 +4933,30 @@ public:
    return true;
  }

  bool StopStreamIfUpdatedContextIsNoLongerSupporteded(uint8_t direction, LeAudioDeviceGroup* group,
                                                       AudioContexts local_contexts) {
    AudioContexts allowed_contexts = group->GetAllowedContextMask(direction);

    /* Stream should be suspended if:
     * - updated metadata is only not allowed
     * - there is no metadata (cleared) but configuration is for not allowed context
     */
    if (group->IsStreaming() && !allowed_contexts.test_any(local_contexts) &&
        !(allowed_contexts.test(configuration_context_type_) && local_contexts.none())) {
      /* SuspendForReconfiguration and ReconfigurationComplete is a workaround method to let Audio
       * Framework know that session is suspended. Strem resume would be handled from
       * suspended session context with stopped group.
       */
      SuspendedForReconfiguration();
      ReconfigurationComplete(direction);
      GroupStop(active_group_id_);

      return true;
    }

    return false;
  }

  void OnLocalAudioSourceMetadataUpdate(
          const std::vector<struct playback_track_metadata_v7>& source_metadata, DsaMode dsa_mode) {
    if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
@@ -4946,6 +4992,23 @@ public:
    /* Set the remote sink metadata context from the playback tracks metadata */
    local_metadata_context_types_.source = GetAudioContextsFromSourceMetadata(source_metadata);

    /* Check if stream should be suspended due to reamaining only not allowed contexts in metadata
     * or configured context.
     */
    if (com::android::bluetooth::flags::leaudio_stop_updated_to_not_available_context_stream() &&
        StopStreamIfUpdatedContextIsNoLongerSupporteded(
                bluetooth::le_audio::types::kLeAudioDirectionSink, group,
                local_metadata_context_types_.source)) {
      log::info(
              "Updated source metadata contexts are not allowed context types: {} | configured: {} "
              "vs allowed context mask: {}",
              ToString(local_metadata_context_types_.source), ToString(configuration_context_type_),
              ToString(group->GetAllowedContextMask(
                      bluetooth::le_audio::types::kLeAudioDirectionSink)));

      return;
    }

    local_metadata_context_types_.source =
            ChooseMetadataContextType(local_metadata_context_types_.source);

@@ -5096,6 +5159,22 @@ public:
    /* Set remote source metadata context from the recording tracks metadata */
    local_metadata_context_types_.sink = GetAudioContextsFromSinkMetadata(sink_metadata);

    /* Check if stream should be suspended due to only reamaining not allowed contexts in metadata
     * or configured context.
     */
    if (com::android::bluetooth::flags::leaudio_stop_updated_to_not_available_context_stream() &&
        StopStreamIfUpdatedContextIsNoLongerSupporteded(
                bluetooth::le_audio::types::kLeAudioDirectionSource, group,
                local_metadata_context_types_.sink)) {
      log::info(
              "Updated sink metadata contexts are not allowed context types: {} | configured: {} "
              "vs allowed context mask: {}",
              ToString(local_metadata_context_types_.sink), ToString(configuration_context_type_),
              ToString(group->GetAllowedContextMask(
                      bluetooth::le_audio::types::kLeAudioDirectionSource)));
      return;
    }

    local_metadata_context_types_.sink =
            ChooseMetadataContextType(local_metadata_context_types_.sink);

+61 −0
Original line number Diff line number Diff line
@@ -13009,4 +13009,65 @@ TEST_F(UnicastTest, CodecFrameBlocks2) {
  ASSERT_EQ(codec_manager_stream_params.sink.codec_frames_blocks_per_sdu, max_codec_frames_per_sdu);
}
TEST_F(UnicastTestHandoverMode, UpdateMetadataToNotAllowedContexts) {
  com::android::bluetooth::flags::provider_->leaudio_stop_updated_to_not_available_context_stream(
          true);
  const RawAddress test_address0 = GetTestAddress(0);
  int group_id = bluetooth::groups::kGroupUnknown;
  available_snk_context_types_ =
          (types::LeAudioContextType::RINGTONE | types::LeAudioContextType::CONVERSATIONAL |
           types::LeAudioContextType::UNSPECIFIED | types::LeAudioContextType::MEDIA |
           types::LeAudioContextType::SOUNDEFFECTS)
                  .value();
  available_src_context_types_ = available_snk_context_types_;
  supported_snk_context_types_ = types::kLeAudioContextAllTypes.value();
  supported_src_context_types_ =
          (types::kLeAudioContextAllRemoteSource | types::LeAudioContextType::UNSPECIFIED).value();
  /* Don't allow SOUNDEFFECTS context type to be streamed */
  int allowed_context_types =
          (types::LeAudioContextType::RINGTONE | types::LeAudioContextType::CONVERSATIONAL |
           types::LeAudioContextType::UNSPECIFIED | types::LeAudioContextType::MEDIA)
                  .value();
  SetSampleDatabaseEarbudsValid(1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
                                codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt,
                                default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/,
                                true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/,
                                0 /*rank*/);
  EXPECT_CALL(mock_audio_hal_client_callbacks_,
              OnConnectionState(ConnectionState::CONNECTED, test_address0))
          .Times(1);
  EXPECT_CALL(mock_audio_hal_client_callbacks_,
              OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
          .WillOnce(DoAll(SaveArg<1>(&group_id)));
  ConnectLeAudio(test_address0);
  ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
  EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1);
  types::BidirectionalPair<types::AudioContexts> metadata = {.sink = types::AudioContexts(),
                                                             .source = types::AudioContexts()};
  EXPECT_CALL(mock_state_machine_, StartStream(_, types::LeAudioContextType::MEDIA, _, _)).Times(1);
  LeAudioClient::Get()->GroupSetActive(group_id);
  SyncOnMainLoop();
  /* Set the same allowed context mask for sink and source */
  LeAudioClient::Get()->SetGroupAllowedContextMask(group_id, allowed_context_types,
                                                   allowed_context_types);
  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_UNKNOWN, group_id, AUDIO_SOURCE_INVALID,
                 false, false);
  SyncOnMainLoop();
  Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);
  Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_);
  /* Expect stream to be stopped when not allowed context would be updated in metadata */
  EXPECT_CALL(mock_state_machine_, StopStream(_));
  UpdateLocalSourceMetadata(AUDIO_USAGE_ASSISTANCE_SONIFICATION, AUDIO_CONTENT_TYPE_UNKNOWN, true);
}
}  // namespace bluetooth::le_audio