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 void GetCisCount(types::LeAudioContextType context_type, uint8_t& out_cis_count_bidir, 66 uint8_t& out_cis_count_unidir_sink, 67 uint8_t& out_cis_count_unidir_source) const; 68 void GenerateCisIds(types::LeAudioContextType context_type); 69 bool AssignCisIds(LeAudioDevice* leAudioDevice); 70 void AssignCisConnHandles(const std::vector<uint16_t>& conn_handles); 71 void UnassignCis(LeAudioDevice* leAudioDevice, uint16_t conn_handle); 72 73 std::vector<struct types::cis> cises; 74 75 private: 76 uint8_t GetFirstFreeCisId(types::CisType cis_type) const; 77 78 LeAudioDeviceGroup* group_; 79 types::CigState state_; 80 } cig; 81 IsGroupConfiguredTo(const types::AudioSetConfiguration & cfg)82 bool IsGroupConfiguredTo(const types::AudioSetConfiguration& cfg) { 83 if (!stream_conf.conf) { 84 return false; 85 } 86 return cfg == *stream_conf.conf; 87 } 88 89 /* Current configuration strategy - recalculated on demand */ 90 mutable std::optional<types::LeAudioConfigurationStrategy> strategy_ = std::nullopt; 91 92 /* Current audio stream configuration */ 93 struct stream_configuration stream_conf; 94 bool notify_streaming_when_cises_are_ready_; 95 96 uint8_t audio_directions_; 97 types::BidirectionalPair<std::optional<types::AudioLocations>> audio_locations_; 98 99 /* Whether LE Audio is preferred for OUTPUT_ONLY and DUPLEX cases */ 100 bool is_output_preference_le_audio; 101 bool is_duplex_preference_le_audio; 102 103 struct { 104 DsaMode mode; 105 bool active; 106 } dsa_; 107 bool asymmetric_phy_for_unidirectional_cis_supported; 108 LeAudioDeviceGroup(const int group_id)109 explicit LeAudioDeviceGroup(const int group_id) 110 : group_id_(group_id), 111 cig(this), 112 stream_conf({}), 113 notify_streaming_when_cises_are_ready_(false), 114 audio_directions_(0), 115 dsa_({DsaMode::DISABLED, false}), 116 asymmetric_phy_for_unidirectional_cis_supported(true), 117 is_enabled_(true), 118 transport_latency_mtos_us_(0), 119 transport_latency_stom_us_(0), 120 configuration_context_type_(types::LeAudioContextType::UNINITIALIZED), 121 metadata_context_type_( 122 {.sink = types::AudioContexts(types::LeAudioContextType::UNINITIALIZED), 123 .source = types::AudioContexts(types::LeAudioContextType::UNINITIALIZED)}), 124 streaming_metadata_context_type_( 125 {.sink = types::AudioContexts(types::LeAudioContextType::UNINITIALIZED), 126 .source = types::AudioContexts(types::LeAudioContextType::UNINITIALIZED)}), 127 group_user_allowed_context_mask_( 128 {.sink = types::AudioContexts(types::kLeAudioContextAllTypes), 129 .source = types::AudioContexts(types::kLeAudioContextAllTypes)}), 130 preferred_config_({.sink = nullptr, .source = nullptr}), 131 target_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE), 132 current_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE), 133 in_transition_(false) { 134 #ifdef __ANDROID__ 135 // 22 maps to BluetoothProfile#LE_AUDIO 136 is_output_preference_le_audio = 137 android::sysprop::BluetoothProperties::getDefaultOutputOnlyAudioProfile() == 138 LE_AUDIO_PROFILE_CONSTANT; 139 is_duplex_preference_le_audio = 140 android::sysprop::BluetoothProperties::getDefaultDuplexAudioProfile() == 141 LE_AUDIO_PROFILE_CONSTANT; 142 #else 143 is_output_preference_le_audio = true; 144 is_duplex_preference_le_audio = true; 145 #endif 146 } 147 ~LeAudioDeviceGroup(void); 148 149 void AddNode(const std::shared_ptr<LeAudioDevice>& leAudioDevice); 150 void RemoveNode(const std::shared_ptr<LeAudioDevice>& leAudioDevice); 151 bool IsEmpty(void) const; 152 bool IsAnyDeviceConnected(void) const; 153 int Size(void) const; 154 int DesiredSize(void) const; 155 int NumOfConnected() const; 156 int NumOfAvailableForDirection(int direction) const; 157 bool Activate(types::LeAudioContextType context_type, 158 const types::BidirectionalPair<types::AudioContexts>& metadata_context_types, 159 types::BidirectionalPair<std::vector<uint8_t>> ccid_lists); 160 void Deactivate(void); 161 void ClearSinksFromConfiguration(void); 162 void ClearSourcesFromConfiguration(void); 163 void Cleanup(void); 164 LeAudioDevice* GetFirstDevice(void) const; 165 LeAudioDevice* GetFirstDeviceWithAvailableContext(types::LeAudioContextType context_type) const; 166 types::LeAudioConfigurationStrategy GetGroupSinkStrategy(void) const; InvalidateGroupStrategy(void)167 inline void InvalidateGroupStrategy(void) { strategy_ = std::nullopt; } 168 int GetAseCount(uint8_t direction) const; 169 LeAudioDevice* GetNextDevice(LeAudioDevice* leAudioDevice) const; 170 LeAudioDevice* GetNextDeviceWithAvailableContext(LeAudioDevice* leAudioDevice, 171 types::LeAudioContextType context_type) const; 172 LeAudioDevice* GetFirstActiveDevice(void) const; 173 LeAudioDevice* GetNextActiveDevice(LeAudioDevice* leAudioDevice) const; 174 LeAudioDevice* GetFirstActiveDeviceByCisAndDataPathState( 175 types::CisState cis_state, types::DataPathState data_path_state) const; 176 LeAudioDevice* GetNextActiveDeviceByCisAndDataPathState( 177 LeAudioDevice* leAudioDevice, types::CisState cis_state, 178 types::DataPathState data_path_state) const; 179 int GetNumOfActiveDevices(void) 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 UpdateAudioSetConfigurationCache(types::LeAudioContextType ctx_type, 210 bool use_preferred = false) const; 211 CodecManager::UnicastConfigurationRequirements GetAudioSetConfigurationRequirements( 212 types::LeAudioContextType ctx_type) const; 213 bool SetPreferredAudioSetConfiguration( 214 const bluetooth::le_audio::btle_audio_codec_config_t& input_codec_config, 215 const bluetooth::le_audio::btle_audio_codec_config_t& output_codec_config) const; 216 bool IsUsingPreferredAudioSetConfiguration(const types::LeAudioContextType& context_type) const; 217 void ResetPreferredAudioSetConfiguration(void) const; 218 bool ReloadAudioLocations(void); 219 bool ReloadAudioDirections(void); 220 types::AudioContexts GetAllSupportedBidirectionalContextTypes(void) const; 221 types::AudioContexts GetAllSupportedSingleDirectionOnlyContextTypes(uint8_t direction) const; 222 std::shared_ptr<const types::AudioSetConfiguration> GetActiveConfiguration(void) const; 223 bool IsPendingConfiguration(void) const; 224 std::shared_ptr<const types::AudioSetConfiguration> GetConfiguration( 225 types::LeAudioContextType ctx_type) const; 226 std::shared_ptr<const types::AudioSetConfiguration> GetPreferredConfiguration( 227 types::LeAudioContextType ctx_type) const; 228 std::shared_ptr<const types::AudioSetConfiguration> GetCachedConfiguration( 229 types::LeAudioContextType ctx_type) const; 230 std::shared_ptr<const types::AudioSetConfiguration> GetCachedPreferredConfiguration( 231 types::LeAudioContextType ctx_type) const; 232 void InvalidateCachedConfigurations(void); 233 void SetPendingConfiguration(void); 234 void ClearPendingConfiguration(void); 235 void AddToAllowListNotConnectedGroupMembers(int gatt_if); 236 void ApplyReconnectionMode(int gatt_if, tBTM_BLE_CONN_TYPE reconnection_mode); 237 void Disable(int gatt_if); 238 void Enable(int gatt_if, tBTM_BLE_CONN_TYPE reconnection_mode); 239 bool IsEnabled(void) const; 240 LeAudioCodecConfiguration GetAudioSessionCodecConfigForDirection( 241 types::LeAudioContextType group_context_type, uint8_t direction) const; 242 bool HasCodecConfigurationForDirection(types::LeAudioContextType group_context_type, 243 uint8_t direction) const; 244 bool IsAudioSetConfigurationAvailable(types::LeAudioContextType group_context_type); 245 bool IsMetadataChanged(const types::BidirectionalPair<types::AudioContexts>& context_types, 246 const types::BidirectionalPair<std::vector<uint8_t>>& ccid_lists) const; 247 bool IsConfiguredForContext(types::LeAudioContextType context_type) const; 248 void RemoveCisFromStreamIfNeeded(LeAudioDevice* leAudioDevice, uint16_t cis_conn_hdl); 249 GetState(void)250 inline types::AseState GetState(void) const { return current_state_; } SetState(types::AseState state)251 void SetState(types::AseState state) { 252 log::info("group_id: {} current state: {}, new state {}, in_transition_ {}", group_id_, 253 bluetooth::common::ToString(current_state_), bluetooth::common::ToString(state), 254 in_transition_); 255 LeAudioLogHistory::Get()->AddLogHistory(kLogStateMachineTag, group_id_, RawAddress::kEmpty, 256 kLogStateChangedOp, 257 bluetooth::common::ToString(current_state_) + "->" + 258 bluetooth::common::ToString(state)); 259 current_state_ = state; 260 261 if (target_state_ == current_state_) { 262 in_transition_ = false; 263 log::info("In transition flag cleared"); 264 } 265 } 266 GetTargetState(void)267 inline types::AseState GetTargetState(void) const { return target_state_; } SetNotifyStreamingWhenCisesAreReadyFlag(bool value)268 inline void SetNotifyStreamingWhenCisesAreReadyFlag(bool value) { 269 notify_streaming_when_cises_are_ready_ = value; 270 } GetNotifyStreamingWhenCisesAreReadyFlag(void)271 inline bool GetNotifyStreamingWhenCisesAreReadyFlag(void) { 272 return notify_streaming_when_cises_are_ready_; 273 } SetTargetState(types::AseState state)274 void SetTargetState(types::AseState state) { 275 log::info("group_id: {} target state: {}, new target state: {}, in_transition_ {}", group_id_, 276 bluetooth::common::ToString(target_state_), bluetooth::common::ToString(state), 277 in_transition_); 278 LeAudioLogHistory::Get()->AddLogHistory( 279 kLogStateMachineTag, group_id_, RawAddress::kEmpty, kLogTargetStateChangedOp, 280 bluetooth::common::ToString(target_state_) + "->" + bluetooth::common::ToString(state)); 281 282 target_state_ = state; 283 284 in_transition_ = target_state_ != current_state_; 285 log::info("In transition flag = {}", in_transition_); 286 } 287 SetConfigurationContextType(types::LeAudioContextType context_type)288 inline void SetConfigurationContextType(types::LeAudioContextType context_type) { 289 configuration_context_type_ = context_type; 290 } 291 GetConfigurationContextType(void)292 inline types::LeAudioContextType GetConfigurationContextType(void) const { 293 return configuration_context_type_; 294 } 295 SetMetadataContexts(const types::BidirectionalPair<types::AudioContexts> & metadata)296 inline void SetMetadataContexts(const types::BidirectionalPair<types::AudioContexts>& metadata) { 297 log::debug("group_id: {}, sink: {}, source: {}", group_id_, common::ToString(metadata.sink), 298 common::ToString(metadata.source)); 299 metadata_context_type_ = metadata; 300 } 301 GetMetadataContexts()302 inline types::BidirectionalPair<types::AudioContexts> GetMetadataContexts() const { 303 return metadata_context_type_; 304 } 305 SetStreamingMetadataContexts(types::AudioContexts & metadata,int remote_direction)306 inline void SetStreamingMetadataContexts(types::AudioContexts& metadata, int remote_direction) { 307 log::debug("group_id: {}, direction: {}, metadata: {}", group_id_, 308 remote_direction == types::kLeAudioDirectionSink ? "sink" : "source", 309 common::ToString(metadata)); 310 streaming_metadata_context_type_.get(remote_direction) = metadata; 311 } 312 GetStreamingMetadataContexts()313 inline types::BidirectionalPair<types::AudioContexts> GetStreamingMetadataContexts() const { 314 log::debug("group_id: {}, sink: {}, source: {}", group_id_, 315 common::ToString(streaming_metadata_context_type_.sink), 316 common::ToString(streaming_metadata_context_type_.source)); 317 return streaming_metadata_context_type_; 318 } 319 ClearStreamingMetadataContexts()320 inline void ClearStreamingMetadataContexts() { 321 log::debug("group_id: {}", group_id_); 322 streaming_metadata_context_type_.sink.clear(); 323 streaming_metadata_context_type_.source.clear(); 324 } 325 326 types::AudioContexts GetAvailableContexts(int direction = types::kLeAudioDirectionBoth) const { 327 log::assert_that(direction <= (types::kLeAudioDirectionBoth), "Invalid direction used."); 328 329 auto streaming_metadata = GetStreamingMetadataContexts(); 330 types::BidirectionalPair<types::AudioContexts> available_contexts = 331 GetLatestAvailableContexts(); 332 333 log::debug( 334 "group id: {}, streaming contexts sink: {}, streaming contexts source: {}, available " 335 "contexts sink: {}, available contexts source: {}", 336 group_id_, streaming_metadata.sink.to_string(), streaming_metadata.source.to_string(), 337 available_contexts.sink.to_string(), available_contexts.source.to_string()); 338 339 available_contexts.sink |= streaming_metadata.sink; 340 available_contexts.source |= streaming_metadata.source; 341 342 if (direction < types::kLeAudioDirectionBoth) { 343 return available_contexts.get(direction); 344 } else { 345 return types::get_bidirectional(available_contexts); 346 } 347 } 348 SetAllowedContextMask(types::BidirectionalPair<types::AudioContexts> & context_types)349 inline void SetAllowedContextMask(types::BidirectionalPair<types::AudioContexts>& context_types) { 350 group_user_allowed_context_mask_ = context_types; 351 log::debug("group id: {}, allowed contexts sink: {}, allowed contexts source: {}", group_id_, 352 group_user_allowed_context_mask_.sink.to_string(), 353 group_user_allowed_context_mask_.source.to_string()); 354 } 355 356 types::AudioContexts GetAllowedContextMask(int direction = types::kLeAudioDirectionBoth) const { 357 log::assert_that(direction <= (types::kLeAudioDirectionBoth), "Invalid direction used."); 358 if (direction < types::kLeAudioDirectionBoth) { 359 log::debug("group id: {}, allowed contexts sink: {}, allowed contexts source: {}", group_id_, 360 group_user_allowed_context_mask_.sink.to_string(), 361 group_user_allowed_context_mask_.source.to_string()); 362 return group_user_allowed_context_mask_.get(direction); 363 } else { 364 return types::get_bidirectional(group_user_allowed_context_mask_); 365 } 366 } 367 368 types::AudioContexts GetSupportedContexts(int direction = types::kLeAudioDirectionBoth) const; 369 GetAllowedDsaModes()370 DsaModes GetAllowedDsaModes() { 371 DsaModes dsa_modes{}; 372 std::set<DsaMode> dsa_mode_set{}; 373 374 for (auto leAudioDevice : leAudioDevices_) { 375 if (leAudioDevice.expired()) { 376 continue; 377 } 378 379 auto device_dsa_modes = leAudioDevice.lock()->GetDsaModes(); 380 381 dsa_mode_set.insert(device_dsa_modes.begin(), device_dsa_modes.end()); 382 } 383 384 dsa_modes.assign(dsa_mode_set.begin(), dsa_mode_set.end()); 385 386 return dsa_modes; 387 } 388 GetAllowedDsaModesList()389 std::vector<DsaModes> GetAllowedDsaModesList() { 390 std::vector<DsaModes> dsa_modes_list = {}; 391 for (auto leAudioDevice : leAudioDevices_) { 392 DsaModes dsa_modes = {}; 393 394 if (!leAudioDevice.expired()) { 395 dsa_modes = leAudioDevice.lock()->GetDsaModes(); 396 } 397 dsa_modes_list.push_back(dsa_modes); 398 } 399 return dsa_modes_list; 400 } 401 DsaReducedSduSizeSupported()402 bool DsaReducedSduSizeSupported() { 403 bool reduced_sdu = false; 404 for (auto leAudioDevice : leAudioDevices_) { 405 if (!leAudioDevice.expired()) { 406 reduced_sdu |= leAudioDevice.lock()->DsaReducedSduSizeSupported(); 407 } 408 } 409 return reduced_sdu; 410 } 411 412 types::BidirectionalPair<types::AudioContexts> GetLatestAvailableContexts(void) const; 413 414 bool IsInTransition(void) const; IsInTransitionTo(types::AseState state)415 bool IsInTransitionTo(types::AseState state) const { 416 return (GetTargetState() == state) && IsInTransition(); 417 } 418 bool IsStreaming(void) const; 419 bool IsReleasingOrIdle(void) const; 420 bool IsReleasing(void) const; 421 422 void PrintDebugState(void) const; 423 void Dump(std::stringstream& stream, int active_group_id) const; 424 425 /* Codec configuration matcher supporting the legacy configuration provider 426 * mechanism for the non-vendor and software codecs. Only if the codec 427 * parameters are using the common LTV data format, the BT stack can verify 428 * them against the remote device capabilities and find the best possible 429 * configurations. This will not be used for finding best possible vendor 430 * codec configuration. 431 */ 432 std::unique_ptr<types::AudioSetConfiguration> FindFirstSupportedConfiguration( 433 const CodecManager::UnicastConfigurationRequirements& requirements, 434 const types::AudioSetConfigurations* confs, bool use_preferred) const; 435 436 private: 437 bool is_enabled_; 438 439 uint32_t transport_latency_mtos_us_; 440 uint32_t transport_latency_stom_us_; 441 442 bool ConfigureAses(const types::AudioSetConfiguration* audio_set_conf, 443 types::LeAudioContextType context_type, 444 const types::BidirectionalPair<types::AudioContexts>& metadata_context_types, 445 const types::BidirectionalPair<std::vector<uint8_t>>& ccid_lists); 446 bool IsAudioSetConfigurationSupported( 447 const CodecManager::UnicastConfigurationRequirements& requirements, 448 const types::AudioSetConfiguration* audio_set_configuratio, 449 bool use_preferred = false) const; 450 uint32_t GetTransportLatencyUs(uint8_t direction) const; 451 bool IsCisPartOfCurrentStream(uint16_t cis_conn_hdl) const; 452 453 /* Current configuration and metadata context types */ 454 types::LeAudioContextType configuration_context_type_; 455 types::BidirectionalPair<types::AudioContexts> metadata_context_type_; 456 types::BidirectionalPair<types::AudioContexts> streaming_metadata_context_type_; 457 458 /* Mask of currently allowed context types. Not set a value not set will 459 * result in streaming rejection. 460 */ 461 types::BidirectionalPair<types::AudioContexts> group_user_allowed_context_mask_; 462 463 /* Possible configuration cache - refreshed on each group context availability 464 * change. Stored as a pair of (is_valid_cache, configuration*). `pair.first` 465 * being `false` means that the cached value should be refreshed. 466 */ 467 mutable std::map<types::LeAudioContextType, 468 std::pair<bool, const std::shared_ptr<types::AudioSetConfiguration>>> 469 context_to_configuration_cache_map_; 470 471 /* Possible preferred configuration cache - refreshed on each group context 472 * availability change. Stored as a pair of (is_valid_cache, configuration*). 473 * `pair.first` being `false` means that the cached value should be refreshed. 474 */ 475 mutable std::map<types::LeAudioContextType, 476 std::pair<bool, const std::shared_ptr<types::AudioSetConfiguration>>> 477 context_to_preferred_configuration_cache_map_; 478 479 mutable types::BidirectionalPair< 480 std::unique_ptr<const bluetooth::le_audio::btle_audio_codec_config_t>> 481 preferred_config_; 482 483 types::AseState target_state_; 484 types::AseState current_state_; 485 bool in_transition_; 486 std::vector<std::weak_ptr<LeAudioDevice>> leAudioDevices_; 487 }; 488 489 /* LeAudioDeviceGroup class represents a wraper helper over all device groups in 490 * le audio implementation. It allows to operate on device group from a list 491 * (vector container) using determinants like id. 492 */ 493 class LeAudioDeviceGroups { 494 public: 495 LeAudioDeviceGroup* Add(int group_id); 496 void Remove(const int group_id); 497 LeAudioDeviceGroup* FindById(int group_id) const; 498 std::vector<int> GetGroupsIds(void) const; 499 size_t Size() const; 500 bool IsAnyInTransition() const; 501 void Cleanup(void); 502 void Dump(std::stringstream& stream, int active_group_id) const; 503 504 private: 505 std::vector<std::unique_ptr<LeAudioDeviceGroup>> groups_; 506 }; 507 508 } // namespace bluetooth::le_audio 509