1 /* 2 * Copyright 2023 The Android Open Source Project 3 * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA 4 * - www.ehima.com 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 /* LeAudioDeviceGroup class represents group of LeAudioDevices and allows to 20 * perform operations on them. Group states are ASE states due to nature of 21 * group which operates finally of ASEs. 22 * 23 * Group is created after adding a node to new group id (which is not on list). 24 */ 25 26 #pragma once 27 28 #include <map> 29 #include <memory> 30 #include <optional> 31 #include <utility> // for std::pair 32 #include <vector> 33 34 #include "hardware/bt_le_audio.h" 35 36 #ifdef __ANDROID__ 37 #include <android/sysprop/BluetoothProperties.sysprop.h> 38 #endif 39 40 #include <bluetooth/log.h> 41 #include <com_android_bluetooth_flags.h> 42 43 #include "common/strings.h" 44 #include "devices.h" 45 #include "le_audio_log_history.h" 46 #include "le_audio_types.h" 47 48 namespace bluetooth::le_audio { 49 50 class LeAudioDeviceGroup { 51 public: 52 const int group_id_; 53 54 class CigConfiguration { 55 public: 56 CigConfiguration() = delete; CigConfiguration(LeAudioDeviceGroup * group)57 CigConfiguration(LeAudioDeviceGroup* group) : group_(group), state_(types::CigState::NONE) {} 58 GetState(void)59 types::CigState GetState(void) const { return state_; } SetState(bluetooth::le_audio::types::CigState state)60 void SetState(bluetooth::le_audio::types::CigState state) { 61 log::verbose("{} -> {}", bluetooth::common::ToString(state_), 62 bluetooth::common::ToString(state)); 63 state_ = state; 64 } 65 66 void GenerateCisIds(types::LeAudioContextType context_type); 67 bool AssignCisIds(LeAudioDevice* leAudioDevice); 68 void AssignCisConnHandles(const std::vector<uint16_t>& conn_handles); 69 void UnassignCis(LeAudioDevice* leAudioDevice, uint16_t conn_handle); 70 71 std::vector<struct types::cis> cises; 72 73 private: 74 uint8_t GetFirstFreeCisId(types::CisType cis_type) const; 75 76 LeAudioDeviceGroup* group_; 77 types::CigState state_; 78 } cig; 79 IsGroupConfiguredTo(const set_configurations::AudioSetConfiguration & cfg)80 bool IsGroupConfiguredTo(const set_configurations::AudioSetConfiguration& cfg) { 81 if (!stream_conf.conf) { 82 return false; 83 } 84 return cfg == *stream_conf.conf; 85 } 86 87 /* Current configuration strategy - recalculated on demand */ 88 mutable std::optional<types::LeAudioConfigurationStrategy> strategy_ = std::nullopt; 89 90 /* Current audio stream configuration */ 91 struct stream_configuration stream_conf; 92 bool notify_streaming_when_cises_are_ready_; 93 94 uint8_t audio_directions_; 95 types::AudioLocations snk_audio_locations_; 96 types::AudioLocations src_audio_locations_; 97 98 /* Whether LE Audio is preferred for OUTPUT_ONLY and DUPLEX cases */ 99 bool is_output_preference_le_audio; 100 bool is_duplex_preference_le_audio; 101 102 struct { 103 DsaMode mode; 104 bool active; 105 } dsa_; 106 bool asymmetric_phy_for_unidirectional_cis_supported; 107 LeAudioDeviceGroup(const int group_id)108 explicit LeAudioDeviceGroup(const int group_id) 109 : group_id_(group_id), 110 cig(this), 111 stream_conf({}), 112 notify_streaming_when_cises_are_ready_(false), 113 audio_directions_(0), 114 dsa_({DsaMode::DISABLED, false}), 115 is_enabled_(true), 116 transport_latency_mtos_us_(0), 117 transport_latency_stom_us_(0), 118 configuration_context_type_(types::LeAudioContextType::UNINITIALIZED), 119 metadata_context_type_( 120 {.sink = types::AudioContexts(types::LeAudioContextType::UNINITIALIZED), 121 .source = types::AudioContexts(types::LeAudioContextType::UNINITIALIZED)}), 122 group_available_contexts_( 123 {.sink = types::AudioContexts(types::LeAudioContextType::UNINITIALIZED), 124 .source = types::AudioContexts(types::LeAudioContextType::UNINITIALIZED)}), 125 pending_group_available_contexts_change_(types::LeAudioContextType::UNINITIALIZED), 126 group_user_allowed_context_mask_( 127 {.sink = types::AudioContexts(types::kLeAudioContextAllTypes), 128 .source = types::AudioContexts(types::kLeAudioContextAllTypes)}), 129 preferred_config_({.sink = nullptr, .source = nullptr}), 130 target_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE), 131 current_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE), 132 in_transition_(false) { 133 #ifdef __ANDROID__ 134 // 22 maps to BluetoothProfile#LE_AUDIO 135 is_output_preference_le_audio = 136 android::sysprop::BluetoothProperties::getDefaultOutputOnlyAudioProfile() == 137 LE_AUDIO_PROFILE_CONSTANT; 138 is_duplex_preference_le_audio = 139 android::sysprop::BluetoothProperties::getDefaultDuplexAudioProfile() == 140 LE_AUDIO_PROFILE_CONSTANT; 141 #else 142 is_output_preference_le_audio = true; 143 is_duplex_preference_le_audio = true; 144 #endif 145 asymmetric_phy_for_unidirectional_cis_supported = 146 com::android::bluetooth::flags::asymmetric_phy_for_unidirectional_cis(); 147 } 148 ~LeAudioDeviceGroup(void); 149 150 void AddNode(const std::shared_ptr<LeAudioDevice>& leAudioDevice); 151 void RemoveNode(const std::shared_ptr<LeAudioDevice>& leAudioDevice); 152 bool IsEmpty(void) const; 153 bool IsAnyDeviceConnected(void) const; 154 int Size(void) const; 155 int DesiredSize(void) const; 156 int NumOfConnected() const; 157 int NumOfAvailableForDirection(int direction) const; 158 bool Activate(types::LeAudioContextType context_type, 159 const types::BidirectionalPair<types::AudioContexts>& metadata_context_types, 160 types::BidirectionalPair<std::vector<uint8_t>> ccid_lists); 161 void Deactivate(void); 162 void ClearSinksFromConfiguration(void); 163 void ClearSourcesFromConfiguration(void); 164 void Cleanup(void); 165 LeAudioDevice* GetFirstDevice(void) const; 166 LeAudioDevice* GetFirstDeviceWithAvailableContext(types::LeAudioContextType context_type) const; 167 types::LeAudioConfigurationStrategy GetGroupSinkStrategy(void) const; InvalidateGroupStrategy(void)168 inline void InvalidateGroupStrategy(void) { strategy_ = std::nullopt; } 169 int GetAseCount(uint8_t direction) const; 170 LeAudioDevice* GetNextDevice(LeAudioDevice* leAudioDevice) const; 171 LeAudioDevice* GetNextDeviceWithAvailableContext(LeAudioDevice* leAudioDevice, 172 types::LeAudioContextType context_type) const; 173 LeAudioDevice* GetFirstActiveDevice(void) const; 174 LeAudioDevice* GetNextActiveDevice(LeAudioDevice* leAudioDevice) const; 175 LeAudioDevice* GetFirstActiveDeviceByCisAndDataPathState( 176 types::CisState cis_state, types::DataPathState data_path_state) const; 177 LeAudioDevice* GetNextActiveDeviceByCisAndDataPathState( 178 LeAudioDevice* leAudioDevice, types::CisState cis_state, 179 types::DataPathState data_path_state) const; 180 bool IsDeviceInTheGroup(LeAudioDevice* leAudioDevice) const; 181 bool HaveAllActiveDevicesAsesTheSameState(types::AseState state) const; 182 bool HaveAnyActiveDeviceInStreamingState() const; 183 bool HaveAnyActiveDeviceInUnconfiguredState() const; 184 bool IsGroupStreamReady(void) const; 185 bool IsGroupReadyToCreateStream(void) const; 186 bool IsGroupReadyToSuspendStream(void) const; 187 bool HaveAllCisesDisconnected(void) const; 188 void ClearAllCises(void); 189 void UpdateCisConfiguration(uint8_t direction); 190 void AssignCisConnHandlesToAses(LeAudioDevice* leAudioDevice); 191 void AssignCisConnHandlesToAses(void); 192 bool Configure(types::LeAudioContextType context_type, 193 const types::BidirectionalPair<types::AudioContexts>& metadata_context_types, 194 types::BidirectionalPair<std::vector<uint8_t>> ccid_lists = {.sink = {}, 195 .source = {}}); 196 uint32_t GetSduInterval(uint8_t direction) const; 197 uint8_t GetSCA(void) const; 198 uint8_t GetPacking(void) const; 199 uint8_t GetFraming(void) const; 200 uint16_t GetMaxTransportLatencyStom(void) const; 201 uint16_t GetMaxTransportLatencyMtos(void) const; 202 void SetTransportLatency(uint8_t direction, uint32_t transport_latency_us); 203 uint8_t GetRtn(uint8_t direction, uint8_t cis_id) const; 204 uint16_t GetMaxSduSize(uint8_t direction, uint8_t cis_id) const; 205 uint8_t GetPhyBitmask(uint8_t direction) const; 206 uint8_t GetTargetPhy(uint8_t direction) const; 207 bool GetPresentationDelay(uint32_t* delay, uint8_t direction) const; 208 uint16_t GetRemoteDelay(uint8_t direction) const; 209 bool UpdateAudioContextAvailability(void); 210 bool UpdateAudioSetConfigurationCache(types::LeAudioContextType ctx_type, 211 bool use_preferred = false) const; 212 CodecManager::UnicastConfigurationRequirements GetAudioSetConfigurationRequirements( 213 types::LeAudioContextType ctx_type) const; 214 bool SetPreferredAudioSetConfiguration( 215 const bluetooth::le_audio::btle_audio_codec_config_t& input_codec_config, 216 const bluetooth::le_audio::btle_audio_codec_config_t& output_codec_config) const; 217 bool IsUsingPreferredAudioSetConfiguration(const types::LeAudioContextType& context_type) const; 218 void ResetPreferredAudioSetConfiguration(void) const; 219 bool ReloadAudioLocations(void); 220 bool ReloadAudioDirections(void); 221 types::AudioContexts GetAllSupportedBidirectionalContextTypes(void); 222 types::AudioContexts GetAllSupportedSingleDirectionOnlyContextTypes(uint8_t direction); 223 std::shared_ptr<const set_configurations::AudioSetConfiguration> GetActiveConfiguration( 224 void) const; 225 bool IsPendingConfiguration(void) const; 226 std::shared_ptr<const set_configurations::AudioSetConfiguration> GetConfiguration( 227 types::LeAudioContextType ctx_type) const; 228 std::shared_ptr<const set_configurations::AudioSetConfiguration> GetPreferredConfiguration( 229 types::LeAudioContextType ctx_type) const; 230 std::shared_ptr<const set_configurations::AudioSetConfiguration> GetCachedConfiguration( 231 types::LeAudioContextType ctx_type) const; 232 std::shared_ptr<const set_configurations::AudioSetConfiguration> GetCachedPreferredConfiguration( 233 types::LeAudioContextType ctx_type) const; 234 void InvalidateCachedConfigurations(void); 235 void SetPendingConfiguration(void); 236 void ClearPendingConfiguration(void); 237 void AddToAllowListNotConnectedGroupMembers(int gatt_if); 238 void ApplyReconnectionMode(int gatt_if, tBTM_BLE_CONN_TYPE reconnection_mode); 239 void Disable(int gatt_if); 240 void Enable(int gatt_if, tBTM_BLE_CONN_TYPE reconnection_mode); 241 bool IsEnabled(void) const; 242 LeAudioCodecConfiguration GetAudioSessionCodecConfigForDirection( 243 types::LeAudioContextType group_context_type, uint8_t direction) const; 244 bool HasCodecConfigurationForDirection(types::LeAudioContextType group_context_type, 245 uint8_t direction) const; 246 bool IsAudioSetConfigurationAvailable(types::LeAudioContextType group_context_type); 247 bool IsMetadataChanged(const types::BidirectionalPair<types::AudioContexts>& context_types, 248 const types::BidirectionalPair<std::vector<uint8_t>>& ccid_lists) const; 249 bool IsConfiguredForContext(types::LeAudioContextType context_type) const; 250 void RemoveCisFromStreamIfNeeded(LeAudioDevice* leAudioDevice, uint16_t cis_conn_hdl); 251 GetState(void)252 inline types::AseState GetState(void) const { return current_state_; } SetState(types::AseState state)253 void SetState(types::AseState state) { 254 log::info("group_id: {} current state: {}, new state {}, in_transition_ {}", group_id_, 255 bluetooth::common::ToString(current_state_), bluetooth::common::ToString(state), 256 in_transition_); 257 LeAudioLogHistory::Get()->AddLogHistory(kLogStateMachineTag, group_id_, RawAddress::kEmpty, 258 kLogStateChangedOp, 259 bluetooth::common::ToString(current_state_) + "->" + 260 bluetooth::common::ToString(state)); 261 current_state_ = state; 262 263 if (target_state_ == current_state_) { 264 in_transition_ = false; 265 log::info("In transition flag cleared"); 266 } 267 } 268 GetTargetState(void)269 inline types::AseState GetTargetState(void) const { return target_state_; } SetNotifyStreamingWhenCisesAreReadyFlag(bool value)270 inline void SetNotifyStreamingWhenCisesAreReadyFlag(bool value) { 271 notify_streaming_when_cises_are_ready_ = value; 272 } GetNotifyStreamingWhenCisesAreReadyFlag(void)273 inline bool GetNotifyStreamingWhenCisesAreReadyFlag(void) { 274 return notify_streaming_when_cises_are_ready_; 275 } SetTargetState(types::AseState state)276 void SetTargetState(types::AseState state) { 277 log::info("group_id: {} target state: {}, new target state: {}, in_transition_ {}", group_id_, 278 bluetooth::common::ToString(target_state_), bluetooth::common::ToString(state), 279 in_transition_); 280 LeAudioLogHistory::Get()->AddLogHistory( 281 kLogStateMachineTag, group_id_, RawAddress::kEmpty, kLogTargetStateChangedOp, 282 bluetooth::common::ToString(target_state_) + "->" + bluetooth::common::ToString(state)); 283 284 target_state_ = state; 285 286 in_transition_ = target_state_ != current_state_; 287 log::info("In transition flag = {}", in_transition_); 288 } 289 290 /* Returns context types for which support was recently added or removed */ GetPendingAvailableContextsChange()291 inline types::AudioContexts GetPendingAvailableContextsChange() const { 292 return pending_group_available_contexts_change_; 293 } 294 295 /* Set which context types were recently added or removed */ SetPendingAvailableContextsChange(types::AudioContexts audio_contexts)296 inline void SetPendingAvailableContextsChange(types::AudioContexts audio_contexts) { 297 pending_group_available_contexts_change_ = audio_contexts; 298 } 299 ClearPendingAvailableContextsChange()300 inline void ClearPendingAvailableContextsChange() { 301 pending_group_available_contexts_change_.clear(); 302 } 303 SetConfigurationContextType(types::LeAudioContextType context_type)304 inline void SetConfigurationContextType(types::LeAudioContextType context_type) { 305 configuration_context_type_ = context_type; 306 } 307 GetConfigurationContextType(void)308 inline types::LeAudioContextType GetConfigurationContextType(void) const { 309 return configuration_context_type_; 310 } 311 GetMetadataContexts()312 inline types::BidirectionalPair<types::AudioContexts> GetMetadataContexts() const { 313 return metadata_context_type_; 314 } 315 SetAvailableContexts(types::BidirectionalPair<types::AudioContexts> new_contexts)316 inline void SetAvailableContexts(types::BidirectionalPair<types::AudioContexts> new_contexts) { 317 group_available_contexts_ = new_contexts; 318 log::debug("group id: {}, available contexts sink: {}, available contexts source: {}", 319 group_id_, group_available_contexts_.sink.to_string(), 320 group_available_contexts_.source.to_string()); 321 } 322 323 types::AudioContexts GetAvailableContexts(int direction = types::kLeAudioDirectionBoth) const { 324 log::assert_that(direction <= (types::kLeAudioDirectionBoth), "Invalid direction used."); 325 if (direction < types::kLeAudioDirectionBoth) { 326 log::debug("group id: {}, available contexts sink: {}, available contexts source: {}", 327 group_id_, group_available_contexts_.sink.to_string(), 328 group_available_contexts_.source.to_string()); 329 return group_available_contexts_.get(direction); 330 } else { 331 return types::get_bidirectional(group_available_contexts_); 332 } 333 } 334 SetAllowedContextMask(types::BidirectionalPair<types::AudioContexts> & context_types)335 inline void SetAllowedContextMask(types::BidirectionalPair<types::AudioContexts>& context_types) { 336 group_user_allowed_context_mask_ = context_types; 337 log::debug("group id: {}, allowed contexts sink: {}, allowed contexts source: {}", group_id_, 338 group_user_allowed_context_mask_.sink.to_string(), 339 group_user_allowed_context_mask_.source.to_string()); 340 } 341 342 types::AudioContexts GetAllowedContextMask(int direction = types::kLeAudioDirectionBoth) const { 343 log::assert_that(direction <= (types::kLeAudioDirectionBoth), "Invalid direction used."); 344 if (direction < types::kLeAudioDirectionBoth) { 345 log::debug("group id: {}, allowed contexts sink: {}, allowed contexts source: {}", group_id_, 346 group_user_allowed_context_mask_.sink.to_string(), 347 group_user_allowed_context_mask_.source.to_string()); 348 return group_user_allowed_context_mask_.get(direction); 349 } else { 350 return types::get_bidirectional(group_user_allowed_context_mask_); 351 } 352 } 353 354 types::AudioContexts GetSupportedContexts(int direction = types::kLeAudioDirectionBoth) const; 355 GetAllowedDsaModes()356 DsaModes GetAllowedDsaModes() { 357 if (!com::android::bluetooth::flags::leaudio_dynamic_spatial_audio()) { 358 return {DsaMode::DISABLED}; 359 } 360 361 DsaModes dsa_modes{}; 362 std::set<DsaMode> dsa_mode_set{}; 363 364 for (auto leAudioDevice : leAudioDevices_) { 365 if (leAudioDevice.expired()) { 366 continue; 367 } 368 369 auto device_dsa_modes = leAudioDevice.lock()->GetDsaModes(); 370 371 dsa_mode_set.insert(device_dsa_modes.begin(), device_dsa_modes.end()); 372 } 373 374 dsa_modes.assign(dsa_mode_set.begin(), dsa_mode_set.end()); 375 376 return dsa_modes; 377 } 378 GetAllowedDsaModesList()379 std::vector<DsaModes> GetAllowedDsaModesList() { 380 std::vector<DsaModes> dsa_modes_list = {}; 381 for (auto leAudioDevice : leAudioDevices_) { 382 DsaModes dsa_modes = {}; 383 384 if (!leAudioDevice.expired()) { 385 dsa_modes = leAudioDevice.lock()->GetDsaModes(); 386 } 387 dsa_modes_list.push_back(dsa_modes); 388 } 389 return dsa_modes_list; 390 } 391 DsaReducedSduSizeSupported()392 bool DsaReducedSduSizeSupported() { 393 bool reduced_sdu = false; 394 for (auto leAudioDevice : leAudioDevices_) { 395 if (!leAudioDevice.expired()) { 396 reduced_sdu |= leAudioDevice.lock()->DsaReducedSduSizeSupported(); 397 } 398 } 399 return reduced_sdu; 400 } 401 402 types::BidirectionalPair<types::AudioContexts> GetLatestAvailableContexts(void) const; 403 404 bool IsInTransition(void) const; IsInTransitionTo(types::AseState state)405 bool IsInTransitionTo(types::AseState state) const { 406 return (GetTargetState() == state) && IsInTransition(); 407 } 408 bool IsStreaming(void) const; 409 bool IsReleasingOrIdle(void) const; 410 bool IsReleasing(void) const; 411 412 void PrintDebugState(void) const; 413 void Dump(std::stringstream& stream, int active_group_id) const; 414 415 /* Codec configuration matcher supporting the legacy configuration provider 416 * mechanism for the non-vendor and software codecs. Only if the codec 417 * parameters are using the common LTV data format, the BT stack can verify 418 * them against the remote device capabilities and find the best possible 419 * configurations. This will not be used for finding best possible vendor 420 * codec configuration. 421 */ 422 std::unique_ptr<set_configurations::AudioSetConfiguration> FindFirstSupportedConfiguration( 423 const CodecManager::UnicastConfigurationRequirements& requirements, 424 const set_configurations::AudioSetConfigurations* confs, bool use_preferred) const; 425 426 private: 427 bool is_enabled_; 428 429 uint32_t transport_latency_mtos_us_; 430 uint32_t transport_latency_stom_us_; 431 432 bool ConfigureAses(const set_configurations::AudioSetConfiguration* audio_set_conf, 433 types::LeAudioContextType context_type, 434 const types::BidirectionalPair<types::AudioContexts>& metadata_context_types, 435 const types::BidirectionalPair<std::vector<uint8_t>>& ccid_lists); 436 bool IsAudioSetConfigurationSupported( 437 const CodecManager::UnicastConfigurationRequirements& requirements, 438 const set_configurations::AudioSetConfiguration* audio_set_configuratio, 439 bool use_preferred = false) const; 440 uint32_t GetTransportLatencyUs(uint8_t direction) const; 441 bool IsCisPartOfCurrentStream(uint16_t cis_conn_hdl) const; 442 443 /* Current configuration and metadata context types */ 444 types::LeAudioContextType configuration_context_type_; 445 types::BidirectionalPair<types::AudioContexts> metadata_context_type_; 446 447 /* Mask of contexts that the whole group can handle at its current state 448 * It's being updated each time group members connect, disconnect or their 449 * individual available audio contexts are changed. 450 */ 451 types::BidirectionalPair<types::AudioContexts> group_available_contexts_; 452 453 /* A temporary mask for bits which were either added or removed when the 454 * group available context type changes. It usually means we should refresh 455 * our group configuration capabilities to clear this. 456 */ 457 types::AudioContexts pending_group_available_contexts_change_; 458 459 /* Mask of currently allowed context types. Not set a value not set will 460 * result in streaming rejection. 461 */ 462 types::BidirectionalPair<types::AudioContexts> group_user_allowed_context_mask_; 463 464 /* Possible configuration cache - refreshed on each group context availability 465 * change. Stored as a pair of (is_valid_cache, configuration*). `pair.first` 466 * being `false` means that the cached value should be refreshed. 467 */ 468 mutable std::map< 469 types::LeAudioContextType, 470 std::pair<bool, const std::shared_ptr<set_configurations::AudioSetConfiguration>>> 471 context_to_configuration_cache_map_; 472 473 /* Possible preferred configuration cache - refreshed on each group context 474 * availability change. Stored as a pair of (is_valid_cache, configuration*). 475 * `pair.first` being `false` means that the cached value should be refreshed. 476 */ 477 mutable std::map< 478 types::LeAudioContextType, 479 std::pair<bool, const std::shared_ptr<set_configurations::AudioSetConfiguration>>> 480 context_to_preferred_configuration_cache_map_; 481 482 mutable types::BidirectionalPair< 483 std::unique_ptr<const bluetooth::le_audio::btle_audio_codec_config_t>> 484 preferred_config_; 485 486 types::AseState target_state_; 487 types::AseState current_state_; 488 bool in_transition_; 489 std::vector<std::weak_ptr<LeAudioDevice>> leAudioDevices_; 490 }; 491 492 /* LeAudioDeviceGroup class represents a wraper helper over all device groups in 493 * le audio implementation. It allows to operate on device group from a list 494 * (vector container) using determinants like id. 495 */ 496 class LeAudioDeviceGroups { 497 public: 498 LeAudioDeviceGroup* Add(int group_id); 499 void Remove(const int group_id); 500 LeAudioDeviceGroup* FindById(int group_id) const; 501 std::vector<int> GetGroupsIds(void) const; 502 size_t Size() const; 503 bool IsAnyInTransition() const; 504 void Cleanup(void); 505 void Dump(std::stringstream& stream, int active_group_id) const; 506 507 private: 508 std::vector<std::unique_ptr<LeAudioDeviceGroup>> groups_; 509 }; 510 511 } // namespace bluetooth::le_audio 512