• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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