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