diff --git a/system/binder/android/bluetooth/IBluetoothLeAudio.aidl b/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
index f8901ed20c60724dd3016a4c442dbac5fa0981ee..2f158e3d3865556c1bf09b797481e2361a1baee9 100644
--- a/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
+++ b/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
@@ -46,6 +46,7 @@ interface IBluetoothLeAudio {
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
     int getConnectionPolicy(in BluetoothDevice device, in AttributionSource attributionSource);
 
+    /* Same value as bluetooth::groups::kGroupUnknown */
     const int LE_AUDIO_GROUP_ID_INVALID = -1;
 
     const int GROUP_STATUS_INACTIVE = 0;
diff --git a/system/bta/Android.bp b/system/bta/Android.bp
index 802fec6bcfe0ecd0d3b968e5a2812db908fdfdcf..24cf506503417af710bbcb60f64d3a5ae0bdacea 100644
--- a/system/bta/Android.bp
+++ b/system/bta/Android.bp
@@ -91,6 +91,12 @@ cc_library_static {
         "groups/groups.cc",
         "vc/device.cc",
         "vc/vc.cc",
+        "le_audio/client.cc",
+        "le_audio/devices.cc",
+        "le_audio/state_machine.cc",
+        "le_audio/client_parser.cc",
+        "le_audio/client_audio.cc",
+        "le_audio/le_audio_types.cc",
         "hearing_aid/hearing_aid.cc",
         "hearing_aid/hearing_aid_audio_source.cc",
         "hf_client/bta_hf_client_act.cc",
@@ -394,3 +400,114 @@ cc_test {
         cfi: false,
     },
 }
+
+// bta unit tests for LE Audio
+// ========================================================
+cc_test {
+    name: "bluetooth_le_audio_test",
+    test_suites: ["device-tests"],
+    defaults: [
+        "fluoride_defaults",
+        "clang_coverage_bin",
+    ],
+    host_supported: true,
+    include_dirs: [
+        "packages/modules/Bluetooth/system",
+        "packages/modules/Bluetooth/system/bta/include",
+        "packages/modules/Bluetooth/system/bta/test/common",
+        "packages/modules/Bluetooth/system/btif/include",
+        "packages/modules/Bluetooth/system/gd",
+        "packages/modules/Bluetooth/system/stack/include",
+    ],
+    srcs : [
+        ":TestStubOsi",
+        "test/common/bta_gatt_api_mock.cc",
+        "test/common/bta_gatt_queue_mock.cc",
+        "test/common/btm_api_mock.cc",
+        "le_audio/client_audio.cc",
+        "le_audio/client_audio_test.cc",
+        "le_audio/client_parser.cc",
+        "le_audio/client_parser_test.cc",
+        "le_audio/devices.cc",
+        "le_audio/devices_test.cc",
+        "le_audio/le_audio_types.cc",
+        "le_audio/le_audio_types_test.cc",
+        "le_audio/mock_iso_manager.cc",
+        "test/common/mock_controller.cc",
+        "le_audio/state_machine.cc",
+        "le_audio/state_machine_test.cc"
+    ],
+    shared_libs: [
+        "libprotobuf-cpp-lite",
+        "libcrypto",
+        "liblog",  // __android_log_print
+    ],
+    static_libs : [
+        "libgmock",
+        "libbt-common",
+        "libbt-protos-lite",
+        "libosi",
+    ],
+    sanitize: {
+        cfi: false,
+    },
+}
+
+cc_test {
+    name: "bluetooth_le_audio_client_test",
+    test_suites: ["device-tests"],
+    defaults: [
+        "fluoride_bta_defaults",
+        "clang_coverage_bin",
+    ],
+    host_supported: true,
+    include_dirs: [
+        "packages/modules/Bluetooth/system",
+        "packages/modules/Bluetooth/system/bta/include",
+        "packages/modules/Bluetooth/system/bta/test/common",
+        "packages/modules/Bluetooth/system/stack/include",
+    ],
+    srcs : [
+        "gatt/database.cc",
+        "gatt/database_builder.cc",
+        "le_audio/client.cc",
+        "le_audio/client_parser.cc",
+        "le_audio/devices.cc",
+        "le_audio/le_audio_client_test.cc",
+        "le_audio/le_audio_types.cc",
+        "le_audio/mock_iso_manager.cc",
+        "le_audio/mock_le_audio_client_audio.cc",
+        "le_audio/mock_state_machine.cc",
+        "test/common/btm_api_mock.cc",
+        "test/common/bta_gatt_api_mock.cc",
+        "test/common/bta_gatt_queue_mock.cc",
+        "test/common/btif_storage_mock.cc",
+        "test/common/mock_csis_client.cc",
+        "test/common/mock_controller.cc",
+        "test/common/mock_device_groups.cc",
+    ],
+    shared_libs: [
+        "libprotobuf-cpp-lite",
+        "libcrypto",
+        "liblog",
+    ],
+    static_libs : [
+        "crypto_toolbox_for_tests",
+        "libgmock",
+        "libbt-common",
+        "libbt-protos-lite",
+        "libosi",
+        "liblc3codec",
+    ],
+    sanitize: {
+        cfi: true,
+        scs: true,
+        address: true,
+        all_undefined: true,
+        integer_overflow: true,
+        diag: {
+            undefined : true
+        },
+    },
+}
+
diff --git a/system/bta/csis/csis_client.cc b/system/bta/csis/csis_client.cc
index ec6318a0f3b1ab61fef8cfb072b51ae13410b97f..dffecf81a119c91324c5659525c5e092bf895878 100644
--- a/system/bta/csis/csis_client.cc
+++ b/system/bta/csis/csis_client.cc
@@ -1144,7 +1144,7 @@ class CsisClientImpl : public CsisClient {
     if ((csis_group->GetDiscoveryState() !=
          CsisDiscoveryState::CSIS_DISCOVERY_IDLE)) {
       LOG(ERROR) << __func__
-                 << " Incorrect ase group: " << loghex(csis_group->GetGroupId())
+                 << " Incorrect ase group: " << csis_group->GetGroupId()
                  << " state "
                  << loghex(static_cast<int>(csis_group->GetDiscoveryState()));
       return;
diff --git a/system/bta/include/bta_le_audio_api.h b/system/bta/include/bta_le_audio_api.h
new file mode 100644
index 0000000000000000000000000000000000000000..e4edcc1374ef54a1a28b97a9f950e47178f83a5e
--- /dev/null
+++ b/system/bta/include/bta_le_audio_api.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <base/callback_forward.h>
+#include <hardware/bt_le_audio.h>
+
+/* Interface class */
+class LeAudioClient {
+ public:
+  virtual ~LeAudioClient(void) = default;
+
+  static void Initialize(bluetooth::le_audio::LeAudioClientCallbacks* callbacks,
+                         base::Closure initCb,
+                         base::Callback<bool()> hal_2_1_verifier);
+  static void Cleanup(void);
+  static LeAudioClient* Get(void);
+  static void DebugDump(int fd);
+
+  virtual void RemoveDevice(const RawAddress& address) = 0;
+  virtual void Connect(const RawAddress& address) = 0;
+  virtual void Disconnect(const RawAddress& address) = 0;
+  virtual void GroupAddNode(const int group_id, const RawAddress& addr) = 0;
+  virtual void GroupRemoveNode(const int group_id, const RawAddress& addr) = 0;
+  virtual void GroupStream(const int group_id, const uint16_t content_type) = 0;
+  virtual void GroupSuspend(const int group_id) = 0;
+  virtual void GroupStop(const int group_id) = 0;
+  virtual void GroupDestroy(const int group_id) = 0;
+  virtual void GroupSetActive(const int group_id) = 0;
+  virtual std::vector<RawAddress> GetGroupDevices(const int group_id) = 0;
+  static void AddFromStorage(const RawAddress& addr, bool autoconnect);
+  static bool IsLeAudioClientRunning();
+};
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
new file mode 100644
index 0000000000000000000000000000000000000000..082609825c7c26d8f0656fe005c3ae47055aa911
--- /dev/null
+++ b/system/bta/le_audio/client.cc
@@ -0,0 +1,2839 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <base/bind.h>
+#include <base/strings/string_number_conversions.h>
+
+#include "advertise_data_parser.h"
+#include "bta/csis/csis_types.h"
+#include "bta_api.h"
+#include "bta_gatt_api.h"
+#include "bta_gatt_queue.h"
+#include "bta_groups.h"
+#include "bta_le_audio_api.h"
+#include "btif_storage.h"
+#include "btm_iso_api.h"
+#include "client_audio.h"
+#include "client_parser.h"
+#include "device/include/controller.h"
+#include "devices.h"
+#include "embdrv/lc3/Api/Lc3Decoder.hpp"
+#include "embdrv/lc3/Api/Lc3Encoder.hpp"
+#include "gatt/bta_gattc_int.h"
+#include "le_audio_types.h"
+#include "osi/include/osi.h"
+#include "stack/btm/btm_dev.h"
+#include "stack/btm/btm_sec.h"
+#include "stack/include/btu.h"  // do_in_main_thread
+#include "state_machine.h"
+
+using base::Closure;
+using bluetooth::Uuid;
+using bluetooth::groups::DeviceGroups;
+using bluetooth::groups::DeviceGroupsCallbacks;
+using bluetooth::hci::IsoManager;
+using bluetooth::hci::iso_manager::cig_create_cmpl_evt;
+using bluetooth::hci::iso_manager::cig_remove_cmpl_evt;
+using bluetooth::hci::iso_manager::CigCallbacks;
+using bluetooth::le_audio::ConnectionState;
+using bluetooth::le_audio::GroupNodeStatus;
+using bluetooth::le_audio::GroupStatus;
+using bluetooth::le_audio::GroupStreamStatus;
+using le_audio::LeAudioDevice;
+using le_audio::LeAudioDeviceGroup;
+using le_audio::LeAudioDeviceGroups;
+using le_audio::LeAudioDevices;
+using le_audio::LeAudioGroupStateMachine;
+using le_audio::types::ase;
+using le_audio::types::AseState;
+using le_audio::types::AudioContexts;
+using le_audio::types::AudioLocations;
+using le_audio::types::AudioStreamDataPathState;
+using le_audio::types::hdl_pair;
+using le_audio::types::kDefaultScanDurationS;
+using le_audio::types::LeAudioContextType;
+
+using le_audio::client_parser::ascs::
+    kCtpResponseCodeInvalidConfigurationParameterValue;
+using le_audio::client_parser::ascs::kCtpResponseCodeSuccess;
+using le_audio::client_parser::ascs::kCtpResponseInvalidAseCisMapping;
+using le_audio::client_parser::ascs::kCtpResponseNoReason;
+
+namespace {
+void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data);
+
+class LeAudioClientImpl;
+LeAudioClientImpl* instance;
+LeAudioClientAudioSinkReceiver* audioSinkReceiver;
+LeAudioClientAudioSourceReceiver* audioSourceReceiver;
+CigCallbacks* stateMachineHciCallbacks;
+LeAudioGroupStateMachine::Callbacks* stateMachineCallbacks;
+DeviceGroupsCallbacks* device_group_callbacks;
+
+/*
+ * Coordinatet Set Identification Profile (CSIP) based on CSIP 1.0
+ * and Coordinatet Set Identification Service (CSIS) 1.0
+ *
+ * CSIP allows to organize audio servers into sets e.g. Stereo Set, 5.1 Set
+ * and speed up connecting it.
+ *
+ * Since leaudio has already grouping API it was decided to integrate here CSIS
+ * and allow it to group devices semi-automatically.
+ *
+ * Flow:
+ * If connected device contains CSIS services, and it is included into CAP
+ * service, implementation marks device as a set member and waits for the
+ * bta/csis to learn about groups and notify implementation about assigned
+ * group id.
+ *
+ */
+/* LeAudioClientImpl class represents main implementation class for le audio
+ * feature in stack. This class implements GATT, le audio and ISO related parts.
+ *
+ * This class is represented in single instance and manages a group of devices,
+ * and devices. All devices calls back static method from it and are dispatched
+ * to target receivers (e.g. ASEs, devices).
+ *
+ * This instance also implements a LeAudioClient which is a upper layer API.
+ * Also LeAudioClientCallbacks are callbacks for upper layer.
+ *
+ * This class may be bonded with Test socket which allows to drive an instance
+ * for test purposes.
+ */
+class LeAudioClientImpl : public LeAudioClient {
+ public:
+  virtual ~LeAudioClientImpl() = default;
+
+  LeAudioClientImpl(
+      bluetooth::le_audio::LeAudioClientCallbacks* callbacks_,
+      LeAudioGroupStateMachine::Callbacks* state_machine_callbacks_,
+      base::Closure initCb)
+      : gatt_if_(0),
+        callbacks_(callbacks_),
+        active_group_id_(bluetooth::groups::kGroupUnknown),
+        current_context_type_(LeAudioContextType::MEDIA),
+        audio_sink_ready_to_receive(false),
+        audio_source_ready_to_send(false),
+        current_source_codec_config({0, 0, 0, 0}),
+        current_sink_codec_config({0, 0, 0, 0}),
+        lc3_encoder(nullptr),
+        lc3_decoder(nullptr),
+        audio_source_instance_(nullptr),
+        audio_sink_instance_(nullptr) {
+    LeAudioGroupStateMachine::Initialize(state_machine_callbacks_);
+    groupStateMachine_ = LeAudioGroupStateMachine::Get();
+
+    BTA_GATTC_AppRegister(
+        le_audio_gattc_callback,
+        base::Bind(
+            [](base::Closure initCb, uint8_t client_id, uint8_t status) {
+              if (status != GATT_SUCCESS) {
+                LOG(ERROR) << "Can't start LeAudio profile - no gatt "
+                              "clients left!";
+                return;
+              }
+              instance->gatt_if_ = client_id;
+              initCb.Run();
+            },
+            initCb),
+        true);
+
+    DeviceGroups::Get()->Initialize(device_group_callbacks);
+  }
+
+  void AseInitialStateReadRequest(LeAudioDevice* leAudioDevice) {
+    int ases_num = leAudioDevice->ases_.size();
+    void* notify_flag_ptr = NULL;
+
+    for (int i = 0; i < ases_num; i++) {
+      /* Last read ase characteristic should issue connected state callback
+       * to upper layer
+       */
+
+      if (leAudioDevice->notify_connected_after_read_ &&
+          (i == (ases_num - 1))) {
+        notify_flag_ptr =
+            INT_TO_PTR(leAudioDevice->notify_connected_after_read_);
+      }
+
+      BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_,
+                                       leAudioDevice->ases_[i].hdls.val_hdl,
+                                       OnGattReadRspStatic, notify_flag_ptr);
+    }
+  }
+
+  void OnGroupAddedCb(const RawAddress& address, const bluetooth::Uuid& uuid,
+                      int group_id) {
+    LOG(INFO) << __func__ << " address: " << address << " group uuid " << uuid
+              << " group_id: " << group_id;
+
+    /* We are interested in the groups which are in the context of CAP */
+    if (uuid != le_audio::uuid::kCapServiceUuid) return;
+
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    if (!leAudioDevice) return;
+    if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
+      LOG(INFO) << __func__
+                << " group already set: " << leAudioDevice->group_id_;
+      return;
+    }
+
+    group_add_node(group_id, address);
+  }
+
+  void OnGroupMemberAddedCb(const RawAddress& address, int group_id) {
+    LOG(INFO) << __func__ << " address: " << address
+              << " group_id: " << group_id;
+
+    auto group = aseGroups_.FindById(group_id);
+    if (!group) {
+      LOG(ERROR) << __func__ << " Not interested in group id: " << group_id;
+      return;
+    }
+
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    if (!leAudioDevice) return;
+    if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
+      LOG(INFO) << __func__
+                << " group already set: " << leAudioDevice->group_id_;
+      return;
+    }
+
+    group_add_node(group_id, address);
+  }
+
+  void OnGroupMemberRemovedCb(const RawAddress& address, int group_id) {
+    LOG(INFO) << __func__ << " address: " << address
+              << " group_id: " << group_id;
+
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    if (!leAudioDevice) return;
+    if (leAudioDevice->group_id_ == bluetooth::groups::kGroupUnknown) {
+      LOG(INFO) << __func__ << " device already not assigned to the group.";
+      return;
+    }
+
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+    if (group == NULL) {
+      LOG(INFO) << __func__
+                << " device not in the group: " << leAudioDevice->address_
+                << ", " << group_id;
+      return;
+    }
+
+    group_remove_node(group, address);
+  }
+
+  /* This callback happens if kLeAudioDeviceSetStateTimeoutMs timeout happens
+   * during transition from origin to target state
+   */
+  void OnLeAudioDeviceSetStateTimeout(int group_id) {
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+
+    if (!group) {
+      /* Group removed */
+      return;
+    }
+
+    /* Releasement didn't finished in time */
+    if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
+      LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
+      LOG_ASSERT(leAudioDevice)
+          << __func__ << " Shouldn't be called without an active device.";
+
+      do {
+        if (instance) instance->DisconnectDevice(leAudioDevice, true);
+        leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
+      } while (leAudioDevice);
+
+      return;
+    }
+
+    LOG(ERROR) << __func__ << ", State not achieved on time, releasing ases";
+
+    groupStateMachine_->StopStream(group);
+  }
+
+  void UpdateContextAndLocations(LeAudioDeviceGroup* group,
+                                 LeAudioDevice* leAudioDevice) {
+    std::optional<AudioContexts> new_group_updated_contexts =
+        group->UpdateActiveContextsMap(leAudioDevice->GetAvailableContexts());
+
+    if (new_group_updated_contexts || group->ReloadAudioLocations()) {
+      callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
+                              group->snk_audio_locations_.to_ulong(),
+                              group->src_audio_locations_.to_ulong(),
+                              new_group_updated_contexts->to_ulong());
+    }
+  }
+
+  void CancelStreamingRequest() {
+    if (audio_source_ready_to_send) {
+      LeAudioClientAudioSource::CancelStreamingRequest();
+      audio_source_ready_to_send = false;
+    }
+
+    if (audio_sink_ready_to_receive) {
+      LeAudioClientAudioSink::CancelStreamingRequest();
+      audio_sink_ready_to_receive = false;
+    }
+  }
+
+  void ControlPointNotificationHandler(
+      struct le_audio::client_parser::ascs::ctp_ntf& ntf) {
+    for (auto& entry : ntf.entries) {
+      switch (entry.response_code) {
+        case kCtpResponseCodeInvalidConfigurationParameterValue:
+          switch (entry.reason) {
+            case kCtpResponseInvalidAseCisMapping:
+              CancelStreamingRequest();
+              break;
+            case kCtpResponseNoReason:
+            default:
+              break;
+          }
+          break;
+        case kCtpResponseCodeSuccess:
+          FALLTHROUGH;
+        default:
+          break;
+      }
+    }
+  }
+
+  void group_add_node(const int group_id, const RawAddress& address,
+                      bool update_group_module = false) {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    LeAudioDeviceGroup* new_group;
+    LeAudioDeviceGroup* old_group = nullptr;
+    int old_group_id = bluetooth::groups::kGroupUnknown;
+
+    if (!leAudioDevice) {
+      /* TODO This part possible to remove as this is to handle adding device to
+       * the group which is unknown and not connected.
+       */
+      LOG(INFO) << __func__ << ", leAudioDevice unknown , address: " << address
+                << " group: " << loghex(group_id);
+
+      if (group_id == bluetooth::groups::kGroupUnknown) return;
+
+      LOG(INFO) << __func__ << "Set member adding ...";
+      leAudioDevices_.Add(address, true);
+      leAudioDevice = leAudioDevices_.FindByAddress(address);
+    } else {
+      if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
+        old_group = aseGroups_.FindById(leAudioDevice->group_id_);
+        old_group_id = old_group->group_id_;
+      }
+    }
+
+    auto id = DeviceGroups::Get()->GetGroupId(address,
+                                              le_audio::uuid::kCapServiceUuid);
+    if (group_id == bluetooth::groups::kGroupUnknown) {
+      if (id == bluetooth::groups::kGroupUnknown) {
+        DeviceGroups::Get()->AddDevice(address,
+                                       le_audio::uuid::kCapServiceUuid);
+        /* We will get back here when group will be created */
+        return;
+      }
+
+      new_group = aseGroups_.Add(id);
+      if (!new_group) {
+        LOG(ERROR) << __func__
+                   << ", can't create group - group is already there?";
+        return;
+      }
+    } else {
+      LOG_ASSERT(id == group_id)
+          << " group id missmatch? leaudio id: " << group_id
+          << " groups module " << id;
+      new_group = aseGroups_.FindById(group_id);
+      if (!new_group) {
+        new_group = aseGroups_.Add(group_id);
+      } else {
+        if (new_group->IsDeviceInTheGroup(leAudioDevice)) return;
+      }
+    }
+
+    /* If device was in the group and it was not removed by the application,
+     * lets do it now
+     */
+    if (old_group) group_remove_node(old_group, address, update_group_module);
+
+    new_group->AddNode(leAudioDevices_.GetByAddress(address));
+
+    callbacks_->OnGroupNodeStatus(address, new_group->group_id_,
+                                  GroupNodeStatus::ADDED);
+
+    /* If device is connected and added to the group, lets read ASE states */
+    if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID)
+      AseInitialStateReadRequest(leAudioDevice);
+
+    /* Group may be destroyed once moved its last node to new group */
+    if (aseGroups_.FindById(old_group_id) != nullptr) {
+      /* Removing node from group may touch its context integrity */
+      std::optional<AudioContexts> old_group_updated_contexts =
+          old_group->UpdateActiveContextsMap(old_group->GetActiveContexts());
+
+      if (old_group_updated_contexts || old_group->ReloadAudioLocations()) {
+        callbacks_->OnAudioConf(old_group->audio_directions_, old_group_id,
+                                old_group->snk_audio_locations_.to_ulong(),
+                                old_group->src_audio_locations_.to_ulong(),
+                                old_group_updated_contexts->to_ulong());
+      }
+    }
+
+    UpdateContextAndLocations(new_group, leAudioDevice);
+  }
+
+  void GroupAddNode(const int group_id, const RawAddress& address) override {
+    auto id = DeviceGroups::Get()->GetGroupId(address,
+                                              le_audio::uuid::kCapServiceUuid);
+    if (id == group_id) return;
+
+    if (id != bluetooth::groups::kGroupUnknown) {
+      DeviceGroups::Get()->RemoveDevice(address, id);
+    }
+
+    DeviceGroups::Get()->AddDevice(address, le_audio::uuid::kCapServiceUuid,
+                                   group_id);
+  }
+
+  void group_remove_node(LeAudioDeviceGroup* group, const RawAddress& address,
+                         bool update_group_module = false) {
+    int group_id = group->group_id_;
+    group->RemoveNode(leAudioDevices_.GetByAddress(address));
+
+    if (update_group_module) {
+      int groups_group_id = DeviceGroups::Get()->GetGroupId(
+          address, le_audio::uuid::kCapServiceUuid);
+      if (groups_group_id == group_id) {
+        DeviceGroups::Get()->RemoveDevice(address, group_id);
+      }
+    }
+
+    callbacks_->OnGroupNodeStatus(address, group_id, GroupNodeStatus::REMOVED);
+
+    /* Remove group if this was the last leAudioDevice in this group */
+    if (group->IsEmpty()) {
+      aseGroups_.Remove(group_id);
+
+      return;
+    }
+
+    /* Removing node from group touch its context integrity */
+    std::optional<AudioContexts> updated_contexts =
+        group->UpdateActiveContextsMap(group->GetActiveContexts());
+
+    if (updated_contexts || group->ReloadAudioLocations())
+      callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
+                              group->snk_audio_locations_.to_ulong(),
+                              group->src_audio_locations_.to_ulong(),
+                              updated_contexts->to_ulong());
+  }
+
+  void GroupRemoveNode(const int group_id, const RawAddress& address) override {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+
+    LOG(INFO) << __func__ << " group_id: " << group_id
+              << " address: " << address;
+
+    if (!leAudioDevice) {
+      LOG(ERROR) << __func__
+                 << ", Skipping unknown leAudioDevice, address: " << address;
+      return;
+    }
+
+    if (leAudioDevice->group_id_ != group_id) {
+      LOG(ERROR) << __func__ << "Device is not in group_id: " << group_id
+                 << ", but in group_id: " << leAudioDevice->group_id_;
+      return;
+    }
+
+    if (group == NULL) {
+      LOG(ERROR) << __func__ << " device not in the group ?!";
+      return;
+    }
+
+    group_remove_node(group, address, true);
+  }
+
+  void GroupStream(const int group_id, const uint16_t context_type) override {
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+    auto final_context_type = context_type;
+
+    if (context_type >= static_cast<uint16_t>(LeAudioContextType::RFU)) {
+      LOG(ERROR) << __func__ << ", stream context type is not supported: "
+                 << loghex(context_type);
+      CancelStreamingRequest();
+      return;
+    }
+
+    if (!group) {
+      LOG(ERROR) << __func__ << ", unknown group id: " << group_id;
+      CancelStreamingRequest();
+      return;
+    }
+
+    auto supported_context_type = group->GetActiveContexts();
+    if (!(context_type & supported_context_type.to_ulong())) {
+      LOG(ERROR) << " Unsupported context type by remote device: "
+                 << loghex(context_type) << ". Switching to unspecified";
+      final_context_type =
+          static_cast<uint16_t>(LeAudioContextType::UNSPECIFIED);
+    }
+
+    if (!group->IsAnyDeviceConnected()) {
+      LOG(ERROR) << __func__ << ", group " << group_id << " is not connected ";
+      CancelStreamingRequest();
+      return;
+    }
+
+    /* Check if any group is in the transition state. If so, we don't allow to
+     * start new group to stream */
+    if (aseGroups_.IsAnyInTransition()) {
+      LOG(INFO) << __func__ << " some group is already in the transition state";
+      CancelStreamingRequest();
+      return;
+    }
+
+    if (groupStateMachine_->StartStream(
+            group, static_cast<LeAudioContextType>(final_context_type)))
+      stream_request_started_ = true;
+    else
+      ClientAudioIntefraceRelease();
+  }
+
+  void GroupSuspend(const int group_id) override {
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+
+    if (!group) {
+      LOG(ERROR) << __func__ << ", unknown group id: " << group_id;
+      return;
+    }
+
+    if (!group->IsAnyDeviceConnected()) {
+      LOG(ERROR) << __func__ << ", group is not connected";
+      return;
+    }
+
+    if (group->IsInTransition()) {
+      LOG(INFO) << __func__
+                << ", group is in transition from: " << group->GetState()
+                << ", to: " << group->GetTargetState();
+      return;
+    }
+
+    if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+      LOG(ERROR) << __func__
+                 << ", invalid current state of group: " << group->GetState();
+      return;
+    }
+
+    audio_source_ready_to_send = false;
+    audio_sink_ready_to_receive = false;
+
+    groupStateMachine_->SuspendStream(group);
+  }
+
+  void GroupStop(const int group_id) override {
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+
+    if (!group) {
+      LOG(ERROR) << __func__ << ", unknown group id: " << group_id;
+      return;
+    }
+
+    if (group->IsEmpty()) {
+      LOG(ERROR) << __func__ << ", group is empty";
+      return;
+    }
+
+    if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
+      LOG(ERROR) << __func__
+                 << ", group already stopped: " << group->GetState();
+      return;
+    }
+
+    groupStateMachine_->StopStream(group);
+  }
+
+  void GroupDestroy(const int group_id) override {
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+
+    if (!group) {
+      LOG(ERROR) << __func__ << ", unknown group id: " << group_id;
+      return;
+    }
+
+    // Disconnect and remove each device within the group
+    auto* dev = group->GetFirstDevice();
+    while (dev) {
+      auto* next_dev = group->GetNextDevice(dev);
+      RemoveDevice(dev->address_);
+      dev = next_dev;
+    }
+  }
+
+  void GroupSetActive(const int group_id) override {
+    DLOG(INFO) << __func__ << " group_id: " << group_id;
+
+    if (group_id == bluetooth::groups::kGroupUnknown) {
+      if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
+        /* Nothing to do */
+        return;
+      }
+
+      StopAudio();
+      GroupStop(active_group_id_);
+      callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE);
+      active_group_id_ = group_id;
+
+      return;
+    }
+
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+    if (!group) {
+      LOG(ERROR) << __func__
+                 << ", Invalid group: " << static_cast<int>(group_id);
+      return;
+    }
+
+    if (active_group_id_ != bluetooth::groups::kGroupUnknown) {
+      LOG(WARNING) << __func__ << ", Another group already active: "
+                   << static_cast<int>(active_group_id_);
+      return;
+    }
+
+    if (!audio_source_instance_) {
+      audio_source_instance_ = LeAudioClientAudioSource::Acquire();
+      if (!audio_source_instance_) {
+        LOG(ERROR) << __func__ << ", could not acquire audio source interface";
+        return;
+      }
+    }
+
+    if (!audio_sink_instance_) {
+      audio_sink_instance_ = LeAudioClientAudioSink::Acquire();
+      if (!audio_sink_instance_) {
+        LOG(ERROR) << __func__ << ", could not acquire audio sink interface";
+        LeAudioClientAudioSource::Release(audio_source_instance_);
+        return;
+      }
+    }
+
+    /* Configure audio HAL sessions with most frequent context */
+    UpdateCurrentHalSessions(group_id, LeAudioContextType::MEDIA);
+    if (current_source_codec_config.IsInvalid() &&
+        current_sink_codec_config.IsInvalid()) {
+      LOG(WARNING) << __func__ << ", unsupported device configurations";
+      callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE);
+      return;
+    }
+
+    active_group_id_ = group_id;
+    callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE);
+  }
+
+  void RemoveDevice(const RawAddress& address) override {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    if (!leAudioDevice) {
+      return;
+    }
+
+    /* Remove the group assignment if not yet removed. It might happen that the
+     * group module has already called the appropriate callback and we have
+     * already removed the group assignment.
+     */
+    if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
+      auto group = aseGroups_.FindById(leAudioDevice->group_id_);
+      group_remove_node(group, address, true);
+    }
+
+    if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) {
+      Disconnect(address);
+      leAudioDevice->removing_device_ = true;
+      return;
+    }
+
+    leAudioDevices_.Remove(address);
+  }
+
+  void Connect(const RawAddress& address) override {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    if (!leAudioDevice) {
+      leAudioDevices_.Add(address, true);
+    } else {
+      leAudioDevice->connecting_actively_ = true;
+    }
+
+    BTA_GATTC_Open(gatt_if_, address, true, false);
+  }
+
+  std::vector<RawAddress> GetGroupDevices(const int group_id) override {
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+    std::vector<RawAddress> all_group_device_addrs;
+
+    if (group != nullptr) {
+      LeAudioDevice* leAudioDevice = group->GetFirstDevice();
+      while (leAudioDevice) {
+        all_group_device_addrs.push_back(leAudioDevice->address_);
+        leAudioDevice = group->GetNextDevice(leAudioDevice);
+      };
+    }
+
+    return all_group_device_addrs;
+  }
+
+  /* Restore paired device from storage to recreate groups */
+  void AddFromStorage(const RawAddress& address, bool autoconnect) {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+
+    LOG(INFO) << __func__ << ", restoring: " << address;
+
+    if (!leAudioDevice) {
+      leAudioDevices_.Add(address, false);
+      leAudioDevice = leAudioDevices_.FindByAddress(address);
+    }
+
+    int group_id = DeviceGroups::Get()->GetGroupId(
+        address, le_audio::uuid::kCapServiceUuid);
+    if (group_id != bluetooth::groups::kGroupUnknown) {
+      group_add_node(group_id, address);
+    }
+
+    if (autoconnect) Connect(address);
+  }
+
+  void Disconnect(const RawAddress& address) override {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+
+    if (!leAudioDevice) {
+      LOG(ERROR) << __func__ << ", leAudioDevice not connected (" << address
+                 << ")";
+      return;
+    }
+
+    /* cancel pending direct connect */
+    if (leAudioDevice->connecting_actively_)
+      BTA_GATTC_CancelOpen(gatt_if_, address, true);
+
+    /* Removes all registrations for connection */
+    BTA_GATTC_CancelOpen(0, address, false);
+
+    if (leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID) {
+      LOG(ERROR) << __func__ << ", leAudioDevice not connected (" << address
+                 << ")";
+      return;
+    }
+
+    DisconnectDevice(leAudioDevice);
+  }
+
+  void DisconnectDevice(LeAudioDevice* leAudioDevice,
+                        bool acl_force_disconnect = false) {
+    if (leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID) {
+      return;
+    }
+
+    if (acl_force_disconnect) {
+      uint16_t acl_handle =
+          BTM_GetHCIConnHandle(leAudioDevice->address_, BT_TRANSPORT_LE);
+      if (acl_handle != HCI_INVALID_HANDLE) {
+        acl_disconnect_from_handle(acl_handle, HCI_ERR_PEER_USER);
+        return;
+      }
+    }
+
+    BtaGattQueue::Clean(leAudioDevice->conn_id_);
+    BTA_GATTC_Close(leAudioDevice->conn_id_);
+    leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
+  }
+
+  void DeregisterNotifications(LeAudioDevice* leAudioDevice) {
+    /* GATTC will ommit not registered previously handles */
+    for (auto pac_tuple : leAudioDevice->snk_pacs_) {
+      BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_,
+                                           std::get<0>(pac_tuple).val_hdl);
+    }
+    for (auto pac_tuple : leAudioDevice->src_pacs_) {
+      BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_,
+                                           std::get<0>(pac_tuple).val_hdl);
+    }
+
+    if (leAudioDevice->snk_audio_locations_hdls_.val_hdl != 0)
+      BTA_GATTC_DeregisterForNotifications(
+          gatt_if_, leAudioDevice->address_,
+          leAudioDevice->snk_audio_locations_hdls_.val_hdl);
+    if (leAudioDevice->src_audio_locations_hdls_.val_hdl != 0)
+      BTA_GATTC_DeregisterForNotifications(
+          gatt_if_, leAudioDevice->address_,
+          leAudioDevice->src_audio_locations_hdls_.val_hdl);
+    if (leAudioDevice->audio_avail_hdls_.val_hdl != 0)
+      BTA_GATTC_DeregisterForNotifications(
+          gatt_if_, leAudioDevice->address_,
+          leAudioDevice->audio_avail_hdls_.val_hdl);
+    if (leAudioDevice->audio_supp_cont_hdls_.val_hdl != 0)
+      BTA_GATTC_DeregisterForNotifications(
+          gatt_if_, leAudioDevice->address_,
+          leAudioDevice->audio_supp_cont_hdls_.val_hdl);
+    if (leAudioDevice->ctp_hdls_.val_hdl != 0)
+      BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_,
+                                           leAudioDevice->ctp_hdls_.val_hdl);
+
+    for (struct ase& ase : leAudioDevice->ases_)
+      BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_,
+                                           ase.hdls.val_hdl);
+  }
+
+  /* This is a generic read/notify/indicate handler for gatt. Here messages
+   * are dispatched to correct elements e.g. ASEs, PACs, audio locations etc.
+   */
+  void LeAudioCharValueHandle(uint16_t conn_id, uint16_t hdl, uint16_t len,
+                              uint8_t* value) {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id);
+    struct ase* ase;
+
+    if (!leAudioDevice) {
+      LOG(ERROR) << __func__ << ", no leAudioDevice assigned to connection id: "
+                 << static_cast<int>(conn_id);
+      return;
+    }
+
+    ase = leAudioDevice->GetAseByValHandle(hdl);
+
+    if (ase) {
+      LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
+      groupStateMachine_->ProcessGattNotifEvent(value, len, ase, leAudioDevice,
+                                                group);
+
+      return;
+    }
+
+    auto snk_pac_ent = std::find_if(
+        leAudioDevice->snk_pacs_.begin(), leAudioDevice->snk_pacs_.end(),
+        [&hdl](auto& pac_ent) { return std::get<0>(pac_ent).val_hdl == hdl; });
+    if (snk_pac_ent != leAudioDevice->snk_pacs_.end()) {
+      std::vector<struct le_audio::types::acs_ac_record> pac_recs;
+
+      /* Guard consistency of PAC records structure */
+      if (!le_audio::client_parser::pacs::ParsePac(pac_recs, len, value))
+        return;
+
+      LOG(INFO) << __func__ << ", Registering sink PACs";
+      leAudioDevice->RegisterPACs(&std::get<1>(*snk_pac_ent), &pac_recs);
+
+      /* Update supported context types including internal capabilities */
+      LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
+
+      /* Active context map should be considered to be updated in response to
+       * PACs update.
+       * Read of available context during initial attribute discovery.
+       * Group would be assigned once service search is completed.
+       */
+      if (group)
+        group->UpdateActiveContextsMap(leAudioDevice->GetAvailableContexts());
+
+      return;
+    }
+
+    auto src_pac_ent = std::find_if(
+        leAudioDevice->src_pacs_.begin(), leAudioDevice->src_pacs_.end(),
+        [&hdl](auto& pac_ent) { return std::get<0>(pac_ent).val_hdl == hdl; });
+    if (src_pac_ent != leAudioDevice->src_pacs_.end()) {
+      std::vector<struct le_audio::types::acs_ac_record> pac_recs;
+
+      /* Guard consistency of PAC records structure */
+      if (!le_audio::client_parser::pacs::ParsePac(pac_recs, len, value))
+        return;
+
+      LOG(INFO) << __func__ << ", Registering source PACs";
+      leAudioDevice->RegisterPACs(&std::get<1>(*src_pac_ent), &pac_recs);
+
+      /* Update supported context types including internal capabilities */
+      LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
+
+      /* Active context map should be considered to be updated in response to
+       * PACs update.
+       * Read of available context during initial attribute discovery.
+       * Group would be assigned once service search is completed.
+       */
+      if (group)
+        group->UpdateActiveContextsMap(leAudioDevice->GetAvailableContexts());
+
+      return;
+    }
+
+    if (hdl == leAudioDevice->snk_audio_locations_hdls_.val_hdl) {
+      AudioLocations snk_audio_locations;
+
+      le_audio::client_parser::pacs::ParseAudioLocations(snk_audio_locations,
+                                                         len, value);
+
+      /* Value may not change */
+      if ((leAudioDevice->audio_directions_ &
+           le_audio::types::kLeAudioDirectionSink) &&
+          (leAudioDevice->snk_audio_locations_ ^ snk_audio_locations).none())
+        return;
+
+      /* Presence of PAC characteristic for source means support for source
+       * audio location. Value of 0x00000000 means mono/unspecified
+       */
+      leAudioDevice->audio_directions_ |=
+          le_audio::types::kLeAudioDirectionSink;
+      leAudioDevice->snk_audio_locations_ = snk_audio_locations;
+
+      LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
+      /* Read of source audio locations during initial attribute discovery.
+       * Group would be assigned once service search is completed.
+       */
+      if (group && group->ReloadAudioLocations()) {
+        callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
+                                group->snk_audio_locations_.to_ulong(),
+                                group->src_audio_locations_.to_ulong(),
+                                group->GetActiveContexts().to_ulong());
+      }
+    } else if (hdl == leAudioDevice->src_audio_locations_hdls_.val_hdl) {
+      AudioLocations src_audio_locations;
+
+      le_audio::client_parser::pacs::ParseAudioLocations(src_audio_locations,
+                                                         len, value);
+
+      /* Value may not change */
+      if ((leAudioDevice->audio_directions_ &
+           le_audio::types::kLeAudioDirectionSource) &&
+          (leAudioDevice->src_audio_locations_ ^ src_audio_locations).none())
+        return;
+
+      /* Presence of PAC characteristic for source means support for source
+       * audio location. Value of 0x00000000 means mono/unspecified
+       */
+      leAudioDevice->audio_directions_ |=
+          le_audio::types::kLeAudioDirectionSource;
+      leAudioDevice->src_audio_locations_ = src_audio_locations;
+
+      LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
+      /* Read of source audio locations during initial attribute discovery.
+       * Group would be assigned once service search is completed.
+       */
+      if (group && group->ReloadAudioLocations()) {
+        callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
+                                group->snk_audio_locations_.to_ulong(),
+                                group->src_audio_locations_.to_ulong(),
+                                group->GetActiveContexts().to_ulong());
+      }
+    } else if (hdl == leAudioDevice->audio_avail_hdls_.val_hdl) {
+      auto avail_audio_contexts = std::make_unique<
+          struct le_audio::client_parser::pacs::acs_available_audio_contexts>();
+
+      le_audio::client_parser::pacs::ParseAvailableAudioContexts(
+          *avail_audio_contexts, len, value);
+
+      auto updated_avail_contexts = leAudioDevice->SetAvailableContexts(
+          avail_audio_contexts->snk_avail_cont,
+          avail_audio_contexts->src_avail_cont);
+
+      if (updated_avail_contexts.any()) {
+        /* Update scenario map considering changed active context types */
+        LeAudioDeviceGroup* group =
+            aseGroups_.FindById(leAudioDevice->group_id_);
+        /* Read of available context during initial attribute discovery.
+         * Group would be assigned once service search is completed.
+         */
+        if (group) {
+          std::optional<AudioContexts> updated_contexts =
+              group->UpdateActiveContextsMap(updated_avail_contexts);
+          if (updated_contexts) {
+            callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
+                                    group->snk_audio_locations_.to_ulong(),
+                                    group->src_audio_locations_.to_ulong(),
+                                    updated_contexts->to_ulong());
+          }
+        }
+      }
+    } else if (hdl == leAudioDevice->audio_supp_cont_hdls_.val_hdl) {
+      auto supp_audio_contexts = std::make_unique<
+          struct le_audio::client_parser::pacs::acs_supported_audio_contexts>();
+
+      le_audio::client_parser::pacs::ParseSupportedAudioContexts(
+          *supp_audio_contexts, len, value);
+      /* Just store if for now */
+      leAudioDevice->SetSupportedContexts(supp_audio_contexts->snk_supp_cont,
+                                          supp_audio_contexts->src_supp_cont);
+    } else if (hdl == leAudioDevice->ctp_hdls_.val_hdl) {
+      auto ntf =
+          std::make_unique<struct le_audio::client_parser::ascs::ctp_ntf>();
+
+      if (ParseAseCtpNotification(*ntf, len, value))
+        ControlPointNotificationHandler(*ntf);
+    } else {
+      LOG(ERROR) << __func__ << ", Unknown attribute read: " << loghex(hdl);
+    }
+  }
+
+  void OnGattReadRsp(uint16_t conn_id, tGATT_STATUS status, uint16_t hdl,
+                     uint16_t len, uint8_t* value, void* data) {
+    LeAudioCharValueHandle(conn_id, hdl, len, value);
+  }
+
+  void OnGattConnected(tGATT_STATUS status, uint16_t conn_id,
+                       tGATT_IF client_if, RawAddress address,
+                       tBT_TRANSPORT transport, uint16_t mtu) {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+
+    if (!leAudioDevice) return;
+
+    if (status != GATT_SUCCESS) {
+      /* autoconnect connection failed, that's ok */
+      if (!leAudioDevice->connecting_actively_) return;
+
+      LOG(ERROR) << "Failed to connect to LeAudio leAudioDevice, status: "
+                 << +status;
+      callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
+      return;
+    }
+
+    if (controller_get_interface()->supports_ble_2m_phy()) {
+      LOG(INFO) << address << " set preferred PHY to 2M";
+      BTM_BleSetPhy(address, PHY_LE_2M, PHY_LE_2M, 0);
+    }
+
+    BTM_RequestPeerSCA(leAudioDevice->address_, transport);
+
+    leAudioDevice->connecting_actively_ = false;
+    leAudioDevice->conn_id_ = conn_id;
+
+    if (mtu == GATT_DEF_BLE_MTU_SIZE) {
+      LOG(INFO) << __func__ << ", Configure MTU";
+      BtaGattQueue::ConfigureMtu(leAudioDevice->conn_id_, 240);
+    }
+
+    /* If we know services, register for notifications */
+    if (leAudioDevice->known_service_handles_)
+      RegisterKnownNotifications(leAudioDevice);
+
+    if (BTM_SecIsSecurityPending(address)) {
+      /* if security collision happened, wait for encryption done
+       * (BTA_GATTC_ENC_CMPL_CB_EVT) */
+      return;
+    }
+
+    /* verify bond */
+    if (BTM_IsEncrypted(address, BT_TRANSPORT_LE)) {
+      /* if link has been encrypted */
+      OnEncryptionComplete(address, BTM_SUCCESS);
+      return;
+    }
+
+    if (BTM_IsLinkKeyKnown(address, BT_TRANSPORT_LE)) {
+      int result = BTM_SetEncryption(
+          address, BT_TRANSPORT_LE,
+          [](const RawAddress* bd_addr, tBT_TRANSPORT transport,
+             void* p_ref_data, tBTM_STATUS status) {
+            if (instance) instance->OnEncryptionComplete(*bd_addr, status);
+          },
+          nullptr, BTM_BLE_SEC_ENCRYPT);
+
+      LOG(INFO) << __func__
+                << "Encryption required. Request result: " << result;
+      return;
+    }
+
+    LOG(ERROR) << __func__ << " Encryption error";
+  }
+
+  void RegisterKnownNotifications(LeAudioDevice* leAudioDevice) {
+    LOG(INFO) << __func__ << " device: " << leAudioDevice->address_;
+
+    /* GATTC will ommit not registered previously handles */
+    for (auto pac_tuple : leAudioDevice->snk_pacs_) {
+      BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_,
+                                         std::get<0>(pac_tuple).val_hdl);
+    }
+    for (auto pac_tuple : leAudioDevice->src_pacs_) {
+      BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_,
+                                         std::get<0>(pac_tuple).val_hdl);
+    }
+
+    if (leAudioDevice->snk_audio_locations_hdls_.val_hdl != 0)
+      BTA_GATTC_RegisterForNotifications(
+          gatt_if_, leAudioDevice->address_,
+          leAudioDevice->snk_audio_locations_hdls_.val_hdl);
+    if (leAudioDevice->src_audio_locations_hdls_.val_hdl != 0)
+      BTA_GATTC_RegisterForNotifications(
+          gatt_if_, leAudioDevice->address_,
+          leAudioDevice->src_audio_locations_hdls_.val_hdl);
+    if (leAudioDevice->audio_avail_hdls_.val_hdl != 0)
+      BTA_GATTC_RegisterForNotifications(
+          gatt_if_, leAudioDevice->address_,
+          leAudioDevice->audio_avail_hdls_.val_hdl);
+    if (leAudioDevice->audio_supp_cont_hdls_.val_hdl != 0)
+      BTA_GATTC_RegisterForNotifications(
+          gatt_if_, leAudioDevice->address_,
+          leAudioDevice->audio_supp_cont_hdls_.val_hdl);
+    if (leAudioDevice->ctp_hdls_.val_hdl != 0)
+      BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_,
+                                         leAudioDevice->ctp_hdls_.val_hdl);
+
+    for (struct ase& ase : leAudioDevice->ases_)
+      BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_,
+                                         ase.hdls.val_hdl);
+  }
+
+  void OnEncryptionComplete(const RawAddress& address, uint8_t status) {
+    LOG(INFO) << __func__ << " " << address << "status: " << int{status};
+
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    if (leAudioDevice == NULL) {
+      LOG(WARNING) << "Skipping unknown device" << address;
+      return;
+    }
+
+    if (status != BTM_SUCCESS) {
+      LOG(ERROR) << "Encryption failed"
+                 << " status: " << int{status};
+      BTA_GATTC_Close(leAudioDevice->conn_id_);
+      if (leAudioDevice->connecting_actively_) {
+        callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
+      }
+      return;
+    }
+
+    if (leAudioDevice->encrypted_) {
+      LOG(INFO) << __func__ << " link already encrypted, nothing to do";
+      return;
+    }
+
+    leAudioDevice->encrypted_ = true;
+
+    /* If we know services and read is not ongoing, this is reconnection and
+     * just notify connected  */
+    if (leAudioDevice->known_service_handles_ &&
+        !leAudioDevice->notify_connected_after_read_) {
+      connectionReady(leAudioDevice);
+      return;
+    }
+
+    BTA_GATTC_ServiceSearchRequest(
+        leAudioDevice->conn_id_,
+        &le_audio::uuid::kPublishedAudioCapabilityServiceUuid);
+  }
+
+  void OnGattDisconnected(uint16_t conn_id, tGATT_IF client_if,
+                          RawAddress address, tGATT_DISCONN_REASON reason) {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+
+    if (!leAudioDevice) {
+      LOG(ERROR) << ", skipping unknown leAudioDevice, address: " << address;
+      return;
+    }
+
+    LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
+
+    groupStateMachine_->ProcessHciNotifAclDisconnected(group, leAudioDevice);
+
+    DeregisterNotifications(leAudioDevice);
+
+    callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
+    leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
+    leAudioDevice->encrypted_ = false;
+
+    if (leAudioDevice->removing_device_) leAudioDevices_.Remove(address);
+  }
+
+  bool subscribe_for_indications(uint16_t conn_id, const RawAddress& address,
+                                 uint16_t handle, uint16_t ccc_handle,
+                                 bool ntf) {
+    std::vector<uint8_t> value(2);
+    uint8_t* ptr = value.data();
+
+    if (BTA_GATTC_RegisterForNotifications(gatt_if_, address, handle) !=
+        GATT_SUCCESS) {
+      LOG(ERROR) << __func__ << ", cannot register for notification: "
+                 << static_cast<int>(handle);
+      return false;
+    }
+
+    UINT16_TO_STREAM(ptr, ntf ? GATT_CHAR_CLIENT_CONFIG_NOTIFICATION
+                              : GATT_CHAR_CLIENT_CONFIG_INDICTION);
+
+    BtaGattQueue::WriteDescriptor(
+        conn_id, ccc_handle, std::move(value), GATT_WRITE,
+        [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, void* data) {
+          if (instance) instance->OnGattWriteCcc(conn_id, status, handle, data);
+        },
+        nullptr);
+    return true;
+  }
+
+  /* Find the handle for the client characteristics configuration of a given
+   * characteristics.
+   */
+  uint16_t find_ccc_handle(const gatt::Characteristic& charac) {
+    auto iter = std::find_if(
+        charac.descriptors.begin(), charac.descriptors.end(),
+        [](const auto& desc) {
+          return desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG);
+        });
+
+    return iter == charac.descriptors.end() ? 0 : (*iter).handle;
+  }
+
+  void OnServiceChangeEvent(const RawAddress& address) {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    if (!leAudioDevice) {
+      DLOG(ERROR) << __func__
+                  << ", skipping unknown leAudioDevice, address: " << address;
+      return;
+    }
+
+    LOG(INFO) << __func__ << ": address=" << address;
+    leAudioDevice->known_service_handles_ = false;
+    leAudioDevice->csis_member_ = false;
+    BtaGattQueue::Clean(leAudioDevice->conn_id_);
+    DeregisterNotifications(leAudioDevice);
+  }
+
+  void OnGattServiceDiscoveryDone(const RawAddress& address) {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    if (!leAudioDevice) {
+      DLOG(ERROR) << __func__
+                  << ", skipping unknown leAudioDevice, address: " << address;
+      return;
+    }
+
+    if (!leAudioDevice->known_service_handles_)
+      BTA_GATTC_ServiceSearchRequest(
+          leAudioDevice->conn_id_,
+          &le_audio::uuid::kPublishedAudioCapabilityServiceUuid);
+  }
+  /* This method is called after connection beginning to identify and initialize
+   * a le audio device. Any missing mandatory attribute will result in reverting
+   * and cleaning up device.
+   */
+  void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id);
+
+    if (!leAudioDevice) {
+      DLOG(ERROR) << __func__ << ", skipping unknown leAudioDevice, conn_id: "
+                  << loghex(conn_id);
+      return;
+    }
+
+    LOG(INFO) << __func__ << " test csis_member "
+              << leAudioDevice->csis_member_;
+
+    if (status != GATT_SUCCESS) {
+      /* close connection and report service discovery complete with error */
+      LOG(ERROR) << "Service discovery failed";
+
+      DisconnectDevice(leAudioDevice);
+      return;
+    }
+
+    const std::list<gatt::Service>* services = BTA_GATTC_GetServices(conn_id);
+
+    const gatt::Service* pac_svc = nullptr;
+    const gatt::Service* ase_svc = nullptr;
+
+    for (const gatt::Service& tmp : *services) {
+      if (tmp.uuid == le_audio::uuid::kPublishedAudioCapabilityServiceUuid) {
+        LOG(INFO) << "Found Audio Capability service, handle: "
+                  << loghex(tmp.handle);
+        pac_svc = &tmp;
+      } else if (tmp.uuid == le_audio::uuid::kAudioStreamControlServiceUuid) {
+        LOG(INFO) << "Found Audio Stream Endpoint service, handle: "
+                  << loghex(tmp.handle);
+        ase_svc = &tmp;
+      } else if (tmp.uuid == le_audio::uuid::kCapServiceUuid) {
+        LOG(INFO) << "Found CAP Service, handle: " << loghex(tmp.handle);
+
+        /* Try to find context for CSIS instances */
+        for (auto& included_srvc : tmp.included_services) {
+          if (included_srvc.uuid == bluetooth::csis::kCsisServiceUuid) {
+            LOG(INFO) << __func__ << " CSIS included into CAS";
+            if (bluetooth::csis::CsisClient::IsCsisClientRunning())
+              leAudioDevice->csis_member_ = true;
+
+            break;
+          }
+        }
+      }
+    }
+
+    if (!pac_svc || !ase_svc) {
+      LOG(ERROR) << "No mandatory le audio services found";
+
+      DisconnectDevice(leAudioDevice);
+      return;
+    }
+
+    /* Refresh PACs handles */
+    leAudioDevice->ClearPACs();
+
+    for (const gatt::Characteristic& charac : pac_svc->characteristics) {
+      if (charac.uuid ==
+          le_audio::uuid::kSinkPublishedAudioCapabilityCharacteristicUuid) {
+        struct hdl_pair hdl_pair;
+        hdl_pair.val_hdl = charac.value_handle;
+        hdl_pair.ccc_hdl = find_ccc_handle(charac);
+
+        if (hdl_pair.ccc_hdl == 0) {
+          LOG(ERROR) << __func__ << ", snk pac char doesn't have ccc";
+
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        if (!subscribe_for_indications(conn_id, leAudioDevice->address_,
+                                       hdl_pair.val_hdl, hdl_pair.ccc_hdl,
+                                       true)) {
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        /* Obtain initial state of sink PACs */
+        BtaGattQueue::ReadCharacteristic(conn_id, hdl_pair.val_hdl,
+                                         OnGattReadRspStatic, NULL);
+
+        leAudioDevice->snk_pacs_.push_back(std::make_tuple(
+            hdl_pair, std::vector<struct le_audio::types::acs_ac_record>()));
+
+        LOG(INFO) << "Found Sink PAC characteristic, handle: "
+                  << loghex(charac.value_handle)
+                  << ", ccc handle: " << loghex(hdl_pair.ccc_hdl);
+      } else if (charac.uuid ==
+                 le_audio::uuid::
+                     kSourcePublishedAudioCapabilityCharacteristicUuid) {
+        struct hdl_pair hdl_pair;
+        hdl_pair.val_hdl = charac.value_handle;
+        hdl_pair.ccc_hdl = find_ccc_handle(charac);
+
+        if (hdl_pair.ccc_hdl == 0) {
+          LOG(ERROR) << __func__ << ", src pac char doesn't have ccc";
+
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        if (!subscribe_for_indications(conn_id, leAudioDevice->address_,
+                                       hdl_pair.val_hdl, hdl_pair.ccc_hdl,
+                                       true)) {
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        /* Obtain initial state of source PACs */
+        BtaGattQueue::ReadCharacteristic(conn_id, hdl_pair.val_hdl,
+                                         OnGattReadRspStatic, NULL);
+
+        leAudioDevice->src_pacs_.push_back(std::make_tuple(
+            hdl_pair, std::vector<struct le_audio::types::acs_ac_record>()));
+
+        LOG(INFO) << "Found Source PAC characteristic, handle: "
+                  << loghex(charac.value_handle)
+                  << ", ccc handle: " << loghex(hdl_pair.ccc_hdl);
+      } else if (charac.uuid ==
+                 le_audio::uuid::kSinkAudioLocationCharacteristicUuid) {
+        leAudioDevice->snk_audio_locations_hdls_.val_hdl = charac.value_handle;
+        leAudioDevice->snk_audio_locations_hdls_.ccc_hdl =
+            find_ccc_handle(charac);
+
+        if (leAudioDevice->snk_audio_locations_hdls_.ccc_hdl == 0)
+          LOG(INFO) << __func__
+                    << ", snk audio locations char doesn't have"
+                       "ccc";
+
+        if (leAudioDevice->snk_audio_locations_hdls_.ccc_hdl != 0 &&
+            !subscribe_for_indications(
+                conn_id, leAudioDevice->address_,
+                leAudioDevice->snk_audio_locations_hdls_.val_hdl,
+                leAudioDevice->snk_audio_locations_hdls_.ccc_hdl, true)) {
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        /* Obtain initial state of sink audio locations */
+        BtaGattQueue::ReadCharacteristic(
+            conn_id, leAudioDevice->snk_audio_locations_hdls_.val_hdl,
+            OnGattReadRspStatic, NULL);
+
+        LOG(INFO) << "Found Sink audio locations characteristic, handle: "
+                  << loghex(charac.value_handle) << ", ccc handle: "
+                  << loghex(leAudioDevice->snk_audio_locations_hdls_.ccc_hdl);
+      } else if (charac.uuid ==
+                 le_audio::uuid::kSourceAudioLocationCharacteristicUuid) {
+        leAudioDevice->src_audio_locations_hdls_.val_hdl = charac.value_handle;
+        leAudioDevice->src_audio_locations_hdls_.ccc_hdl =
+            find_ccc_handle(charac);
+
+        if (leAudioDevice->src_audio_locations_hdls_.ccc_hdl == 0)
+          LOG(INFO) << __func__
+                    << ", snk audio locations char doesn't have"
+                       "ccc";
+
+        if (leAudioDevice->src_audio_locations_hdls_.ccc_hdl != 0 &&
+            !subscribe_for_indications(
+                conn_id, leAudioDevice->address_,
+                leAudioDevice->src_audio_locations_hdls_.val_hdl,
+                leAudioDevice->src_audio_locations_hdls_.ccc_hdl, true)) {
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        /* Obtain initial state of source audio locations */
+        BtaGattQueue::ReadCharacteristic(
+            conn_id, leAudioDevice->src_audio_locations_hdls_.val_hdl,
+            OnGattReadRspStatic, NULL);
+
+        LOG(INFO) << "Found Source audio locations characteristic, handle: "
+                  << loghex(charac.value_handle) << ", ccc handle: "
+                  << loghex(leAudioDevice->src_audio_locations_hdls_.ccc_hdl);
+      } else if (charac.uuid ==
+                 le_audio::uuid::kAudioContextAvailabilityCharacteristicUuid) {
+        leAudioDevice->audio_avail_hdls_.val_hdl = charac.value_handle;
+        leAudioDevice->audio_avail_hdls_.ccc_hdl = find_ccc_handle(charac);
+
+        if (leAudioDevice->audio_avail_hdls_.ccc_hdl == 0) {
+          LOG(ERROR) << __func__ << ", audio avails char doesn't have ccc";
+
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        if (!subscribe_for_indications(conn_id, leAudioDevice->address_,
+                                       leAudioDevice->audio_avail_hdls_.val_hdl,
+                                       leAudioDevice->audio_avail_hdls_.ccc_hdl,
+                                       true)) {
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        /* Obtain initial state */
+        BtaGattQueue::ReadCharacteristic(
+            conn_id, leAudioDevice->audio_avail_hdls_.val_hdl,
+            OnGattReadRspStatic, NULL);
+
+        LOG(INFO) << "Found Audio Availability Context characteristic, handle: "
+                  << loghex(charac.value_handle) << ", ccc handle: "
+                  << loghex(leAudioDevice->audio_avail_hdls_.ccc_hdl);
+      } else if (charac.uuid ==
+                 le_audio::uuid::kAudioSupportedContextCharacteristicUuid) {
+        leAudioDevice->audio_supp_cont_hdls_.val_hdl = charac.value_handle;
+        leAudioDevice->audio_supp_cont_hdls_.ccc_hdl = find_ccc_handle(charac);
+
+        if (leAudioDevice->audio_supp_cont_hdls_.ccc_hdl == 0)
+          LOG(INFO) << __func__ << ", audio avails char doesn't have ccc";
+
+        if (leAudioDevice->audio_supp_cont_hdls_.ccc_hdl != 0 &&
+            !subscribe_for_indications(
+                conn_id, leAudioDevice->address_,
+                leAudioDevice->audio_supp_cont_hdls_.val_hdl,
+                leAudioDevice->audio_supp_cont_hdls_.ccc_hdl, true)) {
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        /* Obtain initial state */
+        BtaGattQueue::ReadCharacteristic(
+            conn_id, leAudioDevice->audio_supp_cont_hdls_.val_hdl,
+            OnGattReadRspStatic, NULL);
+
+        LOG(INFO) << "Found Audio Supported Context characteristic, handle: "
+                  << loghex(charac.value_handle) << ", ccc handle: "
+                  << loghex(leAudioDevice->audio_supp_cont_hdls_.ccc_hdl);
+      }
+    }
+
+    /* Refresh ASE handles */
+    leAudioDevice->ases_.clear();
+
+    for (const gatt::Characteristic& charac : ase_svc->characteristics) {
+      LOG(INFO) << "Found characteristic, uuid: " << charac.uuid.ToString();
+      if (charac.uuid == le_audio::uuid::kSinkAudioStreamEndpointUuid ||
+          charac.uuid == le_audio::uuid::kSourceAudioStreamEndpointUuid) {
+        uint16_t ccc_handle = find_ccc_handle(charac);
+        if (ccc_handle == 0) {
+          LOG(ERROR) << __func__ << ", audio avails char doesn't have ccc";
+
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        if (!subscribe_for_indications(conn_id, leAudioDevice->address_,
+                                       charac.value_handle, ccc_handle, true)) {
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        int direction =
+            charac.uuid == le_audio::uuid::kSinkAudioStreamEndpointUuid
+                ? le_audio::types::kLeAudioDirectionSink
+                : le_audio::types::kLeAudioDirectionSource;
+
+        leAudioDevice->ases_.emplace_back(charac.value_handle, ccc_handle,
+                                          direction);
+
+        LOG(INFO) << "Found ASE characteristic, handle: "
+                  << loghex(charac.value_handle)
+                  << ", ccc handle: " << loghex(ccc_handle)
+                  << ", direction: " << direction;
+      } else if (charac.uuid ==
+                 le_audio::uuid::
+                     kAudioStreamEndpointControlPointCharacteristicUuid) {
+        leAudioDevice->ctp_hdls_.val_hdl = charac.value_handle;
+        leAudioDevice->ctp_hdls_.ccc_hdl = find_ccc_handle(charac);
+
+        if (leAudioDevice->ctp_hdls_.ccc_hdl == 0) {
+          LOG(ERROR) << __func__ << ", ase ctp doesn't have ccc";
+
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        if (!subscribe_for_indications(conn_id, leAudioDevice->address_,
+                                       leAudioDevice->ctp_hdls_.val_hdl,
+                                       leAudioDevice->ctp_hdls_.ccc_hdl,
+                                       true)) {
+          DisconnectDevice(leAudioDevice);
+          return;
+        }
+
+        LOG(INFO) << "Found ASE Control Point characteristic, handle: "
+                  << loghex(charac.value_handle) << ", ccc handle: "
+                  << loghex(leAudioDevice->ctp_hdls_.ccc_hdl);
+      }
+    }
+
+    leAudioDevice->known_service_handles_ = true;
+    leAudioDevice->notify_connected_after_read_ = true;
+
+    /* If already known group id */
+    if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
+      AseInitialStateReadRequest(leAudioDevice);
+      return;
+    }
+
+    /* If device does not belong to any group yet we either add it to the
+     * group by our selfs now or wait for Csis to do it. In both cases, let's
+     * check if group is already assigned.
+     */
+    int group_id = DeviceGroups::Get()->GetGroupId(
+        leAudioDevice->address_, le_audio::uuid::kCapServiceUuid);
+    if (group_id != bluetooth::groups::kGroupUnknown) {
+      instance->group_add_node(group_id, leAudioDevice->address_);
+      return;
+    }
+
+    /* CSIS will trigger adding to group */
+    if (leAudioDevice->csis_member_) {
+      LOG(INFO) << __func__ << " waiting for CSIS to create group for device "
+                << leAudioDevice->address_;
+      return;
+    }
+
+    /* If there is no Csis just add device by our own */
+    DeviceGroups::Get()->AddDevice(leAudioDevice->address_,
+                                   le_audio::uuid::kCapServiceUuid);
+  }
+
+  void OnGattWriteCcc(uint16_t conn_id, tGATT_STATUS status, uint16_t hdl,
+                      void* data) {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id);
+    std::vector<struct ase>::iterator ase_it;
+
+    if (!leAudioDevice) {
+      LOG(ERROR) << __func__ << ", unknown conn_id=" << loghex(conn_id);
+      return;
+    }
+
+    if (status == GATT_SUCCESS) {
+      LOG(INFO) << __func__
+                << ", successfully registered on ccc: " << loghex(hdl);
+      return;
+    }
+
+    LOG(ERROR) << __func__
+               << ", Failed to register for indications: " << loghex(hdl)
+               << ", status: " << loghex((int)(status));
+
+    ase_it =
+        std::find_if(leAudioDevice->ases_.begin(), leAudioDevice->ases_.end(),
+                     [&hdl](const struct ase& ase) -> bool {
+                       return ase.hdls.ccc_hdl == hdl;
+                     });
+
+    if (ase_it == leAudioDevice->ases_.end()) {
+      LOG(ERROR) << __func__
+                 << ", unknown ccc handle: " << static_cast<int>(hdl);
+      return;
+    }
+
+    BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_,
+                                         ase_it->hdls.val_hdl);
+  }
+
+  void AttachToStreamingGroupIfNeeded(LeAudioDevice* leAudioDevice) {
+    if (leAudioDevice->group_id_ != active_group_id_) {
+      LOG(INFO) << __func__ << " group  " << leAudioDevice->group_id_
+                << " is not streaming. Nothing to do";
+      return;
+    }
+
+    LOG(INFO) << __func__ << " attaching to group  "
+              << leAudioDevice->group_id_;
+
+    /* Restore configuration */
+    LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_);
+    auto* stream_conf = &group->stream_conf;
+
+    if (!stream_conf->valid) {
+      LOG(ERROR) << __func__
+                 << " Configuration not valid. (btw not sure we need this "
+                    "flag)";
+      return;
+    }
+
+    le_audio::types::AudioLocations sink_group_audio_locations = 0;
+    uint8_t sink_num_of_active_ases = 0;
+
+    for (auto [cis_handle, audio_location] : stream_conf->sink_streams) {
+      sink_group_audio_locations |= audio_location;
+      sink_num_of_active_ases++;
+    }
+
+    le_audio::types::AudioLocations source_group_audio_locations = 0;
+    uint8_t source_num_of_active_ases = 0;
+
+    for (auto [cis_handle, audio_location] : stream_conf->source_streams) {
+      source_group_audio_locations |= audio_location;
+      source_num_of_active_ases++;
+    }
+
+    for (auto& ent : stream_conf->conf->confs) {
+      if (ent.direction == le_audio::types::kLeAudioDirectionSink) {
+        /* Sink*/
+        if (!leAudioDevice->ConfigureAses(ent, group->GetCurrentContextType(),
+                                          &sink_num_of_active_ases,
+                                          sink_group_audio_locations,
+                                          source_group_audio_locations, true)) {
+          LOG(INFO) << __func__ << " Could not set sink configuration of "
+                    << stream_conf->conf->name;
+          return;
+        }
+      } else {
+        /* Source*/
+        if (!leAudioDevice->ConfigureAses(ent, group->GetCurrentContextType(),
+                                          &source_num_of_active_ases,
+                                          sink_group_audio_locations,
+                                          source_group_audio_locations, true)) {
+          LOG(INFO) << __func__ << " Could not set source configuration of "
+                    << stream_conf->conf->name;
+          return;
+        }
+      }
+    }
+
+    groupStateMachine_->AttachToStream(group, leAudioDevice);
+  }
+
+  void connectionReady(LeAudioDevice* leAudioDevice) {
+    callbacks_->OnConnectionState(ConnectionState::CONNECTED,
+                                  leAudioDevice->address_);
+
+    if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
+      LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
+      UpdateContextAndLocations(group, leAudioDevice);
+      AttachToStreamingGroupIfNeeded(leAudioDevice);
+    }
+
+    if (leAudioDevice->first_connection_) {
+      btif_storage_set_leaudio_autoconnect(leAudioDevice->address_, true);
+      leAudioDevice->first_connection_ = false;
+    }
+  }
+
+  bool IsAseAcceptingAudioData(struct ase* ase) {
+    if (ase == nullptr) return false;
+    if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) return false;
+    if (ase->data_path_state != AudioStreamDataPathState::DATA_PATH_ESTABLISHED)
+      return false;
+
+    return true;
+  }
+
+  void get_left_and_right_stream(const std::vector<uint8_t>& data,
+                                 std::vector<int16_t>& chan_left,
+                                 std::vector<int16_t>& chan_right,
+                                 bool prepare_mono = false) {
+    uint16_t num_of_frames_per_ch = lc3_encoder->lc3Config.NF;
+
+    chan_left.reserve(num_of_frames_per_ch);
+    chan_right.reserve(num_of_frames_per_ch);
+    for (int i = 0; i < num_of_frames_per_ch; i++) {
+      const uint8_t* sample = data.data() + i * 4;
+
+      int16_t left = (int16_t)((*(sample + 1) << 8) + *sample) >> 1;
+
+      sample += 2;
+      int16_t right = (int16_t)((*(sample + 1) << 8) + *sample) >> 1;
+
+      if (prepare_mono) {
+        uint16_t mono_data = (int16_t)(((uint32_t)left + (uint32_t)right) >> 1);
+        left = mono_data;
+        right = mono_data;
+      }
+
+      chan_left.push_back(left);
+      chan_right.push_back(right);
+    }
+  }
+
+  void PrepareAndSendToTwoDevices(
+      const std::vector<uint8_t>& data,
+      struct le_audio::stream_configuration* stream_conf) {
+    uint16_t byte_count = stream_conf->sink_octets_per_codec_frame;
+    uint16_t left_cis_handle = 0;
+    uint16_t right_cis_handle = 0;
+    uint16_t number_of_required_samples_per_channel = lc3_encoder->lc3Config.NF;
+
+    for (auto [cis_handle, audio_location] : stream_conf->sink_streams) {
+      if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyLeft)
+        left_cis_handle = cis_handle;
+      if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyRight)
+        right_cis_handle = cis_handle;
+    }
+
+    if (data.size() < 2 /* bytes per sample */ * 2 /* channels */ *
+                          number_of_required_samples_per_channel) {
+      LOG(ERROR) << __func__ << "Missing samples";
+      return;
+    }
+
+    bool mono = (left_cis_handle == 0) || (right_cis_handle == 0);
+    std::vector<int16_t> chan_left;
+    std::vector<int16_t> chan_right;
+    get_left_and_right_stream(data, chan_left, chan_right, mono);
+
+    std::vector<uint8_t> chan_left_enc(byte_count, 0);
+    std::vector<uint8_t> chan_right_enc(byte_count, 0);
+
+    uint8_t err = 0;
+    if (left_cis_handle)
+      err |= lc3_encoder->run((const int16_t*)chan_left.data(),
+                              chan_left_enc.size(), chan_left_enc.data(), 0);
+    if (right_cis_handle)
+      err |= lc3_encoder->run((const int16_t*)chan_right.data(),
+                              chan_right_enc.size(), chan_right_enc.data(), 1);
+
+    if (err != Lc3Encoder::ERROR_FREE) {
+      LOG(ERROR) << " error while encoding; error code: "
+                 << "\t encoded samples: " << chan_left_enc.size()
+                 << "\t err: " << static_cast<uint8_t>(err);
+      return;
+    }
+
+    /* Send data to the controller */
+    if (left_cis_handle)
+      IsoManager::GetInstance()->SendIsoData(
+          left_cis_handle, chan_left_enc.data(), chan_left_enc.size());
+
+    if (right_cis_handle)
+      IsoManager::GetInstance()->SendIsoData(
+          right_cis_handle, chan_right_enc.data(), chan_right_enc.size());
+  }
+
+  void PrepareAndSendToSingleDevice(
+      const std::vector<uint8_t>& data,
+      struct le_audio::stream_configuration* stream_conf) {
+    int num_channels = lc3_encoder->lc3Config.Nc;
+    uint16_t byte_count = stream_conf->sink_octets_per_codec_frame;
+    auto cis_handle = stream_conf->sink_streams.front().first;
+    uint16_t number_of_required_samples_per_channel = lc3_encoder->lc3Config.NF;
+
+    if ((int)data.size() < (2 /* bytes per sample */ * num_channels *
+                            number_of_required_samples_per_channel)) {
+      LOG(ERROR) << __func__ << "Missing samples";
+      return;
+    }
+
+    std::vector<uint8_t> chan_encoded(num_channels * byte_count, 0);
+    uint8_t err = 0;
+    if (num_channels == 1) {
+      err = lc3_encoder->run((const int16_t*)data.data(), byte_count,
+                             chan_encoded.data(), 0);
+
+    } else {
+      std::vector<int16_t> chan_left;
+      std::vector<int16_t> chan_right;
+      get_left_and_right_stream(data, chan_left, chan_right, false);
+
+      err |= lc3_encoder->run((const int16_t*)chan_left.data(), byte_count,
+                              chan_encoded.data(), 0);
+
+      err |= lc3_encoder->run((const int16_t*)chan_right.data(), byte_count,
+                              chan_encoded.data() + byte_count, 1);
+    }
+
+    if (err != Lc3Encoder::ERROR_FREE) {
+      LOG(ERROR) << " error while encoding; error code: "
+                 << "\t err: " << static_cast<uint8_t>(err);
+      return;
+    }
+
+    /* Send data to the controller */
+    IsoManager::GetInstance()->SendIsoData(cis_handle, chan_encoded.data(),
+                                           chan_encoded.size());
+  }
+
+  struct le_audio::stream_configuration* GetStreamSinkConfiguration(
+      LeAudioDeviceGroup* group) {
+    struct le_audio::stream_configuration* stream_conf = &group->stream_conf;
+    int num_of_devices = 0;
+    int num_of_channels = 0;
+    uint32_t sample_freq_hz = 0;
+    uint32_t frame_duration_us = 0;
+    uint16_t octets_per_frame = 0;
+
+    LOG(INFO) << __func__ << " group_id: " << group->group_id_;
+
+    /* This contains pair of cis handle and audio location */
+    std::vector<std::pair<uint16_t, uint32_t>> streams;
+
+    for (auto* device = group->GetFirstActiveDevice(); device != nullptr;
+         device = group->GetNextActiveDevice(device)) {
+      auto* ase = device->GetFirstActiveAseByDirection(
+          le_audio::types::kLeAudioDirectionSink);
+
+      if (ase) {
+        LOG(INFO) << __func__ << "device: " << device->address_;
+        num_of_devices++;
+      }
+
+      for (; ase != nullptr;
+           ase = device->GetNextActiveAseWithSameDirection(ase)) {
+        streams.emplace_back(std::make_pair(
+            ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation));
+        num_of_channels += ase->codec_config.channel_count;
+        if (sample_freq_hz == 0) {
+          sample_freq_hz = ase->codec_config.GetSamplingFrequencyHz();
+        } else {
+          LOG_ASSERT(sample_freq_hz ==
+                     ase->codec_config.GetSamplingFrequencyHz())
+              << __func__ << " sample freq mismatch: " << +sample_freq_hz
+              << " != " << ase->codec_config.GetSamplingFrequencyHz();
+        }
+
+        if (frame_duration_us == 0) {
+          frame_duration_us = ase->codec_config.GetFrameDurationUs();
+        } else {
+          LOG_ASSERT(frame_duration_us ==
+                     ase->codec_config.GetFrameDurationUs())
+              << __func__ << " frame duration mismatch: " << +frame_duration_us
+              << " != " << ase->codec_config.GetFrameDurationUs();
+        }
+
+        if (octets_per_frame == 0) {
+          octets_per_frame = ase->codec_config.octets_per_codec_frame;
+        } else {
+          LOG_ASSERT(octets_per_frame ==
+                     ase->codec_config.octets_per_codec_frame)
+              << __func__ << " octets per frame mismatch: " << +octets_per_frame
+              << " != " << ase->codec_config.octets_per_codec_frame;
+        }
+
+        LOG(INFO) << __func__ << " Added CIS: " << +ase->cis_conn_hdl
+                  << " to stream. Allocation: "
+                  << +ase->codec_config.audio_channel_allocation
+                  << " sample_freq: " << +sample_freq_hz
+                  << " frame_duration: " << +frame_duration_us
+                  << " octects per frame: " << +octets_per_frame;
+      }
+    }
+
+    if (streams.empty()) return nullptr;
+
+    stream_conf->sink_streams = std::move(streams);
+    stream_conf->sink_num_of_devices = num_of_devices;
+    stream_conf->sink_num_of_channels = num_of_channels;
+    stream_conf->sink_sample_frequency_hz = sample_freq_hz;
+    stream_conf->sink_frame_duration_us = frame_duration_us;
+    stream_conf->sink_octets_per_codec_frame = octets_per_frame;
+    stream_conf->valid = true;
+    stream_conf->conf = group->GetActiveConfiguration();
+
+    LOG(INFO) << __func__ << " configuration: " << stream_conf->conf->name;
+
+    return stream_conf;
+  }
+
+  void OnAudioDataReady(const std::vector<uint8_t>& data) {
+    if (active_group_id_ == bluetooth::groups::kGroupUnknown ||
+        !audio_source_ready_to_send)
+      return;
+
+    LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_);
+    if (!group) {
+      LOG(ERROR) << __func__ << "There is no streaming group available";
+      return;
+    }
+
+    auto stream_conf = group->stream_conf;
+    if (!stream_conf.valid || (stream_conf.sink_num_of_devices > 2)) {
+      LOG(ERROR) << __func__ << " Stream configufation is not valid.";
+      return;
+    }
+
+    if (stream_conf.sink_num_of_devices == 2) {
+      PrepareAndSendToTwoDevices(data, &stream_conf);
+    } else {
+      PrepareAndSendToSingleDevice(data, &stream_conf);
+    }
+  }
+
+  void SendAudioData(uint8_t* data, uint16_t size) {
+    /* Get only one channel for MONO microphone */
+    /* Gather data for channel */
+    uint16_t required_for_channel_byte_count =
+        lc3_decoder->lc3Config.getByteCountFromBitrate(32000);
+    size_t required_byte_count = current_sink_codec_config.num_channels *
+                                 required_for_channel_byte_count;
+
+    if (required_byte_count != size) {
+      LOG(ERROR) << "Insufficient data for decoding and send, required: "
+                 << int(required_byte_count) << ", received: " << int(size);
+      return;
+    }
+
+    uint8_t BEC_detect = 0;
+    std::vector<int16_t> pcm_data_decoded(lc3_decoder->lc3Config.NF, 0);
+    auto err = lc3_decoder->run(data, required_for_channel_byte_count, 0,
+                                pcm_data_decoded.data(),
+                                pcm_data_decoded.size(), BEC_detect);
+
+    /* TODO: How handle failing decoding ? */
+    if (err != Lc3Decoder::ERROR_FREE) {
+      LOG(ERROR) << " error while decoding error code: "
+                 << static_cast<int>(err);
+      return;
+    }
+
+    uint16_t to_write = sizeof(int16_t) * pcm_data_decoded.size();
+    uint16_t written = LeAudioClientAudioSink::SendData(
+        (uint8_t*)pcm_data_decoded.data(), to_write);
+
+    /* TODO: What to do if not all data sinked ? */
+    if (written != to_write) LOG(ERROR) << __func__ << ", not all data sinked";
+
+    LOG(INFO) << __func__;
+  }
+
+  static inline Lc3Config::FrameDuration Lc3ConfigFrameDuration(
+      uint32_t frame_duration_us) {
+    if (frame_duration_us == LeAudioCodecConfiguration::kInterval7500Us)
+      return Lc3Config::FrameDuration::d7p5ms;
+    else
+      return Lc3Config::FrameDuration::d10ms;
+  }
+
+  bool StartSendingAudio(int group_id) {
+    LOG(INFO) << __func__;
+
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+    LeAudioDevice* device = group->GetFirstActiveDevice();
+    LOG_ASSERT(device) << __func__
+                       << " Shouldn't be called without an active device.";
+
+    /* Assume 2 ases max just for now. */
+    auto* stream_conf = GetStreamSinkConfiguration(group);
+    if (stream_conf == nullptr) {
+      LOG(ERROR) << __func__ << " could not get sink configuration";
+      return false;
+    }
+
+    if (lc3_encoder) {
+      LOG(WARNING)
+          << " The encoder instance should have been already released.";
+      delete lc3_encoder;
+      lc3_encoder = nullptr;
+    }
+
+    /* One or multiple audio channels encoder */
+    lc3_encoder = new Lc3Encoder(Lc3Config(
+        current_source_codec_config.sample_rate,
+        Lc3ConfigFrameDuration(current_source_codec_config.data_interval_us),
+        current_source_codec_config.num_channels));
+
+    uint16_t remote_delay_ms =
+        group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSink);
+
+    LeAudioClientAudioSource::UpdateRemoteDelay(remote_delay_ms);
+    LeAudioClientAudioSource::ConfirmStreamingRequest();
+
+    return true;
+  }
+
+  struct le_audio::stream_configuration* GetStreamSourceConfiguration(
+      LeAudioDeviceGroup* group) {
+    LeAudioDevice* device = group->GetFirstActiveDevice();
+    LOG_ASSERT(device) << __func__
+                       << " Shouldn't be called without an active device.";
+
+    ase* ase = device->GetFirstActiveAseByDirection(
+        le_audio::types::kLeAudioDirectionSource);
+
+    if (!ase) return nullptr;
+
+    /* For now we support one microphone only*/
+
+    auto* stream_conf = &group->stream_conf;
+    std::vector<std::pair<uint16_t, uint32_t>> streams;
+
+    stream_conf->source_streams.emplace_back(std::make_pair(
+        ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation));
+
+    stream_conf->source_num_of_devices = 1;
+    stream_conf->source_num_of_channels = 1;
+    stream_conf->source_sample_frequency_hz =
+        ase->codec_config.GetSamplingFrequencyHz();
+    stream_conf->source_frame_duration_us =
+        ase->codec_config.GetFrameDurationUs();
+    stream_conf->source_octets_per_codec_frame =
+        ase->codec_config.octets_per_codec_frame;
+    stream_conf->valid = true;
+
+    stream_conf->conf = group->GetActiveConfiguration();
+
+    LOG(INFO) << __func__ << " Added CIS: " << +ase->cis_conn_hdl
+              << " to stream. Allocation: "
+              << +ase->codec_config.audio_channel_allocation
+              << " sample_freq: " << +stream_conf->source_sample_frequency_hz
+              << " frame_duration: " << +stream_conf->source_frame_duration_us
+              << " octects per frame: "
+              << +stream_conf->source_octets_per_codec_frame;
+
+    return stream_conf;
+  }
+
+  void StartReceivingAudio(int group_id) {
+    LOG(INFO) << __func__;
+
+    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
+
+    auto* stream_conf = GetStreamSourceConfiguration(group);
+    if (!stream_conf) {
+      LOG(WARNING) << " Could not get source configuration for group "
+                   << active_group_id_ << " probably microphone not configured";
+      return;
+    }
+
+    Lc3Config lc3Config(
+        current_sink_codec_config.sample_rate,
+        Lc3ConfigFrameDuration(current_sink_codec_config.data_interval_us), 1);
+
+    lc3_decoder = new Lc3Decoder(lc3Config);
+
+    uint16_t remote_delay_ms =
+        group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSource);
+
+    LeAudioClientAudioSink::UpdateRemoteDelay(remote_delay_ms);
+    LeAudioClientAudioSink::ConfirmStreamingRequest();
+  }
+
+  void SuspendAudio(void) {
+    audio_sink_ready_to_receive = false;
+    audio_source_ready_to_send = false;
+
+    if (lc3_encoder) {
+      delete lc3_encoder;
+      lc3_encoder = nullptr;
+    }
+
+    if (lc3_decoder) {
+      LOG(INFO) << __func__ << " stopping sink";
+
+      delete lc3_decoder;
+      lc3_decoder = nullptr;
+    }
+  }
+
+  void StopAudio(void) {
+    SuspendAudio();
+    ClientAudioIntefraceRelease();
+  }
+
+  void Dump(int fd) {
+    dprintf(fd, "  Active group: %d\n", active_group_id_);
+    dprintf(fd, "  LE Audio Groups:\n");
+    aseGroups_.Dump(fd);
+    dprintf(fd, "  Not grouped devices:\n");
+    leAudioDevices_.Dump(fd, bluetooth::groups::kGroupUnknown);
+  }
+
+  void Cleanup(void) {
+    leAudioDevices_.Cleanup();
+    aseGroups_.Cleanup();
+    StopAudio();
+    if (gatt_if_) BTA_GATTC_AppDeregister(gatt_if_);
+  }
+
+  void UpdateCurrentHalSessions(int group_id, LeAudioContextType context_type) {
+    if (group_id == bluetooth::groups::kGroupUnknown) {
+      LOG(WARNING) << ", cannot start straming if no active group set";
+      return;
+    }
+
+    auto group = aseGroups_.FindById(group_id);
+    if (!group) {
+      LOG(ERROR) << __func__
+                 << ", Invalid group: " << static_cast<int>(group_id);
+      return;
+    }
+
+    std::optional<LeAudioCodecConfiguration> source_configuration =
+        group->GetCodecConfigurationByDirection(
+            context_type, le_audio::types::kLeAudioDirectionSink);
+    std::optional<LeAudioCodecConfiguration> sink_configuration =
+        group->GetCodecConfigurationByDirection(
+            context_type, le_audio::types::kLeAudioDirectionSource);
+
+    if (source_configuration) {
+      /* Stream configuration differs from previous one */
+      if (!current_source_codec_config.IsInvalid() &&
+          (*source_configuration != current_source_codec_config))
+        LeAudioClientAudioSource::Stop();
+
+      current_source_codec_config = *source_configuration;
+
+      LeAudioClientAudioSource::Start(current_source_codec_config,
+                                      audioSinkReceiver);
+    } else {
+      if (!current_source_codec_config.IsInvalid()) {
+        LeAudioClientAudioSource::Stop();
+        current_source_codec_config = {0, 0, 0, 0};
+      }
+
+      LOG(INFO) << __func__
+                << ", group does not supports source direction for"
+                   " context: "
+                << static_cast<int>(context_type);
+    }
+
+    if (sink_configuration) {
+      /* Stream configuration differs from previous one */
+      if (!current_sink_codec_config.IsInvalid() &&
+          (*sink_configuration != current_sink_codec_config))
+        LeAudioClientAudioSink::Stop();
+
+      current_sink_codec_config = *sink_configuration;
+
+      LeAudioClientAudioSink::Start(current_sink_codec_config,
+                                    audioSourceReceiver);
+    } else {
+      if (!current_sink_codec_config.IsInvalid()) {
+        LeAudioClientAudioSink::Stop();
+        current_sink_codec_config = {0, 0, 0, 0};
+      }
+
+      LOG(INFO) << __func__
+                << ", group does not supports sink direction for"
+                   " context: "
+                << static_cast<int>(context_type);
+    }
+  }
+
+  void OnAudioResume() {
+    if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
+      LOG(WARNING) << ", cannot start straming if no active group set";
+      return;
+    }
+
+    auto group = aseGroups_.FindById(active_group_id_);
+    if (!group) {
+      LOG(ERROR) << __func__
+                 << ", Invalid group: " << static_cast<int>(active_group_id_);
+      return;
+    }
+
+    std::optional<LeAudioCodecConfiguration> source_configuration =
+        group->GetCodecConfigurationByDirection(
+            current_context_type_, le_audio::types::kLeAudioDirectionSink);
+    std::optional<LeAudioCodecConfiguration> sink_configuration =
+        group->GetCodecConfigurationByDirection(
+            current_context_type_, le_audio::types::kLeAudioDirectionSource);
+
+    /* Check if Bluetooth audio HAL session requires reconfiguration */
+    bool sessions_requires_update =
+        (((source_configuration &&
+           (*source_configuration != current_source_codec_config)) ||
+          (!source_configuration &&
+           !current_source_codec_config.IsInvalid()))) ||
+        ((sink_configuration &&
+          (*sink_configuration != current_sink_codec_config)) ||
+         (!sink_configuration && !current_sink_codec_config.IsInvalid()));
+    if (sessions_requires_update) {
+      CancelStreamingRequest();
+      do_in_main_thread(FROM_HERE,
+                        base::Bind(&LeAudioClientImpl::UpdateCurrentHalSessions,
+                                   base::Unretained(instance), active_group_id_,
+                                   current_context_type_));
+      return;
+    }
+
+    /* TODO check if group already started streaming */
+
+    GroupStream(active_group_id_, static_cast<uint16_t>(current_context_type_));
+  }
+
+  void OnAudioSuspend() {
+    if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
+      LOG(WARNING) << ", there is no longer active group";
+      return;
+    }
+
+    GroupStop(active_group_id_);
+  }
+
+  void OnAudioSinkSuspend() {
+    LOG(INFO) << __func__;
+
+    if (!audio_source_ready_to_send) return;
+
+    audio_source_ready_to_send = false;
+
+    /* Last suspends group - triggers group stop */
+    if (!audio_sink_ready_to_receive && !audio_source_ready_to_send)
+      OnAudioSuspend();
+  }
+
+  void OnAudioSinkResume() {
+    LOG(INFO) << __func__;
+
+    auto group = aseGroups_.FindById(active_group_id_);
+    if (!group) {
+      LOG(ERROR) << __func__
+                 << ", Invalid group: " << static_cast<int>(active_group_id_);
+      return;
+    }
+
+    /* Check if the device resume is expected */
+    if (!group->GetCodecConfigurationByDirection(
+            current_context_type_, le_audio::types::kLeAudioDirectionSink)) {
+      LOG(ERROR) << __func__ << ", invalid resume request for context type: "
+                 << loghex(static_cast<int>(current_context_type_));
+      LeAudioClientAudioSource::CancelStreamingRequest();
+      return;
+    }
+
+    /* First resume request from sink/source triggers group start */
+    if (!audio_sink_ready_to_receive && !audio_source_ready_to_send) {
+      audio_source_ready_to_send = true;
+      OnAudioResume();
+
+      return;
+    }
+
+    if (audio_sink_ready_to_receive) {
+      audio_source_ready_to_send = true;
+      /* If signalling part is completed trigger start reveivin audio here,
+       * otherwise it'll be called on group streaming state callback
+       */
+      if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)
+        StartSendingAudio(active_group_id_);
+    }
+  }
+
+  void OnAudioSourceSuspend() {
+    LOG(INFO) << __func__;
+
+    if (!audio_sink_ready_to_receive) return;
+
+    audio_sink_ready_to_receive = false;
+
+    /* Last suspends group - triggers group stop */
+    if (!audio_sink_ready_to_receive && !audio_source_ready_to_send)
+      OnAudioSuspend();
+  }
+
+  void OnAudioSourceResume() {
+    LOG(INFO) << __func__;
+
+    auto group = aseGroups_.FindById(active_group_id_);
+    if (!group) {
+      LOG(ERROR) << __func__
+                 << ", Invalid group: " << static_cast<int>(active_group_id_);
+      return;
+    }
+
+    /* Check if the device resume is expected */
+    if (!group->GetCodecConfigurationByDirection(
+            current_context_type_, le_audio::types::kLeAudioDirectionSource)) {
+      LOG(ERROR) << __func__ << ", invalid resume request for context type: "
+                 << loghex(static_cast<int>(current_context_type_));
+      LeAudioClientAudioSink::CancelStreamingRequest();
+      return;
+    }
+
+    /* First resume request from sink/source triggers group start */
+    if (!audio_sink_ready_to_receive && !audio_source_ready_to_send) {
+      OnAudioResume();
+      audio_sink_ready_to_receive = true;
+
+      return;
+    }
+
+    if (audio_source_ready_to_send) {
+      audio_sink_ready_to_receive = true;
+      /* If signalling part is completed trigger start reveivin audio here,
+       * otherwise it'll be called on group streaming state callback
+       */
+      if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)
+        StartReceivingAudio(active_group_id_);
+    }
+  }
+
+  void OnAudioMetadataUpdate(audio_usage_t usage,
+                             audio_content_type_t content_type) {
+    LOG(INFO) << __func__ << ", content_type = "
+              << audio_content_type_to_string(content_type)
+              << ", usage = " << audio_usage_to_string(usage);
+
+    LeAudioContextType new_context = LeAudioContextType::RFU;
+
+    switch (content_type) {
+      case AUDIO_CONTENT_TYPE_SPEECH:
+        new_context = LeAudioContextType::CONVERSATIONAL;
+        break;
+      case AUDIO_CONTENT_TYPE_MUSIC:
+      case AUDIO_CONTENT_TYPE_MOVIE:
+      case AUDIO_CONTENT_TYPE_SONIFICATION:
+        new_context = LeAudioContextType::MEDIA;
+        break;
+      default:
+        break;
+    }
+
+    /* Context is not clear, consider also usage of stream */
+    if (new_context == LeAudioContextType::RFU) {
+      switch (usage) {
+        case AUDIO_USAGE_VOICE_COMMUNICATION:
+          new_context = LeAudioContextType::CONVERSATIONAL;
+          break;
+        case AUDIO_USAGE_GAME:
+          new_context = LeAudioContextType::GAME;
+          break;
+        case AUDIO_USAGE_NOTIFICATION:
+          new_context = LeAudioContextType::NOTIFICATIONS;
+          break;
+        case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
+          new_context = LeAudioContextType::RINGTONE;
+          break;
+        case AUDIO_USAGE_ALARM:
+          new_context = LeAudioContextType::ALERTS;
+          break;
+        case AUDIO_USAGE_EMERGENCY:
+          new_context = LeAudioContextType::EMERGENCYALARM;
+          break;
+        default:
+          new_context = LeAudioContextType::MEDIA;
+          break;
+      }
+    }
+
+    auto group = aseGroups_.FindById(active_group_id_);
+    if (!group) {
+      LOG(ERROR) << __func__
+                 << ", Invalid group: " << static_cast<int>(active_group_id_);
+      return;
+    }
+
+    if ((new_context != current_context_type_) &&
+        (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
+      if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
+        LOG(WARNING) << ", cannot start straming if no active group set";
+        return;
+      }
+
+      std::optional<LeAudioCodecConfiguration> source_configuration =
+          group->GetCodecConfigurationByDirection(
+              current_context_type_, le_audio::types::kLeAudioDirectionSink);
+      std::optional<LeAudioCodecConfiguration> sink_configuration =
+          group->GetCodecConfigurationByDirection(
+              current_context_type_, le_audio::types::kLeAudioDirectionSource);
+
+      if (source_configuration &&
+          (*source_configuration != current_source_codec_config))
+        return;
+      if (sink_configuration &&
+          (*sink_configuration != current_sink_codec_config))
+        return;
+
+      /* Configuration is the same for new context, just will do update
+       * metadata of stream
+       */
+      GroupStream(active_group_id_, static_cast<uint16_t>(new_context));
+    }
+
+    current_context_type_ = new_context;
+  }
+
+  static void OnGattReadRspStatic(uint16_t conn_id, tGATT_STATUS status,
+                                  uint16_t hdl, uint16_t len, uint8_t* value,
+                                  void* data) {
+    if (!instance) return;
+
+    if (status == GATT_SUCCESS) {
+      instance->LeAudioCharValueHandle(conn_id, hdl, len,
+                                       static_cast<uint8_t*>(value));
+    }
+
+    /* We use data to keep notify connected flag. */
+    if (data && !!PTR_TO_INT(data)) {
+      LeAudioDevice* leAudioDevice =
+          instance->leAudioDevices_.FindByConnId(conn_id);
+      leAudioDevice->notify_connected_after_read_ = false;
+      instance->connectionReady(leAudioDevice);
+    }
+  }
+
+  void IsoCigEventsCb(uint16_t event_type, void* data) {
+    switch (event_type) {
+      case bluetooth::hci::iso_manager::kIsoEventCigOnCreateCmpl: {
+        auto* evt = static_cast<cig_create_cmpl_evt*>(data);
+        LeAudioDeviceGroup* group = aseGroups_.FindById(evt->cig_id);
+        groupStateMachine_->ProcessHciNotifOnCigCreate(
+            group, evt->status, evt->cig_id, evt->conn_handles);
+      } break;
+      case bluetooth::hci::iso_manager::kIsoEventCigOnRemoveCmpl: {
+        auto* evt = static_cast<cig_remove_cmpl_evt*>(data);
+        LeAudioDeviceGroup* group = aseGroups_.FindById(evt->cig_id);
+        groupStateMachine_->ProcessHciNotifOnCigRemove(evt->status, group);
+      } break;
+      default:
+        LOG(ERROR) << __func__ << " Invalid event " << int{event_type};
+    }
+  }
+
+  void IsoCisEventsCb(uint16_t event_type, void* data) {
+    switch (event_type) {
+      case bluetooth::hci::iso_manager::kIsoEventCisDataAvailable: {
+        auto* event =
+            static_cast<bluetooth::hci::iso_manager::cis_data_evt*>(data);
+
+        if (!audio_sink_ready_to_receive) break;
+
+        SendAudioData(event->p_msg->data + event->p_msg->offset,
+                      event->p_msg->len - event->p_msg->offset);
+      } break;
+      case bluetooth::hci::iso_manager::kIsoEventCisEstablishCmpl: {
+        auto* event =
+            static_cast<bluetooth::hci::iso_manager::cis_establish_cmpl_evt*>(
+                data);
+
+        LeAudioDevice* leAudioDevice =
+            leAudioDevices_.FindByCisConnHdl(event->cis_conn_hdl);
+        if (!leAudioDevice) {
+          LOG(ERROR) << __func__ << ", no bonded Le Audio Device with CIS: "
+                     << +event->cis_conn_hdl;
+          break;
+        }
+        LeAudioDeviceGroup* group =
+            aseGroups_.FindById(leAudioDevice->group_id_);
+
+        if (event->max_pdu_mtos > 0)
+          group->SetTransportLatency(le_audio::types::kLeAudioDirectionSink,
+                                     event->trans_lat_mtos);
+        if (event->max_pdu_stom > 0)
+          group->SetTransportLatency(le_audio::types::kLeAudioDirectionSource,
+                                     event->trans_lat_stom);
+
+        groupStateMachine_->ProcessHciNotifCisEstablished(group, leAudioDevice,
+                                                          event);
+      } break;
+      case bluetooth::hci::iso_manager::kIsoEventCisDisconnected: {
+        auto* event =
+            static_cast<bluetooth::hci::iso_manager::cis_disconnected_evt*>(
+                data);
+
+        LeAudioDevice* leAudioDevice =
+            leAudioDevices_.FindByCisConnHdl(event->cis_conn_hdl);
+        if (!leAudioDevice) {
+          LOG(ERROR) << __func__ << ", no bonded Le Audio Device with CIS: "
+                     << +event->cis_conn_hdl;
+          break;
+        }
+        LeAudioDeviceGroup* group =
+            aseGroups_.FindById(leAudioDevice->group_id_);
+
+        groupStateMachine_->ProcessHciNotifCisDisconnected(group, leAudioDevice,
+                                                           event);
+      } break;
+      default:
+        LOG(INFO) << ", Not handeled ISO event";
+        break;
+    }
+  }
+
+  void IsoSetupIsoDataPathCb(uint8_t status, uint16_t conn_handle,
+                             uint8_t /* cig_id */) {
+    LeAudioDevice* leAudioDevice =
+        leAudioDevices_.FindByCisConnHdl(conn_handle);
+    LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
+
+    instance->groupStateMachine_->ProcessHciNotifSetupIsoDataPath(
+        group, leAudioDevice, status, conn_handle);
+  }
+
+  void IsoRemoveIsoDataPathCb(uint8_t status, uint16_t conn_handle,
+                              uint8_t /* cig_id */) {
+    LeAudioDevice* leAudioDevice =
+        leAudioDevices_.FindByCisConnHdl(conn_handle);
+    LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
+
+    instance->groupStateMachine_->ProcessHciNotifRemoveIsoDataPath(
+        group, leAudioDevice, status, conn_handle);
+  }
+
+  void IsoLinkQualityReadCb(
+      uint8_t conn_handle, uint8_t cig_id, uint32_t txUnackedPackets,
+      uint32_t txFlushedPackets, uint32_t txLastSubeventPackets,
+      uint32_t retransmittedPackets, uint32_t crcErrorPackets,
+      uint32_t rxUnreceivedPackets, uint32_t duplicatePackets) {
+    LeAudioDevice* leAudioDevice =
+        leAudioDevices_.FindByCisConnHdl(conn_handle);
+    if (!leAudioDevice) {
+      LOG(WARNING) << __func__ << ", device under connection handle: "
+                   << loghex(conn_handle)
+                   << ", has been disconnecected in meantime";
+      return;
+    }
+    LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
+
+    instance->groupStateMachine_->ProcessHciNotifIsoLinkQualityRead(
+        group, leAudioDevice, conn_handle, txUnackedPackets, txFlushedPackets,
+        txLastSubeventPackets, retransmittedPackets, crcErrorPackets,
+        rxUnreceivedPackets, duplicatePackets);
+  }
+
+  void StatusReportCb(int group_id, GroupStreamStatus status) {
+    switch (status) {
+      case GroupStreamStatus::STREAMING:
+        stream_request_started_ = false;
+        if (audio_source_ready_to_send) StartSendingAudio(active_group_id_);
+        if (audio_sink_ready_to_receive) StartReceivingAudio(active_group_id_);
+        break;
+      case GroupStreamStatus::SUSPENDED:
+        /** Stop Audio but don't release all the Audio resources */
+        SuspendAudio();
+        break;
+      case GroupStreamStatus::IDLE:
+        if (stream_request_started_) {
+          stream_request_started_ = false;
+          CancelStreamingRequest();
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+ private:
+  tGATT_IF gatt_if_;
+  bluetooth::le_audio::LeAudioClientCallbacks* callbacks_;
+  LeAudioDevices leAudioDevices_;
+  LeAudioDeviceGroups aseGroups_;
+  LeAudioGroupStateMachine* groupStateMachine_;
+  int active_group_id_;
+  bool stream_request_started_;
+  LeAudioContextType current_context_type_;
+
+  bool audio_sink_ready_to_receive;
+  bool audio_source_ready_to_send;
+
+  LeAudioCodecConfiguration current_source_codec_config;
+  LeAudioCodecConfiguration current_sink_codec_config;
+  Lc3Encoder* lc3_encoder;
+  Lc3Decoder* lc3_decoder;
+  std::vector<uint8_t> encoded_data;
+  const void* audio_source_instance_;
+  const void* audio_sink_instance_;
+
+  void ClientAudioIntefraceRelease() {
+    if (audio_source_instance_) {
+      LeAudioClientAudioSource::Stop();
+      LeAudioClientAudioSource::Release(audio_source_instance_);
+      audio_source_instance_ = nullptr;
+    }
+
+    if (audio_sink_instance_) {
+      LeAudioClientAudioSink::Stop();
+      LeAudioClientAudioSink::Release(audio_sink_instance_);
+      audio_sink_instance_ = nullptr;
+    }
+  }
+};
+
+/* This is a generic callback method for gatt client which handles every client
+ * application events.
+ */
+void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
+  if (!p_data || !instance) return;
+
+  DLOG(INFO) << __func__ << " event = " << +event;
+
+  switch (event) {
+    case BTA_GATTC_DEREG_EVT:
+      break;
+
+    case BTA_GATTC_NOTIF_EVT:
+      instance->LeAudioCharValueHandle(
+          p_data->notify.conn_id, p_data->notify.handle, p_data->notify.len,
+          static_cast<uint8_t*>(p_data->notify.value));
+
+      if (!p_data->notify.is_notify)
+        BTA_GATTC_SendIndConfirm(p_data->notify.conn_id, p_data->notify.handle);
+
+      break;
+
+    case BTA_GATTC_OPEN_EVT:
+      instance->OnGattConnected(p_data->open.status, p_data->open.conn_id,
+                                p_data->open.client_if, p_data->open.remote_bda,
+                                p_data->open.transport, p_data->open.mtu);
+      break;
+
+    case BTA_GATTC_ENC_CMPL_CB_EVT:
+      instance->OnEncryptionComplete(p_data->enc_cmpl.remote_bda, BTM_SUCCESS);
+      break;
+
+    case BTA_GATTC_CLOSE_EVT:
+      instance->OnGattDisconnected(
+          p_data->close.conn_id, p_data->close.client_if,
+          p_data->close.remote_bda, p_data->close.reason);
+      break;
+
+    case BTA_GATTC_SEARCH_CMPL_EVT:
+      instance->OnServiceSearchComplete(p_data->search_cmpl.conn_id,
+                                        p_data->search_cmpl.status);
+      break;
+
+    case BTA_GATTC_SRVC_DISC_DONE_EVT:
+      instance->OnGattServiceDiscoveryDone(p_data->service_changed.remote_bda);
+      break;
+
+    case BTA_GATTC_SRVC_CHG_EVT:
+      instance->OnServiceChangeEvent(p_data->remote_bda);
+      break;
+    case BTA_GATTC_CFG_MTU_EVT:
+      break;
+
+    default:
+      break;
+  }
+}
+
+class LeAudioStateMachineHciCallbacksImpl : public CigCallbacks {
+ public:
+  void OnCigEvent(uint8_t event, void* data) override {
+    if (instance) instance->IsoCigEventsCb(event, data);
+  }
+
+  void OnCisEvent(uint8_t event, void* data) override {
+    if (instance) instance->IsoCisEventsCb(event, data);
+  }
+
+  void OnSetupIsoDataPath(uint8_t status, uint16_t conn_handle,
+                          uint8_t cig_id) override {
+    if (instance) instance->IsoSetupIsoDataPathCb(status, conn_handle, cig_id);
+  }
+
+  void OnRemoveIsoDataPath(uint8_t status, uint16_t conn_handle,
+                           uint8_t cig_id) override {
+    if (instance) instance->IsoRemoveIsoDataPathCb(status, conn_handle, cig_id);
+  }
+
+  void OnIsoLinkQualityRead(
+      uint8_t conn_handle, uint8_t cig_id, uint32_t txUnackedPackets,
+      uint32_t txFlushedPackets, uint32_t txLastSubeventPackets,
+      uint32_t retransmittedPackets, uint32_t crcErrorPackets,
+      uint32_t rxUnreceivedPackets, uint32_t duplicatePackets) {
+    if (instance)
+      instance->IsoLinkQualityReadCb(conn_handle, cig_id, txUnackedPackets,
+                                     txFlushedPackets, txLastSubeventPackets,
+                                     retransmittedPackets, crcErrorPackets,
+                                     rxUnreceivedPackets, duplicatePackets);
+  }
+};
+
+LeAudioStateMachineHciCallbacksImpl stateMachineHciCallbacksImpl;
+
+class CallbacksImpl : public LeAudioGroupStateMachine::Callbacks {
+ public:
+  void StatusReportCb(int group_id, GroupStreamStatus status) override {
+    if (instance) instance->StatusReportCb(group_id, status);
+  }
+
+  void OnStateTransitionTimeout(int group_id) override {
+    if (instance) instance->OnLeAudioDeviceSetStateTimeout(group_id);
+  }
+};
+
+CallbacksImpl stateMachineCallbacksImpl;
+
+class LeAudioClientAudioSinkReceiverImpl
+    : public LeAudioClientAudioSinkReceiver {
+ public:
+  void OnAudioDataReady(const std::vector<uint8_t>& data) override {
+    if (instance) instance->OnAudioDataReady(data);
+  }
+  void OnAudioSuspend(std::promise<void> do_suspend_promise) override {
+    if (instance) instance->OnAudioSinkSuspend();
+    do_suspend_promise.set_value();
+  }
+
+  void OnAudioResume(std::promise<void> do_resume_promise) override {
+    if (instance) instance->OnAudioSinkResume();
+    do_resume_promise.set_value();
+  }
+
+  void OnAudioMetadataUpdate(std::promise<void> do_metadata_update_promise,
+                             audio_usage_t usage,
+                             audio_content_type_t content_type) override {
+    if (instance) instance->OnAudioMetadataUpdate(usage, content_type);
+    do_metadata_update_promise.set_value();
+  }
+};
+
+class LeAudioClientAudioSourceReceiverImpl
+    : public LeAudioClientAudioSourceReceiver {
+ public:
+  void OnAudioSuspend(std::promise<void> do_suspend_promise) override {
+    if (instance) instance->OnAudioSourceSuspend();
+    do_suspend_promise.set_value();
+  }
+  void OnAudioResume(std::promise<void> do_resume_promise) override {
+    if (instance) instance->OnAudioSourceResume();
+    do_resume_promise.set_value();
+  }
+};
+
+LeAudioClientAudioSinkReceiverImpl audioSinkReceiverImpl;
+LeAudioClientAudioSourceReceiverImpl audioSourceReceiverImpl;
+
+class DeviceGroupsCallbacksImpl : public DeviceGroupsCallbacks {
+ public:
+  void OnGroupAdded(const RawAddress& address, const bluetooth::Uuid& uuid,
+                    int group_id) override {
+    if (instance) instance->OnGroupAddedCb(address, uuid, group_id);
+  }
+  void OnGroupMemberAdded(const RawAddress& address, int group_id) override {
+    if (instance) instance->OnGroupMemberAddedCb(address, group_id);
+  }
+  void OnGroupMemberRemoved(const RawAddress& address, int group_id) override {
+    if (instance) instance->OnGroupMemberRemovedCb(address, group_id);
+  }
+  void OnGroupRemoved(const bluetooth::Uuid& uuid, int group_id) {
+    /* to implement if needed */
+  }
+  void OnGroupAddFromStorage(const RawAddress& address,
+                             const bluetooth::Uuid& uuid, int group_id) {
+    /* to implement if needed */
+  }
+};
+
+class DeviceGroupsCallbacksImpl;
+DeviceGroupsCallbacksImpl deviceGroupsCallbacksImpl;
+
+}  // namespace
+
+void LeAudioClient::AddFromStorage(const RawAddress& addr, bool autoconnect) {
+  if (!instance) {
+    LOG(ERROR) << "Not initialized yet";
+    return;
+  }
+
+  instance->AddFromStorage(addr, autoconnect);
+}
+
+bool LeAudioClient::IsLeAudioClientRunning(void) { return instance != nullptr; }
+
+LeAudioClient* LeAudioClient::Get() {
+  CHECK(instance);
+  return instance;
+}
+
+/* Initializer of main le audio implementation class and its instance */
+void LeAudioClient::Initialize(
+    bluetooth::le_audio::LeAudioClientCallbacks* callbacks_,
+    base::Closure initCb, base::Callback<bool()> hal_2_1_verifier) {
+  if (instance) {
+    LOG(ERROR) << "Already initialized";
+    return;
+  }
+
+  if (!controller_get_interface()
+           ->supports_ble_connected_isochronous_stream_central() &&
+      !controller_get_interface()
+           ->supports_ble_connected_isochronous_stream_peripheral()) {
+    LOG(ERROR) << "Controller reports no ISO support."
+                  " LeAudioClient Init aborted.";
+    return;
+  }
+
+  LOG_ASSERT(std::move(hal_2_1_verifier).Run())
+      << __func__
+      << ", LE Audio Client requires Bluetooth Audio HAL V2.1 at least. Either "
+         "disable LE Audio Profile, or update your HAL";
+
+  IsoManager::GetInstance()->Start();
+
+  audioSinkReceiver = &audioSinkReceiverImpl;
+  audioSourceReceiver = &audioSourceReceiverImpl;
+  stateMachineHciCallbacks = &stateMachineHciCallbacksImpl;
+  stateMachineCallbacks = &stateMachineCallbacksImpl;
+  device_group_callbacks = &deviceGroupsCallbacksImpl;
+  instance = new LeAudioClientImpl(callbacks_, stateMachineCallbacks, initCb);
+
+  IsoManager::GetInstance()->RegisterCigCallbacks(stateMachineHciCallbacks);
+}
+
+void LeAudioClient::DebugDump(int fd) {
+  dprintf(fd, "LeAudio Manager: \n");
+  if (instance)
+    instance->Dump(fd);
+  else
+    dprintf(fd, "  Not initialized \n");
+
+  LeAudioClientAudioSource::DebugDump(fd);
+  LeAudioClientAudioSink::DebugDump(fd);
+  dprintf(fd, "\n");
+}
+
+void LeAudioClient::Cleanup(void) {
+  if (!instance) {
+    LOG(ERROR) << "Not initialized";
+    return;
+  }
+
+  LeAudioClientImpl* ptr = instance;
+  instance = nullptr;
+  ptr->Cleanup();
+  delete ptr;
+
+  LeAudioGroupStateMachine::Cleanup();
+  IsoManager::GetInstance()->Stop();
+}
diff --git a/system/bta/le_audio/client_audio.cc b/system/bta/le_audio/client_audio.cc
new file mode 100644
index 0000000000000000000000000000000000000000..e7583503740a679e0082a865b0507a7851f4b638
--- /dev/null
+++ b/system/bta/le_audio/client_audio.cc
@@ -0,0 +1,573 @@
+/******************************************************************************
+ *
+ * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+#include "client_audio.h"
+
+#include "audio_hal_interface/le_audio_software.h"
+#include "btu.h"
+#include "common/repeating_timer.h"
+#include "common/time_util.h"
+#include "osi/include/wakelock.h"
+
+using bluetooth::audio::le_audio::LeAudioClientInterface;
+
+namespace {
+LeAudioCodecConfiguration source_codec_config;
+bluetooth::common::RepeatingTimer audio_timer;
+LeAudioClientInterface* leAudioClientInterface = nullptr;
+LeAudioClientInterface::Sink* sinkClientInterface = nullptr;
+LeAudioClientInterface::Source* sourceClientInterface = nullptr;
+LeAudioClientAudioSinkReceiver* localAudioSinkReceiver = nullptr;
+LeAudioClientAudioSourceReceiver* localAudioSourceReceiver = nullptr;
+
+enum {
+  HAL_UNINITIALIZED,
+  HAL_STOPPED,
+  HAL_STARTED,
+} le_audio_sink_hal_state,
+    le_audio_source_hal_state;
+
+struct AudioHalStats {
+  size_t media_read_total_underflow_bytes;
+  size_t media_read_total_underflow_count;
+  uint64_t media_read_last_underflow_us;
+
+  AudioHalStats() { Reset(); }
+
+  void Reset() {
+    media_read_total_underflow_bytes = 0;
+    media_read_total_underflow_count = 0;
+    media_read_last_underflow_us = 0;
+  }
+};
+
+AudioHalStats stats;
+
+bool le_audio_sink_on_resume_req(bool start_media_task);
+bool le_audio_sink_on_suspend_req();
+
+void send_audio_data() {
+  uint32_t bytes_per_tick =
+      (source_codec_config.num_channels * source_codec_config.sample_rate *
+       source_codec_config.data_interval_us / 1000 *
+       (source_codec_config.bits_per_sample / 8)) /
+      1000;
+
+  std::vector<uint8_t> data(bytes_per_tick);
+
+  uint32_t bytes_read = 0;
+  if (sinkClientInterface != nullptr) {
+    bytes_read = sinkClientInterface->Read(data.data(), bytes_per_tick);
+  } else {
+    LOG(ERROR) << __func__ << ", no LE Audio sink client interface - aborting.";
+    return;
+  }
+
+  // LOG(INFO) << __func__ << ", bytes_read: " << static_cast<int>(bytes_read)
+  //          << ", bytes_per_tick: " << static_cast<int>(bytes_per_tick);
+
+  if (bytes_read < bytes_per_tick) {
+    stats.media_read_total_underflow_bytes += bytes_per_tick - bytes_read;
+    stats.media_read_total_underflow_count++;
+    stats.media_read_last_underflow_us =
+        bluetooth::common::time_get_os_boottime_us();
+  }
+
+  if (localAudioSinkReceiver != nullptr) {
+    localAudioSinkReceiver->OnAudioDataReady(data);
+  }
+}
+
+void start_audio_ticks() {
+  wakelock_acquire();
+  audio_timer.SchedulePeriodic(
+      get_main_thread()->GetWeakPtr(), FROM_HERE, base::Bind(&send_audio_data),
+      base::TimeDelta::FromMicroseconds(source_codec_config.data_interval_us));
+}
+
+void stop_audio_ticks() {
+  audio_timer.CancelAndWait();
+  wakelock_release();
+}
+
+bool le_audio_sink_on_resume_req(bool start_media_task) {
+  if (localAudioSinkReceiver != nullptr) {
+    // Call OnAudioResume and block till it returns.
+    std::promise<void> do_resume_promise;
+    std::future<void> do_resume_future = do_resume_promise.get_future();
+    bt_status_t status = do_in_main_thread(
+        FROM_HERE,
+        base::BindOnce(&LeAudioClientAudioSinkReceiver::OnAudioResume,
+                       base::Unretained(localAudioSinkReceiver),
+                       std::move(do_resume_promise)));
+    if (status == BT_STATUS_SUCCESS) {
+      do_resume_future.wait();
+    } else {
+      LOG(ERROR) << __func__
+                 << ": LE_AUDIO_CTRL_CMD_START: do_in_main_thread err="
+                 << status;
+      return false;
+    }
+  } else {
+    LOG(ERROR) << __func__
+               << ": LE_AUDIO_CTRL_CMD_START: audio sink receiver not started";
+    return false;
+  }
+
+  return true;
+}
+
+bool le_audio_source_on_resume_req(bool start_media_task) {
+  if (localAudioSourceReceiver != nullptr) {
+    // Call OnAudioResume and block till it returns.
+    std::promise<void> do_resume_promise;
+    std::future<void> do_resume_future = do_resume_promise.get_future();
+    bt_status_t status = do_in_main_thread(
+        FROM_HERE,
+        base::BindOnce(&LeAudioClientAudioSourceReceiver::OnAudioResume,
+                       base::Unretained(localAudioSourceReceiver),
+                       std::move(do_resume_promise)));
+    if (status == BT_STATUS_SUCCESS) {
+      do_resume_future.wait();
+    } else {
+      LOG(ERROR) << __func__
+                 << ": LE_AUDIO_CTRL_CMD_START: do_in_main_thread err="
+                 << status;
+      return false;
+    }
+  } else {
+    LOG(ERROR)
+        << __func__
+        << ": LE_AUDIO_CTRL_CMD_START: audio source receiver not started";
+    return false;
+  }
+
+  return true;
+}
+
+bool le_audio_sink_on_suspend_req() {
+  stop_audio_ticks();
+  if (localAudioSinkReceiver != nullptr) {
+    // Call OnAudioSuspend and block till it returns.
+    std::promise<void> do_suspend_promise;
+    std::future<void> do_suspend_future = do_suspend_promise.get_future();
+    bt_status_t status = do_in_main_thread(
+        FROM_HERE,
+        base::BindOnce(&LeAudioClientAudioSinkReceiver::OnAudioSuspend,
+                       base::Unretained(localAudioSinkReceiver),
+                       std::move(do_suspend_promise)));
+    if (status == BT_STATUS_SUCCESS) {
+      do_suspend_future.wait();
+      return true;
+    } else {
+      LOG(ERROR) << __func__
+                 << ": LE_AUDIO_CTRL_CMD_SUSPEND: do_in_main_thread err="
+                 << status;
+    }
+  } else {
+    LOG(ERROR) << __func__
+               << ": LE_AUDIO_CTRL_CMD_SUSPEND: audio receiver not started";
+  }
+  return false;
+}
+
+bool le_audio_source_on_suspend_req() {
+  if (localAudioSourceReceiver != nullptr) {
+    // Call OnAudioSuspend and block till it returns.
+    std::promise<void> do_suspend_promise;
+    std::future<void> do_suspend_future = do_suspend_promise.get_future();
+    bt_status_t status = do_in_main_thread(
+        FROM_HERE,
+        base::BindOnce(&LeAudioClientAudioSourceReceiver::OnAudioSuspend,
+                       base::Unretained(localAudioSourceReceiver),
+                       std::move(do_suspend_promise)));
+    if (status == BT_STATUS_SUCCESS) {
+      do_suspend_future.wait();
+      return true;
+    } else {
+      LOG(ERROR) << __func__
+                 << ": LE_AUDIO_CTRL_CMD_SUSPEND: do_in_main_thread err="
+                 << status;
+    }
+  } else {
+    LOG(ERROR) << __func__
+               << ": LE_AUDIO_CTRL_CMD_SUSPEND: audio receiver not started";
+  }
+  return false;
+}
+
+bool le_audio_sink_on_metadata_update_req(audio_usage_t usage,
+                                          audio_content_type_t content_type) {
+  if (localAudioSinkReceiver == nullptr) {
+    LOG(ERROR) << __func__ << ", audio receiver not started";
+    return false;
+  }
+
+  // Call OnAudioSuspend and block till it returns.
+  std::promise<void> do_update_metadata_promise;
+  std::future<void> do_update_metadata_future =
+      do_update_metadata_promise.get_future();
+  bt_status_t status = do_in_main_thread(
+      FROM_HERE,
+      base::BindOnce(&LeAudioClientAudioSinkReceiver::OnAudioMetadataUpdate,
+                     base::Unretained(localAudioSinkReceiver),
+                     std::move(do_update_metadata_promise), usage,
+                     content_type));
+
+  if (status == BT_STATUS_SUCCESS) {
+    do_update_metadata_future.wait();
+    return true;
+  }
+
+  LOG(ERROR) << __func__ << ", do_in_main_thread err=" << status;
+
+  return false;
+}
+
+}  // namespace
+
+bool LeAudioClientAudioSource::Start(
+    const LeAudioCodecConfiguration& codec_configuration,
+    LeAudioClientAudioSinkReceiver* audioReceiver) {
+  LOG(INFO) << __func__;
+
+  if (!sinkClientInterface) {
+    LOG(ERROR) << "sinkClientInterface is not Acquired!";
+    return false;
+  }
+
+  if (le_audio_sink_hal_state == HAL_STARTED) {
+    LOG(ERROR) << "LE audio device HAL is already in use!";
+    return false;
+  }
+
+  LOG(INFO) << __func__ << ": Le Audio Source Open, bits per sample: "
+            << int{codec_configuration.bits_per_sample}
+            << ", num channels: " << int{codec_configuration.num_channels}
+            << ", sample rate: " << codec_configuration.sample_rate
+            << ", data interval: " << codec_configuration.data_interval_us;
+
+  stats.Reset();
+
+  /* Global config for periodic audio data */
+  source_codec_config = codec_configuration;
+  LeAudioClientInterface::PcmParameters pcmParameters = {
+      .data_interval_us = codec_configuration.data_interval_us,
+      .sample_rate = codec_configuration.sample_rate,
+      .bits_per_sample = codec_configuration.bits_per_sample,
+      .channels_count = codec_configuration.num_channels};
+
+  sinkClientInterface->SetPcmParameters(pcmParameters);
+  sinkClientInterface->StartSession();
+
+  localAudioSinkReceiver = audioReceiver;
+  le_audio_sink_hal_state = HAL_STARTED;
+
+  return true;
+}
+
+void LeAudioClientAudioSource::Stop() {
+  LOG(INFO) << __func__;
+  if (!sinkClientInterface) {
+    LOG(ERROR) << __func__ << " sinkClientInterface stopped";
+    return;
+  }
+
+  if (le_audio_sink_hal_state != HAL_STARTED) {
+    LOG(ERROR) << "LE audio device HAL was not started!";
+    return;
+  }
+
+  LOG(INFO) << __func__ << ": Le Audio Source Close";
+
+  sinkClientInterface->StopSession();
+  le_audio_sink_hal_state = HAL_STOPPED;
+  localAudioSinkReceiver = nullptr;
+
+  stop_audio_ticks();
+}
+
+const void* LeAudioClientAudioSource::Acquire() {
+  LOG(INFO) << __func__;
+  if (sinkClientInterface != nullptr) {
+    LOG(WARNING) << __func__ << ", Sink client interface already initialized";
+    return nullptr;
+  }
+
+  /* Get pointer to singleton LE audio client interface */
+  if (leAudioClientInterface == nullptr) {
+    leAudioClientInterface = LeAudioClientInterface::Get();
+
+    if (leAudioClientInterface == nullptr) {
+      LOG(ERROR) << __func__ << ", can't get LE audio client interface";
+      return nullptr;
+    }
+  }
+
+  auto sink_stream_cb = bluetooth::audio::le_audio::StreamCallbacks{
+      .on_resume_ = le_audio_sink_on_resume_req,
+      .on_suspend_ = le_audio_sink_on_suspend_req,
+      .on_metadata_update_ = le_audio_sink_on_metadata_update_req,
+  };
+
+  sinkClientInterface =
+      leAudioClientInterface->GetSink(sink_stream_cb, get_main_thread());
+
+  if (sinkClientInterface == nullptr) {
+    LOG(ERROR) << __func__ << ", can't get LE audio sink client interface";
+    return nullptr;
+  }
+
+  le_audio_sink_hal_state = HAL_STOPPED;
+  return sinkClientInterface;
+}
+
+void LeAudioClientAudioSource::Release(const void* instance) {
+  LOG(INFO) << __func__;
+  if (sinkClientInterface != instance) {
+    LOG(WARNING) << "Trying to release not own session";
+    return;
+  }
+
+  if (le_audio_sink_hal_state == HAL_UNINITIALIZED) {
+    LOG(WARNING) << "LE audio device HAL is not running.";
+    return;
+  }
+
+  sinkClientInterface->Cleanup();
+  leAudioClientInterface->ReleaseSink(sinkClientInterface);
+  le_audio_sink_hal_state = HAL_UNINITIALIZED;
+  sinkClientInterface = nullptr;
+}
+
+void LeAudioClientAudioSource::ConfirmStreamingRequest() {
+  LOG(INFO) << __func__;
+  if ((sinkClientInterface == nullptr) ||
+      (le_audio_sink_hal_state != HAL_STARTED)) {
+    LOG(ERROR) << "LE audio device HAL was not started!";
+    return;
+  }
+
+  sinkClientInterface->ConfirmStreamingRequest();
+  LOG(INFO) << __func__ << ", start_audio_ticks";
+  start_audio_ticks();
+}
+
+void LeAudioClientAudioSource::CancelStreamingRequest() {
+  LOG(INFO) << __func__;
+  if ((sinkClientInterface == nullptr) ||
+      (le_audio_sink_hal_state != HAL_STARTED)) {
+    LOG(ERROR) << "LE audio device HAL was not started!";
+    return;
+  }
+
+  sinkClientInterface->CancelStreamingRequest();
+}
+
+void LeAudioClientAudioSource::UpdateRemoteDelay(uint16_t remote_delay_ms) {
+  LOG(INFO) << __func__;
+  if ((sinkClientInterface == nullptr) ||
+      (le_audio_sink_hal_state != HAL_STARTED)) {
+    LOG(ERROR) << "LE audio device HAL was not started!";
+    return;
+  }
+
+  sinkClientInterface->SetRemoteDelay(remote_delay_ms);
+}
+
+void LeAudioClientAudioSource::DebugDump(int fd) {
+  uint64_t now_us = bluetooth::common::time_get_os_boottime_us();
+  std::stringstream stream;
+  stream << "  Le Audio Audio HAL:"
+         << "\n    Counts (underflow)                                      : "
+         << stats.media_read_total_underflow_count
+         << "\n    Bytes (underflow)                                       : "
+         << stats.media_read_total_underflow_bytes
+         << "\n    Last update time ago in ms (underflow)                  : "
+         << (stats.media_read_last_underflow_us > 0
+                 ? (unsigned long long)(now_us -
+                                        stats.media_read_last_underflow_us) /
+                       1000
+                 : 0)
+         << std::endl;
+  dprintf(fd, "%s", stream.str().c_str());
+}
+
+bool LeAudioClientAudioSink::Start(
+    const LeAudioCodecConfiguration& codec_configuration,
+    LeAudioClientAudioSourceReceiver* audioReceiver) {
+  LOG(INFO) << __func__;
+  if (!sourceClientInterface) {
+    LOG(ERROR) << "sourceClientInterface is not Acquired!";
+    return false;
+  }
+
+  if (le_audio_source_hal_state == HAL_STARTED) {
+    LOG(ERROR) << "LE audio device HAL is already in use!";
+    return false;
+  }
+
+  LOG(INFO) << __func__ << ": Le Audio Sink Open, bit rate: "
+            << codec_configuration.bits_per_sample
+            << ", num channels: " << int{codec_configuration.num_channels}
+            << ", sample rate: " << codec_configuration.sample_rate
+            << ", data interval: " << codec_configuration.data_interval_us;
+
+  LeAudioClientInterface::PcmParameters pcmParameters = {
+      .data_interval_us = codec_configuration.data_interval_us,
+      .sample_rate = codec_configuration.sample_rate,
+      .bits_per_sample = codec_configuration.bits_per_sample,
+      .channels_count = codec_configuration.num_channels};
+
+  sourceClientInterface->SetPcmParameters(pcmParameters);
+  sourceClientInterface->StartSession();
+
+  localAudioSourceReceiver = audioReceiver;
+  le_audio_source_hal_state = HAL_STARTED;
+  return true;
+}
+
+void LeAudioClientAudioSink::Stop() {
+  LOG(INFO) << __func__;
+  if (!sourceClientInterface) {
+    LOG(ERROR) << __func__ << " sourceClientInterface stopped";
+    return;
+  }
+
+  if (le_audio_source_hal_state != HAL_STARTED) {
+    LOG(ERROR) << "LE audio device HAL was not started!";
+    return;
+  }
+
+  LOG(INFO) << __func__ << ": Le Audio Sink Close";
+
+  sourceClientInterface->StopSession();
+  le_audio_source_hal_state = HAL_STOPPED;
+  localAudioSourceReceiver = nullptr;
+}
+
+const void* LeAudioClientAudioSink::Acquire() {
+  LOG(INFO) << __func__;
+  if (sourceClientInterface != nullptr) {
+    LOG(WARNING) << __func__ << ", Source client interface already initialized";
+    return nullptr;
+  }
+
+  /* Get pointer to singleton LE audio client interface */
+  if (leAudioClientInterface == nullptr) {
+    leAudioClientInterface = LeAudioClientInterface::Get();
+
+    if (leAudioClientInterface == nullptr) {
+      LOG(ERROR) << __func__ << ", can't get LE audio client interface";
+      return nullptr;
+    }
+  }
+
+  auto source_stream_cb = bluetooth::audio::le_audio::StreamCallbacks{
+      .on_resume_ = le_audio_source_on_resume_req,
+      .on_suspend_ = le_audio_source_on_suspend_req,
+  };
+
+  sourceClientInterface =
+      leAudioClientInterface->GetSource(source_stream_cb, get_main_thread());
+
+  if (sourceClientInterface == nullptr) {
+    LOG(ERROR) << __func__ << ", can't get LE audio source client interface";
+    return nullptr;
+  }
+
+  le_audio_source_hal_state = HAL_STOPPED;
+  return sourceClientInterface;
+}
+
+void LeAudioClientAudioSink::Release(const void* instance) {
+  LOG(INFO) << __func__;
+  if (sourceClientInterface != instance) {
+    LOG(WARNING) << "Trying to release not own session";
+    return;
+  }
+
+  if (le_audio_source_hal_state == HAL_UNINITIALIZED) {
+    LOG(WARNING) << ", LE audio device source HAL is not running.";
+    return;
+  }
+
+  sourceClientInterface->Cleanup();
+  leAudioClientInterface->ReleaseSource(sourceClientInterface);
+  le_audio_source_hal_state = HAL_UNINITIALIZED;
+  sourceClientInterface = nullptr;
+}
+
+size_t LeAudioClientAudioSink::SendData(uint8_t* data, uint16_t size) {
+  size_t bytes_written;
+  if (!sourceClientInterface) {
+    LOG(ERROR) << "sourceClientInterface not initialized!";
+    return 0;
+  }
+
+  if (le_audio_source_hal_state != HAL_STARTED) {
+    LOG(ERROR) << "LE audio device HAL was not started!";
+    return 0;
+  }
+
+  /* TODO: What to do if not all data is written ? */
+  bytes_written = sourceClientInterface->Write(data, size);
+  if (bytes_written != size)
+    LOG(ERROR) << ", Not all data is written to source HAL. bytes written: "
+               << static_cast<int>(bytes_written)
+               << ", total: " << static_cast<int>(size);
+
+  return bytes_written;
+}
+
+void LeAudioClientAudioSink::ConfirmStreamingRequest() {
+  LOG(INFO) << __func__;
+  if ((sourceClientInterface == nullptr) ||
+      (le_audio_source_hal_state != HAL_STARTED)) {
+    LOG(ERROR) << "LE audio device HAL was not started!";
+    return;
+  }
+
+  sourceClientInterface->ConfirmStreamingRequest();
+}
+
+void LeAudioClientAudioSink::CancelStreamingRequest() {
+  LOG(INFO) << __func__;
+  if ((sourceClientInterface == nullptr) ||
+      (le_audio_source_hal_state != HAL_STARTED)) {
+    LOG(ERROR) << "LE audio device HAL was not started!";
+    return;
+  }
+
+  sourceClientInterface->CancelStreamingRequest();
+}
+
+void LeAudioClientAudioSink::UpdateRemoteDelay(uint16_t remote_delay_ms) {
+  if ((sourceClientInterface == nullptr) ||
+      (le_audio_source_hal_state != HAL_STARTED)) {
+    LOG(ERROR) << "LE audio device HAL was not started!";
+    return;
+  }
+
+  sourceClientInterface->SetRemoteDelay(remote_delay_ms);
+}
+
+void LeAudioClientAudioSink::DebugDump(int fd) {
+  /* TODO: Add some statistic for source client interface */
+}
diff --git a/system/bta/le_audio/client_audio.h b/system/bta/le_audio/client_audio.h
new file mode 100644
index 0000000000000000000000000000000000000000..1bca6185fe7aec60dcb9bf5933646226e990bee2
--- /dev/null
+++ b/system/bta/le_audio/client_audio.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <future>
+
+#include "audio_hal_interface/le_audio_software.h"
+
+/* Implementations of Le Audio will also implement this interface */
+class LeAudioClientAudioSinkReceiver {
+ public:
+  virtual ~LeAudioClientAudioSinkReceiver() = default;
+  virtual void OnAudioDataReady(const std::vector<uint8_t>& data) = 0;
+  virtual void OnAudioSuspend(std::promise<void> do_suspend_promise) = 0;
+  virtual void OnAudioResume(std::promise<void> do_resume_promise) = 0;
+  virtual void OnAudioMetadataUpdate(
+      std::promise<void> do_update_metadata_promise, audio_usage_t usage,
+      audio_content_type_t content_type) = 0;
+};
+class LeAudioClientAudioSourceReceiver {
+ public:
+  virtual ~LeAudioClientAudioSourceReceiver() = default;
+  virtual void OnAudioSuspend(std::promise<void> do_suspend_promise) = 0;
+  virtual void OnAudioResume(std::promise<void> do_resume_promise) = 0;
+};
+
+/* Represents configuration of audio codec, as exchanged between le audio and
+ * phone.
+ * It can also be passed to the audio source to configure its parameters.
+ */
+struct LeAudioCodecConfiguration {
+  static constexpr uint8_t kChannelNumberMono =
+      bluetooth::audio::le_audio::kChannelNumberMono;
+  static constexpr uint8_t kChannelNumberStereo =
+      bluetooth::audio::le_audio::kChannelNumberStereo;
+
+  static constexpr uint32_t kSampleRate48000 =
+      bluetooth::audio::le_audio::kSampleRate48000;
+  static constexpr uint32_t kSampleRate44100 =
+      bluetooth::audio::le_audio::kSampleRate44100;
+  static constexpr uint32_t kSampleRate32000 =
+      bluetooth::audio::le_audio::kSampleRate32000;
+  static constexpr uint32_t kSampleRate24000 =
+      bluetooth::audio::le_audio::kSampleRate24000;
+  static constexpr uint32_t kSampleRate16000 =
+      bluetooth::audio::le_audio::kSampleRate16000;
+  static constexpr uint32_t kSampleRate8000 =
+      bluetooth::audio::le_audio::kSampleRate8000;
+
+  static constexpr uint8_t kBitsPerSample16 =
+      bluetooth::audio::le_audio::kBitsPerSample16;
+  static constexpr uint8_t kBitsPerSample24 =
+      bluetooth::audio::le_audio::kBitsPerSample24;
+  static constexpr uint8_t kBitsPerSample32 =
+      bluetooth::audio::le_audio::kBitsPerSample32;
+
+  static constexpr uint32_t kInterval7500Us = 7500;
+  static constexpr uint32_t kInterval10000Us = 10000;
+
+  /** number of channels */
+  uint8_t num_channels;
+
+  /** sampling rate that the codec expects to receive from audio framework */
+  uint32_t sample_rate;
+
+  /** bits per sample that codec expects to receive from audio framework */
+  uint8_t bits_per_sample;
+
+  /** Data interval determines how often we send samples to the remote. This
+   * should match how often we grab data from audio source, optionally we can
+   * grab data every 2 or 3 intervals, but this would increase latency.
+   *
+   * Value is provided in us.
+   */
+  uint32_t data_interval_us;
+
+  bool operator!=(const LeAudioCodecConfiguration& other) {
+    return !((num_channels == other.num_channels) &&
+             (sample_rate == other.sample_rate) &&
+             (bits_per_sample == other.bits_per_sample) &&
+             (data_interval_us == other.data_interval_us));
+  }
+
+  bool IsInvalid() {
+    return (num_channels == 0) || (sample_rate == 0) ||
+           (bits_per_sample == 0) || (data_interval_us == 0);
+  }
+};
+
+/* Represents source of audio for le audio client */
+class LeAudioClientAudioSource {
+ public:
+  static bool Start(const LeAudioCodecConfiguration& codecConfiguration,
+                    LeAudioClientAudioSinkReceiver* audioReceiver);
+  static void Stop();
+  static const void* Acquire();
+  static void Release(const void* instance);
+  static void ConfirmStreamingRequest();
+  static void CancelStreamingRequest();
+  static void UpdateRemoteDelay(uint16_t remote_delay_ms);
+  static void DebugDump(int fd);
+};
+
+/* Represents audio sink for le audio client */
+class LeAudioClientAudioSink {
+ public:
+  static bool Start(const LeAudioCodecConfiguration& codecConfiguration,
+                    LeAudioClientAudioSourceReceiver* audioReceiver);
+  static void Stop();
+  static const void* Acquire();
+  static void Release(const void* instance);
+  static size_t SendData(uint8_t* data, uint16_t size);
+  static void ConfirmStreamingRequest();
+  static void CancelStreamingRequest();
+  static void UpdateRemoteDelay(uint16_t remote_delay_ms);
+  static void DebugDump(int fd);
+};
diff --git a/system/bta/le_audio/client_audio_test.cc b/system/bta/le_audio/client_audio_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..919f267aebf3eaf6ec7e8beba7edfa5883a5efef
--- /dev/null
+++ b/system/bta/le_audio/client_audio_test.cc
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "client_audio.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <future>
+
+#include "audio_hal_interface/le_audio_software.h"
+#include "base/bind_helpers.h"
+#include "common/message_loop_thread.h"
+#include "hardware/bluetooth.h"
+#include "osi/include/wakelock.h"
+
+using ::testing::_;
+using ::testing::Assign;
+using ::testing::AtLeast;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::Mock;
+using ::testing::Return;
+using ::testing::ReturnPointee;
+using ::testing::SaveArg;
+using std::chrono_literals::operator""ms;
+
+bluetooth::common::MessageLoopThread message_loop_thread("test message loop");
+bluetooth::common::MessageLoopThread* get_main_thread() {
+  return &message_loop_thread;
+}
+bt_status_t do_in_main_thread(const base::Location& from_here,
+                              base::OnceClosure task) {
+  if (!message_loop_thread.DoInThread(from_here, std::move(task))) {
+    LOG(ERROR) << __func__ << ": failed from " << from_here.ToString();
+    return BT_STATUS_FAIL;
+  }
+  return BT_STATUS_SUCCESS;
+}
+
+static base::MessageLoop* message_loop_;
+base::MessageLoop* get_main_message_loop() { return message_loop_; }
+
+static void init_message_loop_thread() {
+  message_loop_thread.StartUp();
+  if (!message_loop_thread.IsRunning()) {
+    FAIL() << "unable to create message loop thread.";
+  }
+
+  if (!message_loop_thread.EnableRealTimeScheduling())
+    LOG(ERROR) << "Unable to set real time scheduling";
+
+  message_loop_ = message_loop_thread.message_loop();
+  if (message_loop_ == nullptr) FAIL() << "unable to get message loop.";
+}
+
+static void cleanup_message_loop_thread() {
+  message_loop_ = nullptr;
+  message_loop_thread.ShutDown();
+}
+
+using bluetooth::audio::le_audio::LeAudioClientInterface;
+
+class MockLeAudioClientInterfaceSink : public LeAudioClientInterface::Sink {
+ public:
+  MOCK_METHOD((void), Cleanup, (), (override));
+  MOCK_METHOD((void), SetPcmParameters,
+              (const LeAudioClientInterface::PcmParameters& params),
+              (override));
+  MOCK_METHOD((void), SetRemoteDelay, (uint16_t delay_report_ms), (override));
+  MOCK_METHOD((void), StartSession, (), (override));
+  MOCK_METHOD((void), StopSession, (), (override));
+  MOCK_METHOD((void), ConfirmStreamingRequest, (), (override));
+  MOCK_METHOD((void), CancelStreamingRequest, (), (override));
+  MOCK_METHOD((size_t), Read, (uint8_t * p_buf, uint32_t len));
+};
+
+class MockLeAudioClientInterfaceSource : public LeAudioClientInterface::Source {
+ public:
+  MOCK_METHOD((void), Cleanup, (), (override));
+  MOCK_METHOD((void), SetPcmParameters,
+              (const LeAudioClientInterface::PcmParameters& params),
+              (override));
+  MOCK_METHOD((void), SetRemoteDelay, (uint16_t delay_report_ms), (override));
+  MOCK_METHOD((void), StartSession, (), (override));
+  MOCK_METHOD((void), StopSession, (), (override));
+  MOCK_METHOD((void), ConfirmStreamingRequest, (), (override));
+  MOCK_METHOD((void), CancelStreamingRequest, (), (override));
+  MOCK_METHOD((size_t), Write, (const uint8_t* p_buf, uint32_t len));
+};
+
+class MockLeAudioClientInterface : public LeAudioClientInterface {
+ public:
+  MockLeAudioClientInterface() = default;
+  ~MockLeAudioClientInterface() = default;
+
+  MOCK_METHOD((Sink*), GetSink,
+              (bluetooth::audio::le_audio::StreamCallbacks stream_cb,
+               bluetooth::common::MessageLoopThread* message_loop));
+  MOCK_METHOD((Source*), GetSource,
+              (bluetooth::audio::le_audio::StreamCallbacks stream_cb,
+               bluetooth::common::MessageLoopThread* message_loop));
+};
+
+LeAudioClientInterface* mockInterface;
+
+namespace bluetooth {
+namespace audio {
+namespace le_audio {
+MockLeAudioClientInterface* interface_mock;
+MockLeAudioClientInterfaceSink* sink_mock;
+MockLeAudioClientInterfaceSource* source_mock;
+
+LeAudioClientInterface* LeAudioClientInterface::Get() { return interface_mock; }
+
+LeAudioClientInterface::Sink* LeAudioClientInterface::GetSink(
+    StreamCallbacks stream_cb,
+    bluetooth::common::MessageLoopThread* message_loop) {
+  return interface_mock->GetSink(stream_cb, message_loop);
+}
+
+LeAudioClientInterface::Source* LeAudioClientInterface::GetSource(
+    StreamCallbacks stream_cb,
+    bluetooth::common::MessageLoopThread* message_loop) {
+  return interface_mock->GetSource(stream_cb, message_loop);
+}
+
+bool LeAudioClientInterface::ReleaseSink(LeAudioClientInterface::Sink* sink) {
+  return true;
+}
+bool LeAudioClientInterface::ReleaseSource(
+    LeAudioClientInterface::Source* source) {
+  return true;
+}
+
+void LeAudioClientInterface::Sink::Cleanup() {}
+void LeAudioClientInterface::Sink::SetPcmParameters(
+    const PcmParameters& params) {}
+void LeAudioClientInterface::Sink::SetRemoteDelay(uint16_t delay_report_ms) {}
+void LeAudioClientInterface::Sink::StartSession() {}
+void LeAudioClientInterface::Sink::StopSession() {}
+void LeAudioClientInterface::Sink::ConfirmStreamingRequest(){};
+void LeAudioClientInterface::Sink::CancelStreamingRequest(){};
+
+void LeAudioClientInterface::Source::Cleanup() {}
+void LeAudioClientInterface::Source::SetPcmParameters(
+    const PcmParameters& params) {}
+void LeAudioClientInterface::Source::SetRemoteDelay(uint16_t delay_report_ms) {}
+void LeAudioClientInterface::Source::StartSession() {}
+void LeAudioClientInterface::Source::StopSession() {}
+void LeAudioClientInterface::Source::ConfirmStreamingRequest(){};
+void LeAudioClientInterface::Source::CancelStreamingRequest(){};
+
+size_t LeAudioClientInterface::Source::Write(const uint8_t* p_buf,
+                                             uint32_t len) {
+  return source_mock->Write(p_buf, len);
+}
+
+size_t LeAudioClientInterface::Sink::Read(uint8_t* p_buf, uint32_t len) {
+  return sink_mock->Read(p_buf, len);
+}
+}  // namespace le_audio
+}  // namespace audio
+}  // namespace bluetooth
+
+class MockLeAudioClientAudioSinkEventReceiver
+    : public LeAudioClientAudioSinkReceiver {
+ public:
+  MOCK_METHOD((void), OnAudioDataReady, (const std::vector<uint8_t>& data),
+              (override));
+  MOCK_METHOD((void), OnAudioSuspend, (std::promise<void> do_suspend_promise),
+              (override));
+  MOCK_METHOD((void), OnAudioResume, (std::promise<void> do_resume_promise),
+              (override));
+  MOCK_METHOD((void), OnAudioMetadataUpdate,
+              (std::promise<void> do_update_metadata_promise,
+               audio_usage_t usage, audio_content_type_t content_type),
+              (override));
+};
+
+class MockLeAudioClientAudioSourceEventReceiver
+    : public LeAudioClientAudioSourceReceiver {
+ public:
+  MOCK_METHOD((void), OnAudioSuspend, (std::promise<void> do_suspend_promise),
+              (override));
+  MOCK_METHOD((void), OnAudioResume, (std::promise<void> do_resume_promise),
+              (override));
+};
+
+class LeAudioClientAudioTest : public ::testing::Test {
+ public:
+  void SetUp(void) override {
+    init_message_loop_thread();
+    bluetooth::audio::le_audio::interface_mock = &mock_client_interface_;
+    bluetooth::audio::le_audio::sink_mock = &mock_client_interface_sink_;
+    bluetooth::audio::le_audio::source_mock = &mock_client_interface_source_;
+
+    // Init sink Audio HAL mock
+    is_sink_acquired = false;
+    hal_sink_stream_cb = {.on_suspend_ = nullptr, .on_resume_ = nullptr};
+
+    ON_CALL(mock_client_interface_, GetSink(_, _))
+        .WillByDefault(DoAll(SaveArg<0>(&hal_sink_stream_cb),
+                             Assign(&is_sink_acquired, true),
+                             Return(bluetooth::audio::le_audio::sink_mock)));
+    ON_CALL(mock_client_interface_sink_, Cleanup())
+        .WillByDefault(Assign(&is_sink_acquired, false));
+
+    // Init source Audio HAL mock
+    is_source_acquired = false;
+    hal_source_stream_cb = {.on_suspend_ = nullptr, .on_resume_ = nullptr};
+
+    ON_CALL(mock_client_interface_, GetSource(_, _))
+        .WillByDefault(DoAll(SaveArg<0>(&hal_source_stream_cb),
+                             Assign(&is_source_acquired, true),
+                             Return(bluetooth::audio::le_audio::source_mock)));
+    ON_CALL(mock_client_interface_source_, Cleanup())
+        .WillByDefault(Assign(&is_source_acquired, false));
+  }
+
+  void AcquireAudioSink(void) {
+    audio_sink_instance_ = LeAudioClientAudioSink::Acquire();
+  }
+
+  void ReleaseAudioSink(void) {
+    LeAudioClientAudioSink::Release(audio_sink_instance_);
+    audio_sink_instance_ = nullptr;
+  }
+
+  void AcquireAudioSource(void) {
+    audio_source_instance_ = LeAudioClientAudioSource::Acquire();
+  }
+
+  void ReleaseAudioSource(void) {
+    LeAudioClientAudioSource::Release(audio_source_instance_);
+    audio_source_instance_ = nullptr;
+  }
+
+  void TearDown(void) override {
+    /* We have to call Cleanup to tidy up some static variables.
+     * If on the HAL end Source is running it means we are running the Sink
+     * on our end, and vice versa.
+     */
+    if (is_source_acquired == true) ReleaseAudioSink();
+    if (is_sink_acquired == true) ReleaseAudioSource();
+
+    cleanup_message_loop_thread();
+
+    bluetooth::audio::le_audio::sink_mock = nullptr;
+    bluetooth::audio::le_audio::source_mock = nullptr;
+  }
+
+  MockLeAudioClientInterface mock_client_interface_;
+  MockLeAudioClientInterfaceSink mock_client_interface_sink_;
+  MockLeAudioClientInterfaceSource mock_client_interface_source_;
+
+  MockLeAudioClientAudioSinkEventReceiver mock_hal_sink_event_receiver_;
+  MockLeAudioClientAudioSourceEventReceiver mock_hal_source_event_receiver_;
+
+  bool is_source_acquired = false;
+  bool is_sink_acquired = false;
+  const void* audio_sink_instance_ = nullptr;
+  const void* audio_source_instance_ = nullptr;
+
+  bluetooth::audio::le_audio::StreamCallbacks hal_source_stream_cb;
+  bluetooth::audio::le_audio::StreamCallbacks hal_sink_stream_cb;
+
+  const LeAudioCodecConfiguration default_codec_conf{
+      .num_channels = LeAudioCodecConfiguration::kChannelNumberMono,
+      .sample_rate = LeAudioCodecConfiguration::kSampleRate44100,
+      .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample24,
+      .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us,
+  };
+};
+
+TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkInitializeCleanup) {
+  EXPECT_CALL(mock_client_interface_, GetSource(_, _))
+      .WillOnce(DoAll(Assign(&is_source_acquired, true),
+                      Return(bluetooth::audio::le_audio::source_mock)));
+  AcquireAudioSink();
+
+  EXPECT_CALL(mock_client_interface_source_, Cleanup())
+      .WillOnce(Assign(&is_source_acquired, false));
+  ReleaseAudioSink();
+}
+
+TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceInitializeCleanup) {
+  EXPECT_CALL(mock_client_interface_, GetSink(_, _))
+      .WillOnce(DoAll(Assign(&is_sink_acquired, true),
+                      Return(bluetooth::audio::le_audio::sink_mock)));
+  AcquireAudioSource();
+
+  EXPECT_CALL(mock_client_interface_sink_, Cleanup())
+      .WillOnce(Assign(&is_sink_acquired, false));
+  ReleaseAudioSource();
+}
+
+TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkStartStop) {
+  LeAudioClientInterface::PcmParameters params;
+  EXPECT_CALL(mock_client_interface_source_, SetPcmParameters(_))
+      .Times(1)
+      .WillOnce(SaveArg<0>(&params));
+  EXPECT_CALL(mock_client_interface_source_, StartSession()).Times(1);
+
+  AcquireAudioSink();
+  ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf,
+                                            &mock_hal_source_event_receiver_));
+
+  ASSERT_EQ(params.channels_count,
+            bluetooth::audio::le_audio::kChannelNumberMono);
+  ASSERT_EQ(params.sample_rate, bluetooth::audio::le_audio::kSampleRate44100);
+  ASSERT_EQ(params.bits_per_sample,
+            bluetooth::audio::le_audio::kBitsPerSample24);
+  ASSERT_EQ(params.data_interval_us, 10000u);
+
+  EXPECT_CALL(mock_client_interface_source_, StopSession()).Times(1);
+
+  LeAudioClientAudioSink::Stop();
+}
+
+TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceStartStop) {
+  LeAudioClientInterface::PcmParameters params;
+  EXPECT_CALL(mock_client_interface_sink_, SetPcmParameters(_))
+      .Times(1)
+      .WillOnce(SaveArg<0>(&params));
+  EXPECT_CALL(mock_client_interface_sink_, StartSession()).Times(1);
+
+  AcquireAudioSource();
+  ASSERT_TRUE(LeAudioClientAudioSource::Start(default_codec_conf,
+                                              &mock_hal_sink_event_receiver_));
+
+  ASSERT_EQ(params.channels_count,
+            bluetooth::audio::le_audio::kChannelNumberMono);
+  ASSERT_EQ(params.sample_rate, bluetooth::audio::le_audio::kSampleRate44100);
+  ASSERT_EQ(params.bits_per_sample,
+            bluetooth::audio::le_audio::kBitsPerSample24);
+  ASSERT_EQ(params.data_interval_us, 10000u);
+
+  EXPECT_CALL(mock_client_interface_sink_, StopSession()).Times(1);
+
+  LeAudioClientAudioSource::Stop();
+}
+
+TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkSendData) {
+  AcquireAudioSink();
+  ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf,
+                                            &mock_hal_source_event_receiver_));
+
+  const uint8_t* exp_p = nullptr;
+  uint32_t exp_len = 0;
+  uint8_t input_buf[] = {
+      0x02,
+      0x03,
+      0x05,
+      0x19,
+  };
+  ON_CALL(mock_client_interface_source_, Write(_, _))
+      .WillByDefault(DoAll(SaveArg<0>(&exp_p), SaveArg<1>(&exp_len),
+                           ReturnPointee(&exp_len)));
+
+  ASSERT_EQ(LeAudioClientAudioSink::SendData(input_buf, sizeof(input_buf)),
+            sizeof(input_buf));
+  ASSERT_EQ(exp_len, sizeof(input_buf));
+  ASSERT_EQ(exp_p, input_buf);
+
+  LeAudioClientAudioSource::Stop();
+}
+
+TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkSuspend) {
+  AcquireAudioSink();
+  ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf,
+                                            &mock_hal_source_event_receiver_));
+
+  ASSERT_NE(hal_source_stream_cb.on_suspend_, nullptr);
+
+  /* Expect LeAudio registered event listener to get called when HAL calls the
+   * client_audio's internal suspend callback.
+   */
+  EXPECT_CALL(mock_hal_source_event_receiver_, OnAudioSuspend(_)).Times(1);
+  ASSERT_TRUE(hal_source_stream_cb.on_suspend_());
+}
+
+TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceSuspend) {
+  AcquireAudioSource();
+  ASSERT_TRUE(LeAudioClientAudioSource::Start(default_codec_conf,
+                                              &mock_hal_sink_event_receiver_));
+
+  ASSERT_NE(hal_sink_stream_cb.on_suspend_, nullptr);
+
+  /* Expect LeAudio registered event listener to get called when HAL calls the
+   * client_audio's internal suspend callback.
+   */
+  EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioSuspend(_)).Times(1);
+  ASSERT_TRUE(hal_sink_stream_cb.on_suspend_());
+}
+
+TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkResume) {
+  AcquireAudioSink();
+  ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf,
+                                            &mock_hal_source_event_receiver_));
+
+  ASSERT_NE(hal_source_stream_cb.on_resume_, nullptr);
+
+  /* Expect LeAudio registered event listener to get called when HAL calls the
+   * client_audio's internal resume callback.
+   */
+  EXPECT_CALL(mock_hal_source_event_receiver_, OnAudioResume(_)).Times(1);
+  bool start_media_task = false;
+  ASSERT_TRUE(hal_source_stream_cb.on_resume_(start_media_task));
+}
+
+TEST_F(LeAudioClientAudioTest,
+       testLeAudioClientAudioSourceResumeStartSourceTask) {
+  const LeAudioCodecConfiguration codec_conf{
+      .num_channels = LeAudioCodecConfiguration::kChannelNumberStereo,
+      .sample_rate = LeAudioCodecConfiguration::kSampleRate16000,
+      .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample24,
+      .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us,
+  };
+  AcquireAudioSource();
+  ASSERT_TRUE(LeAudioClientAudioSource::Start(codec_conf,
+                                              &mock_hal_sink_event_receiver_));
+
+  std::chrono::time_point<std::chrono::system_clock> resumed_ts;
+  std::chrono::time_point<std::chrono::system_clock> executed_ts;
+  std::promise<void> promise;
+  auto future = promise.get_future();
+
+  uint32_t calculated_bytes_per_tick = 0;
+  EXPECT_CALL(mock_client_interface_sink_, Read(_, _))
+      .Times(AtLeast(1))
+      .WillOnce(Invoke([&](uint8_t* p_buf, uint32_t len) -> uint32_t {
+        executed_ts = std::chrono::system_clock::now();
+        calculated_bytes_per_tick = len;
+
+        // fake some data from audio framework
+        for (uint32_t i = 0u; i < len; ++i) {
+          p_buf[i] = i;
+        }
+
+        // Return exactly as much data as requested
+        promise.set_value();
+        return len;
+      }));
+
+  std::promise<void> data_promise;
+  auto data_future = data_promise.get_future();
+
+  /* Expect this callback to be called to Client by the HAL glue layer */
+  std::vector<uint8_t> media_data_to_send;
+  EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioDataReady(_))
+      .WillOnce(Invoke([&](const std::vector<uint8_t>& data) -> void {
+        media_data_to_send = std::move(data);
+        data_promise.set_value();
+      }));
+
+  /* Expect LeAudio registered event listener to get called when HAL calls the
+   * client_audio's internal resume callback.
+   */
+  ASSERT_NE(hal_sink_stream_cb.on_resume_, nullptr);
+  EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioResume(_)).Times(1);
+  resumed_ts = std::chrono::system_clock::now();
+  bool start_media_task = true;
+  ASSERT_TRUE(hal_sink_stream_cb.on_resume_(start_media_task));
+  LeAudioClientAudioSource::ConfirmStreamingRequest();
+
+  ASSERT_EQ(future.wait_for(std::chrono::seconds(1)),
+            std::future_status::ready);
+
+  ASSERT_EQ(data_future.wait_for(std::chrono::seconds(1)),
+            std::future_status::ready);
+
+  // Check agains expected payload size
+  const uint32_t channel_bytes_per_sample = (24 /*bps*/ / 8);
+  const uint32_t channel_bytes_per_10ms_at_16000Hz =
+      ((10ms).count() * channel_bytes_per_sample * 16000 /*Hz*/) /
+      (1000ms).count();
+
+  // Expect 2 channel (stereo) data
+  ASSERT_EQ(calculated_bytes_per_tick, 2 * channel_bytes_per_10ms_at_16000Hz);
+
+  // Verify callback call interval for the requested 10ms (+2ms error margin)
+  auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(
+      executed_ts - resumed_ts);
+  EXPECT_TRUE((delta >= 10ms) && (delta <= 12ms));
+
+  // Verify if we got just right amount of data in the callback call
+  ASSERT_EQ(media_data_to_send.size(), calculated_bytes_per_tick);
+}
+
+TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceResume) {
+  AcquireAudioSource();
+  ASSERT_TRUE(LeAudioClientAudioSource::Start(default_codec_conf,
+                                              &mock_hal_sink_event_receiver_));
+
+  ASSERT_NE(hal_sink_stream_cb.on_resume_, nullptr);
+
+  /* Expect LeAudio registered event listener to get called when HAL calls the
+   * client_audio's internal resume callback.
+   */
+  EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioResume(_)).Times(1);
+  bool start_media_task = false;
+  ASSERT_TRUE(hal_sink_stream_cb.on_resume_(start_media_task));
+}
diff --git a/system/bta/le_audio/client_parser.cc b/system/bta/le_audio/client_parser.cc
new file mode 100644
index 0000000000000000000000000000000000000000..cb6936d0fa3ca3ddc4fc2ee728f6117ab26b5cf9
--- /dev/null
+++ b/system/bta/le_audio/client_parser.cc
@@ -0,0 +1,628 @@
+/*
+ * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ *  This module contains API of the audio stream control protocol.
+ */
+
+#include "client_parser.h"
+
+#include <base/strings/string_number_conversions.h>
+#include <endian.h>
+#include <hardware/bt_common_types.h>
+
+#include <map>
+#include <memory>
+#include <numeric>
+
+#include "bta_le_audio_api.h"
+#include "gap_api.h"
+#include "le_audio_types.h"
+#include "osi/include/allocator.h"
+
+using le_audio::types::acs_ac_record;
+
+namespace le_audio {
+namespace client_parser {
+namespace ascs {
+static std::map<uint8_t, std::string> ase_state_map_string = {
+    {kAseStateIdle, "Idle"},
+    {kAseStateCodecConfigured, "Codec Configured"},
+    {kAseStateQosConfigured, "QoS Configured"},
+    {kAseStateEnabling, "Enabling"},
+    {kAseStateStreaming, "Streaming"},
+    {kAseStateDisabling, "Disabling"},
+    {kAseStateReleasing, "Releasing"},
+};
+
+static std::map<uint8_t, std::string> ctp_opcode_map_string = {
+    {kCtpOpcodeCodecConfiguration, "Config Codec"},
+    {kCtpOpcodeQosConfiguration, "Config QoS"},
+    {kCtpOpcodeEnable, "Enable"},
+    {kCtpOpcodeReceiverStartReady, "Receiver Start Ready"},
+    {kCtpOpcodeDisable, "Disable"},
+    {kCtpOpcodeReceiverStopReady, "Receiver Stop Ready"},
+    {kCtpOpcodeUpdateMetadata, "Update Metadata"},
+    {kCtpOpcodeRelease, "Release"},
+};
+
+static std::map<uint8_t, std::string> ctp_reason_map_string = {
+    {kCtpResponseNoReason, ""},
+    {kCtpResponseCodecId, "Codec ID"},
+    {kCtpResponseCodecSpecificConfiguration, "Codec specific configuration"},
+    {kCtpResponseSduInterval, "SDU interval"},
+    {kCtpResponseFraming, "Framing"},
+    {kCtpResponsePhy, "PHY"},
+    {kCtpResponseMaximumSduSize, "Maximum SDU size"},
+    {kCtpResponseRetransmissionNumber, "Retransmission number"},
+    {kCtpResponseMaxTransportLatency, "Max Transport latency"},
+    {kCtpResponsePresentationDelay, "Presentation delay"},
+    {kCtpResponseInvalidAseCisMapping, "Invalid ASE CIS mapping"},
+};
+
+static std::map<uint8_t, std::string> ctp_response_code_map_string = {
+    {kCtpResponseCodeSuccess, "Success"},
+    {kCtpResponseCodeUnsupportedOpcode, "Unsupported Opcode"},
+    {kCtpResponseCodeInvalidLength, "Invalid Length"},
+    {kCtpResponseCodeInvalidAseId, "Invalid ASE ID"},
+    {kCtpResponseCodeInvalidAseStateMachineTransition,
+     "Invalid ASE State Machine Transition"},
+    {kCtpResponseCodeInvalidAseDirection, "Invalid ASE Direction"},
+    {kCtpResponseCodeUnsupportedAudioCapabilities,
+     "Unsupported Audio Capabilities"},
+    {kCtpResponseCodeUnsupportedConfigurationParameterValue,
+     "Unsupported Configuration Parameter Value"},
+    {kCtpResponseCodeRejectedConfigurationParameterValue,
+     "Rejected Configuration Parameter Value"},
+    {kCtpResponseCodeInvalidConfigurationParameterValue,
+     "Invalid Configuration Parameter Value"},
+    {kCtpResponseCodeUnsupportedMetadata, "Unsupported Metadata"},
+    {kCtpResponseCodeRejectedMetadata, "Rejected Metadata"},
+    {kCtpResponseCodeInvalidMetadata, "Invalid Metadata"},
+    {kCtpResponseCodeInsufficientResources, "Insufficient Resources"},
+    {kCtpResponseCodeUnspecifiedError, "Unspecified Error"},
+};
+
+bool ParseAseStatusHeader(ase_rsp_hdr& arh, uint16_t len,
+                          const uint8_t* value) {
+  if (len < kAseRspHdrMinLen) {
+    LOG(ERROR) << __func__
+               << ", wrong len of ASE char (header): " << static_cast<int>(len);
+
+    return false;
+  }
+
+  STREAM_TO_UINT8(arh.id, value);
+  STREAM_TO_UINT8(arh.state, value);
+
+  LOG(INFO) << "ASE status: "
+            << "\tASE id: " << loghex(arh.id)
+            << "\tASE state: " << ase_state_map_string[arh.state] << " ("
+            << loghex(arh.state) << ")";
+
+  return true;
+}
+
+bool ParseAseStatusCodecConfiguredStateParams(
+    struct ase_codec_configured_state_params& rsp, uint16_t len,
+    const uint8_t* value) {
+  uint8_t codec_spec_conf_len;
+
+  if (len < kAseStatusCodecConfMinLen) {
+    LOG(ERROR) << "Wrong len of codec conf status (Codec conf header)";
+    return false;
+  }
+
+  STREAM_TO_UINT8(rsp.framing, value);
+  STREAM_TO_UINT8(rsp.preferred_phy, value);
+  STREAM_TO_UINT8(rsp.preferred_retrans_nb, value);
+  STREAM_TO_UINT16(rsp.max_transport_latency, value);
+  STREAM_TO_UINT24(rsp.pres_delay_min, value);
+  STREAM_TO_UINT24(rsp.pres_delay_max, value);
+  STREAM_TO_UINT24(rsp.preferred_pres_delay_min, value);
+  STREAM_TO_UINT24(rsp.preferred_pres_delay_max, value);
+  STREAM_TO_UINT8(rsp.codec_id.coding_format, value);
+  STREAM_TO_UINT16(rsp.codec_id.vendor_company_id, value);
+  STREAM_TO_UINT16(rsp.codec_id.vendor_codec_id, value);
+  STREAM_TO_UINT8(codec_spec_conf_len, value);
+
+  len -= kAseStatusCodecConfMinLen;
+
+  if (len != codec_spec_conf_len) {
+    LOG(ERROR) << "Wrong len of codec conf status (Codec spec conf)";
+    return false;
+  }
+  if (codec_spec_conf_len)
+    rsp.codec_spec_conf =
+        std::vector<uint8_t>(value, value + codec_spec_conf_len);
+
+  LOG(INFO) << __func__ << ", Codec configuration"
+            << "\n\tFraming: " << loghex(rsp.framing)
+            << "\n\tPreferred PHY: " << loghex(rsp.preferred_phy)
+            << "\n\tPreferred retransmission number: "
+            << loghex(rsp.preferred_retrans_nb) << "\n\tMax transport latency: "
+            << loghex(rsp.max_transport_latency)
+            << "\n\tPresence delay min: " << loghex(rsp.pres_delay_min)
+            << "\n\tPresence delay max: " << loghex(rsp.pres_delay_max)
+            << "\n\tPreferredPresentationDelayMin: "
+            << loghex(rsp.preferred_pres_delay_min)
+            << "\n\tPreferredPresentationDelayMax: "
+            << loghex(rsp.preferred_pres_delay_max)
+            << "\n\tCoding format: " << loghex(rsp.codec_id.coding_format)
+            << "\n\tVendor codec company ID: "
+            << loghex(rsp.codec_id.vendor_company_id)
+            << "\n\tVendor codec ID: " << loghex(rsp.codec_id.vendor_codec_id)
+            << "\n\tCodec specific conf len: " << (int)codec_spec_conf_len
+            << "\n\tCodec specific conf: "
+            << base::HexEncode(rsp.codec_spec_conf.data(),
+                               rsp.codec_spec_conf.size());
+
+  return true;
+}
+
+bool ParseAseStatusQosConfiguredStateParams(
+    struct ase_qos_configured_state_params& rsp, uint16_t len,
+    const uint8_t* value) {
+  if (len != kAseStatusCodecQosConfMinLen) {
+    LOG(ERROR) << "Wrong len of ASE characteristic (QOS conf header)";
+    return false;
+  }
+
+  STREAM_TO_UINT8(rsp.cig_id, value);
+  STREAM_TO_UINT8(rsp.cis_id, value);
+  STREAM_TO_UINT24(rsp.sdu_interval, value);
+  STREAM_TO_UINT8(rsp.framing, value);
+  STREAM_TO_UINT8(rsp.phy, value);
+  STREAM_TO_UINT16(rsp.max_sdu, value);
+  STREAM_TO_UINT8(rsp.retrans_nb, value);
+  STREAM_TO_UINT16(rsp.max_transport_latency, value);
+  STREAM_TO_UINT24(rsp.pres_delay, value);
+
+  LOG(INFO) << __func__ << ", Codec QoS Configured"
+            << "\n\tCIG: " << loghex(rsp.cig_id)
+            << "\n\tCIS: " << loghex(rsp.cis_id)
+            << "\n\tSDU interval: " << loghex(rsp.sdu_interval)
+            << "\n\tFraming: " << loghex(rsp.framing)
+            << "\n\tPHY: " << loghex(rsp.phy)
+            << "\n\tMax SDU: " << loghex(rsp.max_sdu)
+            << "\n\tRetransmission number: " << loghex(rsp.retrans_nb)
+            << "\n\tMax transport latency: "
+            << loghex(rsp.max_transport_latency)
+            << "\n\tPresentation delay: " << loghex(rsp.pres_delay);
+
+  return true;
+}
+
+bool ParseAseStatusTransientStateParams(struct ase_transient_state_params& rsp,
+                                        uint16_t len, const uint8_t* value) {
+  uint8_t metadata_len;
+
+  if (len < kAseStatusTransMinLen) {
+    LOG(ERROR) << "Wrong len of ASE characteristic (metadata)";
+    return false;
+  }
+
+  STREAM_TO_UINT8(metadata_len, value);
+  len -= kAseStatusTransMinLen;
+
+  if (len != metadata_len) {
+    LOG(ERROR) << "Wrong len of ASE characteristic (metadata)";
+    return false;
+  }
+
+  if (metadata_len > 0)
+    rsp.metadata = std::vector<uint8_t>(value, value + metadata_len);
+
+  LOG(INFO) << __func__ << ", Status enabling/streaming/disabling metadata:"
+            << base::HexEncode(rsp.metadata.data(), rsp.metadata.size());
+
+  return true;
+}
+
+bool ParseAseCtpNotification(struct ctp_ntf& ntf, uint16_t len,
+                             const uint8_t* value) {
+  uint8_t num_entries;
+
+  if (len < kCtpNtfMinLen) {
+    LOG(ERROR) << "Wrong len of ASE control point notification: " << (int)len;
+    return false;
+  }
+
+  STREAM_TO_UINT8(ntf.op, value);
+  STREAM_TO_UINT8(num_entries, value);
+
+  if (len != kCtpNtfMinLen + (num_entries * kCtpAseEntryMinLen)) {
+    LOG(ERROR) << "Wrong len of ASE control point notification (ASE IDs)";
+    return false;
+  }
+
+  for (int i = 0; i < num_entries; i++) {
+    struct ctp_ase_entry entry;
+
+    STREAM_TO_UINT8(entry.ase_id, value);
+    STREAM_TO_UINT8(entry.response_code, value);
+    STREAM_TO_UINT8(entry.reason, value);
+
+    ntf.entries.push_back(std::move(entry));
+  }
+
+  LOG(INFO) << __func__ << ", Control point notification"
+            << "\n\tOpcode: " << ctp_opcode_map_string[ntf.op] << " ("
+            << loghex(ntf.op) << ")"
+            << "\n\tNum ASE IDs: " << (int)num_entries;
+  for (size_t i = 0; i < num_entries; i++)
+    LOG(INFO) << "\n\tASE ID[" << loghex(ntf.entries[i].ase_id)
+              << "] response: "
+              << ctp_response_code_map_string[ntf.entries[i].response_code]
+              << " (" << loghex(ntf.entries[i].response_code) << ")"
+              << " reason: " << ctp_reason_map_string[ntf.entries[i].reason]
+              << " (" << loghex(ntf.entries[i].reason) << ")";
+
+  return true;
+}
+
+bool PrepareAseCtpCodecConfig(const std::vector<struct ctp_codec_conf>& confs,
+                              std::vector<uint8_t>& value) {
+  if (confs.size() == 0) return false;
+
+  std::string conf_ents_str;
+  size_t msg_len = std::accumulate(
+      confs.begin(), confs.end(),
+      confs.size() * kCtpCodecConfMinLen + kAseNumSize + kCtpOpSize,
+      [&conf_ents_str](size_t cur_len, auto const& conf) {
+        auto ltv_map = conf.codec_config.GetAsLtvMap();
+        for (const auto& [type, value] : ltv_map.Values()) {
+          conf_ents_str +=
+              "\ttype: " + std::to_string(type) +
+              "\tlen: " + std::to_string(value.size()) +
+              "\tdata: " + base::HexEncode(value.data(), value.size()) + "\n";
+        };
+
+        return cur_len + ltv_map.RawPacketSize();
+      });
+  value.resize(msg_len);
+
+  uint8_t* msg = value.data();
+  UINT8_TO_STREAM(msg, kCtpOpcodeCodecConfiguration);
+
+  UINT8_TO_STREAM(msg, confs.size());
+  for (const struct ctp_codec_conf& conf : confs) {
+    UINT8_TO_STREAM(msg, conf.ase_id);
+    UINT8_TO_STREAM(msg, conf.target_latency);
+    UINT8_TO_STREAM(msg, conf.target_phy);
+    UINT8_TO_STREAM(msg, conf.codec_id.coding_format);
+    UINT16_TO_STREAM(msg, conf.codec_id.vendor_company_id);
+    UINT16_TO_STREAM(msg, conf.codec_id.vendor_codec_id);
+
+    auto ltv_map = conf.codec_config.GetAsLtvMap();
+    auto codec_spec_conf_len = ltv_map.RawPacketSize();
+
+    UINT8_TO_STREAM(msg, codec_spec_conf_len);
+    msg = ltv_map.RawPacket(msg);
+
+    LOG(INFO) << __func__ << ", Codec configuration"
+              << "\n\tAse id: " << loghex(conf.ase_id)
+              << "\n\tTarget latency: " << loghex(conf.target_latency)
+              << "\n\tTarget PHY: " << loghex(conf.target_phy)
+              << "\n\tCoding format: " << loghex(conf.codec_id.coding_format)
+              << "\n\tVendor codec company ID: "
+              << loghex(conf.codec_id.vendor_company_id)
+              << "\n\tVendor codec ID: "
+              << loghex(conf.codec_id.vendor_codec_id)
+              << "\n\tCodec config len: "
+              << static_cast<int>(codec_spec_conf_len)
+              << "\n\tCodec spec conf: "
+              << "\n"
+              << conf_ents_str;
+  }
+
+  return true;
+}
+
+bool PrepareAseCtpConfigQos(const std::vector<struct ctp_qos_conf>& confs,
+                            std::vector<uint8_t>& value) {
+  if (confs.size() == 0) return false;
+  value.resize(confs.size() * kCtpQosConfMinLen + kAseNumSize + kCtpOpSize);
+
+  uint8_t* msg = value.data();
+  UINT8_TO_STREAM(msg, kCtpOpcodeQosConfiguration);
+  UINT8_TO_STREAM(msg, confs.size());
+
+  for (const struct ctp_qos_conf& conf : confs) {
+    UINT8_TO_STREAM(msg, conf.ase_id);
+    UINT8_TO_STREAM(msg, conf.cig);
+    UINT8_TO_STREAM(msg, conf.cis);
+    UINT24_TO_STREAM(msg, conf.sdu_interval);
+    UINT8_TO_STREAM(msg, conf.framing);
+    UINT8_TO_STREAM(msg, conf.phy);
+    UINT16_TO_STREAM(msg, conf.max_sdu);
+    UINT8_TO_STREAM(msg, conf.retrans_nb);
+    UINT16_TO_STREAM(msg, conf.max_transport_latency);
+    UINT24_TO_STREAM(msg, conf.pres_delay);
+
+    LOG(INFO) << __func__ << ", QoS configuration"
+              << "\n\tAse id: " << loghex(conf.ase_id)
+              << "\n\tcig: " << loghex(conf.cig)
+              << "\n\tCis: " << loghex(conf.cis)
+              << "\n\tSDU interval: " << loghex(conf.sdu_interval)
+              << "\n\tFraming: " << loghex(conf.framing)
+              << "\n\tPhy: " << loghex(conf.phy)
+              << "\n\tMax sdu size: " << loghex(conf.max_sdu)
+              << "\n\tRetrans nb: " << loghex(conf.retrans_nb)
+              << "\n\tMax Transport latency: "
+              << loghex(conf.max_transport_latency)
+              << "\n\tPres delay: " << loghex(conf.pres_delay);
+  }
+
+  return true;
+}
+
+bool PrepareAseCtpEnable(const std::vector<struct ctp_enable>& confs,
+                         std::vector<uint8_t>& value) {
+  if (confs.size() == 0) return false;
+
+  uint16_t msg_len = confs.size() * kCtpEnableMinLen + kAseNumSize + kCtpOpSize;
+  std::for_each(confs.begin(), confs.end(),
+                [&msg_len](const struct ctp_enable& conf) {
+                  msg_len += conf.metadata.size();
+                });
+  value.resize(msg_len);
+
+  uint8_t* msg = value.data();
+  UINT8_TO_STREAM(msg, kCtpOpcodeEnable);
+  UINT8_TO_STREAM(msg, confs.size());
+
+  for (const struct ctp_enable& conf : confs) {
+    UINT8_TO_STREAM(msg, conf.ase_id);
+    UINT8_TO_STREAM(msg, conf.metadata.size());
+    ARRAY_TO_STREAM(msg, conf.metadata.data(),
+                    static_cast<int>(conf.metadata.size()));
+
+    LOG(INFO) << __func__ << ", Enable"
+              << "\n\tAse id: " << loghex(conf.ase_id) << "\n\tMetadata: "
+              << base::HexEncode(conf.metadata.data(), conf.metadata.size());
+  }
+
+  return true;
+}
+
+bool PrepareAseCtpAudioReceiverStartReady(const std::vector<uint8_t>& ase_ids,
+                                          std::vector<uint8_t>& value) {
+  if (ase_ids.size() == 0) return false;
+  value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize);
+
+  uint8_t* msg = value.data();
+  UINT8_TO_STREAM(msg, kCtpOpcodeReceiverStartReady);
+  UINT8_TO_STREAM(msg, ase_ids.size());
+
+  for (const uint8_t& id : ase_ids) {
+    UINT8_TO_STREAM(msg, id);
+
+    LOG(INFO) << __func__ << ", ReceiverStartReady"
+              << "\n\tAse id: " << loghex(id);
+  }
+
+  return true;
+}
+
+bool PrepareAseCtpDisable(const std::vector<uint8_t>& ase_ids,
+                          std::vector<uint8_t>& value) {
+  if (ase_ids.size() == 0) return false;
+  value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize);
+
+  uint8_t* msg = value.data();
+  UINT8_TO_STREAM(msg, kCtpOpcodeDisable);
+  UINT8_TO_STREAM(msg, ase_ids.size());
+
+  for (const uint8_t& id : ase_ids) {
+    UINT8_TO_STREAM(msg, id);
+
+    LOG(INFO) << __func__ << ", Disable"
+              << "\n\tAse id: " << loghex(id);
+  }
+
+  return true;
+}
+
+bool PrepareAseCtpAudioReceiverStopReady(const std::vector<uint8_t>& ase_ids,
+                                         std::vector<uint8_t>& value) {
+  if (ase_ids.size() == 0) return false;
+  value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize);
+
+  uint8_t* msg = value.data();
+  UINT8_TO_STREAM(msg, kCtpOpcodeReceiverStopReady);
+  UINT8_TO_STREAM(msg, ase_ids.size());
+
+  for (const uint8_t& ase_id : ase_ids) {
+    UINT8_TO_STREAM(msg, ase_id);
+
+    LOG(INFO) << __func__ << ", ReceiverStopReady"
+              << "\n\tAse id: " << loghex(ase_id);
+  }
+
+  return true;
+}
+
+bool PrepareAseCtpUpdateMetadata(
+    const std::vector<struct ctp_update_metadata>& confs,
+    std::vector<uint8_t>& value) {
+  if (confs.size() == 0) return false;
+
+  uint16_t msg_len =
+      confs.size() * kCtpUpdateMetadataMinLen + kAseNumSize + kCtpOpSize;
+  std::for_each(confs.begin(), confs.end(),
+                [&msg_len](const struct ctp_update_metadata& conf) {
+                  msg_len += conf.metadata.size();
+                });
+  value.resize(msg_len);
+
+  uint8_t* msg = value.data();
+  UINT8_TO_STREAM(msg, kCtpOpcodeUpdateMetadata);
+  UINT8_TO_STREAM(msg, confs.size());
+
+  for (const struct ctp_update_metadata& conf : confs) {
+    UINT8_TO_STREAM(msg, conf.ase_id);
+    UINT8_TO_STREAM(msg, conf.metadata.size());
+    ARRAY_TO_STREAM(msg, conf.metadata.data(),
+                    static_cast<int>(conf.metadata.size()));
+
+    LOG(INFO) << __func__ << ", Update Metadata"
+              << "\n\tAse id: " << loghex(conf.ase_id) << "\n\tMetadata: "
+              << base::HexEncode(conf.metadata.data(), conf.metadata.size());
+  }
+
+  return true;
+}
+
+bool PrepareAseCtpRelease(const std::vector<uint8_t>& ase_ids,
+                          std::vector<uint8_t>& value) {
+  if (ase_ids.size() == 0) return true;
+  value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize);
+
+  uint8_t* msg = value.data();
+  UINT8_TO_STREAM(msg, kCtpOpcodeRelease);
+  UINT8_TO_STREAM(msg, ase_ids.size());
+
+  for (const uint8_t& ase_id : ase_ids) {
+    UINT8_TO_STREAM(msg, ase_id);
+
+    LOG(INFO) << __func__ << ", Release"
+              << "\n\tAse id: " << loghex(ase_id);
+  }
+
+  return true;
+}
+}  // namespace ascs
+
+namespace pacs {
+
+bool ParsePac(std::vector<struct acs_ac_record>& pac_recs, uint16_t len,
+              const uint8_t* value) {
+  if (len < kAcsPacDiscoverRspMinLen) {
+    LOG(ERROR) << "Wrong len of PAC characteristic";
+    return false;
+  }
+
+  uint8_t pac_rec_nb;
+  STREAM_TO_UINT8(pac_rec_nb, value);
+  len -= kAcsPacDiscoverRspMinLen;
+
+  pac_recs.reserve(pac_rec_nb);
+  for (int i = 0; i < pac_rec_nb; i++) {
+    struct acs_ac_record rec;
+    uint8_t codec_spec_cap_len, metadata_len;
+
+    if (len < kAcsPacRecordMinLen) {
+      LOG(ERROR) << "Wrong len of PAC record";
+      pac_recs.clear();
+      return false;
+    }
+
+    STREAM_TO_UINT8(rec.codec_id.coding_format, value);
+    STREAM_TO_UINT16(rec.codec_id.vendor_company_id, value);
+    STREAM_TO_UINT16(rec.codec_id.vendor_codec_id, value);
+    STREAM_TO_UINT8(codec_spec_cap_len, value);
+    len -= kAcsPacRecordMinLen - kAcsPacMetadataLenLen;
+
+    if (len < codec_spec_cap_len + kAcsPacMetadataLenLen) {
+      LOG(ERROR) << "Wrong len of PAC record (codec specific capabilities)";
+      pac_recs.clear();
+      return false;
+    }
+
+    bool parsed;
+    rec.codec_spec_caps =
+        types::LeAudioLtvMap::Parse(value, codec_spec_cap_len, parsed);
+    if (!parsed) return false;
+
+    value += codec_spec_cap_len;
+    len -= codec_spec_cap_len;
+
+    STREAM_TO_UINT8(metadata_len, value);
+    len -= kAcsPacMetadataLenLen;
+
+    if (len < metadata_len) {
+      LOG(ERROR) << "Wrong len of PAC record (metadata)";
+      pac_recs.clear();
+      return false;
+    }
+
+    rec.metadata = std::vector<uint8_t>(value, value + metadata_len);
+    value += metadata_len;
+    len -= metadata_len;
+
+    pac_recs.push_back(std::move(rec));
+  }
+
+  return true;
+}
+
+bool ParseAudioLocations(types::AudioLocations& audio_locations, uint16_t len,
+                         const uint8_t* value) {
+  if (len != kAudioLocationsRspMinLen) {
+    LOG(ERROR) << "Wrong len of Audio Location characteristic";
+    return false;
+  }
+
+  STREAM_TO_UINT32(audio_locations, value);
+
+  LOG(INFO) << "Audio locations: " << audio_locations.to_string();
+
+  return true;
+}
+
+bool ParseSupportedAudioContexts(struct acs_supported_audio_contexts& contexts,
+                                 uint16_t len, const uint8_t* value) {
+  if (len != kAseAudioSuppContRspMinLen) {
+    LOG(ERROR) << "Wrong len of Audio Supported Context characteristic";
+    return false;
+  }
+
+  STREAM_TO_UINT16(contexts.snk_supp_cont, value);
+  STREAM_TO_UINT16(contexts.src_supp_cont, value);
+
+  LOG(INFO) << "Supported Audio Contexts: "
+            << "\n\tSupported Sink Contexts: "
+            << contexts.snk_supp_cont.to_string()
+            << "\n\tSupported Source Contexts: "
+            << contexts.src_supp_cont.to_string();
+
+  return true;
+}
+
+bool ParseAvailableAudioContexts(struct acs_available_audio_contexts& contexts,
+                                 uint16_t len, const uint8_t* value) {
+  if (len != kAseAudioAvailRspMinLen) {
+    LOG(ERROR) << "Wrong len of Audio Availability characteristic";
+    return false;
+  }
+
+  STREAM_TO_UINT16(contexts.snk_avail_cont, value);
+  STREAM_TO_UINT16(contexts.src_avail_cont, value);
+
+  LOG(INFO) << "Available Audio Contexts: "
+            << "\n\tAvailable Sink Contexts: "
+            << contexts.snk_avail_cont.to_string()
+            << "\n\tAvailable Source Contexts: "
+            << contexts.src_avail_cont.to_string();
+
+  return true;
+}
+}  // namespace pacs
+
+}  // namespace client_parser
+}  // namespace le_audio
diff --git a/system/bta/le_audio/client_parser.h b/system/bta/le_audio/client_parser.h
new file mode 100644
index 0000000000000000000000000000000000000000..f18d79d2fdee79130318f29048e7c8564caae013
--- /dev/null
+++ b/system/bta/le_audio/client_parser.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This file contains the Audio Stream Control (LE_AUDIO) API function external
+ * definitions.
+ */
+
+#pragma once
+
+#include "le_audio_types.h"
+
+namespace le_audio {
+namespace client_parser {
+namespace ascs {
+/*
+ * All structures and defines are described in Audio Stream Control Service
+ * specification
+ */
+
+constexpr uint8_t kCtpResponseCodeSuccess = 0x00;
+constexpr uint8_t kCtpResponseCodeUnsupportedOpcode = 0x01;
+constexpr uint8_t kCtpResponseCodeInvalidLength = 0x02;
+constexpr uint8_t kCtpResponseCodeInvalidAseId = 0x03;
+constexpr uint8_t kCtpResponseCodeInvalidAseStateMachineTransition = 0x04;
+constexpr uint8_t kCtpResponseCodeInvalidAseDirection = 0x05;
+constexpr uint8_t kCtpResponseCodeUnsupportedAudioCapabilities = 0x06;
+constexpr uint8_t kCtpResponseCodeUnsupportedConfigurationParameterValue = 0x07;
+constexpr uint8_t kCtpResponseCodeRejectedConfigurationParameterValue = 0x08;
+constexpr uint8_t kCtpResponseCodeInvalidConfigurationParameterValue = 0x09;
+constexpr uint8_t kCtpResponseCodeUnsupportedMetadata = 0x0A;
+constexpr uint8_t kCtpResponseCodeRejectedMetadata = 0x0B;
+constexpr uint8_t kCtpResponseCodeInvalidMetadata = 0x0C;
+constexpr uint8_t kCtpResponseCodeInsufficientResources = 0x0D;
+constexpr uint8_t kCtpResponseCodeUnspecifiedError = 0x0E;
+
+constexpr uint8_t kCtpResponseNoReason = 0x00;
+constexpr uint8_t kCtpResponseCodecId = 0x01;
+constexpr uint8_t kCtpResponseCodecSpecificConfiguration = 0x02;
+constexpr uint8_t kCtpResponseSduInterval = 0x03;
+constexpr uint8_t kCtpResponseFraming = 0x04;
+constexpr uint8_t kCtpResponsePhy = 0x05;
+constexpr uint8_t kCtpResponseMaximumSduSize = 0x06;
+constexpr uint8_t kCtpResponseRetransmissionNumber = 0x07;
+constexpr uint8_t kCtpResponseMaxTransportLatency = 0x08;
+constexpr uint8_t kCtpResponsePresentationDelay = 0x09;
+constexpr uint8_t kCtpResponseInvalidAseCisMapping = 0x0A;
+
+constexpr uint8_t kLeAudioErrorCtpUnsupporterdOpcode = 0xFF;
+constexpr uint8_t kLeAudioErrorCtpTruncatedOperation = 0xFE;
+constexpr uint8_t kLeAudioErrorCtpCtpErr = 0xFD;
+
+/* ASE states */
+constexpr uint8_t kAseStateIdle = 0x00;
+constexpr uint8_t kAseStateCodecConfigured = 0x01;
+constexpr uint8_t kAseStateQosConfigured = 0x02;
+constexpr uint8_t kAseStateEnabling = 0x03;
+constexpr uint8_t kAseStateStreaming = 0x04;
+constexpr uint8_t kAseStateDisabling = 0x05;
+constexpr uint8_t kAseStateReleasing = 0x06;
+
+/* Control point opcodes */
+constexpr uint8_t kCtpOpcodeCodecConfiguration = 0x01;
+constexpr uint8_t kCtpOpcodeQosConfiguration = 0x02;
+constexpr uint8_t kCtpOpcodeEnable = 0x03;
+constexpr uint8_t kCtpOpcodeReceiverStartReady = 0x04;
+constexpr uint8_t kCtpOpcodeDisable = 0x05;
+constexpr uint8_t kCtpOpcodeReceiverStopReady = 0x06;
+constexpr uint8_t kCtpOpcodeUpdateMetadata = 0x07;
+constexpr uint8_t kCtpOpcodeRelease = 0x08;
+
+/* ASE status masks */
+static constexpr uint32_t kAseRspHeaderMaskCtrlStatusFailureOpcode = 0x00FF0000;
+static constexpr uint32_t kAseRspHeaderMaskCtrlStatusErrorCode = 0x0000FF00;
+static constexpr uint32_t kAseRspHeaderMaskCtrlStatusErrorReason = 0x000000FF;
+
+constexpr uint16_t kAseStatusCodecConfMinLen = 23;
+struct ase_codec_configured_state_params {
+  uint8_t framing;
+  uint8_t preferred_phy;
+  uint8_t preferred_retrans_nb;
+  uint16_t max_transport_latency;
+  uint32_t pres_delay_min;
+  uint32_t pres_delay_max;
+  uint32_t preferred_pres_delay_min;
+  uint32_t preferred_pres_delay_max;
+  types::LeAudioCodecId codec_id;
+  std::vector<uint8_t> codec_spec_conf;
+};
+
+constexpr uint16_t kAseStatusCodecQosConfMinLen = 15;
+struct ase_qos_configured_state_params {
+  uint8_t cig_id;
+  uint8_t cis_id;
+  uint32_t sdu_interval;
+  uint8_t framing;
+  uint8_t phy;
+  uint16_t max_sdu;
+  uint8_t retrans_nb;
+  uint16_t max_transport_latency;
+  uint32_t pres_delay;
+};
+
+constexpr uint16_t kAseStatusTransMinLen = 1;
+struct ase_transient_state_params {
+  std::vector<uint8_t> metadata;
+};
+
+constexpr uint16_t kCtpAseEntryMinLen = 3;
+struct ctp_ase_entry {
+  uint8_t ase_id;
+  uint8_t response_code;
+  uint8_t reason;
+};
+
+constexpr uint16_t kCtpNtfMinLen = 2;
+struct ctp_ntf {
+  uint8_t op;
+  std::vector<struct ctp_ase_entry> entries;
+};
+
+constexpr uint16_t kAseRspHdrMinLen = 2;
+struct ase_rsp_hdr {
+  uint8_t id;
+  uint8_t state;
+};
+
+constexpr uint8_t kCtpOpSize = 1;
+constexpr uint8_t kAseNumSize = 1;
+constexpr uint8_t kAseIdSize = 1;
+
+constexpr uint16_t kCtpCodecConfMinLen = 9;
+struct ctp_codec_conf {
+  uint8_t ase_id;
+  uint8_t target_latency;
+  uint8_t target_phy;
+  types::LeAudioCodecId codec_id;
+  types::LeAudioLc3Config codec_config;
+};
+
+constexpr uint16_t kCtpQosConfMinLen = 16;
+struct ctp_qos_conf {
+  uint8_t ase_id;
+  uint8_t cig;
+  uint8_t cis;
+  uint32_t sdu_interval;
+  uint8_t framing;
+  uint8_t phy;
+  uint16_t max_sdu;
+  uint8_t retrans_nb;
+  uint16_t max_transport_latency;
+  uint32_t pres_delay;
+};
+
+constexpr uint16_t kCtpEnableMinLen = 2;
+struct ctp_enable {
+  uint8_t ase_id;
+  std::vector<uint8_t> metadata;
+};
+
+constexpr uint16_t kCtpUpdateMetadataMinLen = 2;
+struct ctp_update_metadata {
+  uint8_t ase_id;
+  std::vector<uint8_t> metadata;
+};
+
+/* Device control and common functions */
+bool ParseAseStatusHeader(ase_rsp_hdr& rsp, uint16_t len, const uint8_t* value);
+bool ParseAseStatusCodecConfiguredStateParams(
+    struct ase_codec_configured_state_params& rsp, uint16_t len,
+    const uint8_t* value);
+bool ParseAseStatusQosConfiguredStateParams(
+    struct ase_qos_configured_state_params& rsp, uint16_t len,
+    const uint8_t* value);
+bool ParseAseStatusTransientStateParams(struct ase_transient_state_params& rsp,
+                                        uint16_t len, const uint8_t* value);
+bool ParseAseCtpNotification(struct ctp_ntf& ntf, uint16_t len,
+                             const uint8_t* value);
+bool PrepareAseCtpCodecConfig(const std::vector<struct ctp_codec_conf>& confs,
+                              std::vector<uint8_t>& value);
+bool PrepareAseCtpConfigQos(const std::vector<struct ctp_qos_conf>& confs,
+                            std::vector<uint8_t>& value);
+bool PrepareAseCtpEnable(const std::vector<struct ctp_enable>& confs,
+                         std::vector<uint8_t>& value);
+bool PrepareAseCtpAudioReceiverStartReady(const std::vector<uint8_t>& ids,
+                                          std::vector<uint8_t>& value);
+bool PrepareAseCtpDisable(const std::vector<uint8_t>& ids,
+                          std::vector<uint8_t>& value);
+bool PrepareAseCtpAudioReceiverStopReady(const std::vector<uint8_t>& ids,
+                                         std::vector<uint8_t>& value);
+bool PrepareAseCtpUpdateMetadata(
+    const std::vector<struct ctp_update_metadata>& confs,
+    std::vector<uint8_t>& value);
+bool PrepareAseCtpRelease(const std::vector<uint8_t>& ids,
+                          std::vector<uint8_t>& value);
+}  // namespace ascs
+
+namespace pacs {
+
+constexpr uint16_t kAcsPacRecordMinLen = 7;
+constexpr uint8_t kAcsPacMetadataLenLen = 1;
+constexpr uint16_t kAcsPacDiscoverRspMinLen = 1;
+
+constexpr uint16_t kAudioLocationsRspMinLen = 4;
+
+constexpr uint16_t kAseAudioAvailRspMinLen = 4;
+struct acs_available_audio_contexts {
+  std::bitset<16> snk_avail_cont;
+  std::bitset<16> src_avail_cont;
+};
+
+constexpr uint16_t kAseAudioSuppContRspMinLen = 4;
+struct acs_supported_audio_contexts {
+  std::bitset<16> snk_supp_cont;
+  std::bitset<16> src_supp_cont;
+};
+
+bool ParsePac(std::vector<struct types::acs_ac_record>& pac_recs, uint16_t len,
+              const uint8_t* value);
+bool ParseAudioLocations(types::AudioLocations& audio_locations, uint16_t len,
+                         const uint8_t* value);
+bool ParseAvailableAudioContexts(struct acs_available_audio_contexts& rsp,
+                                 uint16_t len, const uint8_t* value);
+bool ParseSupportedAudioContexts(struct acs_supported_audio_contexts& rsp,
+                                 uint16_t len, const uint8_t* value);
+}  // namespace pacs
+}  // namespace client_parser
+}  // namespace le_audio
diff --git a/system/bta/le_audio/client_parser_test.cc b/system/bta/le_audio/client_parser_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2927792bc5c731289ae823d4874763d0a5fe32eb
--- /dev/null
+++ b/system/bta/le_audio/client_parser_test.cc
@@ -0,0 +1,1647 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "client_parser.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "le_audio_types.h"
+
+namespace le_audio {
+namespace client_parser {
+namespace pacs {
+
+TEST(LeAudioClientParserTest, testParsePacInvalidLength) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+
+  const uint8_t invalid_num_records[] = {0x01};
+  ASSERT_FALSE(
+      ParsePac(pac_recs, sizeof(invalid_num_records), invalid_num_records));
+
+  const uint8_t no_caps_len[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01,
+      0x02,
+      0x03,
+      0x04,
+      0x05,
+  };
+  ASSERT_FALSE(ParsePac(pac_recs, sizeof(no_caps_len), no_caps_len));
+
+  const uint8_t no_metalen[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01,
+      0x02,
+      0x03,
+      0x04,
+      0x05,
+      // Codec Spec. Caps. Len
+      0x00,
+  };
+  ASSERT_FALSE(ParsePac(pac_recs, sizeof(no_metalen), no_metalen));
+}
+
+TEST(LeAudioClientParserTest, testParsePacEmpty) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+  const uint8_t value[] = {0x00};
+
+  ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value));
+}
+
+TEST(LeAudioClientParserTest, testParsePacEmptyCapsEmptyMeta) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+
+  const uint8_t value[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01,
+      0x03,
+      0x02,
+      0x05,
+      0x04,
+      // Codec Spec. Caps. Len
+      0x00,
+      // Metadata Length
+      0x00,
+  };
+  ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value));
+
+  ASSERT_EQ(pac_recs.size(), 1u);
+  ASSERT_EQ(pac_recs[0].codec_id.coding_format, 0x01u);
+  ASSERT_EQ(pac_recs[0].codec_id.vendor_company_id, 0x0203u);
+  ASSERT_EQ(pac_recs[0].codec_id.vendor_codec_id, 0x0405u);
+}
+
+TEST(LeAudioClientParserTest, testParsePacInvalidCapsLen) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+
+  const uint8_t bad_capslem[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01,
+      0x03,
+      0x02,
+      0x05,
+      0x04,
+      // Codec Spec. Caps. Len
+      0x05,
+      // Codec Spec. Caps.
+      0x02,  // [0].length,
+      0x02,  // [0].type,
+      0x03,  // [0].value[0]
+      0x03,  // [1].length
+      0x03,  // [1].type
+      0x04,  // [1].value[0]
+      0x05,  // [1].value[1]
+      // Metadata Length
+      0x00,
+  };
+  ASSERT_FALSE(ParsePac(pac_recs, sizeof(bad_capslem), bad_capslem));
+
+  std::vector<struct types::acs_ac_record> pac_recs2;
+
+  const uint8_t bad_capslen2[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01,
+      0x03,
+      0x02,
+      0x05,
+      0x04,
+      // Codec Spec. Caps. Len
+      0x20,
+      // Codec Spec. Caps.
+      0x02,  // [0].length
+      0x02,  // [0].type
+      0x03,  // [0].value[0]
+      0x03,  // [1].length
+      0x03,  // [1].type
+      0x04,  // [1].value[0]
+      0x05,  // [1].value[1]
+      // Metadata Length
+      0x00,
+  };
+  ASSERT_FALSE(ParsePac(pac_recs2, sizeof(bad_capslen2), bad_capslen2));
+}
+
+TEST(LeAudioClientParserTest, testParsePacInvalidCapsLtvLen) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+
+  const uint8_t bad_ltv_len[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01,
+      0x03,
+      0x02,
+      0x05,
+      0x04,
+      // Codec Spec. Caps. Len
+      0x07,
+      // Codec Spec. Caps.
+      0x02,  // [0].length
+      0x02,  // [0].type
+      0x03,  // [0].value[0]
+      0x06,  // [1].bad_length
+      0x03,  // [1].type
+      0x04,  // [1].value[0]
+      0x05,  // [1].value[1]
+      // Metadata Length
+      0x00,
+  };
+  ASSERT_FALSE(ParsePac(pac_recs, sizeof(bad_ltv_len), bad_ltv_len));
+
+  const uint8_t bad_ltv_len2[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01,
+      0x03,
+      0x02,
+      0x05,
+      0x04,
+      // Codec Spec. Caps. Len
+      0x07,
+      // Codec Spec. Caps.
+      0x02,  // [0].length
+      0x02,  // [0].type
+      0x03,  // [0].value[0]
+      0x04,  // [1].bad_length
+      0x03,  // [1].type
+      0x04,  // [1].value[0]
+      0x05,  // [1].value[1]
+      // Metadata Length
+      0x00,
+  };
+  ASSERT_FALSE(ParsePac(pac_recs, sizeof(bad_ltv_len2), bad_ltv_len2));
+}
+
+TEST(LeAudioClientParserTest, testParsePacNullLtv) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+
+  const uint8_t value[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01,
+      0x03,
+      0x02,
+      0x05,
+      0x04,
+      // Codec Spec. Caps. Len
+      0x0A,
+      // Codec Spec. Caps.
+      0x02,  // [0].length
+      0x02,  // [0].type
+      0x03,  // [0].value[0]
+      0x03,  // [1].length
+      0x03,  // [1].type
+      0x04,  // [1].value[0]
+      0x05,  // [1].value[1]
+      0x01,  // [2].length <-- a capability without a value
+      0x04,  // [2].type
+      0x00,  // [3]length <-- this seems possible although useless
+      // Metadata Length
+      0x00,
+  };
+  ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value));
+
+  ASSERT_EQ(pac_recs.size(), 1u);
+  ASSERT_EQ(pac_recs[0].codec_id.coding_format, 0x01u);
+  ASSERT_EQ(pac_recs[0].codec_id.vendor_company_id, 0x0203u);
+  ASSERT_EQ(pac_recs[0].codec_id.vendor_codec_id, 0x0405u);
+
+  auto codec_spec_caps = pac_recs[0].codec_spec_caps.Values();
+  ASSERT_EQ(codec_spec_caps.size(), 3u);
+  ASSERT_EQ(codec_spec_caps.count(0x02u), 1u);
+  ASSERT_EQ(codec_spec_caps[0x02u].size(), 1u);
+  ASSERT_EQ(codec_spec_caps[0x02u][0], 0x03u);
+  ASSERT_EQ(codec_spec_caps.count(0x03u), 1u);
+  ASSERT_EQ(codec_spec_caps[0x03u].size(), 2u);
+  ASSERT_EQ(codec_spec_caps[0x03u][0], 0x04u);
+  ASSERT_EQ(codec_spec_caps[0x03u][1], 0x05u);
+  ASSERT_EQ(codec_spec_caps.count(0x04u), 1u);
+  ASSERT_EQ(codec_spec_caps[0x04u].size(), 0u);
+}
+
+TEST(LeAudioClientParserTest, testParsePacEmptyMeta) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+
+  const uint8_t value[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01,
+      0x03,
+      0x02,
+      0x05,
+      0x04,
+      // Codec Spec. Caps. Len
+      0x07,
+      // Codec Spec. Caps.
+      0x02,  // [0].length
+      0x02,  // [0].type
+      0x03,  // [0].value[0]
+      0x03,  // [1].length
+      0x03,  // [1].type
+      0x04,  // [1].value[0]
+      0x05,  // [1].value[1]
+      // Metadata Length
+      0x00,
+  };
+  ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value));
+
+  ASSERT_EQ(pac_recs.size(), 1u);
+  ASSERT_EQ(pac_recs[0].codec_id.coding_format, 0x01u);
+  ASSERT_EQ(pac_recs[0].codec_id.vendor_company_id, 0x0203u);
+  ASSERT_EQ(pac_recs[0].codec_id.vendor_codec_id, 0x0405u);
+
+  auto codec_spec_caps = pac_recs[0].codec_spec_caps.Values();
+  ASSERT_EQ(codec_spec_caps.size(), 2u);
+  ASSERT_EQ(codec_spec_caps.count(0x02u), 1u);
+  ASSERT_EQ(codec_spec_caps[0x02u].size(), 1u);
+  ASSERT_EQ(codec_spec_caps[0x02u][0], 0x03u);
+  ASSERT_EQ(codec_spec_caps.count(0x03u), 1u);
+  ASSERT_EQ(codec_spec_caps[0x03u].size(), 2u);
+  ASSERT_EQ(codec_spec_caps[0x03u][0], 0x04u);
+  ASSERT_EQ(codec_spec_caps[0x03u][1], 0x05u);
+}
+
+TEST(LeAudioClientParserTest, testParsePacInvalidMetaLength) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+
+  const uint8_t value[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01, 0x03, 0x02, 0x05, 0x04,
+      // Codec Spec. Caps. Len
+      0x07,
+      // Codec Spec. Caps.
+      0x02,  // [0].length
+      0x02,  // [0].type
+      0x03,  // [0].value[0]
+      0x03,  // [1].length
+      0x03,  // [1].type
+      0x04,  // [1].value[0]
+      0x05,  // [1].value[1]
+             // Metadata Length
+      0x05,
+      // Metadata
+      0x03,  // [0].length
+      0x02,  // [0].type
+      0x01,  // [0].value[0]
+      0x00,  // [0].value[1]
+  };
+  ASSERT_FALSE(ParsePac(pac_recs, sizeof(value), value));
+}
+
+TEST(LeAudioClientParserTest, testParsePacValidMeta) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+
+  const uint8_t value[] = {
+      // Num records
+      0x01,
+      // Codec_ID
+      0x01, 0x03, 0x02, 0x05, 0x04,
+      // Codec Spec. Caps. Len
+      0x07,
+      // Codec Spec. Caps.
+      0x02,  // [0].length
+      0x02,  // [0].type
+      0x03,  // [0].value[0]
+      0x03,  // [1].length
+      0x03,  // [1].type
+      0x04,  // [1].value[0]
+      0x05,  // [1].value[1]
+             // Metadata Length
+      0x04,
+      // Metadata
+      0x03,  // [0].length
+      0x02,  // [0].type
+      0x01,  // [0].value[0]
+      0x00,  // [0].value[1]
+  };
+  ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value));
+
+  ASSERT_EQ(pac_recs.size(), 1u);
+  ASSERT_EQ(pac_recs[0].codec_id.coding_format, 0x01u);
+  ASSERT_EQ(pac_recs[0].codec_id.vendor_company_id, 0x0203u);
+  ASSERT_EQ(pac_recs[0].codec_id.vendor_codec_id, 0x0405u);
+
+  auto codec_spec_caps = pac_recs[0].codec_spec_caps.Values();
+  ASSERT_EQ(codec_spec_caps.size(), 2u);
+  ASSERT_EQ(codec_spec_caps.count(0x02u), 1u);
+  ASSERT_EQ(codec_spec_caps[0x02u].size(), 1u);
+  ASSERT_EQ(codec_spec_caps[0x02u][0], 0x03u);
+  ASSERT_EQ(codec_spec_caps.count(0x03u), 1u);
+  ASSERT_EQ(codec_spec_caps[0x03u].size(), 2u);
+  ASSERT_EQ(codec_spec_caps[0x03u][0], 0x04u);
+  ASSERT_EQ(codec_spec_caps[0x03u][1], 0x05u);
+
+  ASSERT_EQ(pac_recs[0].metadata.size(), 4u);
+  ASSERT_EQ(pac_recs[0].metadata[0], 0x03u);
+  ASSERT_EQ(pac_recs[0].metadata[1], 0x02u);
+  ASSERT_EQ(pac_recs[0].metadata[2], 0x01u);
+  ASSERT_EQ(pac_recs[0].metadata[3], 0x00u);
+}
+
+TEST(LeAudioClientParserTest, testParsePacInvalidNumRecords) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+
+  const uint8_t value[] = {
+      // Num records
+      0x02,
+      // Codec_ID
+      0x01, 0x03, 0x02, 0x05, 0x04,
+      // Codec Spec. Caps. Len
+      0x07,
+      // Codec Spec. Caps.
+      0x02,  // [0].length
+      0x02,  // [0].type
+      0x03,  // [0].value[0]
+      0x03,  // [1].length
+      0x03,  // [1].type
+      0x04,  // [1].value[0]
+      0x05,  // [1].value[1]
+             // Metadata Length
+      0x04,
+      // Metadata
+      0x03,  // [0].length
+      0x02,  // [0].type
+      0x01,  // [0].value[0]
+      0x00,  // [0].value[1]
+  };
+  ASSERT_FALSE(ParsePac(pac_recs, sizeof(value), value));
+}
+
+TEST(LeAudioClientParserTest, testParsePacMultipleRecords) {
+  std::vector<struct types::acs_ac_record> pac_recs;
+
+  const uint8_t value[] = {
+      // Num records
+      0x03,
+      // Codec_ID
+      0x01, 0x03, 0x02, 0x05, 0x04,
+      // Codec Spec. Caps. Len
+      0x00,
+      // Metadata Length
+      0x00,
+      // Codec_ID
+      0x06, 0x08, 0x07, 0x0A, 0x09,
+      // Codec Spec. Caps. Len
+      0x03,
+      // Codec Spec. Caps.
+      0x02,  // [0].length
+      0x02,  // [0].type
+      0x03,  // [0].value[0]
+             // Metadata Length
+      0x04,
+      // Metadata
+      0x03,  // [0].length
+      0x02,  // [0].type
+      0x01,  // [0].value[0]
+      0x00,  // [0].value[1],
+             // Codec_ID
+      0x11, 0x13, 0x12, 0x15, 0x14,
+      // Codec Spec. Caps. Len
+      0x07,
+      // Codec Spec. Caps.
+      0x02,  // [0].length
+      0x12,  // [0].type
+      0x13,  // [0].value[0]
+      0x03,  // [1].length
+      0x13,  // [1].type
+      0x14,  // [1].value[0]
+      0x15,  // [1].value[1]
+             // Metadata Length
+      0x04,
+      // Metadata
+      0x03,  // [0].length
+      0x12,  // [0].type
+      0x11,  // [0].value[0]
+      0x10,  // [0].value[1]
+  };
+  ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value));
+  ASSERT_EQ(pac_recs.size(), 3u);
+
+  // Verify 1st record
+  auto& record0 = pac_recs[0];
+
+  ASSERT_EQ(record0.codec_id.coding_format, 0x01u);
+  ASSERT_EQ(record0.codec_id.vendor_company_id, 0x0203u);
+  ASSERT_EQ(record0.codec_id.vendor_codec_id, 0x0405u);
+  ASSERT_EQ(record0.codec_spec_caps.Size(), 0u);
+  ASSERT_EQ(record0.metadata.size(), 0u);
+
+  // Verify 2nd record
+  auto& record1 = pac_recs[1];
+
+  ASSERT_EQ(record1.codec_id.coding_format, 0x06u);
+  ASSERT_EQ(record1.codec_id.vendor_company_id, 0x0708u);
+  ASSERT_EQ(record1.codec_id.vendor_codec_id, 0x090Au);
+
+  auto codec_spec_caps1 = record1.codec_spec_caps.Values();
+  ASSERT_EQ(codec_spec_caps1.size(), 1u);
+  ASSERT_EQ(codec_spec_caps1.count(0x02u), 1u);
+  ASSERT_EQ(codec_spec_caps1[0x02u].size(), 1u);
+  ASSERT_EQ(codec_spec_caps1[0x02u][0], 0x03u);
+
+  ASSERT_EQ(record1.metadata.size(), 4u);
+  ASSERT_EQ(record1.metadata[0], 0x03u);
+  ASSERT_EQ(record1.metadata[1], 0x02u);
+  ASSERT_EQ(record1.metadata[2], 0x01u);
+  ASSERT_EQ(record1.metadata[3], 0x00u);
+
+  // Verify 3rd record
+  auto& record2 = pac_recs[2];
+
+  ASSERT_EQ(record2.codec_id.coding_format, 0x11u);
+  ASSERT_EQ(record2.codec_id.vendor_company_id, 0x1213u);
+  ASSERT_EQ(record2.codec_id.vendor_codec_id, 0x1415u);
+
+  auto codec_spec_caps2 = record2.codec_spec_caps.Values();
+  ASSERT_EQ(codec_spec_caps2.size(), 2u);
+  ASSERT_EQ(codec_spec_caps2.count(0x12u), 1u);
+  ASSERT_EQ(codec_spec_caps2[0x12u].size(), 1u);
+  ASSERT_EQ(codec_spec_caps2[0x12u][0], 0x13u);
+  ASSERT_EQ(codec_spec_caps2.count(0x13u), 1u);
+  ASSERT_EQ(codec_spec_caps2[0x13u].size(), 2u);
+  ASSERT_EQ(codec_spec_caps2[0x13u][0], 0x14u);
+  ASSERT_EQ(codec_spec_caps2[0x13u][1], 0x15u);
+
+  ASSERT_EQ(record2.metadata.size(), 4u);
+  ASSERT_EQ(record2.metadata[0], 0x03u);
+  ASSERT_EQ(record2.metadata[1], 0x12u);
+  ASSERT_EQ(record2.metadata[2], 0x11u);
+  ASSERT_EQ(record2.metadata[3], 0x10u);
+}
+
+TEST(LeAudioClientParserTest, testParseAudioLocationsInvalidLength) {
+  types::AudioLocations locations =
+      codec_spec_conf::kLeAudioLocationMonoUnspecified;
+  const uint8_t value1[] = {
+      0x01,
+      0x02,
+      0x03,
+  };
+  ParseAudioLocations(locations, sizeof(value1), value1);
+  ASSERT_EQ(locations, 0u);
+
+  const uint8_t value2[] = {0x01, 0x02, 0x03, 0x04, 0x05};
+  ParseAudioLocations(locations, sizeof(value2), value2);
+  ASSERT_EQ(locations, 0u);
+}
+
+TEST(LeAudioClientParserTest, testParseAudioLocations) {
+  types::AudioLocations locations =
+      codec_spec_conf::kLeAudioLocationMonoUnspecified;
+  const uint8_t value1[] = {0x01, 0x02, 0x03, 0x04};
+  ParseAudioLocations(locations, sizeof(value1), value1);
+  ASSERT_EQ(locations, 0x04030201u);
+}
+
+TEST(LeAudioClientParserTest, testParseAvailableAudioContextsInvalidLength) {
+  acs_available_audio_contexts avail_contexts;
+  const uint8_t value1[] = {
+      // Sink available contexts
+      0x01, 0x02,
+      // Missing Source available contexts
+  };
+
+  ParseAvailableAudioContexts(avail_contexts, sizeof(value1), value1);
+  ASSERT_EQ(avail_contexts.snk_avail_cont, 0u);
+  ASSERT_EQ(avail_contexts.src_avail_cont, 0u);
+}
+
+TEST(LeAudioClientParserTest, testParseAvailableAudioContexts) {
+  acs_available_audio_contexts avail_contexts;
+  const uint8_t value1[] = {
+      // Sink available contexts
+      0x01,
+      0x02,
+      // Source available contexts
+      0x03,
+      0x04,
+  };
+
+  ParseAvailableAudioContexts(avail_contexts, sizeof(value1), value1);
+  ASSERT_EQ(avail_contexts.snk_avail_cont, 0x0201u);
+  ASSERT_EQ(avail_contexts.src_avail_cont, 0x0403u);
+}
+
+TEST(LeAudioClientParserTest, testParseSupportedAudioContextsInvalidLength) {
+  acs_supported_audio_contexts supp_contexts;
+  const uint8_t value1[] = {
+      // Sink supported contexts
+      0x01, 0x02,
+      // Missing Source supported contexts
+  };
+
+  ParseSupportedAudioContexts(supp_contexts, sizeof(value1), value1);
+  ASSERT_EQ(supp_contexts.snk_supp_cont, 0u);
+  ASSERT_EQ(supp_contexts.src_supp_cont, 0u);
+}
+
+TEST(LeAudioClientParserTest, testParseSupportedAudioContexts) {
+  acs_supported_audio_contexts supp_contexts;
+  const uint8_t value1[] = {
+      // Sink supported contexts
+      0x01,
+      0x02,
+      // Source supported contexts
+      0x03,
+      0x04,
+  };
+
+  ParseSupportedAudioContexts(supp_contexts, sizeof(value1), value1);
+  ASSERT_EQ(supp_contexts.snk_supp_cont, 0x0201u);
+  ASSERT_EQ(supp_contexts.src_supp_cont, 0x0403u);
+}
+
+}  // namespace pacs
+
+namespace ascs {
+
+TEST(LeAudioClientParserTest, testParseAseStatusHeaderInvalidLength) {
+  ase_rsp_hdr arh;
+  const uint8_t value1[] = {
+      // Ase ID
+      0x01,
+      // ASE State is missing here
+  };
+  ASSERT_FALSE(ParseAseStatusHeader(arh, sizeof(value1), value1));
+}
+
+TEST(LeAudioClientParserTest, testParseAseStatusHeader) {
+  ase_rsp_hdr arh;
+  const uint8_t value1[] = {
+      // Ase ID
+      0x01,
+      // ASE State
+      0x00,  // 'Idle' state
+             // No additional ASE Params for the 'Idle' state
+  };
+  ASSERT_TRUE(ParseAseStatusHeader(arh, sizeof(value1), value1));
+  ASSERT_EQ(arh.id, 0x01u);
+  ASSERT_EQ(arh.state, 0x00u);
+
+  const uint8_t value2[] = {
+      // Ase ID
+      0x02,
+      // ASE State
+      0x04,  // 'Streaming' state
+      // Additional ASE Params for the 'Streaming' state
+      // Metadata Len
+      0x03,
+      // Metadata
+      0x03,  // [0].length
+      0x02,  // [0].type
+      0x01,  // [0].value[0]
+      0x00,  // [0].value[1]
+  };
+  ASSERT_TRUE(ParseAseStatusHeader(arh, sizeof(value2), value2));
+  ASSERT_EQ(arh.id, 0x02u);
+  ASSERT_EQ(arh.state, 0x04u);
+  // Currently additional state parameters are not handled
+}
+
+TEST(LeAudioClientParserTest,
+     testParseAseStatusCodecConfiguredStateParamsInvalidLength) {
+  ase_codec_configured_state_params codec_configured_state_params;
+  const uint8_t value1[] = {
+      // Ase ID
+      0x02,
+      // ASE State
+      0x01,  // 'Codec Configured' state
+      // Framing
+      0x01,  // Unframed
+      // Peferred PHY
+      0x02,  // 2M PHY
+      // Preferred retransimssion Num.
+      0x04,
+      // Max transport Latency
+      0x05, 0x00,
+      // Pressentation delay min.
+      0x00, 0x01, 0x02, 0x03,
+      // Pressentation delay max.
+      0x00, 0x01, 0x02, 0x03,
+      // Preferred presentation delay min.
+      0x01, 0x02, 0x03,
+      // Preferred presentation delay max.
+      0x01, 0x02, 0x03,
+      // Codec ID
+      0x01, 0x02, 0x03, 0x04, 0x05,
+      // Missing Codec spec. conf. length
+  };
+
+  ASSERT_FALSE(ParseAseStatusCodecConfiguredStateParams(
+      codec_configured_state_params, sizeof(value1) - 2, value1 + 2));
+}
+
+TEST(LeAudioClientParserTest, testParseAseStatusCodecConfiguredStateParams) {
+  ase_codec_configured_state_params codec_configured_state_params;
+  const uint8_t value1[] = {
+      // Ase ID
+      0x01,
+      // ASE State
+      0x01,  // 'Codec Configured' state
+      // Framing
+      0x01,  // Unframed
+      // Peferred PHY
+      0x02,  // 2M PHY
+      // Preferred retransimssion Num.
+      0x04,
+      // Max transport Latency
+      0x05,
+      0x00,
+      // Pressentation delay min.
+      0x00,
+      0x01,
+      0x02,
+      // Pressentation delay max.
+      0x10,
+      0x11,
+      0x12,
+      // Preferred presentation delay min.
+      0x01,
+      0x02,
+      0x03,
+      // Preferred presentation delay max.
+      0x09,
+      0x10,
+      0x11,
+      // Codec ID
+      0x01,
+      0x02,
+      0x03,
+      0x04,
+      0x05,
+      // Codec spec. conf. length
+      0x00,
+  };
+
+  // State additional parameters are right after the ASE ID and state bytes
+  ASSERT_TRUE(ParseAseStatusCodecConfiguredStateParams(
+      codec_configured_state_params, sizeof(value1) - 2, value1 + 2));
+  ASSERT_EQ(codec_configured_state_params.framing, 0x01u);
+  ASSERT_EQ(codec_configured_state_params.preferred_phy, 0x02u);
+  ASSERT_EQ(codec_configured_state_params.preferred_retrans_nb, 0x04u);
+  ASSERT_EQ(codec_configured_state_params.max_transport_latency, 0x0005u);
+  ASSERT_EQ(codec_configured_state_params.pres_delay_min, 0x020100u);
+  ASSERT_EQ(codec_configured_state_params.pres_delay_max, 0x121110u);
+  ASSERT_EQ(codec_configured_state_params.preferred_pres_delay_min, 0x030201u);
+  ASSERT_EQ(codec_configured_state_params.preferred_pres_delay_max, 0x111009u);
+  ASSERT_EQ(codec_configured_state_params.codec_id.coding_format, 0x01u);
+  ASSERT_EQ(codec_configured_state_params.codec_id.vendor_company_id, 0x0302u);
+  ASSERT_EQ(codec_configured_state_params.codec_id.vendor_codec_id, 0x0504u);
+  ASSERT_EQ(codec_configured_state_params.codec_spec_conf.size(), 0u);
+
+  const uint8_t value2[] = {
+      // Ase ID
+      0x02,
+      // ASE State
+      0x01,  // 'Codec Configured' state
+      // Framing
+      0x01,  // Unframed
+      // Peferred PHY
+      0x02,  // 2M PHY
+      // Preferred retransimssion Num.
+      0x04,
+      // Max transport Latency
+      0x05,
+      0x00,
+      // Pressentation delay min.
+      0x00,
+      0x01,
+      0x02,
+      // Pressentation delay max.
+      0x10,
+      0x11,
+      0x12,
+      // Preferred presentation delay min.
+      0x01,
+      0x02,
+      0x03,
+      // Preferred presentation delay max.
+      0x09,
+      0x10,
+      0x11,
+      // Codec ID
+      0x01,
+      0x02,
+      0x03,
+      0x04,
+      0x05,
+      // Codec spec. conf. length
+      0x05,
+      // Codec spec. conf.
+      0x0A,
+      0x0B,
+      0x0C,
+      0x0D,
+      0x0E,
+  };
+
+  // State additional parameters are right after the ASE ID and state bytes
+  ASSERT_TRUE(ParseAseStatusCodecConfiguredStateParams(
+      codec_configured_state_params, sizeof(value2) - 2, value2 + 2));
+  ASSERT_EQ(codec_configured_state_params.framing, 0x01u);
+  ASSERT_EQ(codec_configured_state_params.preferred_phy, 0x02u);
+  ASSERT_EQ(codec_configured_state_params.preferred_retrans_nb, 0x04u);
+  ASSERT_EQ(codec_configured_state_params.max_transport_latency, 0x0005u);
+  ASSERT_EQ(codec_configured_state_params.pres_delay_min, 0x020100u);
+  ASSERT_EQ(codec_configured_state_params.pres_delay_max, 0x121110u);
+  ASSERT_EQ(codec_configured_state_params.preferred_pres_delay_min, 0x030201u);
+  ASSERT_EQ(codec_configured_state_params.preferred_pres_delay_max, 0x111009u);
+  ASSERT_EQ(codec_configured_state_params.codec_id.coding_format, 0x01u);
+  ASSERT_EQ(codec_configured_state_params.codec_id.vendor_company_id, 0x0302u);
+  ASSERT_EQ(codec_configured_state_params.codec_id.vendor_codec_id, 0x0504u);
+  ASSERT_EQ(codec_configured_state_params.codec_spec_conf.size(), 5u);
+  ASSERT_EQ(codec_configured_state_params.codec_spec_conf[0], 0x0Au);
+  ASSERT_EQ(codec_configured_state_params.codec_spec_conf[1], 0x0Bu);
+  ASSERT_EQ(codec_configured_state_params.codec_spec_conf[2], 0x0Cu);
+  ASSERT_EQ(codec_configured_state_params.codec_spec_conf[3], 0x0Du);
+  ASSERT_EQ(codec_configured_state_params.codec_spec_conf[4], 0x0Eu);
+}
+
+TEST(LeAudioClientParserTest,
+     testParseAseStatusQosConfiguredStateParamsInvalidLength) {
+  struct ase_qos_configured_state_params rsp {
+    .cig_id = 0, .cis_id = 0
+  };
+  const uint8_t value1[] = {
+      // Ase ID
+      0x01,
+      // ASE State
+      0x02,  // 'QoS Configured' state
+      0x03,  // CIG_ID
+      0x04,  // CIS_ID
+  };
+
+  ParseAseStatusQosConfiguredStateParams(rsp, sizeof(value1) - 2, value1 + 2);
+  ASSERT_EQ(rsp.cig_id, 0);
+  ASSERT_EQ(rsp.cis_id, 0);
+
+  const uint8_t value2[] = {
+      // Ase ID
+      0x01,
+      // ASE State
+      0x02,  // 'QoS Configured' state
+             // CIG_ID
+      0x03,
+      // CIS_ID
+      0x04,
+      // SDU Interval
+      0x05, 0x06, 0x07,
+      // Framing
+      0x01,
+      // PHY
+      0x02,
+      // Max SDU
+      0x08, 0x09,
+      // Retransmission Num.
+      0x0A,
+      // Max Transport Latency
+      0x0B, 0x0C,
+      // Presentation Delay
+      0x0D, 0x0E,
+      // Missing Byte
+  };
+
+  ParseAseStatusQosConfiguredStateParams(rsp, sizeof(value2) - 2, value2 + 2);
+  ASSERT_EQ(rsp.cig_id, 0);
+  ASSERT_EQ(rsp.cis_id, 0);
+}
+
+TEST(LeAudioClientParserTest, testParseAseStatusQosConfiguredStateParams) {
+  struct ase_qos_configured_state_params rsp;
+  const uint8_t value[] = {
+      // Ase ID
+      0x01,
+      // ASE State - 'QoS Configured'
+      0x02,
+      // CIG_ID
+      0x03,
+      // CIS_ID
+      0x04,
+      // SDU Interval
+      0x05,
+      0x06,
+      0x07,
+      // Framing
+      0x01,
+      // PHY
+      0x02,
+      // Max SDU
+      0x18,
+      0x19,
+      // Retransmission Num.
+      0x1A,
+      // Max Transport Latency
+      0x1B,
+      0x1C,
+      // Presentation Delay
+      0x1D,
+      0x1E,
+      0x1F,
+  };
+
+  ParseAseStatusQosConfiguredStateParams(rsp, sizeof(value) - 2, value + 2);
+  ASSERT_EQ(rsp.cig_id, 0x03u);
+  ASSERT_EQ(rsp.cis_id, 0x04u);
+  ASSERT_EQ(rsp.sdu_interval, 0x070605u);
+  ASSERT_EQ(rsp.framing, 0x01u);
+  ASSERT_EQ(rsp.phy, 0x02u);
+  ASSERT_EQ(rsp.max_sdu, 0x1918u);
+  ASSERT_EQ(rsp.retrans_nb, 0x1Au);
+  ASSERT_EQ(rsp.max_transport_latency, 0x1C1Bu);
+  ASSERT_EQ(rsp.pres_delay, 0x1F1E1Du);
+}
+
+TEST(LeAudioClientParserTest,
+     testParseAseStatusTransientStateParamsInvalidLength) {
+  ase_transient_state_params params;
+  const uint8_t value1[] = {
+      // Ase ID
+      0x01,
+      // ASE State
+      0x03,  // 'Enabling' state
+             // missing Metadata length
+             // missing Metadata
+  };
+  ParseAseStatusTransientStateParams(params, sizeof(value1) - 2, value1 + 2);
+}
+
+TEST(LeAudioClientParserTest, testParseAseStatusTransientStateParams) {
+  ase_transient_state_params params;
+  const uint8_t value1[] = {
+      // Ase ID
+      0x01,
+      // ASE State
+      0x03,  // 'Enabling' state
+      // Metadata length
+      0x00,
+  };
+  ParseAseStatusTransientStateParams(params, sizeof(value1) - 2, value1 + 2);
+  ASSERT_EQ(params.metadata.size(), 0u);
+
+  const uint8_t value2[] = {
+      // Ase ID
+      0x01,
+      // ASE State
+      0x03,  // 'Enabling' state
+      // Metadata length
+      0x03,
+      // Metadata
+      0x02,  // [0].length
+      0x01,  // [0].type
+      0x00,  // [0].value[0]
+  };
+  ParseAseStatusTransientStateParams(params, sizeof(value2) - 2, value2 + 2);
+
+  ASSERT_EQ(params.metadata.size(), 3u);
+  ASSERT_EQ(params.metadata[0], 0x02u);
+  ASSERT_EQ(params.metadata[1], 0x01u);
+  ASSERT_EQ(params.metadata[2], 0x00u);
+}
+
+TEST(LeAudioClientParserTest, testParseAseCtpNotificationInvalidLength) {
+  ctp_ntf ntf;
+  const uint8_t value1[] = {
+      // Opcode
+      0x01,
+      // Number of ASEs
+      0x02,
+      // ASE ID
+      0x01,
+      // Response Code
+      0x01,
+      // Reason
+      0x01,
+      // ASE ID
+      0x02,
+      // Response Code
+      0x02,
+      // Missing Reason
+  };
+  ParseAseCtpNotification(ntf, sizeof(value1), value1);
+
+  // In case of invalid payload at least we get the opcode
+  ASSERT_EQ(ntf.op, 0x01u);
+  ASSERT_EQ(ntf.entries.size(), 0u);
+
+  const uint8_t value2[] = {
+      // Opcode
+      0x01,
+      // Missing Number of ASEs
+      // Missing ASE ID
+      // Missing Response Code
+      // Missing Reason
+      // Missing ASE ID
+      // Missing Response Code
+      // Missing Reason
+  };
+  ntf.entries.clear();
+  ParseAseCtpNotification(ntf, sizeof(value2), value2);
+
+  // In case of invalid payload at least we get the opcode
+  ASSERT_EQ(ntf.op, 0x01u);
+  ASSERT_EQ(ntf.entries.size(), 0u);
+
+  const uint8_t value3[] = {
+      // Opcode
+      0x01,
+      // Number of ASEs
+      0x03,
+      // ASE ID
+      0x01,
+      // Response Code
+      0x01,
+      // Reason
+      0x01,
+      // ASE ID
+      0x02,
+      // Response Code
+      0x02,
+      // Reason
+      0x03,
+      // Missing the entire ASE entry
+  };
+
+  ntf.entries.clear();
+  ParseAseCtpNotification(ntf, sizeof(value3), value3);
+  // In case of invalid payload at least we get the opcode
+  ASSERT_EQ(ntf.op, 0x01u);
+  ASSERT_EQ(ntf.entries.size(), 0u);
+}
+
+TEST(LeAudioClientParserTest, testParseAseCtpNotification) {
+  ctp_ntf ntf;
+  const uint8_t value1[] = {
+      // Opcode
+      0x01,
+      // Number of ASEs
+      0x02,
+      // ASE ID
+      0x01,
+      // Response Code
+      0x01,
+      // Reason
+      0x01,
+      // ASE ID
+      0x03,
+      // Response Code
+      0x02,
+      // Reason
+      0x03,
+  };
+  ParseAseCtpNotification(ntf, sizeof(value1), value1);
+
+  ASSERT_EQ(ntf.op, 0x01u);
+  ASSERT_EQ(ntf.entries.size(), 2u);
+  ASSERT_EQ(ntf.entries[0].ase_id, 0x01u);
+  ASSERT_EQ(ntf.entries[0].response_code, 0x01u);
+  ASSERT_EQ(ntf.entries[0].reason, 0x01);
+  ASSERT_EQ(ntf.entries[1].ase_id, 0x03u);
+  ASSERT_EQ(ntf.entries[1].response_code, 0x02u);
+  ASSERT_EQ(ntf.entries[1].reason, 0x03);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpCodecConfigEmpty) {
+  std::vector<struct ctp_codec_conf> confs;
+  std::vector<uint8_t> value;
+
+  PrepareAseCtpCodecConfig(confs, value);
+
+  ASSERT_EQ(value.size(), 0u);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpCodecConfigSingle) {
+  std::vector<struct ctp_codec_conf> confs;
+  std::vector<uint8_t> value;
+
+  types::LeAudioCodecId codec_id{.coding_format = 0x06,
+                                 .vendor_company_id = 0x0203,
+                                 .vendor_codec_id = 0x0405};
+  types::LeAudioLc3Config codec_conf{.sampling_frequency = 0x10,
+                                     .frame_duration = 0x03,
+                                     .audio_channel_allocation = 0x04050607,
+                                     .octets_per_codec_frame = 0x0203};
+
+  confs.push_back(ctp_codec_conf{
+      .ase_id = 0x05,
+      .target_latency = 0x03,
+      .target_phy = 0x02,
+      .codec_id = codec_id,
+      .codec_config = codec_conf,
+  });
+  PrepareAseCtpCodecConfig(confs, value);
+
+  uint8_t i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x01);  // Config Codec Opcode
+  ASSERT_EQ(value[i++], 0x01);  // Number of ASEs
+  ASSERT_EQ(value[i++], 0x05);  // ASE[0] ASE ID
+  ASSERT_EQ(value[i++], 0x03);  // ASE[0] Target Latency
+  ASSERT_EQ(value[i++], 0x02);  // ASE[0] Target Phy
+  ASSERT_EQ(value[i++], 0x06);  // ASE[0].CodecID Coding Format
+  ASSERT_EQ(value[i++], 0x03);  // ASE[0].CodecID Company ID LSB
+  ASSERT_EQ(value[i++], 0x02);  // ASE[0].CodecID Company ID MSB
+  ASSERT_EQ(value[i++], 0x05);  // ASE[0].CodecID Codec ID LSB
+  ASSERT_EQ(value[i++], 0x04);  // ASE[0].CodecID Codec ID MSB
+
+  // ASE[0].Codec Spec. Conf. Length - LC3 specific
+  ASSERT_EQ(value[i++], 8 + 8);  // * 4*2 bytes for 4 LTV types and lengths + 8
+                                 // bytes for the values
+  ASSERT_EQ(value[i++], 0x02);   // Sampling Freq. Length
+  ASSERT_EQ(value[i++], 0x01);   // Sampling Freq. Type
+  ASSERT_EQ(value[i++], 0x10);   // Sampling Freq. Value
+  ASSERT_EQ(value[i++], 0x02);   // Frame Duration. Length
+  ASSERT_EQ(value[i++], 0x02);   // Frame Duration. Type
+  ASSERT_EQ(value[i++], 0x03);   // Frame Duration. Value
+  ASSERT_EQ(value[i++], 0x05);   // Audio Channel Allocations Length
+  ASSERT_EQ(value[i++], 0x03);   // Audio Channel Allocations Type
+  ASSERT_EQ(value[i++], 0x07);   // Audio Channel Allocations Value[0]
+  ASSERT_EQ(value[i++], 0x06);   // Audio Channel Allocations Value[1]
+  ASSERT_EQ(value[i++], 0x05);   // Audio Channel Allocations Value[2]
+  ASSERT_EQ(value[i++], 0x04);   // Audio Channel Allocations Value[3]
+  ASSERT_EQ(value[i++], 0x03);   // Octets Per Frame Length
+  ASSERT_EQ(value[i++], 0x04);   // Octets Per Frame Type
+  ASSERT_EQ(value[i++], 0x03);   // Octets Per Frame Value[0]
+  ASSERT_EQ(value[i++], 0x02);   // Octets Per Frame Value[1]
+  ASSERT_EQ(value.size(), i);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpCodecConfigMultiple) {
+  std::vector<struct ctp_codec_conf> confs;
+  std::vector<uint8_t> value;
+
+  types::LeAudioCodecId codec_id{.coding_format = 0x06,
+                                 .vendor_company_id = 0x0203,
+                                 .vendor_codec_id = 0x0405};
+  types::LeAudioLc3Config codec_conf{.sampling_frequency = 0x10,
+                                     .frame_duration = 0x03,
+                                     .audio_channel_allocation = 0x04050607,
+                                     .octets_per_codec_frame = 0x0203};
+
+  confs.push_back(ctp_codec_conf{
+      .ase_id = 0x05,
+      .target_latency = 0x03,
+      .target_phy = 0x02,
+      .codec_id = codec_id,
+      .codec_config = codec_conf,
+  });
+  PrepareAseCtpCodecConfig(confs, value);
+
+  uint8_t i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x01);  // Config Codec Opcode
+  ASSERT_EQ(value[i++], 0x01);  // Number of ASEs
+  ASSERT_EQ(value[i++], 0x05);  // ASE[0] ASE ID
+  ASSERT_EQ(value[i++], 0x03);  // ASE[0] Target Latency
+  ASSERT_EQ(value[i++], 0x02);  // ASE[0] Target Phy
+  ASSERT_EQ(value[i++], 0x06);  // ASE[0].CodecID Coding Format
+  ASSERT_EQ(value[i++], 0x03);  // ASE[0].CodecID Company ID LSB
+  ASSERT_EQ(value[i++], 0x02);  // ASE[0].CodecID Company ID MSB
+  ASSERT_EQ(value[i++], 0x05);  // ASE[0].CodecID Codec ID LSB
+  ASSERT_EQ(value[i++], 0x04);  // ASE[0].CodecID Codec ID MSB
+
+  // ASE[0].Codec Spec. Conf. Length - LC3 specific
+  ASSERT_EQ(value[i++], 8 + 8);  // * 4*2 bytes for 4 LTV types and lengths + 8
+                                 // bytes for the values
+  ASSERT_EQ(value[i++], 0x02);   // Sampling Freq. Length
+  ASSERT_EQ(value[i++], 0x01);   // Sampling Freq. Type
+  ASSERT_EQ(value[i++], 0x10);   // Sampling Freq. Value
+  ASSERT_EQ(value[i++], 0x02);   // Frame Duration. Length
+  ASSERT_EQ(value[i++], 0x02);   // Frame Duration. Type
+  ASSERT_EQ(value[i++], 0x03);   // Frame Duration. Value
+  ASSERT_EQ(value[i++], 0x05);   // Audio Channel Allocations Length
+  ASSERT_EQ(value[i++], 0x03);   // Audio Channel Allocations Type
+  ASSERT_EQ(value[i++], 0x07);   // Audio Channel Allocations Value[0]
+  ASSERT_EQ(value[i++], 0x06);   // Audio Channel Allocations Value[1]
+  ASSERT_EQ(value[i++], 0x05);   // Audio Channel Allocations Value[2]
+  ASSERT_EQ(value[i++], 0x04);   // Audio Channel Allocations Value[3]
+  ASSERT_EQ(value[i++], 0x03);   // Octets Per Frame Length
+  ASSERT_EQ(value[i++], 0x04);   // Octets Per Frame Type
+  ASSERT_EQ(value[i++], 0x03);   // Octets Per Frame Value[0]
+  ASSERT_EQ(value[i++], 0x02);   // Octets Per Frame Value[1]
+  ASSERT_EQ(value.size(), i);
+
+  types::LeAudioCodecId codec_id2{.coding_format = 0x16,
+                                  .vendor_company_id = 0x1213,
+                                  .vendor_codec_id = 0x1415};
+  types::LeAudioLc3Config codec_conf2{.sampling_frequency = 0x11,
+                                      .frame_duration = 0x13,
+                                      .audio_channel_allocation = 0x14151617,
+                                      .octets_per_codec_frame = 0x1213};
+
+  confs.push_back(ctp_codec_conf{
+      .ase_id = 0x15,
+      .target_latency = 0x13,
+      .target_phy = 0x01,
+      .codec_id = codec_id2,
+      .codec_config = codec_conf2,
+  });
+  PrepareAseCtpCodecConfig(confs, value);
+
+  i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x01);  // Config Codec Opcode
+  ASSERT_EQ(value[i++], 0x02);  // Number of ASEs
+  ASSERT_EQ(value[i++], 0x05);  // ASE[0] ASE ID
+  ASSERT_EQ(value[i++], 0x03);  // ASE[0] Target Latency
+  ASSERT_EQ(value[i++], 0x02);  // ASE[0] Target Phy
+  ASSERT_EQ(value[i++], 0x06);  // ASE[0].CodecID Coding Format
+  ASSERT_EQ(value[i++], 0x03);  // ASE[0].CodecID Company ID LSB
+  ASSERT_EQ(value[i++], 0x02);  // ASE[0].CodecID Company ID MSB
+  ASSERT_EQ(value[i++], 0x05);  // ASE[0].CodecID Codec ID LSB
+  ASSERT_EQ(value[i++], 0x04);  // ASE[0].CodecID Codec ID MSB
+
+  // ASE[0].Codec Spec. Conf. Length - LC3 specific
+  ASSERT_EQ(value[i++], 8 + 8);  // * 4*2 bytes for 4 LTV types and lengths + 8
+                                 // bytes for the values
+  ASSERT_EQ(value[i++], 0x02);   // Sampling Freq. Length
+  ASSERT_EQ(value[i++], 0x01);   // Sampling Freq. Type
+  ASSERT_EQ(value[i++], 0x10);   // Sampling Freq. Value
+  ASSERT_EQ(value[i++], 0x02);   // Frame Duration. Length
+  ASSERT_EQ(value[i++], 0x02);   // Frame Duration. Type
+  ASSERT_EQ(value[i++], 0x03);   // Frame Duration. Value
+  ASSERT_EQ(value[i++], 0x05);   // Audio Channel Allocations Length
+  ASSERT_EQ(value[i++], 0x03);   // Audio Channel Allocations Type
+  ASSERT_EQ(value[i++], 0x07);   // Audio Channel Allocations Value[0]
+  ASSERT_EQ(value[i++], 0x06);   // Audio Channel Allocations Value[1]
+  ASSERT_EQ(value[i++], 0x05);   // Audio Channel Allocations Value[2]
+  ASSERT_EQ(value[i++], 0x04);   // Audio Channel Allocations Value[3]
+  ASSERT_EQ(value[i++], 0x03);   // Octets Per Frame Length
+  ASSERT_EQ(value[i++], 0x04);   // Octets Per Frame Type
+  ASSERT_EQ(value[i++], 0x03);   // Octets Per Frame Value[0]
+  ASSERT_EQ(value[i++], 0x02);   // Octets Per Frame Value[1]
+
+  ASSERT_EQ(value[i++], 0x15);  // ASE[1] ASE ID
+  ASSERT_EQ(value[i++], 0x13);  // ASE[1] Target Latency
+  ASSERT_EQ(value[i++], 0x01);  // ASE[1] Target Phy
+  ASSERT_EQ(value[i++], 0x16);  // ASE[1].CodecID Coding Format
+  ASSERT_EQ(value[i++], 0x13);  // ASE[1].CodecID Company ID LSB
+  ASSERT_EQ(value[i++], 0x12);  // ASE[1].CodecID Company ID MSB
+  ASSERT_EQ(value[i++], 0x15);  // ASE[1].CodecID Codec ID LSB
+  ASSERT_EQ(value[i++], 0x14);  // ASE[1].CodecID Codec ID MSB
+
+  // ASE[1].Codec Spec. Conf. Length - LC3 specific
+  ASSERT_EQ(value[i++], 8 + 8);  // * 4*2 bytes for 4 LTV types and lengths + 8
+                                 // bytes for the values
+  ASSERT_EQ(value[i++], 0x02);   // Sampling Freq. Length
+  ASSERT_EQ(value[i++], 0x01);   // Sampling Freq. Type
+  ASSERT_EQ(value[i++], 0x11);   // Sampling Freq. Value
+  ASSERT_EQ(value[i++], 0x02);   // Frame Duration. Length
+  ASSERT_EQ(value[i++], 0x02);   // Frame Duration. Type
+  ASSERT_EQ(value[i++], 0x13);   // Frame Duration. Value
+  ASSERT_EQ(value[i++], 0x05);   // Audio Channel Allocations Length
+  ASSERT_EQ(value[i++], 0x03);   // Audio Channel Allocations Type
+  ASSERT_EQ(value[i++], 0x17);   // Audio Channel Allocations Value[0]
+  ASSERT_EQ(value[i++], 0x16);   // Audio Channel Allocations Value[1]
+  ASSERT_EQ(value[i++], 0x15);   // Audio Channel Allocations Value[2]
+  ASSERT_EQ(value[i++], 0x14);   // Audio Channel Allocations Value[3]
+  ASSERT_EQ(value[i++], 0x03);   // Octets Per Frame Length
+  ASSERT_EQ(value[i++], 0x04);   // Octets Per Frame Type
+  ASSERT_EQ(value[i++], 0x13);   // Octets Per Frame Value[0]
+  ASSERT_EQ(value[i++], 0x12);   // Octets Per Frame Value[1]
+
+  ASSERT_EQ(value.size(), i);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpConfigQosEmpty) {
+  std::vector<struct ctp_qos_conf> confs;
+  std::vector<uint8_t> value;
+
+  PrepareAseCtpConfigQos(confs, value);
+  ASSERT_EQ(value.size(), 0u);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpConfigQosSingle) {
+  std::vector<struct ctp_qos_conf> confs;
+  std::vector<uint8_t> value;
+
+  const ctp_qos_conf conf{.ase_id = 0x01,
+                          .cig = 0x11,
+                          .cis = 0x12,
+                          .sdu_interval = 0x00131415,
+                          .framing = 0x01,
+                          .phy = 0x01,
+                          .max_sdu = 0x0203,
+                          .retrans_nb = 0x04,
+                          .max_transport_latency = 0x0302,
+                          .pres_delay = 0x00121314};
+  confs.push_back(conf);
+
+  PrepareAseCtpConfigQos(confs, value);
+  ASSERT_NE(value.size(), 0u);
+
+  uint8_t i = 0;
+  ASSERT_EQ(value[i++], 0x02u);  // Config QOS Opcode
+  ASSERT_EQ(value[i++], 0x01u);  // Number of ASE
+  ASSERT_EQ(value[i++], 0x01u);  // ASE ID
+  ASSERT_EQ(value[i++], 0x11u);  // CIG ID
+  ASSERT_EQ(value[i++], 0x12u);  // CIS ID
+  ASSERT_EQ(value[i++], 0x15u);  // SDU Interval [0]
+  ASSERT_EQ(value[i++], 0x14u);  // SDU Interval [1]
+  ASSERT_EQ(value[i++], 0x13u);  // SDU Interval [2]
+  ASSERT_EQ(value[i++], 0x01u);  // Framing
+  ASSERT_EQ(value[i++], 0x01u);  // Phy
+  ASSERT_EQ(value[i++], 0x03u);  // Max SDU LSB
+  ASSERT_EQ(value[i++], 0x02u);  // Max SDU MSB
+  ASSERT_EQ(value[i++], 0x04u);  // Retransmission
+  ASSERT_EQ(value[i++], 0x02u);  // Max. Trans. Latency LSB
+  ASSERT_EQ(value[i++], 0x03u);  // Max. Trans. Latency MSB
+  ASSERT_EQ(value[i++], 0x14u);  // Pres. Delay[0]
+  ASSERT_EQ(value[i++], 0x13u);  // Pres. Delay[1]
+  ASSERT_EQ(value[i++], 0x12u);  // Pres. Delay[2]
+  ASSERT_EQ(value.size(), i);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpConfigQosMultiple) {
+  std::vector<struct ctp_qos_conf> confs;
+  std::vector<uint8_t> value;
+
+  const ctp_qos_conf conf{.ase_id = 0x01,
+                          .cig = 0x11,
+                          .cis = 0x12,
+                          .sdu_interval = 0x131415,
+                          .framing = 0x01,
+                          .phy = 0x01,
+                          .max_sdu = 0x0203,
+                          .retrans_nb = 0x04,
+                          .max_transport_latency = 0x0302,
+                          .pres_delay = 0x121314};
+  confs.push_back(conf);
+
+  const ctp_qos_conf conf2{.ase_id = 0x11,
+                           .cig = 0x21,
+                           .cis = 0x22,
+                           .sdu_interval = 0x232425,
+                           .framing = 0x02,
+                           .phy = 0x02,
+                           .max_sdu = 0x2223,
+                           .retrans_nb = 0x24,
+                           .max_transport_latency = 0x2322,
+                           .pres_delay = 0x222324};
+  confs.push_back(conf2);
+
+  PrepareAseCtpConfigQos(confs, value);
+  ASSERT_NE(value.size(), 0u);
+
+  uint8_t i = 0;
+  ASSERT_EQ(value[i++], 0x02u);  // Config QOS Opcode
+  ASSERT_EQ(value[i++], 0x02u);  // Number of ASE
+  // 1st ASE Config
+  ASSERT_EQ(value[i++], 0x01u);  // ASE ID
+  ASSERT_EQ(value[i++], 0x11u);  // CIG ID
+  ASSERT_EQ(value[i++], 0x12u);  // CIS ID
+  ASSERT_EQ(value[i++], 0x15u);  // SDU Interval [0]
+  ASSERT_EQ(value[i++], 0x14u);  // SDU Interval [1]
+  ASSERT_EQ(value[i++], 0x13u);  // SDU Interval [2]
+  ASSERT_EQ(value[i++], 0x01u);  // Framing
+  ASSERT_EQ(value[i++], 0x01u);  // Phy
+  ASSERT_EQ(value[i++], 0x03u);  // Max SDU LSB
+  ASSERT_EQ(value[i++], 0x02u);  // Max SDU MSB
+  ASSERT_EQ(value[i++], 0x04u);  // Retransmission
+  ASSERT_EQ(value[i++], 0x02u);  // Max. Trans. Latency LSB
+  ASSERT_EQ(value[i++], 0x03u);  // Max. Trans. Latency MSB
+  ASSERT_EQ(value[i++], 0x14u);  // Pres. Delay[0]
+  ASSERT_EQ(value[i++], 0x13u);  // Pres. Delay[1]
+  ASSERT_EQ(value[i++], 0x12u);  // Pres. Delay[2]
+  // 2nd ASE Config
+  ASSERT_EQ(value[i++], 0x11u);  // ASE ID
+  ASSERT_EQ(value[i++], 0x21u);  // CIG ID
+  ASSERT_EQ(value[i++], 0x22u);  // CIS ID
+  ASSERT_EQ(value[i++], 0x25u);  // SDU Interval [0]
+  ASSERT_EQ(value[i++], 0x24u);  // SDU Interval [1]
+  ASSERT_EQ(value[i++], 0x23u);  // SDU Interval [2]
+  ASSERT_EQ(value[i++], 0x02u);  // Framing
+  ASSERT_EQ(value[i++], 0x02u);  // Phy
+  ASSERT_EQ(value[i++], 0x23u);  // Max SDU LSB
+  ASSERT_EQ(value[i++], 0x22u);  // Max SDU MSB
+  ASSERT_EQ(value[i++], 0x24u);  // Retransmission
+  ASSERT_EQ(value[i++], 0x22u);  // Max. Trans. Latency LSB
+  ASSERT_EQ(value[i++], 0x23u);  // Max. Trans. Latency MSB
+  ASSERT_EQ(value[i++], 0x24u);  // Pres. Delay[0]
+  ASSERT_EQ(value[i++], 0x23u);  // Pres. Delay[1]
+  ASSERT_EQ(value[i++], 0x22u);  // Pres. Delay[2]
+
+  ASSERT_EQ(value.size(), i);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpEnableEmpty) {
+  std::vector<struct ctp_enable> confs;
+  std::vector<uint8_t> value;
+
+  PrepareAseCtpEnable(confs, value);
+  ASSERT_EQ(value.size(), 0u);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpEnableSingle) {
+  std::vector<struct ctp_enable> confs;
+  std::vector<uint8_t> value;
+
+  ctp_enable conf{.ase_id = 0x11, .metadata = {0x02, 0x22, 0x21}};
+  confs.push_back(conf);
+
+  PrepareAseCtpEnable(confs, value);
+  ASSERT_NE(value.size(), 0u);
+
+  uint8_t i = 0;
+  ASSERT_EQ(value[i++], 0x03u);  // Enable Opcode
+  ASSERT_EQ(value[i++], 0x01u);  // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(value[i++], 0x03u);  // Metadata Len
+  ASSERT_EQ(value[i++], 0x02u);  // Metadata[0]
+  ASSERT_EQ(value[i++], 0x22u);  // Metadata[1]
+  ASSERT_EQ(value[i++], 0x21u);  // Metadata[2]
+  ASSERT_EQ(value.size(), i);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpEnableMultiple) {
+  std::vector<struct ctp_enable> confs;
+  std::vector<uint8_t> value;
+
+  ctp_enable conf{.ase_id = 0x11, .metadata = {0x02, 0x22, 0x21}};
+  confs.push_back(conf);
+
+  ctp_enable conf2{.ase_id = 0x21, .metadata = {0x03, 0x35, 0x36, 0x37}};
+  confs.push_back(conf2);
+
+  PrepareAseCtpEnable(confs, value);
+  ASSERT_NE(value.size(), 0u);
+
+  uint8_t i = 0;
+  ASSERT_EQ(value[i++], 0x03u);  // Enable Opcode
+  ASSERT_EQ(value[i++], 0x02u);  // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(value[i++], 0x03u);  // ASE[0] Metadata Len
+  ASSERT_EQ(value[i++], 0x02u);  // ASE[0] Metadata[0]
+  ASSERT_EQ(value[i++], 0x22u);  // ASE[0] Metadata[1]
+  ASSERT_EQ(value[i++], 0x21u);  // ASE[0] Metadata[2]
+  ASSERT_EQ(value[i++], 0x21u);  // ASE[1] ID
+  ASSERT_EQ(value[i++], 0x04u);  // ASE[1] Metadata Len
+  ASSERT_EQ(value[i++], 0x03u);  // ASE[1] Metadata[0]
+  ASSERT_EQ(value[i++], 0x35u);  // ASE[1] Metadata[1]
+  ASSERT_EQ(value[i++], 0x36u);  // ASE[1] Metadata[2]
+  ASSERT_EQ(value[i++], 0x37u);  // ASE[1] Metadata[3]
+  ASSERT_EQ(value.size(), i);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpAudioReceiverStartReadyEmpty) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  PrepareAseCtpAudioReceiverStartReady(ase_ids, value);
+  ASSERT_EQ(value.size(), 0u);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpAudioReceiverStartReadySingle) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  ase_ids.push_back(0x11);
+
+  PrepareAseCtpAudioReceiverStartReady(ase_ids, value);
+
+  uint8_t i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x04u);  // Receiver Start Ready Opcode
+  ASSERT_EQ(value[i++], 1u);     // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(i, value.size());
+}
+
+TEST(LeAudioClientParserTest,
+     testPrepareAseCtpAudioReceiverStartReadyMultiple) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  ase_ids.push_back(0x11);
+  ase_ids.push_back(0x36);
+
+  PrepareAseCtpAudioReceiverStartReady(ase_ids, value);
+
+  uint8_t i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x04u);  // Receiver Start Ready Opcode
+  ASSERT_EQ(value[i++], 2u);     // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(value[i++], 0x36u);  // ASE[0] ID
+  ASSERT_EQ(i, value.size());
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpDisableEmpty) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  PrepareAseCtpDisable(ase_ids, value);
+  ASSERT_EQ(value.size(), 0u);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpDisableSingle) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  ase_ids.push_back(0x11);
+
+  PrepareAseCtpDisable(ase_ids, value);
+
+  uint8_t i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x05u);  // Disable Opcode
+  ASSERT_EQ(value[i++], 1u);     // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(i, value.size());
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpDisableMultiple) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  ase_ids.push_back(0x11);
+  ase_ids.push_back(0x36);
+
+  PrepareAseCtpDisable(ase_ids, value);
+
+  uint8_t i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x05u);  // Disable Opcode
+  ASSERT_EQ(value[i++], 2u);     // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(value[i++], 0x36u);  // ASE[0] ID
+  ASSERT_EQ(i, value.size());
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpAudioReceiverStopReadyEmpty) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  PrepareAseCtpAudioReceiverStopReady(ase_ids, value);
+  ASSERT_EQ(value.size(), 0u);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpAudioReceiverStopReadySingle) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  ase_ids.push_back(0x11);
+
+  PrepareAseCtpAudioReceiverStopReady(ase_ids, value);
+
+  uint8_t i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x06u);  // Reveicer Stop Ready Opcode
+  ASSERT_EQ(value[i++], 1u);     // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(i, value.size());
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpAudioReceiverStopReadyMultiple) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  ase_ids.push_back(0x11);
+  ase_ids.push_back(0x36);
+
+  PrepareAseCtpAudioReceiverStopReady(ase_ids, value);
+
+  uint8_t i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x06u);  // Reveicer Stop Ready Opcode
+  ASSERT_EQ(value[i++], 2u);     // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(value[i++], 0x36u);  // ASE[0] ID
+  ASSERT_EQ(i, value.size());
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpUpdateMetadataEmpty) {
+  std::vector<struct ctp_update_metadata> confs;
+  std::vector<uint8_t> value;
+
+  PrepareAseCtpUpdateMetadata(confs, value);
+  ASSERT_EQ(value.size(), 0u);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpUpdateMetadataSingle) {
+  std::vector<struct ctp_update_metadata> confs;
+  std::vector<uint8_t> value;
+
+  ctp_update_metadata conf{.ase_id = 0x11, .metadata = {0x02, 0x22, 0x21}};
+  confs.push_back(conf);
+
+  PrepareAseCtpUpdateMetadata(confs, value);
+  ASSERT_NE(value.size(), 0u);
+
+  uint8_t i = 0;
+  ASSERT_EQ(value[i++], 0x07u);  // Update Metadata Opcode
+  ASSERT_EQ(value[i++], 0x01u);  // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(value[i++], 0x03u);  // Metadata Len
+  ASSERT_EQ(value[i++], 0x02u);  // Metadata[0]
+  ASSERT_EQ(value[i++], 0x22u);  // Metadata[1]
+  ASSERT_EQ(value[i++], 0x21u);  // Metadata[2]
+  ASSERT_EQ(i, value.size());
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpUpdateMetadataMultiple) {
+  std::vector<struct ctp_update_metadata> confs;
+  std::vector<uint8_t> value;
+
+  ctp_update_metadata conf{.ase_id = 0x11, .metadata = {0x02, 0x22, 0x21}};
+  confs.push_back(conf);
+
+  ctp_update_metadata conf2{.ase_id = 0x21,
+                            .metadata = {0x03, 0x35, 0x36, 0x37}};
+  confs.push_back(conf2);
+
+  PrepareAseCtpUpdateMetadata(confs, value);
+  ASSERT_NE(value.size(), 0u);
+
+  uint8_t i = 0;
+  ASSERT_EQ(value[i++], 0x07u);  // Update Metadata Opcode
+  ASSERT_EQ(value[i++], 0x02u);  // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(value[i++], 0x03u);  // ASE[0] Metadata Len
+  ASSERT_EQ(value[i++], 0x02u);  // ASE[0] Metadata[0]
+  ASSERT_EQ(value[i++], 0x22u);  // ASE[0] Metadata[1]
+  ASSERT_EQ(value[i++], 0x21u);  // ASE[0] Metadata[2]
+  ASSERT_EQ(value[i++], 0x21u);  // ASE[1] ID
+  ASSERT_EQ(value[i++], 0x04u);  // ASE[1] Metadata Len
+  ASSERT_EQ(value[i++], 0x03u);  // ASE[1] Metadata[0]
+  ASSERT_EQ(value[i++], 0x35u);  // ASE[1] Metadata[1]
+  ASSERT_EQ(value[i++], 0x36u);  // ASE[1] Metadata[2]
+  ASSERT_EQ(value[i++], 0x37u);  // ASE[1] Metadata[2]
+  ASSERT_EQ(i, value.size());
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpReleaseEmpty) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  PrepareAseCtpRelease(ase_ids, value);
+  ASSERT_EQ(value.size(), 0u);
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpReleaseSingle) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  ase_ids.push_back(0x11);
+
+  PrepareAseCtpRelease(ase_ids, value);
+
+  uint8_t i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x08u);  // Release Opcode
+  ASSERT_EQ(value[i++], 1u);     // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(i, value.size());
+}
+
+TEST(LeAudioClientParserTest, testPrepareAseCtpReleaseMultiple) {
+  std::vector<uint8_t> ase_ids;
+  std::vector<uint8_t> value;
+
+  ase_ids.push_back(0x11);
+  ase_ids.push_back(0x36);
+
+  PrepareAseCtpRelease(ase_ids, value);
+
+  uint8_t i = 0;
+  ASSERT_NE(value.size(), 0u);
+  ASSERT_EQ(value[i++], 0x08u);  // Release Opcode
+  ASSERT_EQ(value[i++], 2u);     // Number of ASEs
+  ASSERT_EQ(value[i++], 0x11u);  // ASE[0] ID
+  ASSERT_EQ(value[i++], 0x36u);  // ASE[0] ID
+  ASSERT_EQ(i, value.size());
+}
+
+}  // namespace ascs
+
+}  // namespace client_parser
+}  // namespace le_audio
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1fd6f6170ddca291f875c08b212c63179e550f7a
--- /dev/null
+++ b/system/bta/le_audio/devices.cc
@@ -0,0 +1,1778 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA
+ * - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "devices.h"
+
+#include <base/strings/string_number_conversions.h>
+
+#include <map>
+
+#include "bta_gatt_queue.h"
+#include "bta_groups.h"
+#include "bta_le_audio_api.h"
+#include "btm_iso_api.h"
+#include "btm_iso_api_types.h"
+#include "client_audio.h"
+#include "device/include/controller.h"
+#include "stack/btm/btm_int.h"
+
+using bluetooth::hci::kIsoCigFramingFramed;
+using bluetooth::hci::kIsoCigFramingUnframed;
+using bluetooth::hci::kIsoCigPackingSequential;
+using bluetooth::hci::kIsoCigPhy1M;
+using bluetooth::hci::kIsoCigPhy2M;
+using bluetooth::hci::iso_manager::kIsoSca0To20Ppm;
+using le_audio::set_configurations::CodecCapabilitySetting;
+using le_audio::types::ase;
+using le_audio::types::AseState;
+using le_audio::types::AudioContexts;
+using le_audio::types::AudioLocations;
+using le_audio::types::AudioStreamDataPathState;
+using le_audio::types::BidirectAsesPair;
+using le_audio::types::LeAudioCodecId;
+using le_audio::types::LeAudioContextType;
+using le_audio::types::LeAudioLc3Config;
+
+namespace le_audio {
+/* LeAudioDeviceGroup Class methods implementation */
+void LeAudioDeviceGroup::AddNode(
+    const std::shared_ptr<LeAudioDevice>& leAudioDevice) {
+  leAudioDevice->group_id_ = group_id_;
+  leAudioDevices_.push_back(std::weak_ptr<LeAudioDevice>(leAudioDevice));
+}
+
+void LeAudioDeviceGroup::RemoveNode(
+    const std::shared_ptr<LeAudioDevice>& leAudioDevice) {
+  leAudioDevice->group_id_ = bluetooth::groups::kGroupUnknown;
+  leAudioDevices_.erase(
+      std::remove_if(
+          leAudioDevices_.begin(), leAudioDevices_.end(),
+          [&leAudioDevice](auto& d) { return d.lock() == leAudioDevice; }),
+      leAudioDevices_.end());
+}
+
+bool LeAudioDeviceGroup::IsEmpty(void) { return leAudioDevices_.size() == 0; }
+
+bool LeAudioDeviceGroup::IsAnyDeviceConnected(void) {
+  return (NumOfConnected() != 0);
+}
+
+int LeAudioDeviceGroup::Size(void) { return leAudioDevices_.size(); }
+
+int LeAudioDeviceGroup::NumOfConnected(types::LeAudioContextType context_type) {
+  if (leAudioDevices_.empty()) return 0;
+
+  bool check_context_type = (context_type != LeAudioContextType::RFU);
+  AudioContexts type_set = static_cast<uint16_t>(context_type);
+
+  /* return number of connected devices from the set*/
+  return std::count_if(
+      leAudioDevices_.begin(), leAudioDevices_.end(),
+      [type_set, check_context_type](auto& iter) {
+        if (iter.expired()) return false;
+        if (iter.lock()->conn_id_ == GATT_INVALID_CONN_ID) return false;
+
+        if (!check_context_type) return true;
+
+        return (iter.lock()->GetAvailableContexts() & type_set).any();
+      });
+}
+
+void LeAudioDeviceGroup::Cleanup(void) { leAudioDevices_.clear(); }
+
+void LeAudioDeviceGroup::Deactivate(void) {
+  for (auto* leAudioDevice = GetFirstActiveDevice(); leAudioDevice;
+       leAudioDevice = GetNextActiveDevice(leAudioDevice)) {
+    for (auto* ase = leAudioDevice->GetFirstActiveAse(); ase;
+         ase = leAudioDevice->GetNextActiveAse(ase)) {
+      ase->active = false;
+    }
+  }
+}
+
+LeAudioDevice* LeAudioDeviceGroup::GetFirstDevice(void) {
+  return (leAudioDevices_.front().lock()).get();
+}
+
+LeAudioDevice* LeAudioDeviceGroup::GetFirstDeviceWithActiveContext(
+    types::LeAudioContextType context_type) {
+  AudioContexts type_set = static_cast<uint16_t>(context_type);
+
+  auto iter = std::find_if(
+      leAudioDevices_.begin(), leAudioDevices_.end(), [&type_set](auto& iter) {
+        if (iter.expired()) return false;
+        return (iter.lock()->GetAvailableContexts() & type_set).any();
+      });
+
+  if ((iter == leAudioDevices_.end()) || (iter->expired())) return nullptr;
+
+  return (iter->lock()).get();
+}
+
+LeAudioDevice* LeAudioDeviceGroup::GetNextDevice(LeAudioDevice* leAudioDevice) {
+  auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
+                           [&leAudioDevice](auto& d) {
+                             if (d.expired())
+                               return false;
+                             else
+                               return (d.lock()).get() == leAudioDevice;
+                           });
+
+  /* If reference device not found */
+  if (iter == leAudioDevices_.end()) return nullptr;
+
+  std::advance(iter, 1);
+  /* If reference device is last in group */
+  if (iter == leAudioDevices_.end()) return nullptr;
+
+  if (iter->expired()) return nullptr;
+
+  return (iter->lock()).get();
+}
+
+LeAudioDevice* LeAudioDeviceGroup::GetNextDeviceWithActiveContext(
+    LeAudioDevice* leAudioDevice, types::LeAudioContextType context_type) {
+  AudioContexts type_set = static_cast<uint16_t>(context_type);
+
+  auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
+                           [&leAudioDevice](auto& d) {
+                             if (d.expired())
+                               return false;
+                             else
+                               return (d.lock()).get() == leAudioDevice;
+                           });
+
+  /* If reference device not found */
+  if (iter == leAudioDevices_.end()) return nullptr;
+
+  std::advance(iter, 1);
+  /* If reference device is last in group */
+  if (iter == leAudioDevices_.end()) return nullptr;
+
+  iter = std::find_if(iter, leAudioDevices_.end(), [&type_set](auto& d) {
+    if (d.expired())
+      return false;
+    else
+      return (d.lock()->GetAvailableContexts() & type_set).any();
+    ;
+  });
+
+  return (iter == leAudioDevices_.end()) ? nullptr : (iter->lock()).get();
+}
+
+bool LeAudioDeviceGroup::IsDeviceInTheGroup(LeAudioDevice* leAudioDevice) {
+  auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
+                           [&leAudioDevice](auto& d) {
+                             if (d.expired())
+                               return false;
+                             else
+                               return (d.lock()).get() == leAudioDevice;
+                           });
+
+  if ((iter == leAudioDevices_.end()) || (iter->expired())) return false;
+
+  return true;
+}
+
+bool LeAudioDeviceGroup::HaveAllActiveDevicesAsesTheSameState(AseState state) {
+  auto iter = std::find_if(
+      leAudioDevices_.begin(), leAudioDevices_.end(), [&state](auto& d) {
+        if (d.expired())
+          return false;
+        else
+          return !(((d.lock()).get())->HaveAllActiveAsesSameState(state));
+      });
+
+  return iter == leAudioDevices_.end();
+}
+
+LeAudioDevice* LeAudioDeviceGroup::GetFirstActiveDevice(void) {
+  auto iter =
+      std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
+        if (d.expired())
+          return false;
+        else
+          return ((d.lock()).get())->HaveActiveAse();
+      });
+
+  if (iter == leAudioDevices_.end() || iter->expired()) return nullptr;
+
+  return (iter->lock()).get();
+}
+
+LeAudioDevice* LeAudioDeviceGroup::GetNextActiveDevice(
+    LeAudioDevice* leAudioDevice) {
+  auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
+                           [&leAudioDevice](auto& d) {
+                             if (d.expired())
+                               return false;
+                             else
+                               return (d.lock()).get() == leAudioDevice;
+                           });
+
+  if (iter == leAudioDevices_.end() ||
+      std::distance(iter, leAudioDevices_.end()) < 1)
+    return nullptr;
+
+  iter = std::find_if(std::next(iter, 1), leAudioDevices_.end(), [](auto& d) {
+    if (d.expired())
+      return false;
+    else
+      return ((d.lock()).get())->HaveActiveAse();
+  });
+
+  return (iter == leAudioDevices_.end()) ? nullptr : (iter->lock()).get();
+}
+
+bool LeAudioDeviceGroup::SetContextType(LeAudioContextType context_type) {
+  /* XXX: group context policy ? / may it disallow to change type ?) */
+  context_type_ = context_type;
+
+  return true;
+}
+
+LeAudioContextType LeAudioDeviceGroup::GetContextType(void) {
+  return context_type_;
+}
+
+uint32_t LeAudioDeviceGroup::GetSduInterval(uint8_t direction) {
+  for (LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
+       leAudioDevice != nullptr;
+       leAudioDevice = GetNextActiveDevice(leAudioDevice)) {
+    struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction);
+    if (!ase) continue;
+
+    return ase->codec_config.GetFrameDurationUs();
+  }
+
+  return 0;
+}
+
+uint8_t LeAudioDeviceGroup::GetSCA(void) {
+  uint8_t sca = kIsoSca0To20Ppm;
+
+  for (const auto& leAudioDevice : leAudioDevices_) {
+    uint8_t dev_sca =
+        BTM_GetPeerSCA(leAudioDevice.lock()->address_, BT_TRANSPORT_LE);
+
+    /* If we could not read SCA from the peer device or sca is 0,
+     * then there is no reason to continue.
+     */
+    if ((dev_sca == 0xFF) || (dev_sca == 0)) return 0;
+
+    /* The Slaves_Clock_Accuracy parameter shall be the worst-case sleep clock
+     *accuracy of all the slaves that will participate in the CIG.
+     */
+    if (dev_sca < sca) {
+      sca = dev_sca;
+    }
+  }
+
+  return sca;
+}
+
+uint8_t LeAudioDeviceGroup::GetPacking(void) {
+  /* TODO: Decide about packing */
+  return kIsoCigPackingSequential;
+}
+
+uint8_t LeAudioDeviceGroup::GetFraming(void) {
+  LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
+  LOG_ASSERT(leAudioDevice)
+      << __func__ << " Shouldn't be called without an active device.";
+
+  do {
+    struct ase* ase = leAudioDevice->GetFirstActiveAse();
+    if (!ase) continue;
+
+    do {
+      if (ase->framing == types::kFramingUnframedPduUnsupported)
+        return kIsoCigFramingFramed;
+    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+  } while ((leAudioDevice = GetNextActiveDevice(leAudioDevice)));
+
+  return kIsoCigFramingUnframed;
+}
+
+uint8_t LeAudioDeviceGroup::GetTargetLatency(void) {
+  /* TODO: Decide about target latency */
+  return types::kTargetLatencyBalancedLatencyReliability;
+}
+
+/* TODO: Preferred parameter may be other than minimum */
+static uint16_t find_max_transport_latency(LeAudioDeviceGroup* group,
+                                           uint8_t direction) {
+  uint16_t max_transport_latency = 0;
+
+  for (LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
+       leAudioDevice != nullptr;
+       leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
+    for (ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction);
+         ase != nullptr;
+         ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase)) {
+      if (!ase) break;
+
+      if (!max_transport_latency)
+        // first assignment
+        max_transport_latency = ase->max_transport_latency;
+      else if (ase->max_transport_latency < max_transport_latency)
+        max_transport_latency = ase->max_transport_latency;
+    }
+  }
+
+  if (max_transport_latency < types::kMaxTransportLatencyMin)
+    max_transport_latency = types::kMaxTransportLatencyMin;
+  else if (max_transport_latency > types::kMaxTransportLatencyMax)
+    max_transport_latency = types::kMaxTransportLatencyMax;
+
+  return max_transport_latency;
+}
+
+uint16_t LeAudioDeviceGroup::GetMaxTransportLatencyStom(void) {
+  return find_max_transport_latency(this, types::kLeAudioDirectionSource);
+}
+
+uint16_t LeAudioDeviceGroup::GetMaxTransportLatencyMtos(void) {
+  return find_max_transport_latency(this, types::kLeAudioDirectionSink);
+}
+
+uint16_t LeAudioDeviceGroup::GetTransportLatency(uint8_t direction) {
+  if (direction == types::kLeAudioDirectionSink) {
+    return transport_latency_mtos_;
+  } else if (direction == types::kLeAudioDirectionSource) {
+    return transport_latency_stom_;
+  } else {
+    LOG(ERROR) << __func__ << ", invalid direction";
+    return 0;
+  }
+}
+
+void LeAudioDeviceGroup::SetTransportLatency(uint8_t direction,
+                                             uint16_t new_transport_latency) {
+  uint16_t* transport_latency;
+
+  if (direction == types::kLeAudioDirectionSink) {
+    transport_latency = &transport_latency_mtos_;
+  } else if (direction == types::kLeAudioDirectionSource) {
+    transport_latency = &transport_latency_stom_;
+  } else {
+    LOG(ERROR) << __func__ << ", invalid direction";
+    return;
+  }
+
+  if (*transport_latency == new_transport_latency) return;
+
+  if ((*transport_latency != 0) &&
+      (*transport_latency != new_transport_latency)) {
+    LOG(WARNING) << __func__ << ", Different transport latency for group: "
+                 << " old: " << static_cast<int>(*transport_latency)
+                 << " [ms], new: " << static_cast<int>(new_transport_latency)
+                 << " [ms]";
+    return;
+  }
+
+  LOG(INFO) << __func__ << ", updated group " << static_cast<int>(group_id_)
+            << " transport latency: " << static_cast<int>(new_transport_latency)
+            << " [ms]";
+  *transport_latency = new_transport_latency;
+}
+
+uint8_t LeAudioDeviceGroup::GetPhyBitmask(uint8_t direction) {
+  LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
+  LOG_ASSERT(leAudioDevice)
+      << __func__ << " Shouldn't be called without an active device.";
+
+  // local supported PHY's
+  uint8_t phy_bitfield = kIsoCigPhy1M;
+  if (controller_get_interface()->supports_ble_2m_phy())
+    phy_bitfield |= kIsoCigPhy2M;
+
+  if (!leAudioDevice) {
+    LOG(ERROR) << "No active leaudio device for direction?: " << +direction;
+    return phy_bitfield;
+  }
+
+  do {
+    struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction);
+    if (!ase) return phy_bitfield;
+
+    do {
+      if (direction == ase->direction) {
+        phy_bitfield &= leAudioDevice->GetPhyBitmask();
+
+        // A value of 0x00 denotes no preference
+        if (ase->preferred_phy) phy_bitfield &= ase->preferred_phy;
+      }
+    } while ((ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase)));
+  } while ((leAudioDevice = GetNextActiveDevice(leAudioDevice)));
+
+  return phy_bitfield;
+}
+
+uint8_t LeAudioDeviceGroup::GetTargetPhy(uint8_t direction) {
+  uint8_t phy_bitfield = GetPhyBitmask(direction);
+
+  // prefer to use 2M if supported
+  if (phy_bitfield & kIsoCigPhy2M)
+    return types::kTargetPhy2M;
+  else if (phy_bitfield & kIsoCigPhy1M)
+    return types::kTargetPhy1M;
+  else
+    return 0;
+}
+
+bool LeAudioDeviceGroup::GetPresentationDelay(uint32_t* delay,
+                                              uint8_t direction) {
+  uint32_t delay_min = 0;
+  uint32_t delay_max = UINT32_MAX;
+  uint32_t preferred_delay_min = delay_min;
+  uint32_t preferred_delay_max = delay_max;
+
+  LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
+  LOG_ASSERT(leAudioDevice)
+      << __func__ << " Shouldn't be called without an active device.";
+
+  do {
+    struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction);
+    if (!ase) continue;  // device has no active ASEs in this direction
+
+    do {
+      /* No common range check */
+      if (ase->pres_delay_min > delay_max || ase->pres_delay_max < delay_min)
+        return false;
+
+      if (ase->pres_delay_min > delay_min) delay_min = ase->pres_delay_min;
+      if (ase->pres_delay_max < delay_max) delay_max = ase->pres_delay_max;
+      if (ase->preferred_pres_delay_min > preferred_delay_min)
+        preferred_delay_min = ase->preferred_pres_delay_min;
+      if (ase->preferred_pres_delay_max < preferred_delay_max &&
+          ase->preferred_pres_delay_max != types::kPresDelayNoPreference)
+        preferred_delay_max = ase->preferred_pres_delay_max;
+    } while ((ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase)));
+  } while ((leAudioDevice = GetNextActiveDevice(leAudioDevice)));
+
+  if (preferred_delay_min <= preferred_delay_max &&
+      preferred_delay_min > delay_min && preferred_delay_min < delay_max) {
+    *delay = preferred_delay_min;
+  } else {
+    *delay = delay_min;
+  }
+
+  return true;
+}
+
+uint16_t LeAudioDeviceGroup::GetRemoteDelay(uint8_t direction) {
+  uint16_t remote_delay_ms = 0;
+  uint32_t presentation_delay;
+
+  if (!GetPresentationDelay(&presentation_delay, direction)) {
+    /* This should never happens at stream request time but to be safe return
+     * some sample value to not break streaming
+     */
+    return 100;
+  }
+
+  /* us to ms */
+  remote_delay_ms = presentation_delay / 1000;
+  remote_delay_ms += GetTransportLatency(direction) / 1000;
+
+  return remote_delay_ms;
+}
+
+/* This method returns AudioContext value if support for any type has changed */
+std::optional<AudioContexts> LeAudioDeviceGroup::UpdateActiveContextsMap(void) {
+  DLOG(INFO) << __func__ << " group id: " << group_id_ << " active contexts: "
+             << loghex(active_contexts_mask_.to_ulong());
+  return UpdateActiveContextsMap(active_contexts_mask_);
+}
+
+/* This method returns AudioContext value if support for any type has changed */
+std::optional<AudioContexts> LeAudioDeviceGroup::UpdateActiveContextsMap(
+    AudioContexts update_contexts) {
+  AudioContexts contexts = 0x0000;
+  bool active_contexts_has_been_modified = false;
+
+  for (LeAudioContextType ctx_type : types::kLeAudioContextAllTypesArray) {
+    AudioContexts type_set = static_cast<uint16_t>(ctx_type);
+
+    if ((type_set & update_contexts).none()) {
+      /* Fill context bitset for possible returned value if updated */
+      if (active_context_to_configuration_map.count(ctx_type) > 0)
+        contexts |= type_set;
+
+      continue;
+    }
+
+    auto new_conf = FindFirstSupportedConfiguration(ctx_type);
+
+    /* Check if support for context type has changed */
+    if (active_context_to_configuration_map.count(ctx_type) == 0 ||
+        active_context_to_configuration_map[ctx_type] == nullptr) {
+      /* Current configuration for context type is empty */
+      if (new_conf == nullptr) {
+        /* Configuration remains empty */
+        continue;
+      } else {
+        /* Configuration changes from empty to some */
+        contexts |= type_set;
+        active_contexts_has_been_modified = true;
+      }
+    } else {
+      /* Current configuration for context type is not empty */
+      if (new_conf == nullptr) {
+        /* Configuration changed to empty */
+        contexts &= ~type_set;
+        active_contexts_has_been_modified = true;
+      } else if (new_conf != active_context_to_configuration_map[ctx_type]) {
+        /* Configuration changed to any other */
+        contexts |= type_set;
+        active_contexts_has_been_modified = true;
+      } else {
+        /* Configuration is the same */
+        continue;
+      }
+    }
+
+    LOG(INFO) << __func__ << ", updated context: " << loghex(int(ctx_type))
+              << ", "
+              << (active_context_to_configuration_map[ctx_type] != nullptr
+                      ? active_context_to_configuration_map[ctx_type]->name
+                      : "empty")
+              << " -> " << (new_conf != nullptr ? new_conf->name : "empty");
+    active_context_to_configuration_map[ctx_type] = new_conf;
+  }
+
+  /* Some contexts have changed, return new active context bitset */
+  if (active_contexts_has_been_modified) {
+    active_contexts_mask_ = contexts;
+    return contexts;
+  }
+
+  /* Nothing has changed */
+  return std::nullopt;
+}
+
+bool LeAudioDeviceGroup::ReloadAudioLocations(void) {
+  AudioLocations updated_snk_audio_locations_ =
+      codec_spec_conf::kLeAudioLocationMonoUnspecified;
+  AudioLocations updated_src_audio_locations_ =
+      codec_spec_conf::kLeAudioLocationMonoUnspecified;
+
+  for (const auto& device : leAudioDevices_) {
+    if (device.expired()) continue;
+    updated_snk_audio_locations_ |= device.lock().get()->snk_audio_locations_;
+    updated_src_audio_locations_ |= device.lock().get()->src_audio_locations_;
+  }
+
+  /* Nothing has changed */
+  if ((updated_snk_audio_locations_ == snk_audio_locations_) &&
+      (updated_src_audio_locations_ == src_audio_locations_))
+    return false;
+
+  snk_audio_locations_ = updated_snk_audio_locations_;
+  src_audio_locations_ = updated_src_audio_locations_;
+
+  return true;
+}
+
+bool LeAudioDeviceGroup::IsInTransition(void) {
+  return target_state_ != current_state_;
+}
+
+bool LeAudioDeviceGroup::IsReleasing(void) {
+  return target_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
+}
+
+bool LeAudioDeviceGroup::IsGroupStreamReady(void) {
+  auto iter =
+      std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
+        if (d.expired())
+          return false;
+        else
+          return !(((d.lock()).get())->HaveAllActiveAsesCisEst());
+      });
+
+  return iter == leAudioDevices_.end();
+}
+
+bool LeAudioDeviceGroup::HaveAllActiveDevicesCisDisc(void) {
+  auto iter =
+      std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
+        if (d.expired())
+          return false;
+        else
+          return !(((d.lock()).get())->HaveAllAsesCisDisc());
+      });
+
+  return iter == leAudioDevices_.end();
+}
+
+uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(void) {
+  for (uint8_t id = 0; id < UINT8_MAX; id++) {
+    auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
+                             [id](auto& d) {
+                               if (d.expired())
+                                 return false;
+                               else
+                                 return ((d.lock()).get())->HasCisId(id);
+                             });
+
+    if (iter == leAudioDevices_.end()) return id;
+  }
+
+  return kInvalidCisId;
+}
+
+bool CheckIfStrategySupported(types::LeAudioConfigurationStrategy strategy,
+                              types::AudioLocations audio_locations,
+                              uint8_t requested_channel_count,
+                              uint8_t channel_count_mask) {
+  DLOG(INFO) << __func__ << " strategy: " << (int)strategy
+             << " locations: " << +audio_locations.to_ulong();
+
+  switch (strategy) {
+    case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE:
+      return audio_locations.any();
+    case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE:
+      if ((audio_locations.to_ulong() &
+           codec_spec_conf::kLeAudioLocationAnyLeft) &&
+          (audio_locations.to_ulong() &
+           codec_spec_conf::kLeAudioLocationAnyRight))
+        return true;
+      else
+        return false;
+    case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE:
+      if (!(audio_locations.to_ulong() &
+            codec_spec_conf::kLeAudioLocationAnyLeft) ||
+          !(audio_locations.to_ulong() &
+            codec_spec_conf::kLeAudioLocationAnyRight))
+        return false;
+
+      DLOG(INFO) << __func__ << " requested chan cnt "
+                 << +requested_channel_count
+                 << " chan mask: " << loghex(channel_count_mask);
+
+      /* Return true if requested channel count is set in the channel count
+       * mask. In the channel_count_mask, bit0 is set when 1 channel is
+       * supported.
+       */
+      return ((1 << (requested_channel_count - 1)) & channel_count_mask);
+    default:
+      return false;
+  }
+
+  return false;
+}
+
+/* This method check if group support given audio configuration
+ * requirement for connected devices in the group and available ASEs
+ * (no matter on the ASE state) and for given context type
+ */
+bool LeAudioDeviceGroup::IsConfigurationSupported(
+    const set_configurations::AudioSetConfiguration* audio_set_conf,
+    types::LeAudioContextType context_type) {
+  if (!set_configurations::check_if_may_cover_scenario(
+          audio_set_conf, NumOfConnected(context_type))) {
+    DLOG(INFO) << __func__ << " cannot cover scenario "
+               << static_cast<int>(context_type)
+               << " size of for context type: "
+               << +NumOfConnected(context_type);
+    return false;
+  }
+
+  /* TODO For now: set ase if matching with first pac.
+   * 1) We assume as well that devices will match requirements in order
+   *    e.g. 1 Device - 1 Requirement, 2 Device - 2 Requirement etc.
+   * 2) ASEs should be active only if best (according to priority list) full
+   *    scenarion will be covered.
+   * 3) ASEs should be filled according to performance profile.
+   */
+  for (const auto& ent : (*audio_set_conf).confs) {
+    DLOG(INFO) << __func__
+               << " Looking for configuration: " << audio_set_conf->name
+               << " - "
+               << (ent.direction == types::kLeAudioDirectionSink ? "snk"
+                                                                 : "src");
+
+    uint8_t required_device_cnt = ent.device_cnt;
+    uint8_t max_required_ase_per_dev =
+        ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt);
+    uint8_t active_ase_num = 0;
+    auto strategy = ent.strategy;
+
+    DLOG(INFO) << __func__ << " Number of devices: " << +required_device_cnt
+               << " number of ASEs: " << +ent.ase_cnt
+               << " Max ASE per device: " << +max_required_ase_per_dev
+               << " strategy: " << static_cast<int>(strategy);
+
+    for (auto* device = GetFirstDeviceWithActiveContext(context_type);
+         device != nullptr && required_device_cnt > 0;
+         device = GetNextDeviceWithActiveContext(device, context_type)) {
+      /* Skip if device has ASE configured in this direction already */
+
+      if (device->ases_.empty()) continue;
+
+      if (!device->IsCodecConfigurationSupported(ent.direction, ent.codec))
+        continue;
+
+      int needed_ase = std::min(static_cast<int>(max_required_ase_per_dev),
+                                static_cast<int>(ent.ase_cnt - active_ase_num));
+
+      /* If we required more ASEs per device which means we would like to
+       * create more CISes to one device, we should also check the allocation
+       * if it allows us to do this.
+       */
+
+      types::AudioLocations audio_locations = 0;
+      /* Check direction and if audio location allows to create more cise */
+      if (ent.direction == types::kLeAudioDirectionSink)
+        audio_locations = device->snk_audio_locations_;
+      else
+        audio_locations = device->src_audio_locations_;
+
+      /* TODO Make it no Lc3 specific */
+      if (!CheckIfStrategySupported(
+              strategy, audio_locations,
+              std::get<LeAudioLc3Config>(ent.codec.config).GetChannelCount(),
+              device->GetLc3SupportedChannelCount(ent.direction))) {
+        DLOG(INFO) << __func__ << " insufficient device audio allocation: "
+                   << audio_locations;
+        continue;
+      }
+
+      for (auto& ase : device->ases_) {
+        if (ase.direction != ent.direction) continue;
+
+        active_ase_num++;
+        needed_ase--;
+
+        if (needed_ase == 0) break;
+      }
+
+      required_device_cnt--;
+    }
+
+    if (required_device_cnt > 0) {
+      /* Don't left any active devices if requirements are not met */
+      DLOG(INFO) << __func__ << " could not configure all the devices";
+      return false;
+    }
+  }
+
+  DLOG(INFO) << "Choosed ASE Configuration for group: " << this->group_id_
+             << " configuration: " << audio_set_conf->name;
+  return true;
+}
+
+uint32_t GetFirstLeft(const types::AudioLocations audio_locations) {
+  uint32_t audio_location_ulong = audio_locations.to_ulong();
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeft)
+    return codec_spec_conf::kLeAudioLocationFrontLeft;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBackLeft)
+    return codec_spec_conf::kLeAudioLocationBackLeft;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeftOfCenter)
+    return codec_spec_conf::kLeAudioLocationFrontLeftOfCenter;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationSideLeft)
+    return codec_spec_conf::kLeAudioLocationSideLeft;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopFrontLeft)
+    return codec_spec_conf::kLeAudioLocationTopFrontLeft;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopBackLeft)
+    return codec_spec_conf::kLeAudioLocationTopBackLeft;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopSideLeft)
+    return codec_spec_conf::kLeAudioLocationTopSideLeft;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBottomFrontLeft)
+    return codec_spec_conf::kLeAudioLocationBottomFrontLeft;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeftWide)
+    return codec_spec_conf::kLeAudioLocationFrontLeftWide;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationLeftSurround)
+    return codec_spec_conf::kLeAudioLocationLeftSurround;
+
+  LOG_ASSERT(0) << __func__ << " shall not happen";
+  return 0;
+}
+
+uint32_t GetFirstRight(const types::AudioLocations audio_locations) {
+  uint32_t audio_location_ulong = audio_locations.to_ulong();
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontRight)
+    return codec_spec_conf::kLeAudioLocationFrontRight;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBackRight)
+    return codec_spec_conf::kLeAudioLocationBackRight;
+
+  if (audio_location_ulong &
+      codec_spec_conf::kLeAudioLocationFrontRightOfCenter)
+    return codec_spec_conf::kLeAudioLocationFrontRightOfCenter;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationSideRight)
+    return codec_spec_conf::kLeAudioLocationSideRight;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopFrontRight)
+    return codec_spec_conf::kLeAudioLocationTopFrontRight;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopBackRight)
+    return codec_spec_conf::kLeAudioLocationTopBackRight;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopSideRight)
+    return codec_spec_conf::kLeAudioLocationTopSideRight;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBottomFrontRight)
+    return codec_spec_conf::kLeAudioLocationBottomFrontRight;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontRightWide)
+    return codec_spec_conf::kLeAudioLocationFrontRightWide;
+
+  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationRightSurround)
+    return codec_spec_conf::kLeAudioLocationRightSurround;
+
+  LOG_ASSERT(0) << __func__ << " shall not happen";
+  return 0;
+}
+
+uint32_t PickAudioLocation(types::LeAudioConfigurationStrategy strategy,
+                           types::AudioLocations audio_locations,
+                           types::AudioLocations* group_audio_locations) {
+  DLOG(INFO) << __func__ << " strategy: " << (int)strategy
+             << " locations: " << +audio_locations.to_ulong()
+             << " group locations: " << +group_audio_locations->to_ulong();
+
+  switch (strategy) {
+    case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE:
+    case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE:
+      if ((audio_locations.to_ulong() &
+           codec_spec_conf::kLeAudioLocationAnyLeft) &&
+          !(group_audio_locations->to_ulong() &
+            codec_spec_conf::kLeAudioLocationAnyLeft)) {
+        uint32_t left_location = GetFirstLeft(audio_locations);
+        *group_audio_locations |= left_location;
+        return left_location;
+      }
+
+      if ((audio_locations.to_ulong() &
+           codec_spec_conf::kLeAudioLocationAnyRight) &&
+          !(group_audio_locations->to_ulong() &
+            codec_spec_conf::kLeAudioLocationAnyRight)) {
+        uint32_t right_location = GetFirstRight(audio_locations);
+        *group_audio_locations |= right_location;
+        return right_location;
+      }
+      break;
+    case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE:
+      if ((audio_locations.to_ulong() &
+           codec_spec_conf::kLeAudioLocationAnyLeft) &&
+          (audio_locations.to_ulong() &
+           codec_spec_conf::kLeAudioLocationAnyRight)) {
+        uint32_t left_location = GetFirstLeft(audio_locations);
+        uint32_t right_location = GetFirstRight(audio_locations);
+        *group_audio_locations |= left_location | right_location;
+        return left_location | right_location;
+      }
+      break;
+    default:
+      LOG_ASSERT(0) << " Shall never happen ";
+      return 0;
+  }
+
+  LOG_ASSERT(0) << " Shall never happen ";
+  return 0;
+}
+
+bool LeAudioDevice::ConfigureAses(
+    const le_audio::set_configurations::SetConfiguration& ent,
+    types::LeAudioContextType context_type,
+    uint8_t* number_of_already_active_group_ase,
+    types::AudioLocations& group_snk_audio_locations,
+    types::AudioLocations& group_src_audio_locations, bool reconnect) {
+  struct ase* ase = GetFirstInactiveAse(ent.direction, reconnect);
+  if (!ase) return false;
+
+  uint8_t active_ases = *number_of_already_active_group_ase;
+  uint8_t max_required_ase_per_dev =
+      ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt);
+  le_audio::types::LeAudioConfigurationStrategy strategy = ent.strategy;
+
+  bool is_codec_supported =
+      IsCodecConfigurationSupported(ent.direction, ent.codec);
+  if (!is_codec_supported) return false;
+
+  int needed_ase = std::min((int)(max_required_ase_per_dev),
+                            (int)(ent.ase_cnt - active_ases));
+
+  types::AudioLocations audio_locations = 0;
+  types::AudioLocations* group_audio_locations;
+  /* Check direction and if audio location allows to create more cise */
+  if (ent.direction == types::kLeAudioDirectionSink) {
+    audio_locations = snk_audio_locations_;
+    group_audio_locations = &group_snk_audio_locations;
+  } else {
+    audio_locations = src_audio_locations_;
+    group_audio_locations = &group_src_audio_locations;
+  }
+
+  for (; needed_ase && ase; needed_ase--) {
+    ase->active = true;
+    active_ases++;
+
+    if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)
+      ase->reconfigure = true;
+
+    ase->codec_id = ent.codec.id;
+    /* TODO: find better way to not use LC3 explicitly */
+    ase->codec_config = std::get<LeAudioLc3Config>(ent.codec.config);
+
+    /*Let's choose audio channel allocation if not set */
+    ase->codec_config.audio_channel_allocation =
+        PickAudioLocation(strategy, audio_locations, group_audio_locations);
+
+    ase->max_sdu_size = codec_spec_caps::GetAudioChannelCounts(
+                            ase->codec_config.audio_channel_allocation) *
+                        ase->codec_config.octets_per_codec_frame;
+
+    /* Append additional metadata */
+    std::vector<uint8_t> metadata;
+    metadata.resize(3 + 1);
+    uint8_t* metadata_buf = metadata.data();
+    UINT8_TO_STREAM(metadata_buf, 3);
+    UINT8_TO_STREAM(metadata_buf,
+                    types::kLeAudioMetadataTypeStreamingAudioContext);
+    UINT16_TO_STREAM(metadata_buf, static_cast<uint16_t>(context_type));
+
+    ase->metadata = std::move(metadata);
+
+    DLOG(INFO) << __func__ << " device=" << address_
+               << ", activated ASE id=" << +ase->id
+               << ", direction=" << +ase->direction
+               << ", max_sdu_size=" << +ase->max_sdu_size
+               << ", cis_id=" << +ase->cis_id;
+
+    ase = GetFirstInactiveAse(ent.direction, reconnect);
+  }
+
+  *number_of_already_active_group_ase = active_ases;
+  return true;
+}
+
+/* This method should choose aproperiate ASEs to be active and set a cached
+ * configuration for codec and qos.
+ */
+bool LeAudioDeviceGroup::ConfigureAses(
+    const set_configurations::AudioSetConfiguration* audio_set_conf,
+    types::LeAudioContextType context_type) {
+  if (!set_configurations::check_if_may_cover_scenario(
+          audio_set_conf, NumOfConnected(context_type)))
+    return false;
+
+  /* TODO For now: set ase if matching with first pac.
+   * 1) We assume as well that devices will match requirements in order
+   *    e.g. 1 Device - 1 Requirement, 2 Device - 2 Requirement etc.
+   * 2) ASEs should be active only if best (according to priority list) full
+   *    scenarion will be covered.
+   * 3) ASEs should be filled according to performance profile.
+   */
+
+  types::AudioLocations group_snk_audio_locations = 0;
+  types::AudioLocations group_src_audio_locations = 0;
+
+  for (const auto& ent : (*audio_set_conf).confs) {
+    DLOG(INFO) << __func__
+               << " Looking for requirements: " << audio_set_conf->name << " - "
+               << (ent.direction == 1 ? "snk" : "src");
+
+    uint8_t required_device_cnt = ent.device_cnt;
+    uint8_t max_required_ase_per_dev =
+        ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt);
+    uint8_t active_ase_num = 0;
+    le_audio::types::LeAudioConfigurationStrategy strategy = ent.strategy;
+
+    DLOG(INFO) << __func__ << " Number of devices: " << +required_device_cnt
+               << " number of ASEs: " << +ent.ase_cnt
+               << " Max ASE per device: " << +max_required_ase_per_dev
+               << " strategy: " << (int)strategy;
+
+    for (auto* device = GetFirstDeviceWithActiveContext(context_type);
+         device != nullptr && required_device_cnt > 0;
+         device = GetNextDeviceWithActiveContext(device, context_type)) {
+      /* Skip if device has ASE configured in this direction already */
+      if (device->GetFirstActiveAseByDirection(ent.direction)) continue;
+
+      if (!device->ConfigureAses(ent, context_type, &active_ase_num,
+                                 group_snk_audio_locations,
+                                 group_src_audio_locations))
+        continue;
+
+      required_device_cnt--;
+    }
+
+    if (required_device_cnt > 0) {
+      /* Don't left any active devices if requirements are not met */
+      LOG(ERROR) << __func__ << " could not configure all the devices";
+      Deactivate();
+      return false;
+    }
+  }
+
+  LOG(INFO) << "Choosed ASE Configuration for group: " << this->group_id_
+            << " configuration: " << audio_set_conf->name;
+
+  active_context_type_ = context_type;
+  return true;
+}
+
+const set_configurations::AudioSetConfiguration*
+LeAudioDeviceGroup::GetActiveConfiguration(void) {
+  return active_context_to_configuration_map[active_context_type_];
+}
+AudioContexts LeAudioDeviceGroup::GetActiveContexts(void) {
+  return active_contexts_mask_;
+}
+
+std::optional<LeAudioCodecConfiguration>
+LeAudioDeviceGroup::GetCodecConfigurationByDirection(
+    types::LeAudioContextType group_context_type, uint8_t direction) {
+  const set_configurations::AudioSetConfiguration* audio_set_conf =
+      active_context_to_configuration_map[group_context_type];
+  LeAudioCodecConfiguration group_config = {0, 0, 0, 0};
+  if (!audio_set_conf) return std::nullopt;
+
+  for (const auto& conf : audio_set_conf->confs) {
+    if (conf.direction != direction) continue;
+
+    if (group_config.sample_rate != 0 &&
+        conf.codec.GetConfigSamplingFrequency() != group_config.sample_rate) {
+      LOG(WARNING) << __func__
+                   << ", stream configuration could not be "
+                      "determined (sampling frequency differs) for direction: "
+                   << loghex(direction);
+      return std::nullopt;
+    }
+    group_config.sample_rate = conf.codec.GetConfigSamplingFrequency();
+
+    if (group_config.data_interval_us != 0 &&
+        conf.codec.GetConfigDataIntervalUs() != group_config.data_interval_us) {
+      LOG(WARNING) << __func__
+                   << ", stream configuration could not be "
+                      "determined (data interval differs) for direction: "
+                   << loghex(direction);
+      return std::nullopt;
+    }
+    group_config.data_interval_us = conf.codec.GetConfigDataIntervalUs();
+
+    if (group_config.bits_per_sample != 0 &&
+        conf.codec.GetConfigBitsPerSample() != group_config.bits_per_sample) {
+      LOG(WARNING) << __func__
+                   << ", stream configuration could not be "
+                      "determined (bits per sample differs) for direction: "
+                   << loghex(direction);
+      return std::nullopt;
+    }
+    group_config.bits_per_sample = conf.codec.GetConfigBitsPerSample();
+
+    group_config.num_channels +=
+        conf.codec.GetConfigChannelCount() * conf.device_cnt;
+  }
+
+  if (group_config.IsInvalid()) return std::nullopt;
+
+  return group_config;
+}
+
+types::LeAudioContextType LeAudioDeviceGroup::GetCurrentContextType(void) {
+  return active_context_type_;
+}
+
+const set_configurations::AudioSetConfiguration*
+LeAudioDeviceGroup::FindFirstSupportedConfiguration(
+    LeAudioContextType context_type) {
+  const set_configurations::AudioSetConfigurations* confs =
+      set_configurations::get_confs_by_type(context_type);
+
+  DLOG(INFO) << __func__ << " context type: " << (int)context_type
+             << " number of connected devices: " << NumOfConnected();
+
+  /* Filter out device set for all scenarios */
+  if (!set_configurations::check_if_may_cover_scenario(confs,
+                                                       NumOfConnected())) {
+    LOG(ERROR) << __func__ << ", group is unable to cover scenario";
+    return nullptr;
+  }
+
+  /* Filter out device set for each end every scenario */
+
+  for (const auto& conf : *confs) {
+    if (IsConfigurationSupported(conf, context_type)) {
+      DLOG(INFO) << __func__ << " found: " << conf->name;
+      return conf;
+    }
+  }
+
+  return nullptr;
+}
+
+/* This method should choose aproperiate ASEs to be active and set a cached
+ * configuration for codec and qos.
+ */
+bool LeAudioDeviceGroup::Configure(LeAudioContextType context_type) {
+  const set_configurations::AudioSetConfiguration* conf =
+      active_context_to_configuration_map[context_type];
+
+  DLOG(INFO) << __func__;
+
+  if (!conf) {
+    LOG(ERROR) << __func__ << ", requested context type: "
+               << loghex(static_cast<uint16_t>(context_type))
+               << ", is in mismatch with cached active contexts";
+    return false;
+  }
+
+  DLOG(INFO) << __func__ << " setting context type: " << int(context_type);
+
+  if (!ConfigureAses(conf, context_type)) {
+    LOG(ERROR) << __func__ << ", requested pick ASE config context type: "
+               << loghex(static_cast<uint16_t>(context_type))
+               << ", is in mismatch with cached active contexts";
+    return false;
+  }
+  return true;
+}
+
+LeAudioDeviceGroup::~LeAudioDeviceGroup(void) { this->Cleanup(); }
+void LeAudioDeviceGroup::Dump(int fd) {
+  std::stringstream stream;
+  auto* active_conf = GetActiveConfiguration();
+
+  stream << "    == Group id: " << group_id_ << " == \n"
+         << "      state: " << GetState() << "\n"
+         << "      target state: " << GetTargetState() << "\n"
+         << "      number of devices: " << Size() << "\n"
+         << "      number of connected devices: " << NumOfConnected() << "\n"
+         << "      active context types: "
+         << loghex(GetActiveContexts().to_ulong()) << "\n"
+         << "      current context type: "
+         << static_cast<int>(GetCurrentContextType()) << "\n"
+         << "      active stream configuration name: "
+         << (active_conf ? active_conf->name : " not set") << "\n"
+         << "    Last used stream configuration: \n"
+         << "      valid: " << (stream_conf.valid ? " Yes " : " No") << "\n"
+         << "      codec id : " << +(stream_conf.id.coding_format) << "\n"
+         << "      name: "
+         << (stream_conf.conf != nullptr ? stream_conf.conf->name : " null ")
+         << "\n"
+         << "      number of sinks in the configuration "
+         << stream_conf.sink_num_of_devices << "\n"
+         << "      number of sink_streams connected: "
+         << stream_conf.sink_streams.size() << "\n"
+         << "      number of sources in the configuration "
+         << stream_conf.source_num_of_devices << "\n"
+         << "      number of source_streams connected: "
+         << stream_conf.source_streams.size() << "\n"
+         << "      === devices: ===\n";
+
+  dprintf(fd, "%s", stream.str().c_str());
+
+  for (const auto& device_iter : leAudioDevices_) {
+    device_iter.lock()->Dump(fd);
+  }
+}
+
+/* LeAudioDevice Class methods implementation */
+void LeAudioDevice::ClearPACs(void) {
+  snk_pacs_.clear();
+  src_pacs_.clear();
+}
+
+LeAudioDevice::~LeAudioDevice(void) {
+  alarm_free(link_quality_timer);
+  this->ClearPACs();
+}
+
+void LeAudioDevice::RegisterPACs(
+    std::vector<struct types::acs_ac_record>* pac_db,
+    std::vector<struct types::acs_ac_record>* pac_recs) {
+  /* Clear PAC database for characteristic in case if re-read, indicated */
+  if (!pac_db->empty()) {
+    DLOG(INFO) << __func__ << ", upgrade PACs for characteristic";
+    pac_db->clear();
+  }
+
+  /* TODO wrap this logging part with debug flag */
+  for (const struct types::acs_ac_record& pac : *pac_recs) {
+    LOG(INFO) << "Registering PAC"
+              << "\n\tCoding format: " << loghex(pac.codec_id.coding_format)
+              << "\n\tVendor codec company ID: "
+              << loghex(pac.codec_id.vendor_company_id)
+              << "\n\tVendor codec ID: " << loghex(pac.codec_id.vendor_codec_id)
+              << "\n\tCodec spec caps:\n"
+              << pac.codec_spec_caps.ToString() << "\n\tMetadata: "
+              << base::HexEncode(pac.metadata.data(), pac.metadata.size());
+  }
+
+  pac_db->insert(pac_db->begin(), pac_recs->begin(), pac_recs->end());
+}
+
+struct ase* LeAudioDevice::GetAseByValHandle(uint16_t val_hdl) {
+  auto iter = std::find_if(
+      ases_.begin(), ases_.end(),
+      [&val_hdl](const auto& ase) { return ase.hdls.val_hdl == val_hdl; });
+
+  return (iter == ases_.end()) ? nullptr : &(*iter);
+}
+
+struct ase* LeAudioDevice::GetFirstInactiveAseWithState(uint8_t direction,
+                                                        AseState state) {
+  auto iter = std::find_if(
+      ases_.begin(), ases_.end(), [direction, state](const auto& ase) {
+        return ((ase.direction == direction) && (ase.state == state));
+      });
+
+  return (iter == ases_.end()) ? nullptr : &(*iter);
+}
+
+struct ase* LeAudioDevice::GetFirstActiveAse(void) {
+  auto iter = std::find_if(ases_.begin(), ases_.end(),
+                           [](const auto& ase) { return ase.active; });
+
+  return (iter == ases_.end()) ? nullptr : &(*iter);
+}
+
+struct ase* LeAudioDevice::GetFirstActiveAseByDirection(uint8_t direction) {
+  auto iter =
+      std::find_if(ases_.begin(), ases_.end(), [direction](const auto& ase) {
+        return (ase.active && (ase.direction == direction));
+      });
+
+  return (iter == ases_.end()) ? nullptr : &(*iter);
+}
+
+struct ase* LeAudioDevice::GetNextActiveAseWithSameDirection(
+    struct ase* base_ase) {
+  auto iter = std::find_if(ases_.begin(), ases_.end(),
+                           [&base_ase](auto& ase) { return base_ase == &ase; });
+
+  /* Invalid ase given */
+  if (iter == ases_.end() || std::distance(iter, ases_.end()) < 1)
+    return nullptr;
+
+  iter =
+      std::find_if(std::next(iter, 1), ases_.end(), [&iter](const auto& ase) {
+        return ase.active && (*iter).direction == ase.direction;
+      });
+
+  return (iter == ases_.end()) ? nullptr : &(*iter);
+}
+
+struct ase* LeAudioDevice::GetFirstActiveAseByDataPathState(
+    types::AudioStreamDataPathState state) {
+  auto iter =
+      std::find_if(ases_.begin(), ases_.end(), [state](const auto& ase) {
+        return (ase.active && (ase.data_path_state == state));
+      });
+
+  return (iter == ases_.end()) ? nullptr : &(*iter);
+}
+
+struct ase* LeAudioDevice::GetFirstInactiveAse(uint8_t direction,
+                                               bool reconnect) {
+  auto iter = std::find_if(ases_.begin(), ases_.end(),
+                           [direction, reconnect](const auto& ase) {
+                             if (ase.active || (ase.direction != direction))
+                               return false;
+
+                             if (!reconnect) return true;
+
+                             return (ase.cis_id != kInvalidCisId);
+                           });
+
+  return (iter == ases_.end()) ? nullptr : &(*iter);
+}
+
+struct ase* LeAudioDevice::GetNextActiveAse(struct ase* base_ase) {
+  auto iter = std::find_if(ases_.begin(), ases_.end(),
+                           [&base_ase](auto& ase) { return base_ase == &ase; });
+
+  /* Invalid ase given */
+  if (iter == ases_.end() || std::distance(iter, ases_.end()) < 1)
+    return nullptr;
+
+  iter = std::find_if(std::next(iter, 1), ases_.end(),
+                      [](const auto& ase) { return ase.active; });
+
+  return (iter == ases_.end()) ? nullptr : &(*iter);
+}
+
+struct ase* LeAudioDevice::GetAseToMatchBidirectionCis(struct ase* base_ase) {
+  auto iter = std::find_if(ases_.begin(), ases_.end(), [&base_ase](auto& ase) {
+    return (base_ase->cis_conn_hdl == ase.cis_conn_hdl) &&
+           (base_ase->direction != ase.direction);
+  });
+  return (iter == ases_.end()) ? nullptr : &(*iter);
+}
+
+BidirectAsesPair LeAudioDevice::GetAsesByCisConnHdl(uint16_t conn_hdl) {
+  BidirectAsesPair ases;
+
+  for (auto& ase : ases_) {
+    if (ase.cis_conn_hdl == conn_hdl) {
+      if (ase.direction == types::kLeAudioDirectionSink) {
+        ases.sink = &ase;
+      } else {
+        ases.source = &ase;
+      }
+    }
+  }
+
+  return ases;
+}
+
+BidirectAsesPair LeAudioDevice::GetAsesByCisId(uint8_t cis_id) {
+  BidirectAsesPair ases;
+
+  for (auto& ase : ases_) {
+    if (ase.cis_id == cis_id) {
+      if (ase.direction == types::kLeAudioDirectionSink) {
+        ases.sink = &ase;
+      } else {
+        ases.source = &ase;
+      }
+    }
+  }
+
+  return ases;
+}
+
+bool LeAudioDevice::HaveActiveAse(void) {
+  auto iter = std::find_if(ases_.begin(), ases_.end(),
+                           [](const auto& ase) { return ase.active; });
+
+  return iter != ases_.end();
+}
+
+bool LeAudioDevice::HaveAnyUnconfiguredAses(void) {
+  /* In configuring state when active in Idle or Configured and reconfigure */
+  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
+    if (!ase.active) return false;
+
+    if (ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE ||
+        ((ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) &&
+         ase.reconfigure))
+      return true;
+
+    return false;
+  });
+
+  return iter != ases_.end();
+}
+
+bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) {
+  auto iter = std::find_if(
+      ases_.begin(), ases_.end(),
+      [&state](const auto& ase) { return ase.active && (ase.state != state); });
+
+  return iter == ases_.end();
+}
+
+bool LeAudioDevice::IsReadyToCreateStream(void) {
+  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
+    if (!ase.active) return false;
+
+    if (ase.direction == types::kLeAudioDirectionSink &&
+        (ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
+         ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING))
+      return true;
+
+    if (ase.direction == types::kLeAudioDirectionSource &&
+        ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING)
+      return true;
+
+    return false;
+  });
+
+  return iter == ases_.end();
+}
+
+bool LeAudioDevice::IsReadyToSuspendStream(void) {
+  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
+    if (!ase.active) return false;
+
+    if (ase.direction == types::kLeAudioDirectionSink &&
+        ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)
+      return true;
+
+    if (ase.direction == types::kLeAudioDirectionSource &&
+        ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING)
+      return true;
+
+    return false;
+  });
+
+  return iter == ases_.end();
+}
+
+bool LeAudioDevice::HaveAllActiveAsesCisEst(void) {
+  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
+    return ase.active &&
+           (ase.data_path_state != AudioStreamDataPathState::CIS_ESTABLISHED);
+  });
+
+  return iter == ases_.end();
+}
+
+bool LeAudioDevice::HaveAllAsesCisDisc(void) {
+  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
+    return ase.active &&
+           (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED);
+  });
+
+  return iter == ases_.end();
+}
+
+bool LeAudioDevice::HasCisId(uint8_t id) {
+  struct ase* ase = GetFirstActiveAse();
+
+  while (ase) {
+    if (ase->cis_id == id) return true;
+    ase = GetNextActiveAse(ase);
+  }
+
+  return false;
+}
+
+uint8_t LeAudioDevice::GetMatchingBidirectionCisId(
+    const struct types::ase* base_ase) {
+  for (auto& ase : ases_) {
+    auto& cis = ase.cis_id;
+    if (!ase.active) continue;
+
+    int num_cises =
+        std::count_if(ases_.begin(), ases_.end(), [&cis](const auto& iter_ase) {
+          return iter_ase.active && iter_ase.cis_id == cis;
+        });
+
+    /*
+     * If there is only one ASE for device with unique CIS ID and opposite to
+     * direction - it may be bi-directional/completive.
+     */
+    if (num_cises == 1 &&
+        ((base_ase->direction == types::kLeAudioDirectionSink &&
+          ase.direction == types::kLeAudioDirectionSource) ||
+         (base_ase->direction == types::kLeAudioDirectionSource &&
+          ase.direction == types::kLeAudioDirectionSink))) {
+      return ase.cis_id;
+    }
+  }
+
+  return kInvalidCisId;
+}
+
+uint8_t LeAudioDevice::GetLc3SupportedChannelCount(uint8_t direction) {
+  auto& pacs =
+      direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_;
+
+  if (pacs.size() == 0) {
+    LOG(ERROR) << __func__ << " missing PAC for direction " << +direction;
+    return 0;
+  }
+
+  for (const auto& pac_tuple : pacs) {
+    /* Get PAC records from tuple as second element from tuple */
+    auto& pac_recs = std::get<1>(pac_tuple);
+
+    for (const auto pac : pac_recs) {
+      if (pac.codec_id.coding_format != types::kLeAudioCodingFormatLC3)
+        continue;
+
+      auto supported_channel_count_ltv = pac.codec_spec_caps.Find(
+          codec_spec_caps::kLeAudioCodecLC3TypeAudioChannelCounts);
+
+      return VEC_UINT8_TO_UINT8(supported_channel_count_ltv.value());
+    };
+  }
+
+  return 0;
+}
+
+bool LeAudioDevice::IsCodecConfigurationSupported(
+    uint8_t direction, const CodecCapabilitySetting& codec_capability_setting) {
+  auto& pacs =
+      direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_;
+
+  if (pacs.size() == 0) {
+    LOG(ERROR) << __func__ << " missing PAC for direction " << +direction;
+    return false;
+  }
+
+  /* TODO: Validate channel locations */
+
+  for (const auto& pac_tuple : pacs) {
+    /* Get PAC records from tuple as second element from tuple */
+    auto& pac_recs = std::get<1>(pac_tuple);
+
+    for (const auto pac : pac_recs) {
+      if (!IsCodecCapabilitySettingSupported(pac, codec_capability_setting))
+        continue;
+
+      return true;
+    };
+  }
+
+  /* Doesn't match required configuration with any PAC */
+  return false;
+}
+
+/**
+ * Returns supported PHY's bitfield
+ */
+uint8_t LeAudioDevice::GetPhyBitmask(void) {
+  uint8_t phy_bitfield = kIsoCigPhy1M;
+
+  if (BTM_IsPhy2mSupported(address_, BT_TRANSPORT_LE))
+    phy_bitfield |= kIsoCigPhy2M;
+
+  return phy_bitfield;
+}
+
+void LeAudioDevice::SetSupportedContexts(AudioContexts snk_contexts,
+                                         AudioContexts src_contexts) {
+  supp_snk_context_ = snk_contexts;
+  supp_src_context_ = src_contexts;
+}
+
+void LeAudioDevice::Dump(int fd) {
+  std::stringstream stream;
+  stream << "        address: " << address_ << "\n"
+         << (conn_id_ == GATT_INVALID_CONN_ID ? "          Not connected "
+                                              : "          Connected conn_id =")
+         << (conn_id_ == GATT_INVALID_CONN_ID ? "" : std::to_string(conn_id_))
+         << "\n"
+         << "          set member: " << (csis_member_ ? " Yes" : " No") << "\n"
+         << "          known_service_handles_: " << known_service_handles_
+         << "\n"
+         << "          notify_connected_after_read_: "
+         << notify_connected_after_read_ << "\n"
+         << "          removing_device_: " << removing_device_ << "\n"
+         << "          first_connection_: " << first_connection_ << "\n"
+         << "          encrypted_: " << encrypted_ << "\n"
+         << "          connecting_actively_: " << connecting_actively_ << "\n";
+
+  dprintf(fd, "%s", stream.str().c_str());
+}
+
+AudioContexts LeAudioDevice::GetAvailableContexts(void) {
+  return avail_snk_contexts_ | avail_src_contexts_;
+}
+
+/* Returns XOR of updated sink and source bitset context types */
+AudioContexts LeAudioDevice::SetAvailableContexts(AudioContexts snk_contexts,
+                                                  AudioContexts src_contexts) {
+  AudioContexts updated_contexts;
+
+  updated_contexts = snk_contexts ^ avail_snk_contexts_;
+  updated_contexts |= src_contexts ^ avail_src_contexts_;
+
+  DLOG(INFO) << __func__
+             << "\n\t avail_snk_contexts_: " << avail_snk_contexts_.to_string()
+             << "\n\t avail_src_contexts_: " << avail_src_contexts_.to_string()
+             << "\n\t snk_contexts:" << snk_contexts.to_string()
+             << "\n\t src_contexts: " << src_contexts.to_string()
+             << "\n\t updated_contexts: " << updated_contexts.to_string();
+
+  avail_snk_contexts_ = snk_contexts;
+  avail_src_contexts_ = src_contexts;
+
+  return updated_contexts;
+}
+
+void LeAudioDevice::DeactivateAllAses(void) {
+  /* Just clear states and keep previous configuration for use
+   * in case device will get reconnected
+   */
+  for (auto& ase : ases_) {
+    if (ase.active) {
+      ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
+      ase.data_path_state = AudioStreamDataPathState::IDLE;
+      ase.active = false;
+    }
+  }
+}
+
+LeAudioDeviceGroup* LeAudioDeviceGroups::Add(int group_id) {
+  /* Get first free group id */
+  if (FindById(group_id)) {
+    LOG(ERROR) << __func__
+               << ", group already exists, id: " << loghex(group_id);
+    return nullptr;
+  }
+
+  return (groups_.emplace_back(std::make_unique<LeAudioDeviceGroup>(group_id)))
+      .get();
+}
+
+void LeAudioDeviceGroups::Remove(int group_id) {
+  auto iter = std::find_if(
+      groups_.begin(), groups_.end(),
+      [&group_id](auto const& group) { return group->group_id_ == group_id; });
+
+  if (iter == groups_.end()) {
+    LOG(ERROR) << __func__ << ", no such group_id: " << group_id;
+    return;
+  }
+
+  groups_.erase(iter);
+}
+
+LeAudioDeviceGroup* LeAudioDeviceGroups::FindById(int group_id) {
+  auto iter = std::find_if(
+      groups_.begin(), groups_.end(),
+      [&group_id](auto const& group) { return group->group_id_ == group_id; });
+
+  return (iter == groups_.end()) ? nullptr : iter->get();
+}
+
+void LeAudioDeviceGroups::Cleanup(void) {
+  for (auto& g : groups_) {
+    g->Cleanup();
+  }
+
+  groups_.clear();
+}
+
+void LeAudioDeviceGroups::Dump(int fd) {
+  for (auto& g : groups_) {
+    g->Dump(fd);
+  }
+}
+
+bool LeAudioDeviceGroups::IsAnyInTransition(void) {
+  for (auto& g : groups_) {
+    if (g->IsInTransition()) {
+      DLOG(INFO) << __func__ << " group: " << g->group_id_
+                 << " is in transition";
+      return true;
+    }
+  }
+  return false;
+}
+
+size_t LeAudioDeviceGroups::Size() { return (groups_.size()); }
+
+std::vector<int> LeAudioDeviceGroups::GetGroupsIds(void) {
+  std::vector<int> result;
+
+  for (auto const& group : groups_) {
+    result.push_back(group->group_id_);
+  }
+
+  return result;
+}
+
+/* LeAudioDevices Class methods implementation */
+void LeAudioDevices::Add(const RawAddress& address, bool first_connection,
+                         int group_id) {
+  auto device = FindByAddress(address);
+  if (device != nullptr) {
+    LOG(ERROR) << __func__ << ", address: " << address
+               << " is already assigned to group: " << device->group_id_;
+    return;
+  }
+
+  leAudioDevices_.emplace_back(
+      std::make_shared<LeAudioDevice>(address, first_connection, group_id));
+}
+
+void LeAudioDevices::Remove(const RawAddress& address) {
+  auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
+                           [&address](auto const& leAudioDevice) {
+                             return leAudioDevice->address_ == address;
+                           });
+
+  if (iter == leAudioDevices_.end()) {
+    LOG(ERROR) << __func__ << ", no such address: " << address;
+    return;
+  }
+
+  leAudioDevices_.erase(iter);
+}
+
+LeAudioDevice* LeAudioDevices::FindByAddress(const RawAddress& address) {
+  auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
+                           [&address](auto const& leAudioDevice) {
+                             return leAudioDevice->address_ == address;
+                           });
+
+  return (iter == leAudioDevices_.end()) ? nullptr : iter->get();
+}
+
+std::shared_ptr<LeAudioDevice> LeAudioDevices::GetByAddress(
+    const RawAddress& address) {
+  auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
+                           [&address](auto const& leAudioDevice) {
+                             return leAudioDevice->address_ == address;
+                           });
+
+  return (iter == leAudioDevices_.end()) ? nullptr : *iter;
+}
+
+LeAudioDevice* LeAudioDevices::FindByConnId(uint16_t conn_id) {
+  auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
+                           [&conn_id](auto const& leAudioDevice) {
+                             return leAudioDevice->conn_id_ == conn_id;
+                           });
+
+  return (iter == leAudioDevices_.end()) ? nullptr : iter->get();
+}
+
+LeAudioDevice* LeAudioDevices::FindByCisConnHdl(const uint16_t conn_hdl) {
+  auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
+                           [&conn_hdl](auto& d) {
+                             LeAudioDevice* dev;
+                             BidirectAsesPair ases;
+
+                             dev = d.get();
+                             ases = dev->GetAsesByCisConnHdl(conn_hdl);
+                             if (ases.sink || ases.source)
+                               return true;
+                             else
+                               return false;
+                           });
+
+  if (iter == leAudioDevices_.end()) return nullptr;
+
+  return iter->get();
+}
+
+size_t LeAudioDevices::Size() { return (leAudioDevices_.size()); }
+
+void LeAudioDevices::Dump(int fd, int group_id) {
+  for (auto const& device : leAudioDevices_) {
+    if (device->group_id_ == group_id) {
+      device->Dump(fd);
+    }
+  }
+}
+
+void LeAudioDevices::Cleanup(void) { leAudioDevices_.clear(); }
+
+}  // namespace le_audio
diff --git a/system/bta/le_audio/devices.h b/system/bta/le_audio/devices.h
new file mode 100644
index 0000000000000000000000000000000000000000..464afcd1421caae978fbc1d445855ad07244121c
--- /dev/null
+++ b/system/bta/le_audio/devices.h
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA
+ * - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <optional>
+#include <tuple>
+#include <vector>
+
+#include "bt_types.h"
+#include "bta_groups.h"
+#include "btm_iso_api_types.h"
+#include "client_audio.h"
+#include "gatt_api.h"
+#include "le_audio_types.h"
+#include "osi/include/alarm.h"
+#include "raw_address.h"
+
+namespace le_audio {
+/* Class definitions */
+
+/* LeAudioDevice class represents GATT server device with ASCS, PAC services as
+ * mandatory. Device may contain multiple ASEs, PACs, audio locations. ASEs from
+ * multiple devices may be formed in group.
+ *
+ * Device is created after connection or after storage restoration.
+ *
+ * Active device means that device has at least one ASE which will participate
+ * in any state transition of state machine. ASEs and devices will be activated
+ * according to requested by upper context type.
+ */
+class LeAudioDevice {
+ public:
+  RawAddress address_;
+
+  bool known_service_handles_;
+  bool notify_connected_after_read_;
+  bool removing_device_;
+
+  /* we are making active attempt to connect to this device, 'direct connect'.
+   * This is true only during initial phase of first connection. */
+  bool first_connection_;
+  bool connecting_actively_;
+  uint16_t conn_id_;
+  bool encrypted_;
+  int group_id_;
+  bool csis_member_;
+
+  uint8_t audio_directions_;
+  types::AudioLocations snk_audio_locations_;
+  types::AudioLocations src_audio_locations_;
+
+  types::PublishedAudioCapabilities snk_pacs_;
+  types::PublishedAudioCapabilities src_pacs_;
+
+  struct types::hdl_pair snk_audio_locations_hdls_;
+  struct types::hdl_pair src_audio_locations_hdls_;
+  struct types::hdl_pair audio_avail_hdls_;
+  struct types::hdl_pair audio_supp_cont_hdls_;
+  std::vector<struct types::ase> ases_;
+  struct types::hdl_pair ctp_hdls_;
+
+  alarm_t* link_quality_timer;
+  uint16_t link_quality_timer_data;
+
+  LeAudioDevice(const RawAddress& address_, bool first_connection,
+                int group_id = bluetooth::groups::kGroupUnknown)
+      : address_(address_),
+        known_service_handles_(false),
+        notify_connected_after_read_(false),
+        removing_device_(false),
+        first_connection_(first_connection),
+        connecting_actively_(first_connection),
+        conn_id_(GATT_INVALID_CONN_ID),
+        encrypted_(false),
+        group_id_(group_id),
+        csis_member_(false),
+        link_quality_timer(nullptr) {}
+  ~LeAudioDevice(void);
+
+  void ClearPACs(void);
+  void RegisterPACs(std::vector<struct types::acs_ac_record>* apr_db,
+                    std::vector<struct types::acs_ac_record>* apr);
+  struct types::ase* GetAseByValHandle(uint16_t val_hdl);
+  struct types::ase* GetFirstActiveAse(void);
+  struct types::ase* GetFirstActiveAseByDirection(uint8_t direction);
+  struct types::ase* GetNextActiveAseWithSameDirection(
+      struct types::ase* base_ase);
+  struct types::ase* GetFirstActiveAseByDataPathState(
+      types::AudioStreamDataPathState state);
+  struct types::ase* GetFirstInactiveAse(uint8_t direction,
+                                         bool reconnect = false);
+  struct types::ase* GetFirstInactiveAseWithState(uint8_t direction,
+                                                  types::AseState state);
+  struct types::ase* GetNextActiveAse(struct types::ase* ase);
+  struct types::ase* GetAseToMatchBidirectionCis(struct types::ase* ase);
+  types::BidirectAsesPair GetAsesByCisConnHdl(uint16_t conn_hdl);
+  types::BidirectAsesPair GetAsesByCisId(uint8_t cis_id);
+  bool HaveActiveAse(void);
+  bool HaveAllActiveAsesSameState(types::AseState state);
+  bool HaveAnyUnconfiguredAses(void);
+  bool IsReadyToCreateStream(void);
+  bool IsReadyToSuspendStream(void);
+  bool HaveAllActiveAsesCisEst(void);
+  bool HaveAllAsesCisDisc(void);
+  bool HasCisId(uint8_t id);
+  uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase);
+  bool IsCodecConfigurationSupported(
+      uint8_t direction, const set_configurations::CodecCapabilitySetting&
+                             codec_capability_setting);
+  uint8_t GetLc3SupportedChannelCount(uint8_t direction);
+  uint8_t GetPhyBitmask(void);
+  bool ConfigureAses(const le_audio::set_configurations::SetConfiguration& ent,
+                     types::LeAudioContextType context_type,
+                     uint8_t* number_of_already_active_group_ase,
+                     types::AudioLocations& group_snk_audio_locations,
+                     types::AudioLocations& group_src_audio_locations,
+                     bool reconnect = false);
+  void SetSupportedContexts(types::AudioContexts snk_contexts,
+                            types::AudioContexts src_contexts);
+  types::AudioContexts GetAvailableContexts(void);
+  types::AudioContexts SetAvailableContexts(types::AudioContexts snk_cont_val,
+                                            types::AudioContexts src_cont_val);
+  void DeactivateAllAses(void);
+  void Dump(int fd);
+
+ private:
+  types::AudioContexts avail_snk_contexts_;
+  types::AudioContexts avail_src_contexts_;
+  types::AudioContexts supp_snk_context_;
+  types::AudioContexts supp_src_context_;
+};
+
+/* LeAudioDevices class represents a wraper helper over all devices in le audio
+ * implementation. It allows to operate on device from a list (vector container)
+ * using determinants like address, connection id etc.
+ */
+class LeAudioDevices {
+ public:
+  void Add(const RawAddress& address, bool first_connection,
+           int group_id = bluetooth::groups::kGroupUnknown);
+  void Remove(const RawAddress& address);
+  LeAudioDevice* FindByAddress(const RawAddress& address);
+  std::shared_ptr<LeAudioDevice> GetByAddress(const RawAddress& address);
+  LeAudioDevice* FindByConnId(uint16_t conn_id);
+  LeAudioDevice* FindByCisConnHdl(const uint16_t conn_hdl);
+  size_t Size(void);
+  void Dump(int fd, int group_id);
+  void Cleanup(void);
+
+ private:
+  std::vector<std::shared_ptr<LeAudioDevice>> leAudioDevices_;
+};
+
+/* LeAudioDeviceGroup class represents group of LeAudioDevices and allows to
+ * perform operations on them. Group states are ASE states due to nature of
+ * group which operates finally of ASEs.
+ *
+ * Group is created after adding a node to new group id (which is not on list).
+ */
+
+class LeAudioDeviceGroup {
+ public:
+  const int group_id_;
+  bool cig_created_;
+
+  struct stream_configuration stream_conf;
+
+  uint8_t audio_directions_;
+  types::AudioLocations snk_audio_locations_;
+  types::AudioLocations src_audio_locations_;
+
+  /* The below is used only for Coordinate sets */
+  types::CsisDiscoveryState csis_discovery_state_;
+  explicit LeAudioDeviceGroup(const int group_id)
+      : group_id_(group_id),
+        cig_created_(false),
+        transport_latency_mtos_(0),
+        transport_latency_stom_(0),
+        target_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE),
+        current_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
+    stream_conf.valid = false;
+    stream_conf.conf = nullptr;
+    stream_conf.sink_num_of_devices = 0;
+    stream_conf.source_num_of_devices = 0;
+  }
+  ~LeAudioDeviceGroup(void);
+
+  void AddNode(const std::shared_ptr<LeAudioDevice>& leAudioDevice);
+  void RemoveNode(const std::shared_ptr<LeAudioDevice>& leAudioDevice);
+  bool IsEmpty(void);
+  bool IsAnyDeviceConnected(void);
+  int Size(void);
+  int NumOfConnected(
+      types::LeAudioContextType context_type = types::LeAudioContextType::RFU);
+  void Deactivate(void);
+  void Cleanup(void);
+  LeAudioDevice* GetFirstDevice(void);
+  LeAudioDevice* GetFirstDeviceWithActiveContext(
+      types::LeAudioContextType context_type);
+  LeAudioDevice* GetNextDevice(LeAudioDevice* leAudioDevice);
+  LeAudioDevice* GetNextDeviceWithActiveContext(
+      LeAudioDevice* leAudioDevice, types::LeAudioContextType context_type);
+  LeAudioDevice* GetFirstActiveDevice(void);
+  LeAudioDevice* GetNextActiveDevice(LeAudioDevice* leAudioDevice);
+  bool IsDeviceInTheGroup(LeAudioDevice* leAudioDevice);
+  bool HaveAllActiveDevicesAsesTheSameState(types::AseState state);
+  bool IsGroupStreamReady(void);
+  bool HaveAllActiveDevicesCisDisc(void);
+  uint8_t GetFirstFreeCisId(void);
+  bool Configure(types::LeAudioContextType context_type);
+  bool SetContextType(types::LeAudioContextType context_type);
+  types::LeAudioContextType GetContextType(void);
+  uint32_t GetSduInterval(uint8_t direction);
+  uint8_t GetSCA(void);
+  uint8_t GetPacking(void);
+  uint8_t GetFraming(void);
+  uint8_t GetTargetLatency(void);
+  uint16_t GetMaxTransportLatencyStom(void);
+  uint16_t GetMaxTransportLatencyMtos(void);
+  void SetTransportLatency(uint8_t direction, uint16_t transport_latency);
+  uint8_t GetPhyBitmask(uint8_t direction);
+  uint8_t GetTargetPhy(uint8_t direction);
+  bool GetPresentationDelay(uint32_t* delay, uint8_t direction);
+  uint16_t GetRemoteDelay(uint8_t direction);
+  std::optional<types::AudioContexts> UpdateActiveContextsMap(
+      types::AudioContexts contexts);
+  std::optional<types::AudioContexts> UpdateActiveContextsMap(void);
+  bool ReloadAudioLocations(void);
+  const set_configurations::AudioSetConfiguration* GetActiveConfiguration(void);
+  types::LeAudioContextType GetCurrentContextType(void);
+  types::AudioContexts GetActiveContexts(void);
+  std::optional<LeAudioCodecConfiguration> GetCodecConfigurationByDirection(
+      types::LeAudioContextType group_context_type, uint8_t direction);
+
+  inline types::AseState GetState(void) const { return current_state_; }
+  void SetState(types::AseState state) {
+    LOG(INFO) << __func__ << " current state: " << current_state_
+              << " new state: " << state;
+    current_state_ = state;
+  }
+
+  inline types::AseState GetTargetState(void) const { return target_state_; }
+  void SetTargetState(types::AseState state) {
+    LOG(INFO) << __func__ << " target state: " << target_state_
+              << " new target state: " << state;
+    target_state_ = state;
+  }
+
+  bool IsInTransition(void);
+  bool IsReleasing(void);
+  void Dump(int fd);
+
+ private:
+  uint16_t transport_latency_mtos_;
+  uint16_t transport_latency_stom_;
+
+  const set_configurations::AudioSetConfiguration*
+  FindFirstSupportedConfiguration(types::LeAudioContextType context_type);
+  bool ConfigureAses(
+      const set_configurations::AudioSetConfiguration* audio_set_conf,
+      types::LeAudioContextType context_type);
+  bool IsConfigurationSupported(
+      const set_configurations::AudioSetConfiguration* audio_set_configuration,
+      types::LeAudioContextType context_type);
+  uint16_t GetTransportLatency(uint8_t direction);
+
+  /* Mask and table of currently supported contexts */
+  types::LeAudioContextType active_context_type_;
+  types::AudioContexts active_contexts_mask_;
+  std::map<types::LeAudioContextType,
+           const set_configurations::AudioSetConfiguration*>
+      active_context_to_configuration_map;
+
+  types::AseState target_state_;
+  types::AseState current_state_;
+  types::LeAudioContextType context_type_;
+  std::vector<std::weak_ptr<LeAudioDevice>> leAudioDevices_;
+};
+
+/* LeAudioDeviceGroup class represents a wraper helper over all device groups in
+ * le audio implementation. It allows to operate on device group from a list
+ * (vector container) using determinants like id.
+ */
+class LeAudioDeviceGroups {
+ public:
+  LeAudioDeviceGroup* Add(int group_id);
+  void Remove(const int group_id);
+  LeAudioDeviceGroup* FindById(int group_id);
+  std::vector<int> GetGroupsIds(void);
+  size_t Size();
+  bool IsAnyInTransition();
+  void Cleanup(void);
+  void Dump(int fd);
+
+ private:
+  std::vector<std::unique_ptr<LeAudioDeviceGroup>> groups_;
+};
+}  // namespace le_audio
diff --git a/system/bta/le_audio/devices_test.cc b/system/bta/le_audio/devices_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7b9f5429b0c2eb71205b3ee61b3307d94b702e5f
--- /dev/null
+++ b/system/bta/le_audio/devices_test.cc
@@ -0,0 +1,898 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "devices.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "btm_api_mock.h"
+#include "le_audio_types.h"
+#include "mock_controller.h"
+#include "stack/btm/btm_int_types.h"
+
+tACL_CONN* btm_bda_to_acl(const RawAddress& bda, tBT_TRANSPORT transport) {
+  return nullptr;
+}
+
+namespace bluetooth {
+namespace le_audio {
+namespace internal {
+namespace {
+
+using ::le_audio::LeAudioDevice;
+using ::le_audio::LeAudioDeviceGroup;
+using ::le_audio::LeAudioDevices;
+using ::le_audio::types::AseState;
+using ::le_audio::types::AudioContexts;
+using ::le_audio::types::LeAudioContextType;
+using testing::Test;
+
+RawAddress GetTestAddress(int index) {
+  CHECK_LT(index, UINT8_MAX);
+  RawAddress result = {
+      {0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast<uint8_t>(index)}};
+  return result;
+}
+
+class LeAudioDevicesTest : public Test {
+ protected:
+  void SetUp() override {
+    devices_ = new LeAudioDevices();
+    bluetooth::manager::SetMockBtmInterface(&btm_interface);
+    controller::SetMockControllerInterface(&controller_interface_);
+  }
+
+  void TearDown() override {
+    controller::SetMockControllerInterface(nullptr);
+    bluetooth::manager::SetMockBtmInterface(nullptr);
+    delete devices_;
+  }
+
+  LeAudioDevices* devices_ = nullptr;
+  bluetooth::manager::MockBtmInterface btm_interface;
+  controller::MockControllerInterface controller_interface_;
+};
+
+TEST_F(LeAudioDevicesTest, test_add) {
+  RawAddress test_address_0 = GetTestAddress(0);
+  ASSERT_EQ((size_t)0, devices_->Size());
+  devices_->Add(test_address_0, true);
+  ASSERT_EQ((size_t)1, devices_->Size());
+  devices_->Add(GetTestAddress(1), true, 1);
+  ASSERT_EQ((size_t)2, devices_->Size());
+  devices_->Add(test_address_0, true);
+  ASSERT_EQ((size_t)2, devices_->Size());
+  devices_->Add(GetTestAddress(1), true, 2);
+  ASSERT_EQ((size_t)2, devices_->Size());
+}
+
+TEST_F(LeAudioDevicesTest, test_remove) {
+  RawAddress test_address_0 = GetTestAddress(0);
+  devices_->Add(test_address_0, true);
+  RawAddress test_address_1 = GetTestAddress(1);
+  devices_->Add(test_address_1, true);
+  RawAddress test_address_2 = GetTestAddress(2);
+  devices_->Add(test_address_2, true);
+  ASSERT_EQ((size_t)3, devices_->Size());
+  devices_->Remove(test_address_0);
+  ASSERT_EQ((size_t)2, devices_->Size());
+  devices_->Remove(GetTestAddress(3));
+  ASSERT_EQ((size_t)2, devices_->Size());
+  devices_->Remove(test_address_0);
+  ASSERT_EQ((size_t)2, devices_->Size());
+}
+
+TEST_F(LeAudioDevicesTest, test_find_by_address_success) {
+  RawAddress test_address_0 = GetTestAddress(0);
+  devices_->Add(test_address_0, true);
+  RawAddress test_address_1 = GetTestAddress(1);
+  devices_->Add(test_address_1, false);
+  RawAddress test_address_2 = GetTestAddress(2);
+  devices_->Add(test_address_2, true);
+  LeAudioDevice* device = devices_->FindByAddress(test_address_1);
+  ASSERT_NE(nullptr, device);
+  ASSERT_EQ(test_address_1, device->address_);
+}
+
+TEST_F(LeAudioDevicesTest, test_find_by_address_failed) {
+  RawAddress test_address_0 = GetTestAddress(0);
+  devices_->Add(test_address_0, true);
+  RawAddress test_address_2 = GetTestAddress(2);
+  devices_->Add(test_address_2, true);
+  LeAudioDevice* device = devices_->FindByAddress(GetTestAddress(1));
+  ASSERT_EQ(nullptr, device);
+}
+
+TEST_F(LeAudioDevicesTest, test_get_by_address_success) {
+  RawAddress test_address_0 = GetTestAddress(0);
+  devices_->Add(test_address_0, true);
+  RawAddress test_address_1 = GetTestAddress(1);
+  devices_->Add(test_address_1, false);
+  RawAddress test_address_2 = GetTestAddress(2);
+  devices_->Add(test_address_2, true);
+  std::shared_ptr<LeAudioDevice> device =
+      devices_->GetByAddress(test_address_1);
+  ASSERT_NE(nullptr, device);
+  ASSERT_EQ(test_address_1, device->address_);
+}
+
+TEST_F(LeAudioDevicesTest, test_get_by_address_failed) {
+  RawAddress test_address_0 = GetTestAddress(0);
+  devices_->Add(test_address_0, true);
+  RawAddress test_address_2 = GetTestAddress(2);
+  devices_->Add(test_address_2, true);
+  std::shared_ptr<LeAudioDevice> device =
+      devices_->GetByAddress(GetTestAddress(1));
+  ASSERT_EQ(nullptr, device);
+}
+
+TEST_F(LeAudioDevicesTest, test_find_by_conn_id_success) {
+  devices_->Add(GetTestAddress(1), true);
+  RawAddress test_address_0 = GetTestAddress(0);
+  devices_->Add(test_address_0, true);
+  devices_->Add(GetTestAddress(4), true);
+  LeAudioDevice* device = devices_->FindByAddress(test_address_0);
+  device->conn_id_ = 0x0005;
+  ASSERT_EQ(device, devices_->FindByConnId(0x0005));
+}
+
+TEST_F(LeAudioDevicesTest, test_find_by_conn_id_failed) {
+  devices_->Add(GetTestAddress(1), true);
+  devices_->Add(GetTestAddress(0), true);
+  devices_->Add(GetTestAddress(4), true);
+  ASSERT_EQ(nullptr, devices_->FindByConnId(0x0006));
+}
+
+/* TODO: Add FindByCisConnHdl test cases (ASE) */
+
+}  // namespace
+
+namespace {
+using namespace ::le_audio::codec_spec_caps;
+using namespace ::le_audio::set_configurations;
+using namespace ::le_audio::types;
+
+static const hdl_pair hdl_pair_nil = hdl_pair(0x0000, 0x0000);
+
+enum class Lc3SettingId {
+  _BEGIN,
+  LC3_8_1 = _BEGIN,
+  LC3_8_2,
+  LC3_16_1,
+  LC3_16_2,
+  LC3_24_1,
+  LC3_24_2,
+  LC3_32_1,
+  LC3_32_2,
+  LC3_441_1,
+  LC3_441_2,
+  LC3_48_1,
+  LC3_48_2,
+  LC3_48_3,
+  LC3_48_4,
+  LC3_48_5,
+  LC3_48_6,
+  _END,
+  UNSUPPORTED = _END,
+};
+static constexpr int Lc3SettingIdBegin = static_cast<int>(Lc3SettingId::_BEGIN);
+static constexpr int Lc3SettingIdEnd = static_cast<int>(Lc3SettingId::_END);
+
+bool IsLc3SettingSupported(LeAudioContextType context_type, Lc3SettingId id) {
+  /* Update those values, on any change of codec linked with content type */
+  switch (context_type) {
+    case LeAudioContextType::RINGTONE:
+    case LeAudioContextType::CONVERSATIONAL:
+      if (id == Lc3SettingId::LC3_16_1 || id == Lc3SettingId::LC3_16_2)
+        return true;
+
+      break;
+
+    case LeAudioContextType::MEDIA:
+      if (id == Lc3SettingId::LC3_16_1 || id == Lc3SettingId::LC3_16_2 ||
+          id == Lc3SettingId::LC3_48_4)
+        return true;
+
+      break;
+
+    default:
+      if (id == Lc3SettingId::LC3_16_2) return true;
+
+      break;
+  };
+
+  return false;
+}
+
+static constexpr uint8_t kLeAudioSamplingFreqRfu = 0x0E;
+uint8_t GetSamplingFrequency(Lc3SettingId id) {
+  switch (id) {
+    case Lc3SettingId::LC3_8_1:
+    case Lc3SettingId::LC3_8_2:
+      return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq8000Hz;
+    case Lc3SettingId::LC3_16_1:
+    case Lc3SettingId::LC3_16_2:
+      return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq16000Hz;
+    case Lc3SettingId::LC3_24_1:
+    case Lc3SettingId::LC3_24_2:
+      return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq24000Hz;
+    case Lc3SettingId::LC3_32_1:
+    case Lc3SettingId::LC3_32_2:
+      return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq32000Hz;
+    case Lc3SettingId::LC3_441_1:
+    case Lc3SettingId::LC3_441_2:
+      return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq44100Hz;
+    case Lc3SettingId::LC3_48_1:
+    case Lc3SettingId::LC3_48_2:
+    case Lc3SettingId::LC3_48_3:
+    case Lc3SettingId::LC3_48_4:
+    case Lc3SettingId::LC3_48_5:
+    case Lc3SettingId::LC3_48_6:
+      return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq48000Hz;
+    case Lc3SettingId::UNSUPPORTED:
+      return kLeAudioSamplingFreqRfu;
+  }
+}
+
+static constexpr uint8_t kLeAudioCodecLC3FrameDurRfu = 0x02;
+uint8_t GetFrameDuration(Lc3SettingId id) {
+  switch (id) {
+    case Lc3SettingId::LC3_8_1:
+    case Lc3SettingId::LC3_16_1:
+    case Lc3SettingId::LC3_24_1:
+    case Lc3SettingId::LC3_32_1:
+    case Lc3SettingId::LC3_441_1:
+    case Lc3SettingId::LC3_48_1:
+    case Lc3SettingId::LC3_48_3:
+    case Lc3SettingId::LC3_48_5:
+      return ::le_audio::codec_spec_conf::kLeAudioCodecLC3FrameDur7500us;
+    case Lc3SettingId::LC3_8_2:
+    case Lc3SettingId::LC3_16_2:
+    case Lc3SettingId::LC3_24_2:
+    case Lc3SettingId::LC3_32_2:
+    case Lc3SettingId::LC3_441_2:
+    case Lc3SettingId::LC3_48_2:
+    case Lc3SettingId::LC3_48_4:
+    case Lc3SettingId::LC3_48_6:
+      return ::le_audio::codec_spec_conf::kLeAudioCodecLC3FrameDur10000us;
+    case Lc3SettingId::UNSUPPORTED:
+      return kLeAudioCodecLC3FrameDurRfu;
+  }
+}
+
+static constexpr uint8_t kLeAudioCodecLC3OctetsPerCodecFrameInvalid = 0;
+uint16_t GetOctetsPerCodecFrame(Lc3SettingId id) {
+  switch (id) {
+    case Lc3SettingId::LC3_8_1:
+      return 26;
+    case Lc3SettingId::LC3_8_2:
+    case Lc3SettingId::LC3_16_1:
+      return 30;
+    case Lc3SettingId::LC3_16_2:
+      return 40;
+    case Lc3SettingId::LC3_24_1:
+      return 45;
+    case Lc3SettingId::LC3_24_2:
+    case Lc3SettingId::LC3_32_1:
+      return 60;
+    case Lc3SettingId::LC3_32_2:
+      return 80;
+    case Lc3SettingId::LC3_441_1:
+      return 97;
+    case Lc3SettingId::LC3_441_2:
+      return 130;
+    case Lc3SettingId::LC3_48_1:
+      return 75;
+    case Lc3SettingId::LC3_48_2:
+      return 100;
+    case Lc3SettingId::LC3_48_3:
+      return 90;
+    case Lc3SettingId::LC3_48_4:
+      return 120;
+    case Lc3SettingId::LC3_48_5:
+      return 116;
+    case Lc3SettingId::LC3_48_6:
+      return 155;
+    case Lc3SettingId::UNSUPPORTED:
+      return kLeAudioCodecLC3OctetsPerCodecFrameInvalid;
+  }
+}
+
+class PublishedAudioCapabilitiesBuilder {
+ public:
+  PublishedAudioCapabilitiesBuilder() {}
+
+  void Add(LeAudioCodecId codec_id, uint8_t conf_sampling_frequency,
+           uint8_t conf_frame_duration, uint8_t audio_channel_counts,
+           uint16_t octets_per_frame, uint8_t codec_frames_per_sdu = 0) {
+    uint16_t sampling_frequencies =
+        SamplingFreqConfig2Capability(conf_sampling_frequency);
+    uint8_t frame_durations =
+        FrameDurationConfig2Capability(conf_frame_duration);
+    uint8_t max_codec_frames_per_sdu = codec_frames_per_sdu;
+    uint32_t octets_per_frame_range =
+        octets_per_frame | (octets_per_frame << 16);
+
+    pac_records_.push_back(
+        acs_ac_record({.codec_id = codec_id,
+                       .codec_spec_caps = LeAudioLtvMap({
+                           {kLeAudioCodecLC3TypeSamplingFreq,
+                            UINT16_TO_VEC_UINT8(sampling_frequencies)},
+                           {kLeAudioCodecLC3TypeFrameDuration,
+                            UINT8_TO_VEC_UINT8(frame_durations)},
+                           {kLeAudioCodecLC3TypeAudioChannelCounts,
+                            UINT8_TO_VEC_UINT8(audio_channel_counts)},
+                           {kLeAudioCodecLC3TypeOctetPerFrame,
+                            UINT32_TO_VEC_UINT8(octets_per_frame_range)},
+                           {kLeAudioCodecLC3TypeMaxCodecFramesPerSdu,
+                            UINT8_TO_VEC_UINT8(max_codec_frames_per_sdu)},
+                       }),
+                       .metadata = std::vector<uint8_t>(0)}));
+  }
+
+  void Add(const CodecCapabilitySetting& setting,
+           uint8_t audio_channel_counts) {
+    if (setting.id != LeAudioCodecIdLc3) return;
+
+    const LeAudioLc3Config config = std::get<LeAudioLc3Config>(setting.config);
+
+    Add(setting.id, config.sampling_frequency, config.frame_duration,
+        audio_channel_counts, config.octets_per_codec_frame);
+  }
+
+  void Reset() { pac_records_.clear(); }
+
+  PublishedAudioCapabilities Get() {
+    return PublishedAudioCapabilities({{hdl_pair_nil, pac_records_}});
+  }
+
+ private:
+  std::vector<acs_ac_record> pac_records_;
+};
+
+struct TestGroupAseConfigurationData {
+  LeAudioDevice* device;
+  uint8_t audio_channel_counts_snk;
+  uint8_t audio_channel_counts_src;
+
+  uint8_t active_channel_num_snk;
+  uint8_t active_channel_num_src;
+};
+
+class LeAudioAseConfigurationTest : public Test {
+ protected:
+  void SetUp() override {
+    group_ = new LeAudioDeviceGroup(group_id_);
+    bluetooth::manager::SetMockBtmInterface(&btm_interface_);
+    controller::SetMockControllerInterface(&controller_interface_);
+  }
+
+  void TearDown() override {
+    controller::SetMockControllerInterface(nullptr);
+    bluetooth::manager::SetMockBtmInterface(nullptr);
+    devices_.clear();
+    delete group_;
+  }
+
+  LeAudioDevice* AddTestDevice(int snk_ase_num, int src_ase_num,
+                               int snk_ase_num_cached = 0,
+                               int src_ase_num_cached = 0) {
+    int index = group_->Size() + 1;
+    auto device =
+        (std::make_shared<LeAudioDevice>(GetTestAddress(index), false));
+    devices_.push_back(device);
+    group_->AddNode(device);
+
+    for (int i = 0; i < src_ase_num; i++) {
+      device->ases_.emplace_back(0x0000, 0x0000, kLeAudioDirectionSource);
+    }
+
+    for (int i = 0; i < snk_ase_num; i++) {
+      device->ases_.emplace_back(0x0000, 0x0000, kLeAudioDirectionSink);
+    }
+
+    for (int i = 0; i < src_ase_num_cached; i++) {
+      struct ase ase(0x0000, 0x0000, kLeAudioDirectionSource);
+      ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED;
+      device->ases_.push_back(ase);
+    }
+
+    for (int i = 0; i < snk_ase_num_cached; i++) {
+      struct ase ase(0x0000, 0x0000, kLeAudioDirectionSink);
+      ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED;
+      device->ases_.push_back(ase);
+    }
+
+    device->SetSupportedContexts((uint16_t)kLeAudioContextAllTypes,
+                                 (uint16_t)kLeAudioContextAllTypes);
+    device->SetAvailableContexts((uint16_t)kLeAudioContextAllTypes,
+                                 (uint16_t)kLeAudioContextAllTypes);
+    device->snk_audio_locations_ =
+        ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft |
+        ::le_audio::codec_spec_conf::kLeAudioLocationFrontRight;
+    device->src_audio_locations_ =
+        ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft;
+
+    device->conn_id_ = index;
+    return device.get();
+  }
+
+  void TestGroupAseConfigurationVerdict(
+      const TestGroupAseConfigurationData& data) {
+    uint8_t active_channel_num_snk = 0;
+    uint8_t active_channel_num_src = 0;
+
+    bool have_active_ase =
+        data.active_channel_num_snk + data.active_channel_num_src;
+    ASSERT_EQ(have_active_ase, data.device->HaveActiveAse());
+
+    for (ase* ase = data.device->GetFirstActiveAse(); ase;
+         ase = data.device->GetNextActiveAse(ase)) {
+      if (ase->direction == kLeAudioDirectionSink)
+        active_channel_num_snk +=
+            GetAudioChannelCounts(ase->codec_config.audio_channel_allocation);
+      else
+        active_channel_num_src +=
+            GetAudioChannelCounts(ase->codec_config.audio_channel_allocation);
+    }
+
+    ASSERT_EQ(data.active_channel_num_snk, active_channel_num_snk);
+    ASSERT_EQ(data.active_channel_num_src, active_channel_num_src);
+  }
+
+  void SetCisInformationToActiveAse(void) {
+    uint8_t cis_id = 1;
+    uint16_t cis_conn_hdl = 0x0060;
+
+    for (auto& device : devices_) {
+      for (auto& ase : device->ases_) {
+        if (ase.active) {
+          ase.cis_id = cis_id++;
+          ase.cis_conn_hdl = cis_conn_hdl++;
+        }
+      }
+    }
+  }
+
+  void TestSingleAseConfiguration(LeAudioContextType context_type,
+                                  TestGroupAseConfigurationData* data,
+                                  uint8_t data_size,
+                                  const AudioSetConfiguration* audio_set_conf) {
+    // the configuration should fail if there are no active ases expected
+    bool success_expected = data_size > 0;
+    for (int i = 0; i < data_size; i++) {
+      success_expected &=
+          (data[i].active_channel_num_snk + data[i].active_channel_num_src) > 0;
+
+      /* Prepare PAC's */
+      PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder;
+      for (const auto& entry : (*audio_set_conf).confs) {
+        if (entry.direction == kLeAudioDirectionSink) {
+          snk_pac_builder.Add(entry.codec, data[i].audio_channel_counts_snk);
+        } else {
+          src_pac_builder.Add(entry.codec, data[i].audio_channel_counts_src);
+        }
+      }
+
+      data[i].device->snk_pacs_ = snk_pac_builder.Get();
+      data[i].device->src_pacs_ = src_pac_builder.Get();
+    }
+
+    /* Stimulate update of active context map */
+    group_->UpdateActiveContextsMap(static_cast<uint16_t>(context_type));
+    ASSERT_EQ(success_expected, group_->Configure(context_type));
+
+    for (int i = 0; i < data_size; i++) {
+      TestGroupAseConfigurationVerdict(data[i]);
+    }
+  }
+
+  void TestGroupAseConfiguration(LeAudioContextType context_type,
+                                 TestGroupAseConfigurationData* data,
+                                 uint8_t data_size) {
+    const auto* configurations = get_confs_by_type(context_type);
+    for (const auto& audio_set_conf : *configurations) {
+      // the configuration should fail if there are no active ases expected
+      bool success_expected = data_size > 0;
+      for (int i = 0; i < data_size; i++) {
+        success_expected &= (data[i].active_channel_num_snk +
+                             data[i].active_channel_num_src) > 0;
+
+        /* Prepare PAC's */
+        PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder;
+        for (const auto& entry : (*audio_set_conf).confs) {
+          if (entry.direction == kLeAudioDirectionSink) {
+            snk_pac_builder.Add(entry.codec, data[i].audio_channel_counts_snk);
+          } else {
+            src_pac_builder.Add(entry.codec, data[i].audio_channel_counts_src);
+          }
+        }
+
+        data[i].device->snk_pacs_ = snk_pac_builder.Get();
+        data[i].device->src_pacs_ = src_pac_builder.Get();
+      }
+
+      /* Stimulate update of active context map */
+      group_->UpdateActiveContextsMap(static_cast<uint16_t>(context_type));
+      ASSERT_EQ(success_expected, group_->Configure(context_type));
+
+      for (int i = 0; i < data_size; i++) {
+        TestGroupAseConfigurationVerdict(data[i]);
+      }
+
+      group_->Deactivate();
+      TestAsesInactive();
+    }
+  }
+
+  void TestAsesActive(LeAudioCodecId codec_id, uint8_t sampling_frequency,
+                      uint8_t frame_duration, uint16_t octets_per_frame) {
+    for (const auto& device : devices_) {
+      for (const auto& ase : device->ases_) {
+        ASSERT_TRUE(ase.active);
+        ASSERT_EQ(ase.codec_id, codec_id);
+
+        /* FIXME: Validate other codec parameters than LC3 if any */
+        ASSERT_EQ(ase.codec_id, LeAudioCodecIdLc3);
+        if (ase.codec_id == LeAudioCodecIdLc3) {
+          ASSERT_EQ(ase.codec_config.sampling_frequency, sampling_frequency);
+          ASSERT_EQ(ase.codec_config.frame_duration, frame_duration);
+          ASSERT_EQ(ase.codec_config.octets_per_codec_frame, octets_per_frame);
+        }
+      }
+    }
+  }
+
+  void TestActiveAses(void) {
+    for (auto& device : devices_) {
+      for (const auto& ase : device->ases_) {
+        if (ase.active) {
+          ASSERT_FALSE(ase.cis_id == ::le_audio::kInvalidCisId);
+        }
+      }
+    }
+  }
+
+  void TestAsesInactivated(const LeAudioDevice* device) {
+    for (const auto& ase : device->ases_) {
+      ASSERT_FALSE(ase.active);
+    }
+  }
+
+  void TestAsesInactive() {
+    for (const auto& device : devices_) {
+      for (const auto& ase : device->ases_) {
+        ASSERT_FALSE(ase.active);
+      }
+    }
+  }
+
+  void TestLc3CodecConfig(LeAudioContextType context_type) {
+    for (int i = Lc3SettingIdBegin; i < Lc3SettingIdEnd; i++) {
+      // test each configuration parameter against valid and invalid value
+      std::array<Lc3SettingId, 2> test_variants = {static_cast<Lc3SettingId>(i),
+                                                   Lc3SettingId::UNSUPPORTED};
+
+      const bool is_lc3_setting_supported =
+          IsLc3SettingSupported(context_type, static_cast<Lc3SettingId>(i));
+
+      for (const auto sf_variant : test_variants) {
+        uint8_t sampling_frequency = GetSamplingFrequency(sf_variant);
+        for (const auto fd_variant : test_variants) {
+          uint8_t frame_duration = GetFrameDuration(fd_variant);
+          for (const auto opcf_variant : test_variants) {
+            uint16_t octets_per_frame = GetOctetsPerCodecFrame(opcf_variant);
+
+            PublishedAudioCapabilitiesBuilder pac_builder;
+            pac_builder.Add(
+                LeAudioCodecIdLc3, sampling_frequency, frame_duration,
+                kLeAudioCodecLC3ChannelCountSingleChannel, octets_per_frame);
+            for (auto& device : devices_) {
+              /* For simplicity configure both PACs with the same
+              parameters*/
+              device->snk_pacs_ = pac_builder.Get();
+              device->src_pacs_ = pac_builder.Get();
+            }
+
+            bool success_expected = is_lc3_setting_supported;
+            if (is_lc3_setting_supported &&
+                (sf_variant == Lc3SettingId::UNSUPPORTED ||
+                 fd_variant == Lc3SettingId::UNSUPPORTED ||
+                 opcf_variant == Lc3SettingId::UNSUPPORTED)) {
+              success_expected = false;
+            }
+
+            /* Stimulate update of active context map */
+            group_->UpdateActiveContextsMap(
+                static_cast<uint16_t>(context_type));
+            ASSERT_EQ(success_expected, group_->Configure(context_type));
+            if (success_expected) {
+              TestAsesActive(LeAudioCodecIdLc3, sampling_frequency,
+                             frame_duration, octets_per_frame);
+              group_->Deactivate();
+            }
+
+            TestAsesInactive();
+          }
+        }
+      }
+    }
+  }
+
+  const int group_id_ = 6;
+  std::vector<std::shared_ptr<LeAudioDevice>> devices_;
+  LeAudioDeviceGroup* group_ = nullptr;
+  bluetooth::manager::MockBtmInterface btm_interface_;
+  controller::MockControllerInterface controller_interface_;
+};
+
+TEST_F(LeAudioAseConfigurationTest, test_mono_speaker_ringtone) {
+  LeAudioDevice* mono_speaker = AddTestDevice(1, 0);
+  TestGroupAseConfigurationData data({mono_speaker,
+                                      kLeAudioCodecLC3ChannelCountSingleChannel,
+                                      kLeAudioCodecLC3ChannelCountNone, 1, 0});
+
+  TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_mono_speaker_conversional) {
+  LeAudioDevice* mono_speaker = AddTestDevice(1, 0);
+  TestGroupAseConfigurationData data({mono_speaker,
+                                      kLeAudioCodecLC3ChannelCountSingleChannel,
+                                      kLeAudioCodecLC3ChannelCountNone, 0, 0});
+
+  TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_mono_speaker_media) {
+  LeAudioDevice* mono_speaker = AddTestDevice(1, 0);
+  TestGroupAseConfigurationData data({mono_speaker,
+                                      kLeAudioCodecLC3ChannelCountSingleChannel,
+                                      kLeAudioCodecLC3ChannelCountNone, 1, 0});
+
+  TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_bounded_headphones_ringtone) {
+  LeAudioDevice* bounded_headphones = AddTestDevice(2, 0);
+  TestGroupAseConfigurationData data({bounded_headphones,
+                                      kLeAudioCodecLC3ChannelCountTwoChannel,
+                                      kLeAudioCodecLC3ChannelCountNone, 2, 0});
+
+  TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_bounded_headphones_conversional) {
+  LeAudioDevice* bounded_headphones = AddTestDevice(2, 0);
+  TestGroupAseConfigurationData data({bounded_headphones,
+                                      kLeAudioCodecLC3ChannelCountTwoChannel,
+                                      kLeAudioCodecLC3ChannelCountNone, 0, 0});
+
+  TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_bounded_headphones_media) {
+  LeAudioDevice* bounded_headphones = AddTestDevice(2, 0);
+  TestGroupAseConfigurationData data({bounded_headphones,
+                                      kLeAudioCodecLC3ChannelCountTwoChannel,
+                                      kLeAudioCodecLC3ChannelCountNone, 2, 0});
+
+  TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_bounded_headset_ringtone) {
+  LeAudioDevice* bounded_headset = AddTestDevice(2, 1);
+  TestGroupAseConfigurationData data(
+      {bounded_headset, kLeAudioCodecLC3ChannelCountTwoChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 2, 0});
+
+  TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_bounded_headset_conversional) {
+  LeAudioDevice* bounded_headset = AddTestDevice(2, 1);
+  TestGroupAseConfigurationData data(
+      {bounded_headset, kLeAudioCodecLC3ChannelCountTwoChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 2, 1});
+
+  TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_bounded_headset_media) {
+  LeAudioDevice* bounded_headset = AddTestDevice(2, 1);
+  TestGroupAseConfigurationData data(
+      {bounded_headset, kLeAudioCodecLC3ChannelCountTwoChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 2, 0});
+
+  TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_earbuds_ringtone) {
+  LeAudioDevice* left = AddTestDevice(1, 1);
+  LeAudioDevice* right = AddTestDevice(1, 1);
+  TestGroupAseConfigurationData data[] = {
+      {left, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0},
+      {right, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}};
+
+  TestGroupAseConfiguration(LeAudioContextType::RINGTONE, data, 2);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_earbuds_conversional) {
+  LeAudioDevice* left = AddTestDevice(1, 1);
+  LeAudioDevice* right = AddTestDevice(1, 1);
+  TestGroupAseConfigurationData data[] = {
+      {left, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1},
+      {right, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}};
+
+  TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, data, 2);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_earbuds_media) {
+  LeAudioDevice* left = AddTestDevice(1, 1);
+  LeAudioDevice* right = AddTestDevice(1, 1);
+  TestGroupAseConfigurationData data[] = {
+      {left, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0},
+      {right, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}};
+
+  TestGroupAseConfiguration(LeAudioContextType::MEDIA, data, 2);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_handsfree_ringtone) {
+  LeAudioDevice* handsfree = AddTestDevice(1, 1);
+  TestGroupAseConfigurationData data(
+      {handsfree, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0});
+
+  TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_handsfree_conversional) {
+  LeAudioDevice* handsfree = AddTestDevice(1, 1);
+  TestGroupAseConfigurationData data(
+      {handsfree, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1});
+
+  TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_handsfree_full_cached_conversional) {
+  LeAudioDevice* handsfree = AddTestDevice(0, 0, 1, 1);
+  TestGroupAseConfigurationData data(
+      {handsfree, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1});
+
+  TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest,
+       test_handsfree_partial_cached_conversional) {
+  LeAudioDevice* handsfree = AddTestDevice(1, 0, 0, 1);
+  TestGroupAseConfigurationData data(
+      {handsfree, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1});
+
+  TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_handsfree_media) {
+  LeAudioDevice* handsfree = AddTestDevice(1, 1);
+  TestGroupAseConfigurationData data(
+      {handsfree, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0});
+
+  TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_lc3_config_ringtone) {
+  AddTestDevice(1, 0);
+
+  TestLc3CodecConfig(LeAudioContextType::RINGTONE);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_lc3_config_conversional) {
+  AddTestDevice(1, 1);
+
+  TestLc3CodecConfig(LeAudioContextType::CONVERSATIONAL);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_lc3_config_media) {
+  AddTestDevice(1, 0);
+
+  TestLc3CodecConfig(LeAudioContextType::MEDIA);
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_unsupported_codec) {
+  const LeAudioCodecId UnsupportedCodecId = {
+      .coding_format = kLeAudioCodingFormatVendorSpecific,
+      .vendor_company_id = 0xBAD,
+      .vendor_codec_id = 0xC0DE,
+  };
+
+  LeAudioDevice* device = AddTestDevice(1, 0);
+
+  PublishedAudioCapabilitiesBuilder pac_builder;
+  pac_builder.Add(UnsupportedCodecId,
+                  GetSamplingFrequency(Lc3SettingId::LC3_16_2),
+                  GetFrameDuration(Lc3SettingId::LC3_16_2),
+                  kLeAudioCodecLC3ChannelCountSingleChannel,
+                  GetOctetsPerCodecFrame(Lc3SettingId::LC3_16_2));
+  device->snk_pacs_ = pac_builder.Get();
+  device->src_pacs_ = pac_builder.Get();
+
+  ASSERT_FALSE(group_->Configure(LeAudioContextType::RINGTONE));
+  TestAsesInactive();
+}
+
+TEST_F(LeAudioAseConfigurationTest, test_reconnection_media) {
+  LeAudioDevice* left = AddTestDevice(2, 1);
+  LeAudioDevice* right = AddTestDevice(2, 1);
+
+  TestGroupAseConfigurationData data[] = {
+      {left, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0},
+      {right, kLeAudioCodecLC3ChannelCountSingleChannel,
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}};
+
+  TestSingleAseConfiguration(LeAudioContextType::MEDIA, data, 2,
+                             &kDualDev_OneChanStereoSnk_48_4);
+
+  SetCisInformationToActiveAse();
+
+  /* Left got disconnected */
+  left->DeactivateAllAses();
+  TestAsesInactivated(left);
+
+  /* Prepare reconfiguration */
+  uint8_t number_of_active_ases = 1;  // Right one
+  auto* ase = right->GetFirstActiveAseByDirection(kLeAudioDirectionSink);
+  ::le_audio::types::AudioLocations group_snk_audio_location =
+      ase->codec_config.audio_channel_allocation;
+  ::le_audio::types::AudioLocations group_src_audio_location =
+      ase->codec_config.audio_channel_allocation;
+
+  /* Get known requirement*/
+  auto* configuration = &kDualDev_OneChanStereoSnk_48_4;
+
+  /* Get entry for the sink direction and use it to set configuration */
+  for (auto& ent : configuration->confs) {
+    if (ent.direction == ::le_audio::types::kLeAudioDirectionSink) {
+      left->ConfigureAses(ent, group_->GetCurrentContextType(),
+                          &number_of_active_ases, group_snk_audio_location,
+                          group_src_audio_location);
+    }
+  }
+
+  ASSERT_TRUE(number_of_active_ases == 2);
+  ASSERT_TRUE(group_snk_audio_location == kChannelAllocationStereo);
+
+  for (int i = 0; i < 2; i++) {
+    TestGroupAseConfigurationVerdict(data[i]);
+  }
+
+  TestActiveAses();
+}
+}  // namespace
+}  // namespace internal
+}  // namespace le_audio
+}  // namespace bluetooth
diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..84f2b7f454159369b292ef470361125bf1192272
--- /dev/null
+++ b/system/bta/le_audio/le_audio_client_test.cc
@@ -0,0 +1,2644 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+
+#include "bta/csis/csis_types.h"
+#include "bta_gatt_api_mock.h"
+#include "bta_gatt_queue_mock.h"
+#include "bta_groups.h"
+#include "bta_le_audio_api.h"
+#include "btif_storage_mock.h"
+#include "btm_api_mock.h"
+#include "btm_iso_api.h"
+#include "common/message_loop_thread.h"
+#include "device/include/controller.h"
+#include "gatt/database_builder.h"
+#include "hardware/bt_gatt_types.h"
+#include "le_audio_types.h"
+#include "mock_controller.h"
+#include "mock_csis_client.h"
+#include "mock_device_groups.h"
+#include "mock_iso_manager.h"
+#include "mock_le_audio_client_audio.h"
+#include "mock_state_machine.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::AtMost;
+using testing::DoAll;
+using testing::Invoke;
+using testing::Mock;
+using testing::MockFunction;
+using testing::NotNull;
+using testing::Return;
+using testing::SaveArg;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::WithArg;
+
+using bluetooth::Uuid;
+
+using namespace bluetooth::le_audio;
+
+// Disables most likely false-positives from base::SplitString()
+extern "C" const char* __asan_default_options() {
+  return "detect_container_overflow=0";
+}
+
+std::atomic<int> num_async_tasks;
+bluetooth::common::MessageLoopThread message_loop_thread("test message loop");
+bluetooth::common::MessageLoopThread* get_main_thread() {
+  return &message_loop_thread;
+}
+bt_status_t do_in_main_thread(const base::Location& from_here,
+                              base::OnceClosure task) {
+  // Wrap the task with task counter so we could later know if there are
+  // any callbacks scheduled and we should wait before performing some actions
+  if (!message_loop_thread.DoInThread(
+          from_here,
+          base::BindOnce(
+              [](base::OnceClosure task, std::atomic<int>& num_async_tasks) {
+                std::move(task).Run();
+                num_async_tasks--;
+              },
+              std::move(task), std::ref(num_async_tasks)))) {
+    LOG(ERROR) << __func__ << ": failed from " << from_here.ToString();
+    return BT_STATUS_FAIL;
+  }
+  num_async_tasks++;
+  return BT_STATUS_SUCCESS;
+}
+
+static base::MessageLoop* message_loop_;
+base::MessageLoop* get_main_message_loop() { return message_loop_; }
+
+static void init_message_loop_thread() {
+  num_async_tasks = 0;
+  message_loop_thread.StartUp();
+  if (!message_loop_thread.IsRunning()) {
+    FAIL() << "unable to create message loop thread.";
+  }
+
+  if (!message_loop_thread.EnableRealTimeScheduling())
+    LOG(ERROR) << "Unable to set real time scheduling";
+
+  message_loop_ = message_loop_thread.message_loop();
+  if (message_loop_ == nullptr) FAIL() << "unable to get message loop.";
+}
+
+static void cleanup_message_loop_thread() {
+  message_loop_ = nullptr;
+  message_loop_thread.ShutDown();
+}
+
+namespace le_audio {
+namespace {
+class MockLeAudioClientCallbacks
+    : public bluetooth::le_audio::LeAudioClientCallbacks {
+ public:
+  MOCK_METHOD((void), OnConnectionState,
+              (ConnectionState state, const RawAddress& address), (override));
+  MOCK_METHOD((void), OnGroupStatus, (int group_id, GroupStatus group_status),
+              (override));
+  MOCK_METHOD((void), OnGroupNodeStatus,
+              (const RawAddress& bd_addr, int group_id,
+               GroupNodeStatus node_status),
+              (override));
+  MOCK_METHOD((void), OnAudioConf,
+              (uint8_t direction, int group_id, uint32_t snk_audio_location,
+               uint32_t src_audio_location, uint16_t avail_cont),
+              (override));
+};
+
+class UnicastTestNoInit : public Test {
+ protected:
+  void SetUpMockAudioHal() {
+    // Source
+    is_audio_hal_source_acquired = false;
+
+    ON_CALL(mock_audio_source_, Start(_, _))
+        .WillByDefault(
+            [this](const LeAudioCodecConfiguration& codec_configuration,
+                   LeAudioClientAudioSinkReceiver* audioReceiver) {
+              audio_sink_receiver_ = audioReceiver;
+              return true;
+            });
+    ON_CALL(mock_audio_source_, Acquire).WillByDefault([this]() -> void* {
+      if (!is_audio_hal_source_acquired) {
+        is_audio_hal_source_acquired = true;
+        return &mock_audio_source_;
+      }
+
+      return nullptr;
+    });
+    ON_CALL(mock_audio_source_, Release)
+        .WillByDefault([this](const void* inst) -> void {
+          if (is_audio_hal_source_acquired) {
+            is_audio_hal_source_acquired = false;
+          }
+        });
+
+    MockLeAudioClientAudioSource::SetMockInstanceForTesting(
+        &mock_audio_source_);
+
+    // Sink
+    is_audio_hal_sink_acquired = false;
+
+    ON_CALL(mock_audio_sink_, Start(_, _))
+        .WillByDefault(
+            [this](const LeAudioCodecConfiguration& codec_configuration,
+                   LeAudioClientAudioSourceReceiver* audioReceiver) {
+              audio_source_receiver_ = audioReceiver;
+              return true;
+            });
+    ON_CALL(mock_audio_sink_, Acquire).WillByDefault([this]() -> void* {
+      if (!is_audio_hal_sink_acquired) {
+        is_audio_hal_sink_acquired = true;
+        return &mock_audio_sink_;
+      }
+
+      return nullptr;
+    });
+    ON_CALL(mock_audio_sink_, Release)
+        .WillByDefault([this](const void* inst) -> void {
+          if (is_audio_hal_sink_acquired) {
+            is_audio_hal_sink_acquired = false;
+          }
+        });
+    ON_CALL(mock_audio_sink_, SendData)
+        .WillByDefault([](uint8_t* data, uint16_t size) { return size; });
+
+    MockLeAudioClientAudioSink::SetMockInstanceForTesting(&mock_audio_sink_);
+
+    // HAL
+    ON_CALL(mock_hal_2_1_verifier, Call()).WillByDefault([]() -> bool {
+      return true;
+    });
+  }
+
+  void InjectGroupDeviceRemoved(const RawAddress& address, int group_id) {
+    group_callbacks_->OnGroupMemberRemoved(address, group_id);
+  }
+
+  void InjectGroupDeviceAdded(const RawAddress& address, int group_id) {
+    bluetooth::Uuid uuid = le_audio::uuid::kCapServiceUuid;
+
+    int group_members_num = 0;
+    for (const auto& [addr, id] : groups) {
+      if (id == group_id) group_members_num++;
+    }
+
+    bool first_device = (group_members_num == 1);
+    do_in_main_thread(
+        FROM_HERE,
+        base::BindOnce(
+            [](const RawAddress& addr, int group_id, bluetooth::Uuid uuid,
+               bluetooth::groups::DeviceGroupsCallbacks* group_callbacks,
+               bool first_device) {
+              if (first_device) {
+                group_callbacks->OnGroupAdded(addr, uuid, group_id);
+              } else {
+                group_callbacks->OnGroupMemberAdded(addr, group_id);
+              }
+            },
+            address, group_id, uuid, base::Unretained(this->group_callbacks_),
+            first_device));
+  }
+
+  void InjectConnectedEvent(const RawAddress& address, uint16_t conn_id,
+                            tGATT_STATUS status = GATT_SUCCESS) {
+    ASSERT_NE(conn_id, GATT_INVALID_CONN_ID);
+    tBTA_GATTC_OPEN event_data = {
+        .status = status,
+        .conn_id = conn_id,
+        .client_if = gatt_if,
+        .remote_bda = address,
+        .transport = GATT_TRANSPORT_LE,
+        .mtu = 240,
+    };
+
+    ASSERT_NE(peer_devices.count(conn_id), 0u);
+    peer_devices.at(conn_id)->connected = true;
+    do_in_main_thread(
+        FROM_HERE,
+        base::BindOnce(
+            [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_OPEN event_data) {
+              gatt_callback(BTA_GATTC_OPEN_EVT, (tBTA_GATTC*)&event_data);
+            },
+            base::Unretained(this->gatt_callback), event_data));
+  }
+
+  void InjectDisconnectedEvent(uint16_t conn_id) {
+    ASSERT_NE(conn_id, GATT_INVALID_CONN_ID);
+    ASSERT_NE(peer_devices.count(conn_id), 0u);
+
+    tBTA_GATTC_CLOSE event_data = {
+        .status = GATT_SUCCESS,
+        .conn_id = conn_id,
+        .client_if = gatt_if,
+        .remote_bda = peer_devices.at(conn_id)->addr,
+        .reason = GATT_CONN_TERMINATE_PEER_USER,
+    };
+
+    peer_devices.at(conn_id)->connected = false;
+    do_in_main_thread(
+        FROM_HERE,
+        base::BindOnce(
+            [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_CLOSE event_data) {
+              gatt_callback(BTA_GATTC_CLOSE_EVT, (tBTA_GATTC*)&event_data);
+            },
+            base::Unretained(this->gatt_callback), event_data));
+  }
+
+  void InjectSearchCompleteEvent(uint16_t conn_id) {
+    ASSERT_NE(conn_id, GATT_INVALID_CONN_ID);
+    tBTA_GATTC_SEARCH_CMPL event_data = {
+        .status = GATT_SUCCESS,
+        .conn_id = conn_id,
+    };
+
+    do_in_main_thread(FROM_HERE,
+                      base::BindOnce(
+                          [](tBTA_GATTC_CBACK* gatt_callback,
+                             tBTA_GATTC_SEARCH_CMPL event_data) {
+                            gatt_callback(BTA_GATTC_SEARCH_CMPL_EVT,
+                                          (tBTA_GATTC*)&event_data);
+                          },
+                          base::Unretained(this->gatt_callback), event_data));
+  }
+
+  void InjectNotificationEvent(const RawAddress& test_address, uint16_t conn_id,
+                               uint16_t handle, std::vector<uint8_t> value) {
+    ASSERT_NE(conn_id, GATT_INVALID_CONN_ID);
+    tBTA_GATTC_NOTIFY event_data = {
+        .conn_id = conn_id,
+        .bda = test_address,
+        .handle = handle,
+        .len = (uint8_t)value.size(),
+        .is_notify = true,
+    };
+
+    std::copy(value.begin(), value.end(), event_data.value);
+    do_in_main_thread(
+        FROM_HERE,
+        base::BindOnce(
+            [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_NOTIFY event_data) {
+              gatt_callback(BTA_GATTC_NOTIF_EVT, (tBTA_GATTC*)&event_data);
+            },
+            base::Unretained(this->gatt_callback), event_data));
+  }
+
+  void SetUpMockGatt() {
+    // default action for GetCharacteristic function call
+    ON_CALL(mock_gatt_interface_, GetCharacteristic(_, _))
+        .WillByDefault(
+            Invoke([&](uint16_t conn_id,
+                       uint16_t handle) -> const gatt::Characteristic* {
+              std::list<gatt::Service>& services =
+                  peer_devices.at(conn_id)->services;
+              for (auto const& service : services) {
+                for (auto const& characteristic : service.characteristics) {
+                  if (characteristic.value_handle == handle) {
+                    return &characteristic;
+                  }
+                }
+              }
+
+              return nullptr;
+            }));
+
+    // default action for GetOwningService function call
+    ON_CALL(mock_gatt_interface_, GetOwningService(_, _))
+        .WillByDefault(Invoke(
+            [&](uint16_t conn_id, uint16_t handle) -> const gatt::Service* {
+              std::list<gatt::Service>& services =
+                  peer_devices.at(conn_id)->services;
+              for (auto const& service : services) {
+                if (service.handle <= handle && service.end_handle >= handle) {
+                  return &service;
+                }
+              }
+
+              return nullptr;
+            }));
+
+    // default action for ServiceSearchRequest function call
+    ON_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _))
+        .WillByDefault(WithArg<0>(Invoke(
+            [&](uint16_t conn_id) { InjectSearchCompleteEvent(conn_id); })));
+
+    // default action for GetServices function call
+    ON_CALL(mock_gatt_interface_, GetServices(_))
+        .WillByDefault(WithArg<0>(
+            Invoke([&](uint16_t conn_id) -> std::list<gatt::Service>* {
+              return &peer_devices.at(conn_id)->services;
+            })));
+
+    // default action for RegisterForNotifications function call
+    ON_CALL(mock_gatt_interface_, RegisterForNotifications(gatt_if, _, _))
+        .WillByDefault(Return(GATT_SUCCESS));
+
+    // default action for DeregisterForNotifications function call
+    ON_CALL(mock_gatt_interface_, DeregisterForNotifications(gatt_if, _, _))
+        .WillByDefault(Return(GATT_SUCCESS));
+
+    // default action for WriteDescriptor function call
+    ON_CALL(mock_gatt_queue_, WriteDescriptor(_, _, _, _, _, _))
+        .WillByDefault(
+            Invoke([](uint16_t conn_id, uint16_t handle,
+                      std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type,
+                      GATT_WRITE_OP_CB cb, void* cb_data) -> void {
+              if (cb)
+                do_in_main_thread(
+                    FROM_HERE, base::BindOnce(
+                                   [](GATT_WRITE_OP_CB cb, uint16_t conn_id,
+                                      uint16_t handle, void* cb_data) {
+                                     cb(conn_id, GATT_SUCCESS, handle, cb_data);
+                                   },
+                                   cb, conn_id, handle, cb_data));
+            }));
+
+    global_conn_id = 1;
+    ON_CALL(mock_gatt_interface_, Open(_, _, _, _))
+        .WillByDefault(
+            Invoke([&](tGATT_IF client_if, const RawAddress& remote_bda,
+                       bool is_direct, bool opportunistic) {
+              InjectConnectedEvent(remote_bda, global_conn_id++);
+            }));
+
+    ON_CALL(mock_gatt_interface_, Close(_))
+        .WillByDefault(Invoke(
+            [&](uint16_t conn_id) { InjectDisconnectedEvent(conn_id); }));
+
+    // default Characteristic read handler dispatches requests to service mocks
+    ON_CALL(mock_gatt_queue_, ReadCharacteristic(_, _, _, _))
+        .WillByDefault(Invoke([&](uint16_t conn_id, uint16_t handle,
+                                  GATT_READ_OP_CB cb, void* cb_data) {
+          do_in_main_thread(
+              FROM_HERE,
+              base::BindOnce(
+                  [](std::map<uint16_t, std::unique_ptr<MockDeviceWrapper>>*
+                         peer_devices,
+                     uint16_t conn_id, uint16_t handle, GATT_READ_OP_CB cb,
+                     void* cb_data) -> void {
+                    if (peer_devices->count(conn_id)) {
+                      auto& device = peer_devices->at(conn_id);
+                      auto svc = std::find_if(
+                          device->services.begin(), device->services.end(),
+                          [handle](const gatt::Service& svc) {
+                            return (handle >= svc.handle) &&
+                                   (handle <= svc.end_handle);
+                          });
+                      if (svc == device->services.end()) return;
+
+                      // Dispatch to mockable handler functions
+                      if (svc->handle == device->csis->start) {
+                        device->csis->OnReadCharacteristic(handle, cb, cb_data);
+                      } else if (svc->handle == device->cas->start) {
+                        device->cas->OnReadCharacteristic(handle, cb, cb_data);
+                      } else if (svc->handle == device->ascs->start) {
+                        device->ascs->OnReadCharacteristic(handle, cb, cb_data);
+                      } else if (svc->handle == device->pacs->start) {
+                        device->pacs->OnReadCharacteristic(handle, cb, cb_data);
+                      }
+                    }
+                  },
+                  &peer_devices, conn_id, handle, cb, cb_data));
+        }));
+  }
+
+  void SetUpMockGroups() {
+    MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_);
+    MockDeviceGroups::SetMockInstanceForTesting(&mock_groups_module_);
+    MockLeAudioGroupStateMachine::SetMockInstanceForTesting(
+        &mock_state_machine_);
+
+    ON_CALL(mock_csis_client_module_, Get())
+        .WillByDefault(Return(&mock_csis_client_module_));
+
+    // Store group callbacks so that we could inject grouping events
+    group_callbacks_ = nullptr;
+    ON_CALL(mock_groups_module_, Initialize(_))
+        .WillByDefault(SaveArg<0>(&group_callbacks_));
+
+    ON_CALL(mock_groups_module_, GetGroupId(_, _))
+        .WillByDefault([this](const RawAddress& addr, bluetooth::Uuid uuid) {
+          if (groups.find(addr) != groups.end()) return groups.at(addr);
+          return bluetooth::groups::kGroupUnknown;
+        });
+
+    ON_CALL(mock_groups_module_, RemoveDevice(_, _))
+        .WillByDefault([this](const RawAddress& addr, int group_id_) {
+          int group_id = -1;
+          if (groups.find(addr) != groups.end()) {
+            group_id = groups[addr];
+            groups.erase(addr);
+          }
+          if (group_id < 0) return;
+
+          do_in_main_thread(
+              FROM_HERE,
+              base::BindOnce(
+                  [](const RawAddress& address, int group_id,
+                     bluetooth::groups::DeviceGroupsCallbacks*
+                         group_callbacks) {
+                    group_callbacks->OnGroupMemberRemoved(address, group_id);
+                  },
+                  addr, group_id, base::Unretained(group_callbacks_)));
+        });
+
+    // Our test devices have unique LSB - use it for unique grouping when
+    // devices added with a non-CIS context and no grouping info
+    ON_CALL(mock_groups_module_,
+            AddDevice(_, le_audio::uuid::kCapServiceUuid, _))
+        .WillByDefault(
+            [this](const RawAddress& addr,
+                   bluetooth::Uuid uuid = le_audio::uuid::kCapServiceUuid,
+                   int group_id = bluetooth::groups::kGroupUnknown) -> int {
+              if (group_id == bluetooth::groups::kGroupUnknown) {
+                /* Generate group id from address */
+                groups[addr] = addr.address[RawAddress::kLength - 1];
+                group_id = groups[addr];
+              } else {
+                groups[addr] = group_id;
+              }
+
+              InjectGroupDeviceAdded(addr, groups[addr]);
+              return addr.address[RawAddress::kLength - 1];
+            });
+
+    ON_CALL(mock_state_machine_, Initialize(_))
+        .WillByDefault(SaveArg<0>(&state_machine_callbacks_));
+
+    ON_CALL(mock_state_machine_, StartStream(_, _))
+        .WillByDefault([this](LeAudioDeviceGroup* group,
+                              types::LeAudioContextType context_type) {
+          if (group->GetState() ==
+              types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+            if (group->GetContextType() != context_type) {
+              /* TODO: Switch context of group */
+              group->SetContextType(context_type);
+            }
+            return true;
+          }
+
+          group->Configure(context_type);
+
+          // Fake ASE configuration
+          for (LeAudioDevice* device = group->GetFirstDevice();
+               device != nullptr; device = group->GetNextDevice(device)) {
+            for (auto& ase : device->ases_) {
+              if (!ase.active) continue;
+
+              // And also skip the ase establishment procedure which should
+              // be tested as part of the state machine unit tests
+              ase.data_path_state =
+                  types::AudioStreamDataPathState::DATA_PATH_ESTABLISHED;
+              ase.cis_conn_hdl = iso_con_counter_++;
+              ase.active = true;
+              ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING;
+            }
+          }
+
+          // Inject the state
+          group->SetTargetState(
+              types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+          group->SetState(group->GetTargetState());
+          state_machine_callbacks_->StatusReportCb(
+              group->group_id_, GroupStreamStatus::STREAMING);
+          streaming_groups[group->group_id_] = group;
+          return true;
+        });
+
+    ON_CALL(mock_state_machine_, SuspendStream(_))
+        .WillByDefault([this](LeAudioDeviceGroup* group) {
+          // Fake ASE state
+          for (LeAudioDevice* device = group->GetFirstDevice();
+               device != nullptr; device = group->GetNextDevice(device)) {
+            for (auto& ase : device->ases_) {
+              ase.data_path_state =
+                  types::AudioStreamDataPathState::CIS_ESTABLISHED;
+              ase.active = false;
+              ase.state =
+                  types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED;
+            }
+          }
+
+          // Inject the state
+          group->SetTargetState(
+              types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+          group->SetState(group->GetTargetState());
+          state_machine_callbacks_->StatusReportCb(
+              group->group_id_, GroupStreamStatus::SUSPENDED);
+        });
+
+    ON_CALL(mock_state_machine_, ProcessHciNotifAclDisconnected(_, _))
+        .WillByDefault([](LeAudioDeviceGroup* group,
+                          LeAudioDevice* leAudioDevice) {
+          if (!group) return;
+          auto* stream_conf = &group->stream_conf;
+          if (stream_conf->valid) {
+            stream_conf->sink_streams.erase(
+                std::remove_if(stream_conf->sink_streams.begin(),
+                               stream_conf->sink_streams.end(),
+                               [leAudioDevice](auto& pair) {
+                                 auto ases = leAudioDevice->GetAsesByCisConnHdl(
+                                     pair.first);
+                                 return ases.sink;
+                               }),
+                stream_conf->sink_streams.end());
+
+            stream_conf->source_streams.erase(
+                std::remove_if(stream_conf->source_streams.begin(),
+                               stream_conf->source_streams.end(),
+                               [leAudioDevice](auto& pair) {
+                                 auto ases = leAudioDevice->GetAsesByCisConnHdl(
+                                     pair.first);
+                                 return ases.source;
+                               }),
+                stream_conf->source_streams.end());
+
+            if (stream_conf->sink_streams.empty()) {
+              LOG(INFO) << __func__ << " stream stopped ";
+              stream_conf->valid = false;
+            }
+          }
+        });
+
+    ON_CALL(mock_state_machine_, ProcessHciNotifCisDisconnected(_, _, _))
+        .WillByDefault(
+            [](LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
+               const bluetooth::hci::iso_manager::cis_disconnected_evt* event) {
+              if (!group) return;
+              auto ases_pair =
+                  leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl);
+              if (ases_pair.sink) {
+                ases_pair.sink->data_path_state =
+                    types::AudioStreamDataPathState::CIS_ASSIGNED;
+              }
+              if (ases_pair.source) {
+                ases_pair.source->data_path_state =
+                    types::AudioStreamDataPathState::CIS_ASSIGNED;
+              }
+              /* Invalidate stream configuration if needed */
+              auto* stream_conf = &group->stream_conf;
+              if (stream_conf->valid) {
+                stream_conf->sink_streams.erase(
+                    std::remove_if(
+                        stream_conf->sink_streams.begin(),
+                        stream_conf->sink_streams.end(),
+                        [leAudioDevice](auto& pair) {
+                          auto ases =
+                              leAudioDevice->GetAsesByCisConnHdl(pair.first);
+                          LOG(INFO) << __func__
+                                    << " sink ase to delete. Cis handle:  "
+                                    << (int)(pair.first)
+                                    << " ase pointer: " << ases.sink;
+                          return ases.sink;
+                        }),
+                    stream_conf->sink_streams.end());
+
+                stream_conf->source_streams.erase(
+                    std::remove_if(
+                        stream_conf->source_streams.begin(),
+                        stream_conf->source_streams.end(),
+                        [leAudioDevice](auto& pair) {
+                          auto ases =
+                              leAudioDevice->GetAsesByCisConnHdl(pair.first);
+                          LOG(INFO)
+                              << __func__ << " source to delete. Cis handle: "
+                              << (int)(pair.first)
+                              << " ase pointer:  " << ases.source;
+                          return ases.source;
+                        }),
+                    stream_conf->source_streams.end());
+
+                if (stream_conf->sink_streams.empty()) {
+                  LOG(INFO) << __func__ << " stream stopped ";
+                  stream_conf->valid = false;
+                }
+              }
+            });
+
+    ON_CALL(mock_state_machine_, StopStream(_))
+        .WillByDefault([this](LeAudioDeviceGroup* group) {
+          for (LeAudioDevice* device = group->GetFirstDevice();
+               device != nullptr; device = group->GetNextDevice(device)) {
+            for (auto& ase : device->ases_) {
+              ase.data_path_state = types::AudioStreamDataPathState::IDLE;
+              ase.active = false;
+              ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
+              ase.cis_conn_hdl = 0;
+            }
+          }
+
+          // Inject the state
+          group->SetTargetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+          group->SetState(group->GetTargetState());
+          state_machine_callbacks_->StatusReportCb(group->group_id_,
+                                                   GroupStreamStatus::IDLE);
+        });
+  }
+
+  void SetUp() override {
+    init_message_loop_thread();
+
+    ON_CALL(controller_interface_, SupportsBleConnectedIsochronousStreamCentral)
+        .WillByDefault(Return(true));
+    ON_CALL(controller_interface_,
+            SupportsBleConnectedIsochronousStreamPeripheral)
+        .WillByDefault(Return(true));
+
+    controller::SetMockControllerInterface(&controller_interface_);
+    bluetooth::manager::SetMockBtmInterface(&mock_btm_interface_);
+    gatt::SetMockBtaGattInterface(&mock_gatt_interface_);
+    gatt::SetMockBtaGattQueue(&mock_gatt_queue_);
+    bluetooth::storage::SetMockBtifStorageInterface(&mock_btif_storage_);
+
+    iso_manager_ = bluetooth::hci::IsoManager::GetInstance();
+    ASSERT_NE(iso_manager_, nullptr);
+    iso_manager_->Start();
+
+    mock_iso_manager_ = MockIsoManager::GetInstance();
+    ON_CALL(*mock_iso_manager_, RegisterCigCallbacks(_))
+        .WillByDefault(SaveArg<0>(&cig_callbacks_));
+
+    SetUpMockAudioHal();
+    SetUpMockGroups();
+    SetUpMockGatt();
+
+    ASSERT_FALSE(LeAudioClient::IsLeAudioClientRunning());
+  }
+
+  void TearDown() override {
+    // Message loop cleanup should wait for all the 'till now' scheduled calls
+    // so it should be called right at the very begginning of teardown.
+    cleanup_message_loop_thread();
+
+    // This is required since Stop() and Cleanup() may trigger some callbacks or
+    // drop unique pointers to mocks we have raw pointer for and we want to
+    // verify them all.
+    Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+
+    if (LeAudioClient::IsLeAudioClientRunning()) {
+      EXPECT_CALL(mock_gatt_interface_, AppDeregister(gatt_if)).Times(1);
+      LeAudioClient::Cleanup();
+      ASSERT_FALSE(LeAudioClient::IsLeAudioClientRunning());
+    }
+
+    iso_manager_->Stop();
+  }
+
+ protected:
+  class MockDeviceWrapper {
+    class IGattHandlers {
+     public:
+      // IGattHandlers() = default;
+      virtual ~IGattHandlers() = default;
+      virtual void OnReadCharacteristic(uint16_t handle, GATT_READ_OP_CB cb,
+                                        void* cb_data) = 0;
+      virtual void OnWriteCharacteristic(uint16_t handle,
+                                         std::vector<uint8_t> value,
+                                         tGATT_WRITE_TYPE write_type,
+                                         GATT_WRITE_OP_CB cb,
+                                         void* cb_data) = 0;
+    };
+
+   public:
+    struct csis_mock : public IGattHandlers {
+      uint16_t start = 0;
+      uint16_t end = 0;
+      uint16_t sirk_char = 0;
+      uint16_t sirk_ccc = 0;
+      uint16_t size_char = 0;
+      uint16_t size_ccc = 0;
+      uint16_t lock_char = 0;
+      uint16_t lock_ccc = 0;
+      uint16_t rank_char = 0;
+
+      int rank = 0;
+      int size = 0;
+
+      MOCK_METHOD((void), OnReadCharacteristic,
+                  (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data),
+                  (override));
+      MOCK_METHOD((void), OnWriteCharacteristic,
+                  (uint16_t handle, std::vector<uint8_t> value,
+                   tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb,
+                   void* cb_data),
+                  (override));
+    };
+
+    struct cas_mock : public IGattHandlers {
+      uint16_t start = 0;
+      uint16_t end = 0;
+      uint16_t csis_include = 0;
+
+      MOCK_METHOD((void), OnReadCharacteristic,
+                  (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data),
+                  (override));
+      MOCK_METHOD((void), OnWriteCharacteristic,
+                  (uint16_t handle, std::vector<uint8_t> value,
+                   tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb,
+                   void* cb_data),
+                  (override));
+    };
+
+    struct pacs_mock : public IGattHandlers {
+      uint16_t start = 0;
+      uint16_t sink_pac_char = 0;
+      uint16_t sink_pac_ccc = 0;
+      uint16_t sink_audio_loc_char = 0;
+      uint16_t sink_audio_loc_ccc = 0;
+      uint16_t source_pac_char = 0;
+      uint16_t source_pac_ccc = 0;
+      uint16_t source_audio_loc_char = 0;
+      uint16_t source_audio_loc_ccc = 0;
+      uint16_t avail_contexts_char = 0;
+      uint16_t avail_contexts_ccc = 0;
+      uint16_t supp_contexts_char = 0;
+      uint16_t supp_contexts_ccc = 0;
+      uint16_t end = 0;
+
+      MOCK_METHOD((void), OnReadCharacteristic,
+                  (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data),
+                  (override));
+      MOCK_METHOD((void), OnWriteCharacteristic,
+                  (uint16_t handle, std::vector<uint8_t> value,
+                   tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb,
+                   void* cb_data),
+                  (override));
+    };
+
+    struct ascs_mock : public IGattHandlers {
+      uint16_t start = 0;
+      uint16_t sink_ase_char = 0;
+      uint16_t sink_ase_ccc = 0;
+      uint16_t source_ase_char = 0;
+      uint16_t source_ase_ccc = 0;
+      uint16_t ctp_char = 0;
+      uint16_t ctp_ccc = 0;
+      uint16_t end = 0;
+
+      MOCK_METHOD((void), OnReadCharacteristic,
+                  (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data),
+                  (override));
+      MOCK_METHOD((void), OnWriteCharacteristic,
+                  (uint16_t handle, std::vector<uint8_t> value,
+                   tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb,
+                   void* cb_data),
+                  (override));
+    };
+
+    MockDeviceWrapper(RawAddress addr, const std::list<gatt::Service>& services,
+                      std::unique_ptr<MockDeviceWrapper::csis_mock> csis,
+                      std::unique_ptr<MockDeviceWrapper::cas_mock> cas,
+                      std::unique_ptr<MockDeviceWrapper::ascs_mock> ascs,
+                      std::unique_ptr<MockDeviceWrapper::pacs_mock> pacs)
+        : addr(addr) {
+      this->services = services;
+      this->csis = std::move(csis);
+      this->cas = std::move(cas);
+      this->ascs = std::move(ascs);
+      this->pacs = std::move(pacs);
+    }
+
+    ~MockDeviceWrapper() {
+      Mock::VerifyAndClearExpectations(csis.get());
+      Mock::VerifyAndClearExpectations(cas.get());
+      Mock::VerifyAndClearExpectations(ascs.get());
+      Mock::VerifyAndClearExpectations(pacs.get());
+    }
+
+    RawAddress addr;
+    bool connected = false;
+
+    // A list of services and their useful params
+    std::list<gatt::Service> services;
+    std::unique_ptr<csis_mock> csis;
+    std::unique_ptr<cas_mock> cas;
+    std::unique_ptr<ascs_mock> ascs;
+    std::unique_ptr<pacs_mock> pacs;
+  };
+
+  void SyncOnMainLoop() {
+    // Wait for the main loop to flush
+    // WARNING: Not tested with Timers pushing periodic tasks to the main loop
+    while (num_async_tasks > 0)
+      ;
+  }
+
+  void ConnectLeAudio(const RawAddress& address, bool isEncrypted = true) {
+    // by default indicate link as encrypted
+    ON_CALL(mock_btm_interface_, BTM_IsEncrypted(address, _))
+        .WillByDefault(DoAll(Return(isEncrypted)));
+
+    EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, address, true, _)).Times(1);
+
+    do_in_main_thread(
+        FROM_HERE, base::Bind(&LeAudioClient::Connect,
+                              base::Unretained(LeAudioClient::Get()), address));
+
+    SyncOnMainLoop();
+  }
+
+  void DisconnectLeAudio(const RawAddress& address, uint16_t conn_id) {
+    SyncOnMainLoop();
+    EXPECT_CALL(mock_gatt_interface_, Close(conn_id)).Times(1);
+    EXPECT_CALL(mock_client_callbacks_,
+                OnConnectionState(ConnectionState::DISCONNECTED, address))
+        .Times(1);
+    do_in_main_thread(
+        FROM_HERE, base::Bind(&LeAudioClient::Disconnect,
+                              base::Unretained(LeAudioClient::Get()), address));
+  }
+
+  void ConnectCsisDevice(const RawAddress& addr, uint16_t conn_id,
+                         uint32_t sink_audio_allocation,
+                         uint32_t source_audio_allocation, uint8_t group_size,
+                         int group_id, uint8_t rank,
+                         bool connect_through_csis = false) {
+    SetSampleDatabaseEarbudsValid(conn_id, addr, sink_audio_allocation,
+                                  source_audio_allocation, true, /*add_csis*/
+                                  true,                          /*add_cas*/
+                                  true,                          /*add_pacs*/
+                                  true,                          /*add_ascs*/
+                                  group_size, rank);
+    EXPECT_CALL(mock_client_callbacks_,
+                OnConnectionState(ConnectionState::CONNECTED, addr))
+        .Times(1);
+
+    EXPECT_CALL(mock_client_callbacks_,
+                OnGroupNodeStatus(addr, group_id, GroupNodeStatus::ADDED))
+        .Times(1);
+
+    if (connect_through_csis) {
+      // Add it the way CSIS would do: add to group and then connect
+      do_in_main_thread(
+          FROM_HERE,
+          base::Bind(&LeAudioClient::GroupAddNode,
+                     base::Unretained(LeAudioClient::Get()), group_id, addr));
+      ConnectLeAudio(addr);
+    } else {
+      // The usual connect
+      // Since device has CSIS, lets add it here to groups already now
+      groups[addr] = group_id;
+      ConnectLeAudio(addr);
+      InjectGroupDeviceAdded(addr, group_id);
+    }
+  }
+
+  void ConnectNonCsisDevice(const RawAddress& addr, uint16_t conn_id,
+                            uint32_t sink_audio_allocation,
+                            uint32_t source_audio_allocation) {
+    SetSampleDatabaseEarbudsValid(conn_id, addr, sink_audio_allocation,
+                                  source_audio_allocation, false, /*add_csis*/
+                                  true,                           /*add_cas*/
+                                  true,                           /*add_pacs*/
+                                  true,                           /*add_ascs*/
+                                  0, 0);
+    EXPECT_CALL(mock_client_callbacks_,
+                OnConnectionState(ConnectionState::CONNECTED, addr))
+        .Times(1);
+
+    ConnectLeAudio(addr);
+  }
+
+  void UpdateMetadata(audio_usage_t usage, audio_content_type_t content_type) {
+    std::promise<void> do_metadata_update_promise;
+    auto do_metadata_update_future = do_metadata_update_promise.get_future();
+    audio_sink_receiver_->OnAudioMetadataUpdate(
+        std::move(do_metadata_update_promise), usage, content_type);
+    do_metadata_update_future.wait();
+  }
+
+  void StartStreaming(audio_usage_t usage, audio_content_type_t content_type,
+                      int group_id, bool reconfigured_sink = false) {
+    ASSERT_NE(audio_sink_receiver_, nullptr);
+
+    UpdateMetadata(usage, content_type);
+
+    EXPECT_CALL(mock_audio_source_, ConfirmStreamingRequest()).Times(1);
+    std::promise<void> do_resume_sink_promise;
+    auto do_resume_sink_future = do_resume_sink_promise.get_future();
+    /* It's enough to call only one suspend even if it'll be bi-directional
+     * streaming. First resume will trigger GroupStream.
+     *
+     * There is no - 'only source receiver' scenario (e.g. single microphone).
+     * If there will be such test oriented scenario, such resume choose logic
+     * should be applied.
+     */
+    audio_sink_receiver_->OnAudioResume(std::move(do_resume_sink_promise));
+    do_resume_sink_future.wait();
+    SyncOnMainLoop();
+
+    if (reconfigured_sink) {
+      std::promise<void> do_resume_sink_reconf_promise;
+      auto do_resume_sink_reconf_future =
+          do_resume_sink_reconf_promise.get_future();
+
+      audio_sink_receiver_->OnAudioResume(
+          std::move(do_resume_sink_reconf_promise));
+      do_resume_sink_reconf_future.wait();
+    }
+
+    if (usage == AUDIO_USAGE_VOICE_COMMUNICATION) {
+      ASSERT_NE(audio_source_receiver_, nullptr);
+
+      std::promise<void> do_resume_source_promise;
+      auto do_resume_source_future = do_resume_source_promise.get_future();
+      audio_source_receiver_->OnAudioResume(
+          std::move(do_resume_source_promise));
+      do_resume_source_future.wait();
+    }
+  }
+
+  void StopStreaming(int group_id, bool suspend_source = false) {
+    ASSERT_NE(audio_sink_receiver_, nullptr);
+
+    /* TODO We should have a way to confirm Stop() otherwise, audio framework
+     * might have different state that it is in the le_audio code - as tearing
+     * down CISes might take some time
+     */
+    std::promise<void> do_suspend_sink_promise;
+    auto do_suspend_sink_future = do_suspend_sink_promise.get_future();
+    /* It's enough to call only one resume even if it'll be bi-directional
+     * streaming. First suspend will trigger GroupStop.
+     *
+     * There is no - 'only source receiver' scenario (e.g. single microphone).
+     * If there will be such test oriented scenario, such resume choose logic
+     * should be applied.
+     */
+    audio_sink_receiver_->OnAudioSuspend(std::move(do_suspend_sink_promise));
+    do_suspend_sink_future.wait();
+
+    if (suspend_source) {
+      ASSERT_NE(audio_source_receiver_, nullptr);
+      std::promise<void> do_suspend_source_promise;
+      auto do_suspend_source_future = do_suspend_source_promise.get_future();
+      audio_source_receiver_->OnAudioSuspend(
+          std::move(do_suspend_source_promise));
+      do_suspend_source_future.wait();
+    }
+  }
+
+  void set_sample_database(uint16_t conn_id, RawAddress addr,
+                           std::unique_ptr<MockDeviceWrapper::csis_mock> csis,
+                           std::unique_ptr<MockDeviceWrapper::cas_mock> cas,
+                           std::unique_ptr<MockDeviceWrapper::ascs_mock> ascs,
+                           std::unique_ptr<MockDeviceWrapper::pacs_mock> pacs) {
+    gatt::DatabaseBuilder bob;
+
+    /* Generic Access Service */
+    bob.AddService(0x0001, 0x0003, Uuid::From16Bit(0x1800), true);
+    /* Device Name Char. */
+    bob.AddCharacteristic(0x0002, 0x0003, Uuid::From16Bit(0x2a00),
+                          GATT_CHAR_PROP_BIT_READ);
+
+    if (csis->start) {
+      bool is_primary = true;
+      bob.AddService(csis->start, csis->end, bluetooth::csis::kCsisServiceUuid,
+                     is_primary);
+      if (csis->sirk_char) {
+        bob.AddCharacteristic(
+            csis->sirk_char, csis->sirk_char + 1,
+            bluetooth::csis::kCsisSirkUuid,
+            GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+        if (csis->sirk_ccc)
+          bob.AddDescriptor(csis->sirk_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+
+      if (csis->size_char) {
+        bob.AddCharacteristic(
+            csis->size_char, csis->size_char + 1,
+            bluetooth::csis::kCsisSizeUuid,
+            GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+        if (csis->size_ccc)
+          bob.AddDescriptor(csis->size_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+
+      if (csis->lock_char) {
+        bob.AddCharacteristic(csis->lock_char, csis->lock_char + 1,
+                              bluetooth::csis::kCsisLockUuid,
+                              GATT_CHAR_PROP_BIT_READ |
+                                  GATT_CHAR_PROP_BIT_NOTIFY |
+                                  GATT_CHAR_PROP_BIT_WRITE);
+        if (csis->lock_ccc)
+          bob.AddDescriptor(csis->lock_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+
+      if (csis->rank_char)
+        bob.AddCharacteristic(csis->rank_char, csis->rank_char + 1,
+                              bluetooth::csis::kCsisRankUuid,
+                              GATT_CHAR_PROP_BIT_READ);
+    }
+
+    if (cas->start) {
+      bool is_primary = true;
+      bob.AddService(cas->start, cas->end, le_audio::uuid::kCapServiceUuid,
+                     is_primary);
+      // Include CSIS service inside
+      if (cas->csis_include)
+        bob.AddIncludedService(cas->csis_include,
+                               bluetooth::csis::kCsisServiceUuid, csis->start,
+                               csis->end);
+    }
+
+    if (pacs->start) {
+      bool is_primary = true;
+      bob.AddService(pacs->start, pacs->end,
+                     le_audio::uuid::kPublishedAudioCapabilityServiceUuid,
+                     is_primary);
+
+      if (pacs->sink_pac_char) {
+        bob.AddCharacteristic(
+            pacs->sink_pac_char, pacs->sink_pac_char + 1,
+            le_audio::uuid::kSinkPublishedAudioCapabilityCharacteristicUuid,
+            GATT_CHAR_PROP_BIT_READ);
+        if (pacs->sink_pac_ccc)
+          bob.AddDescriptor(pacs->sink_pac_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+
+      if (pacs->sink_audio_loc_char) {
+        bob.AddCharacteristic(
+            pacs->sink_audio_loc_char, pacs->sink_audio_loc_char + 1,
+            le_audio::uuid::kSinkAudioLocationCharacteristicUuid,
+            GATT_CHAR_PROP_BIT_READ);
+        if (pacs->sink_audio_loc_ccc)
+          bob.AddDescriptor(pacs->sink_audio_loc_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+
+      if (pacs->source_pac_char) {
+        bob.AddCharacteristic(
+            pacs->source_pac_char, pacs->source_pac_char + 1,
+            le_audio::uuid::kSourcePublishedAudioCapabilityCharacteristicUuid,
+            GATT_CHAR_PROP_BIT_READ);
+        if (pacs->source_pac_ccc)
+          bob.AddDescriptor(pacs->source_pac_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+
+      if (pacs->source_audio_loc_char) {
+        bob.AddCharacteristic(
+            pacs->source_audio_loc_char, pacs->source_audio_loc_char + 1,
+            le_audio::uuid::kSourceAudioLocationCharacteristicUuid,
+            GATT_CHAR_PROP_BIT_READ);
+        if (pacs->source_audio_loc_ccc)
+          bob.AddDescriptor(pacs->source_audio_loc_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+
+      if (pacs->avail_contexts_char) {
+        bob.AddCharacteristic(
+            pacs->avail_contexts_char, pacs->avail_contexts_char + 1,
+            le_audio::uuid::kAudioContextAvailabilityCharacteristicUuid,
+            GATT_CHAR_PROP_BIT_READ);
+        if (pacs->avail_contexts_ccc)
+          bob.AddDescriptor(pacs->avail_contexts_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+
+      if (pacs->supp_contexts_char) {
+        bob.AddCharacteristic(
+            pacs->supp_contexts_char, pacs->supp_contexts_char + 1,
+            le_audio::uuid::kAudioSupportedContextCharacteristicUuid,
+            GATT_CHAR_PROP_BIT_READ);
+        if (pacs->supp_contexts_ccc)
+          bob.AddDescriptor(pacs->supp_contexts_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+    }
+
+    if (ascs->start) {
+      bool is_primary = true;
+      bob.AddService(ascs->start, ascs->end,
+                     le_audio::uuid::kAudioStreamControlServiceUuid,
+                     is_primary);
+      if (ascs->sink_ase_char) {
+        bob.AddCharacteristic(ascs->sink_ase_char, ascs->sink_ase_char + 1,
+                              le_audio::uuid::kSinkAudioStreamEndpointUuid,
+                              GATT_CHAR_PROP_BIT_READ);
+        if (ascs->sink_ase_ccc)
+          bob.AddDescriptor(ascs->sink_ase_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+      if (ascs->source_ase_char) {
+        bob.AddCharacteristic(ascs->source_ase_char, ascs->source_ase_char + 1,
+                              le_audio::uuid::kSourceAudioStreamEndpointUuid,
+                              GATT_CHAR_PROP_BIT_READ);
+        if (ascs->source_ase_ccc)
+          bob.AddDescriptor(ascs->source_ase_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+      if (ascs->ctp_char) {
+        bob.AddCharacteristic(
+            ascs->ctp_char, ascs->ctp_char + 1,
+            le_audio::uuid::kAudioStreamEndpointControlPointCharacteristicUuid,
+            GATT_CHAR_PROP_BIT_READ);
+        if (ascs->ctp_ccc)
+          bob.AddDescriptor(ascs->ctp_ccc,
+                            Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+      }
+    }
+
+    // Assign conn_id to a certain device - this does not mean it is connected
+    auto dev_wrapper = std::make_unique<MockDeviceWrapper>(
+        addr, bob.Build().Services(), std::move(csis), std::move(cas),
+        std::move(ascs), std::move(pacs));
+    peer_devices.emplace(conn_id, std::move(dev_wrapper));
+  }
+
+  void SetSampleDatabaseEmpty(uint16_t conn_id, RawAddress addr) {
+    auto csis = std::make_unique<MockDeviceWrapper::csis_mock>();
+    auto cas = std::make_unique<MockDeviceWrapper::cas_mock>();
+    auto pacs = std::make_unique<MockDeviceWrapper::pacs_mock>();
+    auto ascs = std::make_unique<MockDeviceWrapper::ascs_mock>();
+    set_sample_database(conn_id, addr, std::move(csis), std::move(cas),
+                        std::move(ascs), std::move(pacs));
+  }
+
+  void SetSampleDatabaseEarbudsValid(uint16_t conn_id, RawAddress addr,
+                                     uint32_t sink_audio_allocation,
+                                     uint32_t source_audio_allocation,
+                                     bool add_csis = true, bool add_cas = true,
+                                     bool add_pacs = true, bool add_ascs = true,
+                                     uint8_t set_size = 2, uint8_t rank = 1) {
+    auto csis = std::make_unique<MockDeviceWrapper::csis_mock>();
+    if (add_csis) {
+      // attribute handles
+      csis->start = 0x0010;
+      csis->sirk_char = 0x0020;
+      csis->sirk_ccc = 0x0022;
+      csis->size_char = 0x0023;
+      csis->size_ccc = 0x0025;
+      csis->lock_char = 0x0026;
+      csis->lock_ccc = 0x0028;
+      csis->rank_char = 0x0029;
+      csis->end = 0x0030;
+      // other params
+      csis->size = set_size;
+      csis->rank = rank;
+    }
+
+    auto cas = std::make_unique<MockDeviceWrapper::cas_mock>();
+    if (add_cas) {
+      // attribute handles
+      cas->start = 0x0040;
+      if (add_csis) cas->csis_include = 0x0041;
+      cas->end = 0x0050;
+      // other params
+    }
+
+    auto pacs = std::make_unique<MockDeviceWrapper::pacs_mock>();
+    if (add_pacs) {
+      // attribute handles
+      pacs->start = 0x0060;
+      pacs->sink_pac_char = 0x0061;
+      pacs->sink_pac_ccc = 0x0063;
+      pacs->sink_audio_loc_char = 0x0064;
+      pacs->sink_audio_loc_ccc = 0x0066;
+      pacs->source_pac_char = 0x0067;
+      pacs->source_pac_ccc = 0x0069;
+      pacs->source_audio_loc_char = 0x0070;
+      pacs->source_audio_loc_ccc = 0x0072;
+      pacs->avail_contexts_char = 0x0073;
+      pacs->avail_contexts_ccc = 0x0075;
+      pacs->supp_contexts_char = 0x0076;
+      pacs->supp_contexts_ccc = 0x0078;
+      pacs->end = 0x0080;
+      // other params
+    }
+
+    auto ascs = std::make_unique<MockDeviceWrapper::ascs_mock>();
+    if (add_ascs) {
+      // attribute handles
+      ascs->start = 0x0090;
+      ascs->sink_ase_char = 0x0091;
+      ascs->sink_ase_ccc = 0x0093;
+      ascs->source_ase_char = 0x0094;
+      ascs->source_ase_ccc = 0x0096;
+      ascs->ctp_char = 0x0097;
+      ascs->ctp_ccc = 0x0099;
+      ascs->end = 0x00A0;
+      // other params
+    }
+
+    set_sample_database(conn_id, addr, std::move(csis), std::move(cas),
+                        std::move(ascs), std::move(pacs));
+
+    if (add_pacs) {
+      uint8_t snk_allocation[4];
+      uint8_t src_allocation[4];
+
+      snk_allocation[0] = (uint8_t)(sink_audio_allocation);
+      snk_allocation[1] = (uint8_t)(sink_audio_allocation >> 8);
+      snk_allocation[2] = (uint8_t)(sink_audio_allocation >> 16);
+      snk_allocation[3] = (uint8_t)(sink_audio_allocation >> 24);
+
+      src_allocation[0] = (uint8_t)(source_audio_allocation);
+      src_allocation[1] = (uint8_t)(source_audio_allocation >> 8);
+      src_allocation[2] = (uint8_t)(source_audio_allocation >> 16);
+      src_allocation[3] = (uint8_t)(source_audio_allocation >> 24);
+
+      // Set pacs default read values
+      ON_CALL(*peer_devices.at(conn_id)->pacs, OnReadCharacteristic(_, _, _))
+          .WillByDefault(
+              [this, conn_id, snk_allocation, src_allocation](
+                  uint16_t handle, GATT_READ_OP_CB cb, void* cb_data) {
+                auto& pacs = peer_devices.at(conn_id)->pacs;
+                std::vector<uint8_t> value;
+                if (handle == pacs->sink_pac_char + 1) {
+                  value = {
+                      // Num records
+                      0x02,
+                      // Codec_ID
+                      0x06,
+                      0x00,
+                      0x00,
+                      0x00,
+                      0x00,
+                      // Codec Spec. Caps. Len
+                      0x10,
+                      0x03,
+                      0x01,
+                      0x04,
+                      0x00,
+                      0x02,
+                      0x02,
+                      0x03,
+                      0x02,
+                      0x03,
+                      0x03,
+                      0x05,
+                      0x04,
+                      0x1E,
+                      0x00,
+                      0x28,
+                      0x00,
+                      // Metadata Length
+                      0x00,
+                      // Codec_ID
+                      0x06,
+                      0x00,
+                      0x00,
+                      0x00,
+                      0x00,
+                      // Codec Spec. Caps. Len
+                      0x10,
+                      0x03,
+                      0x01,
+                      0x80,
+                      0x00,
+                      0x02,
+                      0x02,
+                      0x03,
+                      0x02,
+                      0x03,
+                      0x03,
+                      0x05,
+                      0x04,
+                      0x78,
+                      0x00,
+                      0x78,
+                      0x00,
+                      // Metadata Length
+                      0x00,
+                  };
+                } else if (handle == pacs->sink_audio_loc_char + 1) {
+                  value = {
+                      // Audio Locations
+                      snk_allocation[0],
+                      snk_allocation[1],
+                      snk_allocation[2],
+                      snk_allocation[3],
+                  };
+                } else if (handle == pacs->source_pac_char + 1) {
+                  value = {
+                      // Num records
+                      0x02,
+                      // Codec_ID
+                      0x06,
+                      0x00,
+                      0x00,
+                      0x00,
+                      0x00,
+                      // Codec Spec. Caps. Len
+                      0x10,
+                      0x03,
+                      0x01,
+                      0x04,
+                      0x00,
+                      0x02,
+                      0x02,
+                      0x03,
+                      0x02,
+                      0x03,
+                      0x03,
+                      0x05,
+                      0x04,
+                      0x1E,
+                      0x00,
+                      0x28,
+                      0x00,
+                      // Metadata Length
+                      0x00,
+                      // Codec_ID
+                      0x06,
+                      0x00,
+                      0x00,
+                      0x00,
+                      0x00,
+                      // Codec Spec. Caps. Len
+                      0x10,
+                      0x03,
+                      0x01,
+                      0x04,
+                      0x00,
+                      0x02,
+                      0x02,
+                      0x03,
+                      0x02,
+                      0x03,
+                      0x03,
+                      0x05,
+                      0x04,
+                      0x1E,
+                      0x00,
+                      0x28,
+                      0x00,
+                      // Metadata Length
+                      0x00,
+                  };
+                } else if (handle == pacs->source_audio_loc_char + 1) {
+                  value = {
+                      // Audio Locations
+                      src_allocation[0],
+                      src_allocation[1],
+                      src_allocation[2],
+                      src_allocation[3],
+                  };
+                } else if (handle == pacs->avail_contexts_char + 1) {
+                  value = {
+                      // Sink Avail Contexts
+                      0xff,
+                      0xff,
+                      // Source Avail Contexts
+                      0xff,
+                      0xff,
+                  };
+                } else if (handle == pacs->supp_contexts_char + 1) {
+                  value = {
+                      // Sink Avail Contexts
+                      0xff,
+                      0xff,
+                      // Source Avail Contexts
+                      0xff,
+                      0xff,
+                  };
+                }
+                cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(),
+                   cb_data);
+              });
+    }
+
+    if (add_ascs) {
+      // Set ascs default read values
+      ON_CALL(*peer_devices.at(conn_id)->ascs, OnReadCharacteristic(_, _, _))
+          .WillByDefault([this, conn_id](uint16_t handle, GATT_READ_OP_CB cb,
+                                         void* cb_data) {
+            auto& ascs = peer_devices.at(conn_id)->ascs;
+            std::vector<uint8_t> value;
+            if (handle == ascs->sink_ase_char + 1) {
+              value = {
+                  // ASE ID
+                  0x01,
+                  // State
+                  static_cast<uint8_t>(
+                      le_audio::types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE),
+                  // No Additional ASE params for IDLE state
+              };
+            } else if (handle == ascs->source_ase_char + 1) {
+              value = {
+                  // ASE ID
+                  0x02,
+                  // State
+                  static_cast<uint8_t>(
+                      le_audio::types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE),
+                  // No Additional ASE params for IDLE state
+              };
+            }
+            cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(),
+               cb_data);
+          });
+    }
+  }
+
+  void TestAudioDataTransfer(int group_id, uint8_t cis_count_out,
+                             uint8_t cis_count_in, int data_len) {
+    ASSERT_NE(audio_sink_receiver_, nullptr);
+
+    // Expect two channels ISO Data to be sent
+    std::vector<uint16_t> handles;
+    EXPECT_CALL(*mock_iso_manager_, SendIsoData(_, _, _))
+        .Times(cis_count_out)
+        .WillRepeatedly(
+            [&handles](uint16_t iso_handle, const uint8_t* data,
+                       uint16_t data_len) { handles.push_back(iso_handle); });
+    std::vector<uint8_t> data(data_len);
+    audio_sink_receiver_->OnAudioDataReady(data);
+
+    // Inject microphone data from a single peer device
+    EXPECT_CALL(mock_audio_sink_, SendData(_, _)).Times(cis_count_in);
+    ASSERT_EQ(streaming_groups.count(group_id), 1u);
+
+    if (cis_count_in) {
+      ASSERT_NE(audio_source_receiver_, nullptr);
+
+      auto group = streaming_groups.at(group_id);
+      for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr;
+           device = group->GetNextDevice(device)) {
+        for (auto& ase : device->ases_) {
+          if (ase.direction == le_audio::types::kLeAudioDirectionSource) {
+            InjectIncomingIsoData(group_id, ase.cis_conn_hdl);
+            --cis_count_in;
+            if (!cis_count_in) break;
+          }
+        }
+        if (!cis_count_in) break;
+      }
+    }
+
+    SyncOnMainLoop();
+    std::sort(handles.begin(), handles.end());
+    ASSERT_EQ(std::unique(handles.begin(), handles.end()) - handles.begin(),
+              cis_count_out);
+    ASSERT_EQ(cis_count_in, 0);
+    handles.clear();
+
+    Mock::VerifyAndClearExpectations(mock_iso_manager_);
+    Mock::VerifyAndClearExpectations(&mock_audio_sink_);
+  }
+
+  void InjectIncomingIsoData(uint16_t cig_id, uint16_t cis_con_hdl,
+                             size_t payload_size = 40) {
+    BT_HDR* bt_hdr = (BT_HDR*)malloc(sizeof(BT_HDR) + payload_size);
+
+    bt_hdr->offset = 0;
+    bt_hdr->len = payload_size;
+
+    bluetooth::hci::iso_manager::cis_data_evt cis_evt;
+    cis_evt.cig_id = cig_id;
+    cis_evt.cis_conn_hdl = cis_con_hdl;
+    cis_evt.ts = 0;
+    cis_evt.evt_lost = 0;
+    cis_evt.p_msg = bt_hdr;
+
+    ASSERT_NE(cig_callbacks_, nullptr);
+    cig_callbacks_->OnCisEvent(
+        bluetooth::hci::iso_manager::kIsoEventCisDataAvailable, &cis_evt);
+    free(bt_hdr);
+  }
+
+  void InjectCisDisconnected(uint16_t cig_id, uint16_t cis_con_hdl,
+                             uint8_t reason = 0) {
+    bluetooth::hci::iso_manager::cis_disconnected_evt cis_evt;
+    cis_evt.cig_id = cig_id;
+    cis_evt.cis_conn_hdl = cis_con_hdl;
+    cis_evt.reason = reason;
+
+    ASSERT_NE(cig_callbacks_, nullptr);
+    cig_callbacks_->OnCisEvent(
+        bluetooth::hci::iso_manager::kIsoEventCisDisconnected, &cis_evt);
+  }
+
+  MockLeAudioClientCallbacks mock_client_callbacks_;
+  MockLeAudioClientAudioSource mock_audio_source_;
+  MockLeAudioClientAudioSink mock_audio_sink_;
+  LeAudioClientAudioSinkReceiver* audio_sink_receiver_ = nullptr;
+  LeAudioClientAudioSourceReceiver* audio_source_receiver_ = nullptr;
+
+  bool is_audio_hal_source_acquired;
+  bool is_audio_hal_sink_acquired;
+
+  MockCsisClient mock_csis_client_module_;
+  MockDeviceGroups mock_groups_module_;
+  bluetooth::groups::DeviceGroupsCallbacks* group_callbacks_;
+  MockLeAudioGroupStateMachine mock_state_machine_;
+
+  MockFunction<void()> mock_storage_load;
+  MockFunction<bool()> mock_hal_2_1_verifier;
+
+  controller::MockControllerInterface controller_interface_;
+  bluetooth::manager::MockBtmInterface mock_btm_interface_;
+  gatt::MockBtaGattInterface mock_gatt_interface_;
+  gatt::MockBtaGattQueue mock_gatt_queue_;
+  tBTA_GATTC_CBACK* gatt_callback;
+  const uint8_t gatt_if = 0xfe;
+  uint8_t global_conn_id = 1;
+  le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks_;
+  std::map<int, LeAudioDeviceGroup*> streaming_groups;
+
+  bluetooth::hci::IsoManager* iso_manager_;
+  MockIsoManager* mock_iso_manager_;
+  bluetooth::hci::iso_manager::CigCallbacks* cig_callbacks_ = nullptr;
+  uint16_t iso_con_counter_ = 1;
+
+  bluetooth::storage::MockBtifStorageInterface mock_btif_storage_;
+
+  std::map<uint16_t, std::unique_ptr<MockDeviceWrapper>> peer_devices;
+  std::list<int> group_locks;
+  std::map<RawAddress, int> groups;
+};
+
+class UnicastTest : public UnicastTestNoInit {
+ protected:
+  void SetUp() override {
+    UnicastTestNoInit::SetUp();
+
+    EXPECT_CALL(mock_hal_2_1_verifier, Call()).Times(1);
+    EXPECT_CALL(mock_storage_load, Call()).Times(1);
+
+    BtaAppRegisterCallback app_register_callback;
+    EXPECT_CALL(mock_gatt_interface_, AppRegister(_, _, _))
+        .WillOnce(DoAll(SaveArg<0>(&gatt_callback),
+                        SaveArg<1>(&app_register_callback)));
+    LeAudioClient::Initialize(
+        &mock_client_callbacks_,
+        base::Bind([](MockFunction<void()>* foo) { foo->Call(); },
+                   &mock_storage_load),
+        base::Bind([](MockFunction<bool()>* foo) { return foo->Call(); },
+                   &mock_hal_2_1_verifier));
+
+    SyncOnMainLoop();
+    ASSERT_TRUE(gatt_callback);
+    ASSERT_TRUE(group_callbacks_);
+    ASSERT_TRUE(app_register_callback);
+    app_register_callback.Run(gatt_if, GATT_SUCCESS);
+    Mock::VerifyAndClearExpectations(&mock_gatt_interface_);
+  }
+
+  void TearDown() override {
+    groups.clear();
+    UnicastTestNoInit::TearDown();
+  }
+};
+
+RawAddress GetTestAddress(uint8_t index) {
+  CHECK_LT(index, UINT8_MAX);
+  RawAddress result = {{0xC0, 0xDE, 0xC0, 0xDE, 0x00, index}};
+  return result;
+}
+
+TEST_F(UnicastTest, Initialize) {
+  ASSERT_NE(LeAudioClient::Get(), nullptr);
+  ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning());
+}
+
+TEST_F(UnicastTestNoInit, InitializeNoHal_2_1) {
+  ASSERT_FALSE(LeAudioClient::IsLeAudioClientRunning());
+
+  // Report False when asked for Audio HAL 2.1 support
+  ON_CALL(mock_hal_2_1_verifier, Call()).WillByDefault([]() -> bool {
+    return false;
+  });
+
+  BtaAppRegisterCallback app_register_callback;
+  ON_CALL(mock_gatt_interface_, AppRegister(_, _, _))
+      .WillByDefault(DoAll(SaveArg<0>(&gatt_callback),
+                           SaveArg<1>(&app_register_callback)));
+
+  EXPECT_DEATH(
+      LeAudioClient::Initialize(
+          &mock_client_callbacks_,
+          base::Bind([](MockFunction<void()>* foo) { foo->Call(); },
+                     &mock_storage_load),
+          base::Bind([](MockFunction<bool()>* foo) { return foo->Call(); },
+                     &mock_hal_2_1_verifier)),
+      ", LE Audio Client requires Bluetooth Audio HAL V2.1 at least. Either "
+      "disable LE Audio Profile, or update your HAL");
+}
+
+TEST_F(UnicastTest, ConnectOneEarbudEmpty) {
+  const RawAddress test_address0 = GetTestAddress(0);
+  SetSampleDatabaseEmpty(1, test_address0);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::DISCONNECTED, test_address0))
+      .Times(1);
+  EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1);
+  ConnectLeAudio(test_address0);
+}
+
+TEST_F(UnicastTest, ConnectOneEarbudNoPacs) {
+  const RawAddress test_address0 = GetTestAddress(0);
+  SetSampleDatabaseEarbudsValid(
+      1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+      codec_spec_conf::kLeAudioLocationStereo, true, /*add_csis*/
+      true,                                          /*add_cas*/
+      false,                                         /*add_pacs*/
+      true /*add_ascs*/);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::DISCONNECTED, test_address0))
+      .Times(1);
+  EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1);
+  ConnectLeAudio(test_address0);
+}
+
+TEST_F(UnicastTest, ConnectOneEarbudNoAscs) {
+  const RawAddress test_address0 = GetTestAddress(0);
+  SetSampleDatabaseEarbudsValid(
+      1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+      codec_spec_conf::kLeAudioLocationStereo, true, /*add_csis*/
+      true,                                          /*add_cas*/
+      true,                                          /*add_pacs*/
+      false /*add_ascs*/);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::DISCONNECTED, test_address0))
+      .Times(1);
+  EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1);
+  ConnectLeAudio(test_address0);
+}
+
+TEST_F(UnicastTest, ConnectOneEarbudNoCas) {
+  const RawAddress test_address0 = GetTestAddress(0);
+  uint16_t conn_id = 1;
+  SetSampleDatabaseEarbudsValid(
+      conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+      codec_spec_conf::kLeAudioLocationStereo, true, /*add_csis*/
+      false,                                         /*add_cas*/
+      true,                                          /*add_pacs*/
+      true /*add_ascs*/);
+
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::CONNECTED, test_address0))
+      .Times(1);
+  ConnectLeAudio(test_address0);
+}
+
+TEST_F(UnicastTest, ConnectOneEarbudNoCsis) {
+  const RawAddress test_address0 = GetTestAddress(0);
+  SetSampleDatabaseEarbudsValid(
+      1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+      codec_spec_conf::kLeAudioLocationStereo, false, /*add_csis*/
+      true,                                           /*add_cas*/
+      true,                                           /*add_pacs*/
+      true /*add_ascs*/);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::CONNECTED, test_address0))
+      .Times(1);
+  ConnectLeAudio(test_address0);
+}
+
+TEST_F(UnicastTest, ConnectDisconnectOneEarbud) {
+  const RawAddress test_address0 = GetTestAddress(0);
+  SetSampleDatabaseEarbudsValid(1, test_address0,
+                                codec_spec_conf::kLeAudioLocationStereo,
+                                codec_spec_conf::kLeAudioLocationStereo);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::CONNECTED, test_address0))
+      .Times(1);
+  ConnectLeAudio(test_address0);
+  DisconnectLeAudio(test_address0, 1);
+}
+
+TEST_F(UnicastTest, ConnectTwoEarbudsCsisGrouped) {
+  uint8_t group_size = 2;
+  int group_id = 2;
+
+  // Report working CSIS
+  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+      .WillByDefault(Return(true));
+
+  // First earbud
+  const RawAddress test_address0 = GetTestAddress(0);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true))
+      .Times(1);
+  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontLeft,
+                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
+                    group_id, 1 /* rank*/);
+
+  // Second earbud
+  const RawAddress test_address1 = GetTestAddress(1);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true))
+      .Times(1);
+  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontRight,
+                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
+                    group_id, 2 /* rank*/, true /*connect_through_csis*/);
+
+  // Verify grouping information
+  std::vector<RawAddress> devs =
+      LeAudioClient::Get()->GetGroupDevices(group_id);
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end());
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end());
+
+  DisconnectLeAudio(test_address0, 1);
+  DisconnectLeAudio(test_address1, 2);
+}
+
+TEST_F(UnicastTest, ConnectTwoEarbudsCsisGroupUnknownAtConnect) {
+  uint8_t group_size = 2;
+  uint8_t group_id = 2;
+
+  // Report working CSIS
+  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+      .WillByDefault(Return(true));
+
+  // First earbud connects without known grouping
+  const RawAddress test_address0 = GetTestAddress(0);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true))
+      .Times(1);
+  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontLeft,
+                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
+                    group_id, 1 /* rank*/);
+
+  // Second earbud
+  const RawAddress test_address1 = GetTestAddress(1);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true))
+      .Times(1);
+  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontRight,
+                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
+                    group_id, 2 /* rank*/, true /*connect_through_csis*/);
+
+  // Verify grouping information
+  std::vector<RawAddress> devs =
+      LeAudioClient::Get()->GetGroupDevices(group_id);
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end());
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end());
+
+  DisconnectLeAudio(test_address0, 1);
+  DisconnectLeAudio(test_address1, 2);
+}
+
+TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGrouped) {
+  // Prepare two devices
+  uint8_t group_size = 2;
+  uint8_t group_id = 2;
+
+  const RawAddress test_address0 = GetTestAddress(0);
+  SetSampleDatabaseEarbudsValid(
+      1, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft,
+      codec_spec_conf::kLeAudioLocationFrontLeft, true, /*add_csis*/
+      true,                                             /*add_cas*/
+      true,                                             /*add_pacs*/
+      true,                                             /*add_ascs*/
+      group_size, 1);
+
+  const RawAddress test_address1 = GetTestAddress(1);
+  SetSampleDatabaseEarbudsValid(
+      2, test_address1, codec_spec_conf::kLeAudioLocationFrontRight,
+      codec_spec_conf::kLeAudioLocationFrontRight, true, /*add_csis*/
+      true,                                              /*add_cas*/
+      true,                                              /*add_pacs*/
+      true,                                              /*add_ascs*/
+      group_size, 2);
+
+  // Load devices from the storage when storage API is called
+  bool autoconnect = true;
+  EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() {
+    do_in_main_thread(FROM_HERE, base::Bind(&LeAudioClient::AddFromStorage,
+                                            test_address0, autoconnect));
+    do_in_main_thread(FROM_HERE, base::Bind(&LeAudioClient::AddFromStorage,
+                                            test_address1, autoconnect));
+  });
+
+  // Expect stored device0 to connect automatically
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::CONNECTED, test_address0))
+      .Times(1);
+  ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _))
+      .WillByDefault(DoAll(Return(true)));
+  EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, true, _))
+      .Times(1);
+
+  // Expect stored device1 to connect automatically
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::CONNECTED, test_address1))
+      .Times(1);
+  ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _))
+      .WillByDefault(DoAll(Return(true)));
+  EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, true, _))
+      .Times(1);
+
+  ON_CALL(mock_groups_module_, GetGroupId(_, _))
+      .WillByDefault(DoAll(Return(group_id)));
+
+  ON_CALL(mock_btm_interface_,
+          GetSecurityFlagsByTransport(test_address0, NotNull(), _))
+      .WillByDefault(
+          DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true)));
+
+  // Initialize
+  BtaAppRegisterCallback app_register_callback;
+  ON_CALL(mock_gatt_interface_, AppRegister(_, _, _))
+      .WillByDefault(DoAll(SaveArg<0>(&gatt_callback),
+                           SaveArg<1>(&app_register_callback)));
+  LeAudioClient::Initialize(
+      &mock_client_callbacks_,
+      base::Bind([](MockFunction<void()>* foo) { foo->Call(); },
+                 &mock_storage_load),
+      base::Bind([](MockFunction<bool()>* foo) { return foo->Call(); },
+                 &mock_hal_2_1_verifier));
+  if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS);
+
+  // We need to wait for the storage callback before verifying stuff
+  SyncOnMainLoop();
+  ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning());
+
+  // Verify if all went well and we got the proper group
+  std::vector<RawAddress> devs =
+      LeAudioClient::Get()->GetGroupDevices(group_id);
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end());
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end());
+
+  DisconnectLeAudio(test_address0, 1);
+  DisconnectLeAudio(test_address1, 2);
+}
+
+TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGroupedDifferently) {
+  // Prepare two devices
+  uint8_t group_size = 1;
+
+  // Device 0
+  uint8_t group_id0 = 2;
+  bool autoconnect0 = true;
+  const RawAddress test_address0 = GetTestAddress(0);
+  SetSampleDatabaseEarbudsValid(
+      1, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft,
+      codec_spec_conf::kLeAudioLocationFrontLeft, true, /*add_csis*/
+      true,                                             /*add_cas*/
+      true,                                             /*add_pacs*/
+      true,                                             /*add_ascs*/
+      group_size, 1);
+
+  ON_CALL(mock_groups_module_, GetGroupId(test_address0, _))
+      .WillByDefault(DoAll(Return(group_id0)));
+
+  // Device 1
+  uint8_t group_id1 = 3;
+  bool autoconnect1 = false;
+  const RawAddress test_address1 = GetTestAddress(1);
+  SetSampleDatabaseEarbudsValid(
+      2, test_address1, codec_spec_conf::kLeAudioLocationFrontRight,
+      codec_spec_conf::kLeAudioLocationFrontRight, true, /*add_csis*/
+      true,                                              /*add_cas*/
+      true,                                              /*add_pacs*/
+      true,                                              /*add_ascs*/
+      group_size, 2);
+
+  ON_CALL(mock_groups_module_, GetGroupId(test_address1, _))
+      .WillByDefault(DoAll(Return(group_id1)));
+
+  // Load devices from the storage when storage API is called
+  EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() {
+    do_in_main_thread(FROM_HERE, base::Bind(&LeAudioClient::AddFromStorage,
+                                            test_address0, autoconnect0));
+    do_in_main_thread(FROM_HERE, base::Bind(&LeAudioClient::AddFromStorage,
+                                            test_address1, autoconnect1));
+  });
+
+  // Expect stored device0 to connect automatically
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::CONNECTED, test_address0))
+      .Times(1);
+  ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _))
+      .WillByDefault(DoAll(Return(true)));
+  EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, true, _))
+      .Times(1);
+
+  // Expect stored device1 to NOT connect automatically
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::CONNECTED, test_address1))
+      .Times(0);
+  ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _))
+      .WillByDefault(DoAll(Return(true)));
+  EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, true, _))
+      .Times(0);
+
+  // Initialize
+  BtaAppRegisterCallback app_register_callback;
+  ON_CALL(mock_gatt_interface_, AppRegister(_, _, _))
+      .WillByDefault(DoAll(SaveArg<0>(&gatt_callback),
+                           SaveArg<1>(&app_register_callback)));
+  LeAudioClient::Initialize(
+      &mock_client_callbacks_,
+      base::Bind([](MockFunction<void()>* foo) { foo->Call(); },
+                 &mock_storage_load),
+      base::Bind([](MockFunction<bool()>* foo) { return foo->Call(); },
+                 &mock_hal_2_1_verifier));
+  if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS);
+
+  // We need to wait for the storage callback before verifying stuff
+  SyncOnMainLoop();
+  ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning());
+
+  std::vector<RawAddress> devs =
+      LeAudioClient::Get()->GetGroupDevices(group_id0);
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end());
+  ASSERT_EQ(std::find(devs.begin(), devs.end(), test_address1), devs.end());
+
+  devs = LeAudioClient::Get()->GetGroupDevices(group_id1);
+  ASSERT_EQ(std::find(devs.begin(), devs.end(), test_address0), devs.end());
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end());
+
+  DisconnectLeAudio(test_address0, 1);
+}
+
+TEST_F(UnicastTest, GroupingAddRemove) {
+  // Earbud connects without known grouping
+  uint8_t group_id0 = bluetooth::groups::kGroupUnknown;
+  const RawAddress test_address0 = GetTestAddress(0);
+
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true))
+      .Times(1);
+  ConnectNonCsisDevice(test_address0, 1 /*conn_id*/,
+                       codec_spec_conf::kLeAudioLocationFrontLeft,
+                       codec_spec_conf::kLeAudioLocationFrontLeft);
+
+  group_id0 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address0);
+
+  // Earbud connects without known grouping
+  uint8_t group_id1 = bluetooth::groups::kGroupUnknown;
+  const RawAddress test_address1 = GetTestAddress(1);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true))
+      .Times(1);
+  ConnectNonCsisDevice(test_address1, 2 /*conn_id*/,
+                       codec_spec_conf::kLeAudioLocationFrontRight,
+                       codec_spec_conf::kLeAudioLocationFrontRight);
+
+  group_id1 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1);
+
+  Mock::VerifyAndClearExpectations(&mock_btif_storage_);
+
+  // Verify individual groups
+  ASSERT_NE(group_id0, bluetooth::groups::kGroupUnknown);
+  ASSERT_NE(group_id1, bluetooth::groups::kGroupUnknown);
+  ASSERT_NE(group_id0, group_id1);
+  ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 1u);
+  ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 1u);
+
+  // Expectations on reassigning second earbud to the first group
+  int dev1_storage_group = bluetooth::groups::kGroupUnknown;
+  int dev1_new_group = bluetooth::groups::kGroupUnknown;
+
+  EXPECT_CALL(
+      mock_client_callbacks_,
+      OnGroupNodeStatus(test_address1, group_id1, GroupNodeStatus::REMOVED))
+      .Times(AtLeast(1));
+  EXPECT_CALL(mock_client_callbacks_,
+              OnGroupNodeStatus(test_address1, _, GroupNodeStatus::ADDED))
+      .WillRepeatedly(SaveArg<1>(&dev1_new_group));
+  EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address1, group_id1))
+      .Times(AtLeast(1));
+  EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, _))
+      .Times(AnyNumber());
+
+  LeAudioClient::Get()->GroupRemoveNode(group_id1, test_address1);
+  SyncOnMainLoop();
+
+  Mock::VerifyAndClearExpectations(&mock_groups_module_);
+
+  EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, group_id0))
+      .Times(1);
+
+  LeAudioClient::Get()->GroupAddNode(group_id0, test_address1);
+  SyncOnMainLoop();
+  Mock::VerifyAndClearExpectations(&mock_groups_module_);
+
+  dev1_storage_group =
+      MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1);
+
+  // Verify regrouping results
+  EXPECT_EQ(dev1_new_group, group_id0);
+  EXPECT_EQ(dev1_new_group, dev1_storage_group);
+  ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 0u);
+  ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 2u);
+  std::vector<RawAddress> devs =
+      LeAudioClient::Get()->GetGroupDevices(group_id0);
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end());
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end());
+}
+
+TEST_F(UnicastTest, GroupingAddTwiceNoRemove) {
+  // Earbud connects without known grouping
+  uint8_t group_id0 = bluetooth::groups::kGroupUnknown;
+  const RawAddress test_address0 = GetTestAddress(0);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true))
+      .WillOnce(Return())
+      .RetiresOnSaturation();
+  ConnectNonCsisDevice(test_address0, 1 /*conn_id*/,
+                       codec_spec_conf::kLeAudioLocationFrontLeft,
+                       codec_spec_conf::kLeAudioLocationFrontLeft);
+
+  group_id0 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address0);
+
+  // Earbud connects without known grouping
+  uint8_t group_id1 = bluetooth::groups::kGroupUnknown;
+  const RawAddress test_address1 = GetTestAddress(1);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true))
+      .WillOnce(Return())
+      .RetiresOnSaturation();
+  ConnectNonCsisDevice(test_address1, 2 /*conn_id*/,
+                       codec_spec_conf::kLeAudioLocationFrontRight,
+                       codec_spec_conf::kLeAudioLocationFrontRight);
+
+  Mock::VerifyAndClearExpectations(&mock_btif_storage_);
+
+  group_id1 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1);
+  // Verify individual groups
+  ASSERT_NE(group_id0, bluetooth::groups::kGroupUnknown);
+  ASSERT_NE(group_id1, bluetooth::groups::kGroupUnknown);
+  ASSERT_NE(group_id0, group_id1);
+  ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 1u);
+  ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 1u);
+
+  // Expectations on reassigning second earbud to the first group
+  int dev1_storage_group = bluetooth::groups::kGroupUnknown;
+  int dev1_new_group = bluetooth::groups::kGroupUnknown;
+
+  EXPECT_CALL(
+      mock_client_callbacks_,
+      OnGroupNodeStatus(test_address1, group_id1, GroupNodeStatus::REMOVED))
+      .Times(AtLeast(1));
+  EXPECT_CALL(mock_client_callbacks_,
+              OnGroupNodeStatus(test_address1, _, GroupNodeStatus::ADDED))
+      .WillRepeatedly(SaveArg<1>(&dev1_new_group));
+
+  // FIXME: We should expect removal with group_id context. No such API exists.
+  EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address1, group_id1))
+      .Times(AtLeast(1));
+  EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, _))
+      .Times(AnyNumber());
+  EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, group_id0))
+      .Times(1);
+
+  // Regroup device: assign new group without removing it from the first one
+  LeAudioClient::Get()->GroupAddNode(group_id0, test_address1);
+  SyncOnMainLoop();
+  Mock::VerifyAndClearExpectations(&mock_groups_module_);
+
+  dev1_storage_group =
+      MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1);
+
+  // Verify regrouping results
+  EXPECT_EQ(dev1_new_group, group_id0);
+  EXPECT_EQ(dev1_new_group, dev1_storage_group);
+  ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 0u);
+  ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 2u);
+  std::vector<RawAddress> devs =
+      LeAudioClient::Get()->GetGroupDevices(group_id0);
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end());
+  ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end());
+}
+
+TEST_F(UnicastTest, RemoveTwoEarbudsCsisGrouped) {
+  uint8_t group_size = 2;
+  int group_id0 = 2;
+  int group_id1 = 3;
+
+  // Report working CSIS
+  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+      .WillByDefault(Return(true));
+
+  // First group - First earbud
+  const RawAddress test_address0 = GetTestAddress(0);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true))
+      .Times(1);
+  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontLeft,
+                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
+                    group_id0, 1 /* rank*/);
+
+  // First group - Second earbud
+  const RawAddress test_address1 = GetTestAddress(1);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true))
+      .Times(1);
+  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontRight,
+                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
+                    group_id0, 2 /* rank*/, true /*connect_through_csis*/);
+
+  // Second group - First earbud
+  const RawAddress test_address2 = GetTestAddress(2);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address2, true))
+      .Times(1);
+  ConnectCsisDevice(test_address2, 3 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontLeft,
+                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
+                    group_id1, 1 /* rank*/);
+
+  // Second group - Second earbud
+  const RawAddress test_address3 = GetTestAddress(3);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address3, true))
+      .Times(1);
+  ConnectCsisDevice(test_address3, 4 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontRight,
+                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
+                    group_id1, 2 /* rank*/, true /*connect_through_csis*/);
+
+  // First group - verify grouping information
+  std::vector<RawAddress> group0_devs =
+      LeAudioClient::Get()->GetGroupDevices(group_id0);
+  ASSERT_NE(std::find(group0_devs.begin(), group0_devs.end(), test_address0),
+            group0_devs.end());
+  ASSERT_NE(std::find(group0_devs.begin(), group0_devs.end(), test_address1),
+            group0_devs.end());
+
+  // Second group - verify grouping information
+  std::vector<RawAddress> group1_devs =
+      LeAudioClient::Get()->GetGroupDevices(group_id1);
+  ASSERT_NE(std::find(group1_devs.begin(), group1_devs.end(), test_address2),
+            group1_devs.end());
+  ASSERT_NE(std::find(group1_devs.begin(), group1_devs.end(), test_address3),
+            group1_devs.end());
+  Mock::VerifyAndClearExpectations(&mock_btif_storage_);
+
+  // Expect one of the groups to be dropped and devices to be disconnected
+  EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address0, group_id0))
+      .Times(1);
+  EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address1, group_id0))
+      .Times(1);
+  EXPECT_CALL(
+      mock_client_callbacks_,
+      OnGroupNodeStatus(test_address0, group_id0, GroupNodeStatus::REMOVED));
+  EXPECT_CALL(
+      mock_client_callbacks_,
+      OnGroupNodeStatus(test_address1, group_id0, GroupNodeStatus::REMOVED));
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::DISCONNECTED, test_address0))
+      .Times(1);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::DISCONNECTED, test_address1))
+      .Times(1);
+
+  // Expect the other groups to be left as is
+  EXPECT_CALL(mock_client_callbacks_, OnGroupStatus(group_id1, _)).Times(0);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::DISCONNECTED, test_address2))
+      .Times(0);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::DISCONNECTED, test_address3))
+      .Times(0);
+
+  do_in_main_thread(
+      FROM_HERE, base::Bind(&LeAudioClient::GroupDestroy,
+                            base::Unretained(LeAudioClient::Get()), group_id0));
+
+  SyncOnMainLoop();
+  Mock::VerifyAndClearExpectations(&mock_btif_storage_);
+}
+
+TEST_F(UnicastTest, SpeakerStreaming) {
+  const RawAddress test_address0 = GetTestAddress(0);
+  int group_id = bluetooth::groups::kGroupUnknown;
+
+  SetSampleDatabaseEarbudsValid(
+      1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+      codec_spec_conf::kLeAudioLocationStereo, false /*add_csis*/,
+      true /*add_cas*/, true /*add_pacs*/, true /*add_ascs*/, 1 /*set_size*/,
+      0 /*rank*/);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::CONNECTED, test_address0))
+      .Times(1);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
+      .WillOnce(DoAll(SaveArg<1>(&group_id)));
+
+  ConnectLeAudio(test_address0);
+  ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
+
+  // Start streaming
+  uint8_t cis_count_out = 1;
+  uint8_t cis_count_in = 0;
+
+  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(group_id);
+
+  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
+
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  SyncOnMainLoop();
+
+  // Verify Data transfer on one audio source cis
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+
+  // Suspend
+  /*TODO Need a way to verify STOP */
+  LeAudioClient::Get()->GroupSuspend(group_id);
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+
+  // Resume
+  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+
+  // Stop
+  StopStreaming(group_id);
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+
+  // Release
+  EXPECT_CALL(mock_audio_source_, Stop()).Times(1);
+  EXPECT_CALL(mock_audio_source_, Release(_)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+}
+
+TEST_F(UnicastTest, SpeakerStreamingAutonomousRelease) {
+  const RawAddress test_address0 = GetTestAddress(0);
+  int group_id = bluetooth::groups::kGroupUnknown;
+
+  SetSampleDatabaseEarbudsValid(
+      1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+      codec_spec_conf::kLeAudioLocationStereo, false /*add_csis*/,
+      true /*add_cas*/, true /*add_pacs*/, true /*add_ascs*/, 1 /*set_size*/,
+      0 /*rank*/);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnConnectionState(ConnectionState::CONNECTED, test_address0))
+      .Times(1);
+  EXPECT_CALL(mock_client_callbacks_,
+              OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
+      .WillOnce(DoAll(SaveArg<1>(&group_id)));
+
+  ConnectLeAudio(test_address0);
+  ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
+
+  // Start streaming
+  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(group_id);
+
+  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
+
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  SyncOnMainLoop();
+
+  // Verify Data transfer on one audio source cis
+  TestAudioDataTransfer(group_id, 1 /* cis_count_out */, 0 /* cis_count_in */,
+                        1920);
+
+  // Inject the IDLE state as if an autonomous release happened
+  auto group = streaming_groups.at(group_id);
+  ASSERT_NE(group, nullptr);
+  for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr;
+       device = group->GetNextDevice(device)) {
+    for (auto& ase : device->ases_) {
+      ase.data_path_state = types::AudioStreamDataPathState::IDLE;
+      ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
+      InjectCisDisconnected(group_id, ase.cis_conn_hdl);
+    }
+  }
+
+  // Verify no Data transfer after the autonomous release
+  TestAudioDataTransfer(group_id, 0 /* cis_count_out */, 0 /* cis_count_in */,
+                        1920);
+}
+
+TEST_F(UnicastTest, TwoEarbudsStreaming) {
+  uint8_t group_size = 2;
+  int group_id = 2;
+
+  // Report working CSIS
+  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+      .WillByDefault(Return(true));
+
+  // First earbud
+  const RawAddress test_address0 = GetTestAddress(0);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true))
+      .Times(1);
+  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontLeft,
+                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
+                    group_id, 1 /* rank*/);
+
+  // Second earbud
+  const RawAddress test_address1 = GetTestAddress(1);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true))
+      .Times(1);
+  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontRight,
+                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
+                    group_id, 2 /* rank*/, true /*connect_through_csis*/);
+
+  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(group_id);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+
+  // Start streaming with reconfiguration from default media stream setup
+  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+  EXPECT_CALL(mock_audio_source_, CancelStreamingRequest()).Times(1);
+  EXPECT_CALL(mock_audio_source_, Stop()).Times(1);
+  EXPECT_CALL(mock_audio_sink_, Start(_, _)).Times(1);
+
+  StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH,
+                 group_id, true /* reconfigure */);
+
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  Mock::VerifyAndClearExpectations(&mock_audio_sink_);
+  SyncOnMainLoop();
+
+  // Verify Data transfer on two peer sinks and one source
+  uint8_t cis_count_out = 2;
+  uint8_t cis_count_in = 1;
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 640);
+
+  // Suspend
+  EXPECT_CALL(mock_audio_source_, Release(_)).Times(0);
+  EXPECT_CALL(mock_audio_sink_, Release(_)).Times(0);
+  EXPECT_CALL(mock_audio_source_, Stop()).Times(0);
+  EXPECT_CALL(mock_audio_sink_, Stop()).Times(0);
+  LeAudioClient::Get()->GroupSuspend(group_id);
+  SyncOnMainLoop();
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  Mock::VerifyAndClearExpectations(&mock_audio_sink_);
+
+  // Resume
+  StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH,
+                 group_id);
+  SyncOnMainLoop();
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  Mock::VerifyAndClearExpectations(&mock_audio_sink_);
+
+  // Verify Data transfer still works
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 640);
+
+  // Stop
+  StopStreaming(group_id, true);
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+
+  // Release
+  EXPECT_CALL(mock_audio_source_, Stop()).Times(1);
+  EXPECT_CALL(mock_audio_source_, Release(_)).Times(1);
+  EXPECT_CALL(mock_audio_sink_, Stop()).Times(1);
+  EXPECT_CALL(mock_audio_sink_, Release(_)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  Mock::VerifyAndClearExpectations(&mock_audio_sink_);
+}
+
+TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchSimple) {
+  uint8_t group_size = 2;
+  int group_id = 2;
+
+  // Report working CSIS
+  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+      .WillByDefault(Return(true));
+
+  // First earbud
+  const RawAddress test_address0 = GetTestAddress(0);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true))
+      .Times(1);
+  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontLeft,
+                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
+                    group_id, 1 /* rank*/);
+
+  // Second earbud
+  const RawAddress test_address1 = GetTestAddress(1);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true))
+      .Times(1);
+  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontRight,
+                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
+                    group_id, 2 /* rank*/, true /*connect_through_csis*/);
+
+  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(group_id);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+
+  // Start streaming with reconfiguration from default media stream setup
+  EXPECT_CALL(
+      mock_state_machine_,
+      StartStream(_, le_audio::types::LeAudioContextType::NOTIFICATIONS))
+      .Times(1);
+  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+  EXPECT_CALL(mock_audio_source_, CancelStreamingRequest()).Times(1);
+  EXPECT_CALL(mock_audio_source_, Stop()).Times(1);
+
+  StartStreaming(AUDIO_USAGE_NOTIFICATION, AUDIO_CONTENT_TYPE_UNKNOWN, group_id,
+                 true /* reconfigure */);
+
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  SyncOnMainLoop();
+
+  // Do a content switch to ALERTS
+  EXPECT_CALL(mock_audio_source_, Release).Times(0);
+  EXPECT_CALL(mock_audio_source_, Stop).Times(0);
+  EXPECT_CALL(mock_audio_source_, Start).Times(0);
+  EXPECT_CALL(mock_state_machine_,
+              StartStream(_, le_audio::types::LeAudioContextType::ALERTS))
+      .Times(1);
+  UpdateMetadata(AUDIO_USAGE_ALARM, AUDIO_CONTENT_TYPE_UNKNOWN);
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+
+  // Do a content switch to EMERGENCY
+  EXPECT_CALL(mock_audio_source_, Release).Times(0);
+  EXPECT_CALL(mock_audio_source_, Stop).Times(0);
+  EXPECT_CALL(mock_audio_source_, Start).Times(0);
+
+  EXPECT_CALL(
+      mock_state_machine_,
+      StartStream(_, le_audio::types::LeAudioContextType::EMERGENCYALARM))
+      .Times(1);
+  UpdateMetadata(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+}
+
+TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure) {
+  uint8_t group_size = 2;
+  int group_id = 2;
+
+  // Report working CSIS
+  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+      .WillByDefault(Return(true));
+
+  // First earbud
+  const RawAddress test_address0 = GetTestAddress(0);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true))
+      .Times(1);
+  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontLeft,
+                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
+                    group_id, 1 /* rank*/);
+
+  // Second earbud
+  const RawAddress test_address1 = GetTestAddress(1);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true))
+      .Times(1);
+  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontRight,
+                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
+                    group_id, 2 /* rank*/, true /*connect_through_csis*/);
+
+  // Start streaming MEDIA
+  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(group_id);
+
+  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
+
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  SyncOnMainLoop();
+
+  // Verify Data transfer on two peer sinks
+  uint8_t cis_count_out = 2;
+  uint8_t cis_count_in = 0;
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+
+  // Stop
+  StopStreaming(group_id);
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+
+  // Start streaming
+  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+  EXPECT_CALL(mock_audio_source_, CancelStreamingRequest()).Times(1);
+  EXPECT_CALL(mock_audio_source_, Stop()).Times(1);
+  EXPECT_CALL(mock_audio_sink_, Start(_, _)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(group_id);
+
+  StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH,
+                 group_id, true /* reconfigure */);
+
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  Mock::VerifyAndClearExpectations(&mock_audio_sink_);
+  SyncOnMainLoop();
+
+  // Verify Data transfer on two peer sinks and one source
+  cis_count_out = 2;
+  cis_count_in = 1;
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 640);
+}
+
+TEST_F(UnicastTest, TwoEarbuds2ndLateConnect) {
+  uint8_t group_size = 2;
+  int group_id = 2;
+
+  // Report working CSIS
+  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+      .WillByDefault(Return(true));
+
+  // First earbud
+  const RawAddress test_address0 = GetTestAddress(0);
+  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontLeft,
+                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
+                    group_id, 1 /* rank*/);
+
+  // Start streaming
+  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(group_id);
+
+  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
+
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  SyncOnMainLoop();
+
+  // Expect one iso channel to be fed with data
+  uint8_t cis_count_out = 1;
+  uint8_t cis_count_in = 0;
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+
+  // Second earbud connects during stream
+  const RawAddress test_address1 = GetTestAddress(1);
+  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontRight,
+                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
+                    group_id, 2 /* rank*/, true /*connect_through_csis*/);
+
+  /* We should expect two iso channels to be fed with data, but for now, when
+   * second device is connected later, we just continue stream to one device.
+   * TODO: improve it.
+   */
+  cis_count_out = 1;
+  cis_count_in = 0;
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+}
+
+TEST_F(UnicastTest, TwoEarbuds2ndDisconnect) {
+  uint8_t group_size = 2;
+  int group_id = 2;
+
+  // Report working CSIS
+  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+      .WillByDefault(Return(true));
+
+  // First earbud
+  const RawAddress test_address0 = GetTestAddress(0);
+  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontLeft,
+                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
+                    group_id, 1 /* rank*/);
+
+  // Second earbud
+  const RawAddress test_address1 = GetTestAddress(1);
+  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontRight,
+                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
+                    group_id, 2 /* rank*/, true /*connect_through_csis*/);
+
+  // Start streaming
+  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(group_id);
+
+  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
+
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(&mock_audio_source_);
+  SyncOnMainLoop();
+
+  // Expect two iso channels to be fed with data
+  uint8_t cis_count_out = 2;
+  uint8_t cis_count_in = 0;
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+
+  // Disconnect one device and expect the group to keep on streaming
+  EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0);
+  auto group = streaming_groups.at(group_id);
+  auto device = group->GetFirstDevice();
+  for (auto& ase : device->ases_) {
+    InjectCisDisconnected(group_id, ase.cis_conn_hdl);
+  }
+  DisconnectLeAudio(device->address_, 1);
+  SyncOnMainLoop();
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+
+  // Expect one channel ISO Data to be sent
+  cis_count_out = 1;
+  cis_count_in = 0;
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+}
+
+}  // namespace
+}  // namespace le_audio
diff --git a/system/bta/le_audio/le_audio_types.cc b/system/bta/le_audio/le_audio_types.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d8286e1acd0e00513c1ca19522b4c76934fe5081
--- /dev/null
+++ b/system/bta/le_audio/le_audio_types.cc
@@ -0,0 +1,409 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This file contains definitions for Basic Audio Profile / Audio Stream Control
+ * and Published Audio Capabilities definitions, structures etc.
+ */
+
+#include "le_audio_types.h"
+
+#include <base/strings/string_number_conversions.h>
+
+#include "bt_types.h"
+#include "bta_api.h"
+#include "bta_le_audio_api.h"
+#include "client_audio.h"
+#include "client_parser.h"
+
+namespace le_audio {
+namespace set_configurations {
+using set_configurations::CodecCapabilitySetting;
+using types::acs_ac_record;
+using types::kLeAudioCodingFormatLC3;
+using types::kLeAudioDirectionSink;
+using types::kLeAudioDirectionSource;
+using types::LeAudioContextType;
+using types::LeAudioLc3Config;
+
+static uint8_t min_req_devices_cnt(
+    const AudioSetConfiguration* audio_set_conf) {
+  std::pair<uint8_t /* sink */, uint8_t /* source */> snk_src_pair(0, 0);
+
+  for (auto ent : (*audio_set_conf).confs) {
+    if (ent.direction == kLeAudioDirectionSink)
+      snk_src_pair.first += ent.device_cnt;
+    if (ent.direction == kLeAudioDirectionSource)
+      snk_src_pair.second += ent.device_cnt;
+  }
+
+  return std::max(snk_src_pair.first, snk_src_pair.second);
+}
+
+static uint8_t min_req_devices_cnt(
+    const AudioSetConfigurations* audio_set_confs) {
+  uint8_t curr_min_req_devices_cnt = 0xff;
+
+  for (auto ent : *audio_set_confs) {
+    uint8_t req_devices_cnt = min_req_devices_cnt(ent);
+    if (req_devices_cnt < curr_min_req_devices_cnt)
+      curr_min_req_devices_cnt = req_devices_cnt;
+  }
+
+  return curr_min_req_devices_cnt;
+}
+
+bool check_if_may_cover_scenario(const AudioSetConfigurations* audio_set_confs,
+                                 uint8_t group_size) {
+  if (!audio_set_confs) {
+    LOG(ERROR) << __func__ << ", no audio requirements for group";
+    return false;
+  }
+
+  return group_size >= min_req_devices_cnt(audio_set_confs);
+}
+
+bool check_if_may_cover_scenario(const AudioSetConfiguration* audio_set_conf,
+                                 uint8_t group_size) {
+  if (!audio_set_conf) {
+    LOG(ERROR) << __func__ << ", no audio requirement for group";
+    return false;
+  }
+
+  return group_size >= min_req_devices_cnt(audio_set_conf);
+}
+
+static bool IsCodecConfigurationSupported(const types::LeAudioLtvMap& pacs,
+                                          const LeAudioLc3Config& lc3_config) {
+  const auto& reqs = lc3_config.GetAsLtvMap();
+  uint8_t u8_req_val, u8_pac_val;
+  uint16_t u16_req_val, u16_pac_val;
+
+  /* Sampling frequency */
+  auto req = reqs.Find(codec_spec_conf::kLeAudioCodecLC3TypeSamplingFreq);
+  auto pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeSamplingFreq);
+  if (!req || !pac) {
+    DLOG(ERROR) << __func__ << ", lack of sampling frequency fields";
+    return false;
+  }
+
+  u8_req_val = VEC_UINT8_TO_UINT8(req.value());
+  u16_pac_val = VEC_UINT8_TO_UINT16(pac.value());
+
+  /*
+   * Note: Requirements are in the codec configuration specification which
+   * are values coming from BAP Appendix A1.2.1
+   */
+  DLOG(INFO) << __func__ << " Req:SamplFreq=" << loghex(u8_req_val);
+  /* NOTE: Below is Codec specific cababilities comes form BAP Appendix A A1.1.1
+   * Note this is a bitfield
+   */
+  DLOG(INFO) << __func__ << " Pac:SamplFreq=" << loghex(u16_pac_val);
+
+  /* TODO: Integrate with codec capabilities */
+  if ((u8_req_val != codec_spec_conf::kLeAudioSamplingFreq16000Hz &&
+       u8_req_val != codec_spec_conf::kLeAudioSamplingFreq48000Hz) ||
+      !(u16_pac_val &
+        codec_spec_caps::SamplingFreqConfig2Capability(u8_req_val))) {
+    DLOG(ERROR) << __func__ << ", sampling frequency not supported";
+    return false;
+  }
+
+  /* Frame duration */
+  req = reqs.Find(codec_spec_conf::kLeAudioCodecLC3TypeFrameDuration);
+  pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeFrameDuration);
+  if (!req || !pac) {
+    DLOG(ERROR) << __func__ << ", lack of frame duration fields";
+    return false;
+  }
+
+  u8_req_val = VEC_UINT8_TO_UINT8(req.value());
+  u8_pac_val = VEC_UINT8_TO_UINT8(pac.value());
+  DLOG(INFO) << __func__ << " Req:FrameDur=" << loghex(u8_req_val);
+  DLOG(INFO) << __func__ << " Pac:FrameDur=" << loghex(u8_pac_val);
+
+  if ((u8_req_val != codec_spec_conf::kLeAudioCodecLC3FrameDur7500us &&
+       u8_req_val != codec_spec_conf::kLeAudioCodecLC3FrameDur10000us) ||
+      !(u8_pac_val &
+        (codec_spec_caps::FrameDurationConfig2Capability(u8_req_val)))) {
+    DLOG(ERROR) << __func__ << ", frame duration not supported";
+    return false;
+  }
+
+  uint8_t required_audio_chan_num = lc3_config.GetChannelCount();
+  pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeAudioChannelCounts);
+
+  /*
+   * BAP_Validation_r07 1.9.2 Audio channel support requirements
+   * "The Unicast Server shall support an Audio_Channel_Counts value of 0x01
+   * (0b00000001 = one channel) and may support other values defined by an
+   * implementation or by a higher-layer specification."
+   *
+   * Thus if Audio_Channel_Counts is not present in PAC LTV structure, we assume
+   * the Unicast Server supports mandatory one channel.
+   */
+  if (!pac) {
+    DLOG(WARNING) << __func__ << ", no Audio_Channel_Counts field in PAC";
+    u8_pac_val = 0x01;
+  } else {
+    u8_pac_val = VEC_UINT8_TO_UINT8(pac.value());
+  }
+
+  DLOG(INFO) << __func__ << " Pac:AudioChanCnt=" << loghex(u8_pac_val);
+  if (!((1 << (required_audio_chan_num - 1)) & u8_pac_val)) {
+    DLOG(ERROR) << __func__ << ", channel count warning";
+    return false;
+  }
+
+  /* Octets per frame */
+  req = reqs.Find(codec_spec_conf::kLeAudioCodecLC3TypeOctetPerFrame);
+  pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeOctetPerFrame);
+
+  if (!req || !pac) {
+    DLOG(ERROR) << __func__ << ", lack of octet per frame fields";
+    return false;
+  }
+
+  u16_req_val = VEC_UINT8_TO_UINT16(req.value());
+  DLOG(INFO) << __func__ << " Req:OctetsPerFrame=" << int(u16_req_val);
+
+  /* Minimal value 0-1 byte */
+  u16_pac_val = VEC_UINT8_TO_UINT16(pac.value());
+  DLOG(INFO) << __func__ << " Pac:MinOctetsPerFrame=" << int(u16_pac_val);
+  if (u16_req_val < u16_pac_val) {
+    DLOG(ERROR) << __func__ << ", octet per frame below minimum";
+    return false;
+  }
+
+  /* Maximal value 2-3 byte */
+  u16_pac_val = OFF_VEC_UINT8_TO_UINT16(pac.value(), 2);
+  DLOG(INFO) << __func__ << " Pac:MaxOctetsPerFrame=" << int(u16_pac_val);
+  if (u16_req_val > u16_pac_val) {
+    DLOG(ERROR) << __func__ << ", octet per frame above maximum";
+    return false;
+  }
+
+  return true;
+}
+
+bool IsCodecCapabilitySettingSupported(
+    const acs_ac_record& pac,
+    const CodecCapabilitySetting& codec_capability_setting) {
+  const auto& codec_id = codec_capability_setting.id;
+
+  if (codec_id != pac.codec_id) return false;
+
+  DLOG(INFO) << __func__ << ": Settings for format " << +codec_id.coding_format;
+
+  switch (codec_id.coding_format) {
+    case kLeAudioCodingFormatLC3:
+      return IsCodecConfigurationSupported(
+          pac.codec_spec_caps,
+          std::get<LeAudioLc3Config>(codec_capability_setting.config));
+    default:
+      return false;
+  }
+}
+
+const AudioSetConfigurations* get_confs_by_type(LeAudioContextType type) {
+  switch (type) {
+    case LeAudioContextType::MEDIA:
+      return &audio_set_conf_media;
+    case LeAudioContextType::CONVERSATIONAL:
+      return &audio_set_conf_conversational;
+    case LeAudioContextType::RINGTONE:
+      return &audio_set_conf_ringtone;
+    default:
+      return &audio_set_conf_default;
+  }
+}
+uint32_t CodecCapabilitySetting::GetConfigSamplingFrequency() const {
+  switch (id.coding_format) {
+    case kLeAudioCodingFormatLC3:
+      return std::get<types::LeAudioLc3Config>(config).GetSamplingFrequencyHz();
+    default:
+      DLOG(WARNING) << __func__ << ", invalid codec id";
+      return 0;
+  }
+};
+
+uint32_t CodecCapabilitySetting::GetConfigDataIntervalUs() const {
+  switch (id.coding_format) {
+    case kLeAudioCodingFormatLC3:
+      return std::get<types::LeAudioLc3Config>(config).GetFrameDurationUs();
+    default:
+      DLOG(WARNING) << __func__ << ", invalid codec id";
+      return 0;
+  }
+};
+
+uint8_t CodecCapabilitySetting::GetConfigBitsPerSample() const {
+  switch (id.coding_format) {
+    case kLeAudioCodingFormatLC3:
+      /* XXX LC3 supports 16, 24, 32 */
+      return 16;
+    default:
+      DLOG(WARNING) << __func__ << ", invalid codec id";
+      return 0;
+  }
+};
+
+uint8_t CodecCapabilitySetting::GetConfigChannelCount() const {
+  switch (id.coding_format) {
+    case kLeAudioCodingFormatLC3:
+      DLOG(INFO) << __func__ << ", count = "
+                 << static_cast<int>(std::get<types::LeAudioLc3Config>(config)
+                                         .channel_count);
+      return std::get<types::LeAudioLc3Config>(config).channel_count;
+    default:
+      DLOG(WARNING) << __func__ << ", invalid codec id";
+      return 0;
+  }
+}
+}  // namespace set_configurations
+
+namespace types {
+/* Helper map for matching various frequency notations */
+const std::map<uint8_t, uint32_t> LeAudioLc3Config::sampling_freq_map = {
+    {codec_spec_conf::kLeAudioSamplingFreq8000Hz,
+     LeAudioCodecConfiguration::kSampleRate8000},
+    {codec_spec_conf::kLeAudioSamplingFreq16000Hz,
+     LeAudioCodecConfiguration::kSampleRate16000},
+    {codec_spec_conf::kLeAudioSamplingFreq24000Hz,
+     LeAudioCodecConfiguration::kSampleRate24000},
+    {codec_spec_conf::kLeAudioSamplingFreq32000Hz,
+     LeAudioCodecConfiguration::kSampleRate32000},
+    {codec_spec_conf::kLeAudioSamplingFreq44100Hz,
+     LeAudioCodecConfiguration::kSampleRate44100},
+    {codec_spec_conf::kLeAudioSamplingFreq48000Hz,
+     LeAudioCodecConfiguration::kSampleRate48000}};
+
+/* Helper map for matching various frame durations notations */
+const std::map<uint8_t, uint32_t> LeAudioLc3Config::frame_duration_map = {
+    {codec_spec_conf::kLeAudioCodecLC3FrameDur7500us,
+     LeAudioCodecConfiguration::kInterval7500Us},
+    {codec_spec_conf::kLeAudioCodecLC3FrameDur10000us,
+     LeAudioCodecConfiguration::kInterval10000Us}};
+
+std::optional<std::vector<uint8_t>> LeAudioLtvMap::Find(uint8_t type) const {
+  auto iter =
+      std::find_if(values.cbegin(), values.cend(),
+                   [type](const auto& value) { return value.first == type; });
+
+  if (iter == values.cend()) return std::nullopt;
+
+  return iter->second;
+}
+
+uint8_t* LeAudioLtvMap::RawPacket(uint8_t* p_buf) const {
+  for (auto const& value : values) {
+    UINT8_TO_STREAM(p_buf, value.second.size() + 1);
+    UINT8_TO_STREAM(p_buf, value.first);
+    ARRAY_TO_STREAM(p_buf, value.second.data(),
+                    static_cast<int>(value.second.size()));
+  }
+
+  return p_buf;
+}
+
+LeAudioLtvMap LeAudioLtvMap::Parse(const uint8_t* p_value, uint8_t len,
+                                   bool& success) {
+  LeAudioLtvMap ltv_map;
+
+  if (len > 0) {
+    const auto p_value_end = p_value + len;
+
+    while ((p_value_end - p_value) > 0) {
+      uint8_t ltv_len;
+      STREAM_TO_UINT8(ltv_len, p_value);
+
+      // Unusual, but possible case
+      if (ltv_len == 0) continue;
+
+      if (p_value_end < (p_value + ltv_len)) {
+        LOG(ERROR) << __func__
+                   << " Invalid ltv_len: " << static_cast<int>(ltv_len);
+        success = false;
+        return LeAudioLtvMap();
+      }
+
+      uint8_t ltv_type;
+      STREAM_TO_UINT8(ltv_type, p_value);
+      ltv_len -= sizeof(ltv_type);
+
+      const auto p_temp = p_value;
+      p_value += ltv_len;
+
+      std::vector<uint8_t> ltv_value(p_temp, p_value);
+      ltv_map.values.emplace(ltv_type, std::move(ltv_value));
+    }
+  }
+
+  success = true;
+  return ltv_map;
+}
+
+size_t LeAudioLtvMap::RawPacketSize() const {
+  size_t bytes = 0;
+
+  for (auto const& value : values) {
+    bytes += (/* ltv_len + ltv_type */ 2 + value.second.size());
+  }
+
+  return bytes;
+}
+
+std::string LeAudioLtvMap::ToString() const {
+  std::string debug_str;
+
+  for (const auto& value : values) {
+    std::stringstream sstream;
+
+    sstream << "\ttype: " << std::to_string(value.first)
+            << "\tlen: " << std::to_string(value.second.size()) << "\tdata: "
+            << base::HexEncode(value.second.data(), value.second.size()) + "\n";
+
+    debug_str += sstream.str();
+  }
+
+  return debug_str;
+}
+
+}  // namespace types
+}  // namespace le_audio
+
+std::ostream& operator<<(std::ostream& os,
+                         const le_audio::types::LeAudioLc3Config& config) {
+  os << " LeAudioLc3Config(SamplFreq=" << loghex(config.sampling_frequency)
+     << ", FrameDur=" << loghex(config.frame_duration)
+     << ", OctetsPerFrame=" << int(config.octets_per_codec_frame)
+     << ", AudioChanLoc=" << loghex(config.audio_channel_allocation) << ")";
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const le_audio::types::AseState& state) {
+  static const char* char_value_[7] = {
+      "IDLE",      "CODEC_CONFIGURED", "QOS_CONFIGURED", "ENABLING",
+      "STREAMING", "DISABLING",        "RELEASING",
+  };
+
+  os << char_value_[static_cast<uint8_t>(state)] << " ("
+     << "0x" << std::setfill('0') << std::setw(2) << static_cast<int>(state)
+     << ")";
+  return os;
+}
diff --git a/system/bta/le_audio/le_audio_types.h b/system/bta/le_audio/le_audio_types.h
new file mode 100644
index 0000000000000000000000000000000000000000..ec0e2e3bc2703d26abc2ced252b8a75f8df8a224
--- /dev/null
+++ b/system/bta/le_audio/le_audio_types.h
@@ -0,0 +1,952 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This file contains definitions for Basic Audio Profile / Audio Stream Control
+ * and Published Audio Capabilities definitions, structures etc.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <map>
+#include <optional>
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "bta_groups.h"
+#include "bta_le_audio_api.h"
+#include "btm_iso_api_types.h"
+
+namespace le_audio {
+
+#define UINT8_TO_VEC_UINT8(u8) \
+  std::vector<uint8_t> { u8 }
+#define UINT16_TO_VEC_UINT8(u16) \
+  std::vector<uint8_t>((uint8_t*)&u16, (uint8_t*)&u16 + sizeof(u16))
+#define UINT32_TO_VEC_UINT8(u32) \
+  std::vector<uint8_t>((uint8_t*)&u32, (uint8_t*)&u32 + sizeof(u32))
+
+#define VEC_UINT8_TO_UINT8(vec) vec.data()[0]
+#define VEC_UINT8_TO_UINT16(vec) ((vec.data()[1] << 8) + vec.data()[0])
+#define OFF_VEC_UINT8_TO_UINT16(vec, off) \
+  ((vec.data()[1 + off] << 8) + vec.data()[0 + off])
+#define VEC_UINT8_TO_UINT32(vec)                                          \
+  ((vec.data()[3] << 24) + (vec.data()[2] << 16) + (vec.data()[1] << 8) + \
+   vec.data()[0])
+
+namespace uuid {
+/* CAP service
+ * This service is used to identify peer role (which we are not using for now)
+ * and to wrap CSIS service as this is required to understand the context of the
+ * CSIS
+ * Place holder
+ */
+static const bluetooth::Uuid kCapServiceUuid =
+    bluetooth::Uuid::From16Bit(0xEEEE);
+
+/* Assigned numbers for attributes */
+static const bluetooth::Uuid kPublishedAudioCapabilityServiceUuid =
+    bluetooth::Uuid::From16Bit(0x1850);
+static const bluetooth::Uuid kAudioStreamControlServiceUuid =
+    bluetooth::Uuid::From16Bit(0x184E);
+
+/* Published Audio Capabilities Service Characteristics */
+static const bluetooth::Uuid kSinkPublishedAudioCapabilityCharacteristicUuid =
+    bluetooth::Uuid::From16Bit(0x2BC9);
+static const bluetooth::Uuid kSourcePublishedAudioCapabilityCharacteristicUuid =
+    bluetooth::Uuid::From16Bit(0x2BCB);
+static const bluetooth::Uuid kSinkAudioLocationCharacteristicUuid =
+    bluetooth::Uuid::From16Bit(0x2BCA);
+static const bluetooth::Uuid kSourceAudioLocationCharacteristicUuid =
+    bluetooth::Uuid::From16Bit(0x2BCC);
+
+/* Audio Stream Control Service Characteristics */
+static const bluetooth::Uuid kAudioContextAvailabilityCharacteristicUuid =
+    bluetooth::Uuid::From16Bit(0x2BCD);
+static const bluetooth::Uuid kAudioSupportedContextCharacteristicUuid =
+    bluetooth::Uuid::From16Bit(0x2BCE);
+
+/* Audio Stream Control Service Characteristics */
+static const bluetooth::Uuid kSinkAudioStreamEndpointUuid =
+    bluetooth::Uuid::From16Bit(0x2BC4);
+static const bluetooth::Uuid kSourceAudioStreamEndpointUuid =
+    bluetooth::Uuid::From16Bit(0x2BC5);
+static const bluetooth::Uuid
+    kAudioStreamEndpointControlPointCharacteristicUuid =
+        bluetooth::Uuid::From16Bit(0x2BC6);
+}  // namespace uuid
+
+namespace codec_spec_conf {
+/* LTV Types */
+constexpr uint8_t kLeAudioCodecLC3TypeSamplingFreq = 0x01;
+constexpr uint8_t kLeAudioCodecLC3TypeFrameDuration = 0x02;
+constexpr uint8_t kLeAudioCodecLC3TypeAudioChannelAllocation = 0x03;
+constexpr uint8_t kLeAudioCodecLC3TypeOctetPerFrame = 0x04;
+constexpr uint8_t kLeAudioCodecLC3TypeCodecFrameBlocksPerSdu = 0x05;
+
+/* Sampling Frequencies */
+constexpr uint8_t kLeAudioSamplingFreq8000Hz = 0x01;
+constexpr uint8_t kLeAudioSamplingFreq11025Hz = 0x02;
+constexpr uint8_t kLeAudioSamplingFreq16000Hz = 0x03;
+constexpr uint8_t kLeAudioSamplingFreq22050Hz = 0x04;
+constexpr uint8_t kLeAudioSamplingFreq24000Hz = 0x05;
+constexpr uint8_t kLeAudioSamplingFreq32000Hz = 0x06;
+constexpr uint8_t kLeAudioSamplingFreq44100Hz = 0x07;
+constexpr uint8_t kLeAudioSamplingFreq48000Hz = 0x08;
+constexpr uint8_t kLeAudioSamplingFreq88200Hz = 0x09;
+constexpr uint8_t kLeAudioSamplingFreq96000Hz = 0x0A;
+constexpr uint8_t kLeAudioSamplingFreq176400Hz = 0x0B;
+constexpr uint8_t kLeAudioSamplingFreq192000Hz = 0x0C;
+constexpr uint8_t kLeAudioSamplingFreq384000Hz = 0x0D;
+
+/* Frame Durations */
+constexpr uint8_t kLeAudioCodecLC3FrameDur7500us = 0x00;
+constexpr uint8_t kLeAudioCodecLC3FrameDur10000us = 0x01;
+
+/* Audio Allocations */
+constexpr uint32_t kLeAudioLocationMonoUnspecified = 0x00000000;
+constexpr uint32_t kLeAudioLocationFrontLeft = 0x00000001;
+constexpr uint32_t kLeAudioLocationFrontRight = 0x00000002;
+constexpr uint32_t kLeAudioLocationFrontCenter = 0x00000004;
+constexpr uint32_t kLeAudioLocationLowFreqEffects1 = 0x00000008;
+constexpr uint32_t kLeAudioLocationBackLeft = 0x00000010;
+constexpr uint32_t kLeAudioLocationBackRight = 0x00000020;
+constexpr uint32_t kLeAudioLocationFrontLeftOfCenter = 0x00000040;
+constexpr uint32_t kLeAudioLocationFrontRightOfCenter = 0x00000080;
+constexpr uint32_t kLeAudioLocationBackCenter = 0x00000100;
+constexpr uint32_t kLeAudioLocationLowFreqEffects2 = 0x00000200;
+constexpr uint32_t kLeAudioLocationSideLeft = 0x00000400;
+constexpr uint32_t kLeAudioLocationSideRight = 0x00000800;
+constexpr uint32_t kLeAudioLocationTopFrontLeft = 0x00001000;
+constexpr uint32_t kLeAudioLocationTopFrontRight = 0x00002000;
+constexpr uint32_t kLeAudioLocationTopFrontCenter = 0x00004000;
+constexpr uint32_t kLeAudioLocationTopCenter = 0x00008000;
+constexpr uint32_t kLeAudioLocationTopBackLeft = 0x00010000;
+constexpr uint32_t kLeAudioLocationTopBackRight = 0x00020000;
+constexpr uint32_t kLeAudioLocationTopSideLeft = 0x00040000;
+constexpr uint32_t kLeAudioLocationTopSideRight = 0x00080000;
+constexpr uint32_t kLeAudioLocationTopSideCenter = 0x00100000;
+constexpr uint32_t kLeAudioLocationBottomFrontCenter = 0x00200000;
+constexpr uint32_t kLeAudioLocationBottomFrontLeft = 0x00400000;
+constexpr uint32_t kLeAudioLocationBottomFrontRight = 0x00800000;
+constexpr uint32_t kLeAudioLocationFrontLeftWide = 0x01000000;
+constexpr uint32_t kLeAudioLocationFrontRightWide = 0x02000000;
+constexpr uint32_t kLeAudioLocationLeftSurround = 0x04000000;
+constexpr uint32_t kLeAudioLocationRightSurround = 0x08000000;
+
+constexpr uint32_t kLeAudioLocationAnyLeft =
+    kLeAudioLocationFrontLeft | kLeAudioLocationBackLeft |
+    kLeAudioLocationFrontLeftOfCenter | kLeAudioLocationSideLeft |
+    kLeAudioLocationTopFrontLeft | kLeAudioLocationTopBackLeft |
+    kLeAudioLocationTopSideLeft | kLeAudioLocationBottomFrontLeft |
+    kLeAudioLocationFrontLeftWide | kLeAudioLocationLeftSurround;
+
+constexpr uint32_t kLeAudioLocationAnyRight =
+    kLeAudioLocationFrontRight | kLeAudioLocationBackRight |
+    kLeAudioLocationFrontRightOfCenter | kLeAudioLocationSideRight |
+    kLeAudioLocationTopFrontRight | kLeAudioLocationTopBackRight |
+    kLeAudioLocationTopSideRight | kLeAudioLocationBottomFrontRight |
+    kLeAudioLocationFrontRightWide | kLeAudioLocationRightSurround;
+
+constexpr uint32_t kLeAudioLocationStereo =
+    kLeAudioLocationFrontLeft | kLeAudioLocationFrontRight;
+
+/* Octets Per Frame */
+constexpr uint16_t kLeAudioCodecLC3FrameLen30 = 30;
+constexpr uint16_t kLeAudioCodecLC3FrameLen40 = 40;
+constexpr uint16_t kLeAudioCodecLC3FrameLen120 = 120;
+
+}  // namespace codec_spec_conf
+
+constexpr uint8_t kInvalidCisId = 0xFF;
+
+namespace codec_spec_caps {
+uint16_t constexpr SamplingFreqConfig2Capability(uint8_t conf) {
+  return (1 << (conf - 1));
+}
+
+uint8_t constexpr FrameDurationConfig2Capability(uint8_t conf) {
+  return (0x01 << (conf));
+}
+
+inline uint8_t GetAudioChannelCounts(std::bitset<32> allocation) {
+  /*
+   * BAP d09r07 B4.2.3 Audio_Channel_Allocation
+   * "(...) Audio_Channel_Allocation bitmap value of all zeros or the
+   * absence of the Audio_Channel_Allocation LTV structure within a
+   * Codec_Specific_Configuration field shall be interpreted as defining a
+   * single audio channel of Mono audio (a single channel of no specified
+   * Audio Location).
+   */
+  uint8_t audio_channel_counts = allocation.count() ?: 1;
+  return (0x01 << (audio_channel_counts - 1));
+}
+
+/* LTV Types - same values as in Codec Specific Configurations but 0x03 is
+ * named differently.
+ */
+constexpr uint8_t kLeAudioCodecLC3TypeSamplingFreq =
+    codec_spec_conf::kLeAudioCodecLC3TypeSamplingFreq;
+constexpr uint8_t kLeAudioCodecLC3TypeFrameDuration =
+    codec_spec_conf::kLeAudioCodecLC3TypeFrameDuration;
+constexpr uint8_t kLeAudioCodecLC3TypeAudioChannelCounts =
+    codec_spec_conf::kLeAudioCodecLC3TypeAudioChannelAllocation;
+constexpr uint8_t kLeAudioCodecLC3TypeOctetPerFrame =
+    codec_spec_conf::kLeAudioCodecLC3TypeOctetPerFrame;
+constexpr uint8_t kLeAudioCodecLC3TypeMaxCodecFramesPerSdu =
+    codec_spec_conf::kLeAudioCodecLC3TypeCodecFrameBlocksPerSdu;
+
+/* Sampling Frequencies */
+constexpr uint16_t kLeAudioSamplingFreq8000Hz =
+    SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq8000Hz);
+constexpr uint16_t kLeAudioSamplingFreq16000Hz =
+    SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq16000Hz);
+constexpr uint16_t kLeAudioSamplingFreq24000Hz =
+    SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq24000Hz);
+constexpr uint16_t kLeAudioSamplingFreq32000Hz =
+    SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq32000Hz);
+constexpr uint16_t kLeAudioSamplingFreq44100Hz =
+    SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq44100Hz);
+constexpr uint16_t kLeAudioSamplingFreq48000Hz =
+    SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq48000Hz);
+
+/* Frame Durations */
+constexpr uint8_t kLeAudioCodecLC3FrameDur7500us =
+    FrameDurationConfig2Capability(
+        codec_spec_conf::kLeAudioCodecLC3FrameDur7500us);
+constexpr uint8_t kLeAudioCodecLC3FrameDur10000us =
+    FrameDurationConfig2Capability(
+        codec_spec_conf::kLeAudioCodecLC3FrameDur10000us);
+constexpr uint8_t kLeAudioCodecLC3FrameDurPrefer7500us = 0x10;
+constexpr uint8_t kLeAudioCodecLC3FrameDurPrefer10000us = 0x20;
+
+/* Audio Channel Counts */
+/* Each bit represents support for additional channel: bit 0 - one channel,
+ * bit 1 - two, bit 3 - four channels. Multiple bits can be enabled at once.
+ */
+constexpr uint8_t kLeAudioCodecLC3ChannelCountNone = 0x00;
+constexpr uint8_t kLeAudioCodecLC3ChannelCountSingleChannel = 0x01;
+constexpr uint8_t kLeAudioCodecLC3ChannelCountTwoChannel = 0x02;
+
+/* Octets Per Frame - same as in Codec Specific Configurations but in
+ * capabilities we get two values: min and max.
+ */
+constexpr uint16_t kLeAudioCodecLC3FrameLen30 =
+    codec_spec_conf::kLeAudioCodecLC3FrameLen30;
+constexpr uint16_t kLeAudioCodecLC3FrameLen40 =
+    codec_spec_conf::kLeAudioCodecLC3FrameLen40;
+constexpr uint16_t kLeAudioCodecLC3FrameLen120 =
+    codec_spec_conf::kLeAudioCodecLC3FrameLen120;
+
+};  // namespace codec_spec_caps
+
+namespace types {
+constexpr uint8_t kLeAudioCodingFormatLC3 = bluetooth::hci::kIsoCodingFormatLc3;
+constexpr uint8_t kLeAudioCodingFormatVendorSpecific =
+    bluetooth::hci::kIsoCodingFormatVendorSpecific;
+constexpr uint16_t kLeAudioVendorCompanyIdUndefined = 0x00;
+constexpr uint16_t kLeAudioVendorCodecIdUndefined = 0x00;
+
+/* Metadata types from Assigned Numbers */
+constexpr uint8_t kLeAudioMetadataTypePreferredAudioContext = 0x01;
+constexpr uint8_t kLeAudioMetadataTypeStreamingAudioContext = 0x02;
+
+/* CSIS Types */
+constexpr uint8_t kDefaultScanDurationS = 5;
+constexpr uint8_t kDefaultCsisSetSize = 2;
+
+constexpr uint8_t kLeAudioDirectionSink = 0x01;
+constexpr uint8_t kLeAudioDirectionSource = 0x02;
+
+/* Audio stream config types */
+constexpr uint8_t kFramingUnframedPduSupported = 0x00;
+constexpr uint8_t kFramingUnframedPduUnsupported = 0x01;
+
+constexpr uint8_t kTargetLatencyLower = 0x01;
+constexpr uint8_t kTargetLatencyBalancedLatencyReliability = 0x02;
+constexpr uint8_t kTargetLatencyHigherReliability = 0x03;
+
+constexpr uint8_t kTargetPhy1M = 0x01;
+constexpr uint8_t kTargetPhy2M = 0x02;
+constexpr uint8_t kTargetPhyCoded = 0x03;
+
+constexpr uint32_t kPresDelayNoPreference = 0x00000000;
+
+constexpr uint16_t kMaxTransportLatencyMin = 0x0005;
+constexpr uint16_t kMaxTransportLatencyMax = 0x0FA0;
+
+/* Enums */
+enum class CsisLockState : uint8_t {
+  CSIS_STATE_UNSET = 0x00,
+  CSIS_STATE_UNLOCKED,
+  CSIS_STATE_LOCKED
+};
+
+enum class CsisDiscoveryState : uint8_t {
+  CSIS_DISCOVERY_IDLE,
+  CSIS_DISCOVERY_ONGOING,
+  CSIS_DISCOVERY_COMPLETED,
+};
+
+/* ASE states according to BAP defined state machine states */
+enum class AseState : uint8_t {
+  BTA_LE_AUDIO_ASE_STATE_IDLE = 0x00,
+  BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED = 0x01,
+  BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED = 0x02,
+  BTA_LE_AUDIO_ASE_STATE_ENABLING = 0x03,
+  BTA_LE_AUDIO_ASE_STATE_STREAMING = 0x04,
+  BTA_LE_AUDIO_ASE_STATE_DISABLING = 0x05,
+  BTA_LE_AUDIO_ASE_STATE_RELEASING = 0x06,
+};
+
+enum class AudioStreamDataPathState {
+  IDLE,
+  CIS_DISCONNECTING,
+  CIS_ASSIGNED,
+  CIS_PENDING,
+  CIS_ESTABLISHED,
+  DATA_PATH_ESTABLISHED,
+};
+
+/* Context Types */
+enum class LeAudioContextType : uint16_t {
+  UNSPECIFIED = 0x0001,
+  CONVERSATIONAL = 0x0002,
+  MEDIA = 0x0004,
+  GAME = 0x0008,
+  INSTRUCTIONAL = 0x0010,
+  VOICEASSISTANTS = 0x0020,
+  LIVE = 0x0040,
+  SOUNDEFFECTS = 0x0080,
+  NOTIFICATIONS = 0x0100,
+  RINGTONE = 0x0200,
+  ALERTS = 0x0400,
+  EMERGENCYALARM = 0x0800,
+  RFU = 0x1000,
+};
+
+/* Configuration strategy */
+enum class LeAudioConfigurationStrategy : uint8_t {
+  MONO_ONE_CIS_PER_DEVICE = 0x00, /* Common true wireless speakers */
+  STEREO_TWO_CISES_PER_DEVICE =
+      0x01, /* Requires 2 ASEs and 2 Audio Allocation for left/right */
+  STEREO_ONE_CIS_PER_DEVICE = 0x02, /* Requires channel count 2*/
+  RFU = 0x03,
+};
+
+constexpr LeAudioContextType operator|(LeAudioContextType lhs,
+                                       LeAudioContextType rhs) {
+  return static_cast<LeAudioContextType>(
+      static_cast<std::underlying_type<LeAudioContextType>::type>(lhs) |
+      static_cast<std::underlying_type<LeAudioContextType>::type>(rhs));
+}
+
+constexpr LeAudioContextType kLeAudioContextAllTypesArray[] = {
+    LeAudioContextType::UNSPECIFIED,   LeAudioContextType::CONVERSATIONAL,
+    LeAudioContextType::MEDIA,         LeAudioContextType::GAME,
+    LeAudioContextType::INSTRUCTIONAL, LeAudioContextType::VOICEASSISTANTS,
+    LeAudioContextType::LIVE,          LeAudioContextType::SOUNDEFFECTS,
+    LeAudioContextType::NOTIFICATIONS, LeAudioContextType::RINGTONE,
+    LeAudioContextType::ALERTS,        LeAudioContextType::EMERGENCYALARM,
+};
+
+constexpr LeAudioContextType kLeAudioContextAllTypes =
+    LeAudioContextType::UNSPECIFIED | LeAudioContextType::CONVERSATIONAL |
+    LeAudioContextType::MEDIA | LeAudioContextType::GAME |
+    LeAudioContextType::INSTRUCTIONAL | LeAudioContextType::VOICEASSISTANTS |
+    LeAudioContextType::LIVE | LeAudioContextType::SOUNDEFFECTS |
+    LeAudioContextType::NOTIFICATIONS | LeAudioContextType::RINGTONE |
+    LeAudioContextType::ALERTS | LeAudioContextType::EMERGENCYALARM;
+
+/* Structures */
+class LeAudioLtvMap {
+ public:
+  LeAudioLtvMap() {}
+  LeAudioLtvMap(std::map<uint8_t, std::vector<uint8_t>> values)
+      : values(std::move(values)) {}
+
+  std::optional<std::vector<uint8_t>> Find(uint8_t type) const;
+  bool IsEmpty() const { return values.empty(); }
+  void Clear() { values.clear(); }
+  size_t Size() const { return values.size(); }
+  const std::map<uint8_t, std::vector<uint8_t>>& Values() const {
+    return values;
+  }
+  std::string ToString() const;
+  size_t RawPacketSize() const;
+  uint8_t* RawPacket(uint8_t* p_buf) const;
+  static LeAudioLtvMap Parse(const uint8_t* value, uint8_t len, bool& success);
+
+ private:
+  std::map<uint8_t, std::vector<uint8_t>> values;
+};
+
+struct LeAudioLc3Config {
+  static const std::map<uint8_t, uint32_t> sampling_freq_map;
+  static const std::map<uint8_t, uint32_t> frame_duration_map;
+
+  uint8_t sampling_frequency;
+  uint8_t frame_duration;
+  uint16_t octets_per_codec_frame;
+  uint32_t audio_channel_allocation;
+  uint8_t channel_count;
+
+  /** Returns the sampling frequency representation in Hz */
+  uint32_t GetSamplingFrequencyHz() const {
+    return sampling_freq_map.count(sampling_frequency)
+               ? sampling_freq_map.at(sampling_frequency)
+               : 0;
+  }
+
+  /** Returns the frame duration representation in us */
+  uint32_t GetFrameDurationUs() const {
+    return frame_duration_map.count(frame_duration)
+               ? frame_duration_map.at(frame_duration)
+               : 0;
+  }
+
+  uint8_t GetChannelCount(void) const { return channel_count; }
+
+  LeAudioLtvMap GetAsLtvMap() const {
+    return LeAudioLtvMap({
+        {codec_spec_conf::kLeAudioCodecLC3TypeSamplingFreq,
+         UINT8_TO_VEC_UINT8(sampling_frequency)},
+        {codec_spec_conf::kLeAudioCodecLC3TypeFrameDuration,
+         UINT8_TO_VEC_UINT8(frame_duration)},
+        {codec_spec_conf::kLeAudioCodecLC3TypeAudioChannelAllocation,
+         UINT32_TO_VEC_UINT8(audio_channel_allocation)},
+        {codec_spec_conf::kLeAudioCodecLC3TypeOctetPerFrame,
+         UINT16_TO_VEC_UINT8(octets_per_codec_frame)},
+    });
+  }
+};
+
+struct LeAudioCodecId {
+  uint8_t coding_format;
+  uint16_t vendor_company_id;
+  uint16_t vendor_codec_id;
+
+  friend bool operator==(const LeAudioCodecId& lhs, const LeAudioCodecId& rhs) {
+    if (lhs.coding_format != rhs.coding_format) return false;
+
+    if (lhs.coding_format == kLeAudioCodingFormatVendorSpecific &&
+        (lhs.vendor_company_id != rhs.vendor_company_id ||
+         lhs.vendor_codec_id != rhs.vendor_codec_id))
+      return false;
+
+    return true;
+  }
+
+  friend bool operator!=(const LeAudioCodecId& lhs, const LeAudioCodecId& rhs) {
+    return !(lhs == rhs);
+  }
+};
+
+struct hdl_pair {
+  hdl_pair() = default;
+  hdl_pair(uint16_t val_hdl, uint16_t ccc_hdl)
+      : val_hdl(val_hdl), ccc_hdl(ccc_hdl) {}
+
+  uint16_t val_hdl = 0;
+  uint16_t ccc_hdl = 0;
+};
+
+struct ase {
+  static constexpr uint8_t kAseIdInvalid = 0x00;
+
+  ase(uint16_t val_hdl, uint16_t ccc_hdl, uint8_t direction)
+      : hdls(val_hdl, ccc_hdl),
+        id(kAseIdInvalid),
+        cis_id(kInvalidCisId),
+        direction(direction),
+        active(false),
+        reconfigure(false),
+        data_path_state(AudioStreamDataPathState::IDLE),
+        preferred_phy(0),
+        state(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {}
+
+  struct hdl_pair hdls;
+  uint8_t id;
+  uint8_t cis_id;
+  const uint8_t direction;
+  uint16_t cis_conn_hdl = 0;
+
+  bool active;
+  bool reconfigure;
+  AudioStreamDataPathState data_path_state;
+
+  /* Codec configuration */
+  LeAudioCodecId codec_id;
+  LeAudioLc3Config codec_config;
+  uint8_t framing;
+  uint8_t preferred_phy;
+
+  /* Qos configuration */
+  uint16_t max_sdu_size;
+  uint8_t retrans_nb;
+  uint16_t max_transport_latency;
+  uint32_t pres_delay_min;
+  uint32_t pres_delay_max;
+  uint32_t preferred_pres_delay_min;
+  uint32_t preferred_pres_delay_max;
+
+  std::vector<uint8_t> metadata;
+
+  AseState state;
+};
+
+struct BidirectAsesPair {
+  struct ase* sink;
+  struct ase* source;
+};
+
+struct acs_ac_record {
+  LeAudioCodecId codec_id;
+  LeAudioLtvMap codec_spec_caps;
+  std::vector<uint8_t> metadata;
+};
+
+using PublishedAudioCapabilities =
+    std::vector<std::tuple<hdl_pair, std::vector<acs_ac_record>>>;
+using AudioLocations = std::bitset<32>;
+using AudioContexts = std::bitset<16>;
+
+}  // namespace types
+
+namespace set_configurations {
+
+struct CodecCapabilitySetting {
+  types::LeAudioCodecId id;
+
+  /* Codec Specific Configuration variant */
+  std::variant<types::LeAudioLc3Config> config;
+
+  /* Sampling freqency requested for codec */
+  uint32_t GetConfigSamplingFrequency() const;
+  /* Data fetch/feed interval for codec in microseconds */
+  uint32_t GetConfigDataIntervalUs() const;
+  /* Audio bit depth required for codec */
+  uint8_t GetConfigBitsPerSample() const;
+  /* Audio channels number for stream */
+  uint8_t GetConfigChannelCount() const;
+};
+
+struct SetConfiguration {
+  SetConfiguration(uint8_t direction, uint8_t device_cnt, uint8_t ase_cnt,
+                   CodecCapabilitySetting codec,
+                   le_audio::types::LeAudioConfigurationStrategy strategy =
+                       le_audio::types::LeAudioConfigurationStrategy::
+                           MONO_ONE_CIS_PER_DEVICE)
+      : direction(direction),
+        device_cnt(device_cnt),
+        ase_cnt(ase_cnt),
+        codec(codec),
+        strategy(strategy) {}
+
+  uint8_t direction;  /* Direction of set */
+  uint8_t device_cnt; /* How many devices must be in set */
+  uint8_t ase_cnt;    /* How many ASE we need in configuration */
+  CodecCapabilitySetting codec;
+  types::LeAudioConfigurationStrategy strategy;
+};
+
+/* Defined audio scenarios */
+struct AudioSetConfiguration {
+  std::string name;
+  std::vector<struct SetConfiguration> confs;
+};
+
+using AudioSetConfigurations = std::vector<const AudioSetConfiguration*>;
+
+const types::LeAudioCodecId LeAudioCodecIdLc3 = {
+    .coding_format = types::kLeAudioCodingFormatLC3,
+    .vendor_company_id = types::kLeAudioVendorCompanyIdUndefined,
+    .vendor_codec_id = types::kLeAudioVendorCodecIdUndefined};
+
+static constexpr uint32_t kChannelAllocationMono =
+    codec_spec_conf::kLeAudioLocationMonoUnspecified;
+static constexpr uint32_t kChannelAllocationStereo =
+    codec_spec_conf::kLeAudioLocationFrontLeft |
+    codec_spec_conf::kLeAudioLocationFrontRight;
+
+/**
+ * Supported audio codec capability settings
+ *
+ * The subset of capabilities defined in BAP_Validation_r13 Table 3.6.
+ */
+constexpr CodecCapabilitySetting codec_lc3_16_1(uint8_t channel_count) {
+  return CodecCapabilitySetting{
+      .id = LeAudioCodecIdLc3,
+      .config = types::LeAudioLc3Config({
+          .sampling_frequency = codec_spec_conf::kLeAudioSamplingFreq16000Hz,
+          .frame_duration = codec_spec_conf::kLeAudioCodecLC3FrameDur7500us,
+          .octets_per_codec_frame = codec_spec_conf::kLeAudioCodecLC3FrameLen30,
+          .channel_count = channel_count,
+          .audio_channel_allocation = 0,
+      })};
+}
+
+constexpr CodecCapabilitySetting codec_lc3_16_2(uint8_t channel_count) {
+  return CodecCapabilitySetting{
+      .id = LeAudioCodecIdLc3,
+      .config = types::LeAudioLc3Config({
+          .sampling_frequency = codec_spec_conf::kLeAudioSamplingFreq16000Hz,
+          .frame_duration = codec_spec_conf::kLeAudioCodecLC3FrameDur10000us,
+          .octets_per_codec_frame = codec_spec_conf::kLeAudioCodecLC3FrameLen40,
+          .channel_count = channel_count,
+          .audio_channel_allocation = 0,
+      })};
+}
+
+constexpr CodecCapabilitySetting codec_lc3_48_4(uint8_t channel_count) {
+  return CodecCapabilitySetting{
+      .id = LeAudioCodecIdLc3,
+      .config = types::LeAudioLc3Config({
+          .sampling_frequency = codec_spec_conf::kLeAudioSamplingFreq48000Hz,
+          .frame_duration = codec_spec_conf::kLeAudioCodecLC3FrameDur10000us,
+          .octets_per_codec_frame =
+              codec_spec_conf::kLeAudioCodecLC3FrameLen120,
+          .channel_count = channel_count,
+          .audio_channel_allocation = 0,
+      })};
+}
+
+/*
+ * AudioSetConfiguration defines the audio set configuration and codec settings
+ * to to be used by le audio policy to match the required configuration with
+ * audio server capabilities. The codec settings are defined with respect to
+ * "Broadcast Source audio capability configuration support requirements"
+ * defined in BAP d09r06
+ */
+const AudioSetConfiguration kSingleDev_OneChanMonoSnk_16_2 = {
+    .name = "kSingleDev_OneChanMonoSnk_16_2",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 1, 1,
+        codec_lc3_16_2(
+            codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_OneChanMonoSnk_16_1 = {
+    .name = "kSingleDev_OneChanMonoSnk_16_1",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 1, 1,
+        codec_lc3_16_1(
+            codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_TwoChanStereoSnk_16_1 = {
+    .name = "kSingleDev_TwoChanStereoSnk_16_1",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 1, 1,
+        codec_lc3_16_1(codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel),
+        le_audio::types::LeAudioConfigurationStrategy::
+            STEREO_ONE_CIS_PER_DEVICE)}};
+
+const AudioSetConfiguration kSingleDev_OneChanStereoSnk_16_1 = {
+    .name = "kSingleDev_OneChanStereoSnk_16_1",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 1, 2,
+        codec_lc3_16_1(
+            codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel),
+        le_audio::types::LeAudioConfigurationStrategy::
+            STEREO_TWO_CISES_PER_DEVICE)}};
+
+const AudioSetConfiguration kDualDev_OneChanStereoSnk_16_1 = {
+    .name = "kDualDev_OneChanStereoSnk_16_1",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 2, 2,
+        codec_lc3_16_1(
+            codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_TwoChanStereoSnk_48_4 = {
+    .name = "kSingleDev_TwoChanStereoSnk_48_4",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 1, 1,
+        codec_lc3_48_4(codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel),
+        le_audio::types::LeAudioConfigurationStrategy::
+            STEREO_ONE_CIS_PER_DEVICE)}};
+
+const AudioSetConfiguration kDualDev_OneChanStereoSnk_48_4 = {
+    .name = "kDualDev_OneChanStereoSnk_48_4",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 2, 2,
+        codec_lc3_48_4(
+            codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_OneChanStereoSnk_48_4 = {
+    .name = "kSingleDev_OneChanStereoSnk_48_4",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 1, 2,
+        codec_lc3_48_4(
+            codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel),
+        le_audio::types::LeAudioConfigurationStrategy::
+            STEREO_TWO_CISES_PER_DEVICE)}};
+
+const AudioSetConfiguration kSingleDev_OneChanMonoSnk_48_4 = {
+    .name = "kSingleDev_OneChanMonoSnk_48_4",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 1, 1,
+        codec_lc3_48_4(
+            codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_TwoChanStereoSnk_16_2 = {
+    .name = "kSingleDev_TwoChanStereoSnk_16_2",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 1, 1,
+        codec_lc3_16_2(codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel),
+        le_audio::types::LeAudioConfigurationStrategy::
+            STEREO_ONE_CIS_PER_DEVICE)}};
+
+const AudioSetConfiguration kSingleDev_OneChanStereoSnk_16_2 = {
+    .name = "kSingleDev_OneChanStereoSnk_16_2",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 1, 2,
+        codec_lc3_16_2(
+            codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel),
+        le_audio::types::LeAudioConfigurationStrategy::
+            STEREO_TWO_CISES_PER_DEVICE)}};
+
+const AudioSetConfiguration kDualDev_OneChanStereoSnk_16_2 = {
+    .name = "kDualDev_OneChanStereoSnk_16_2",
+    .confs = {SetConfiguration(
+        types::kLeAudioDirectionSink, 2, 2,
+        codec_lc3_16_2(
+            codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1 = {
+    .name = "kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1",
+    .confs = {
+        SetConfiguration(
+            types::kLeAudioDirectionSink, 1, 1,
+            codec_lc3_16_1(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel)),
+        SetConfiguration(
+            types::kLeAudioDirectionSource, 1, 1,
+            codec_lc3_16_1(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2 = {
+    .name = "kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2",
+    .confs = {
+        SetConfiguration(
+            types::kLeAudioDirectionSink, 1, 1,
+            codec_lc3_16_2(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel)),
+        SetConfiguration(
+            types::kLeAudioDirectionSource, 1, 1,
+            codec_lc3_16_2(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2 = {
+    .name = "kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2",
+    .confs = {
+        SetConfiguration(
+            types::kLeAudioDirectionSink, 1, 1,
+            codec_lc3_16_2(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel),
+            le_audio::types::LeAudioConfigurationStrategy::
+                STEREO_ONE_CIS_PER_DEVICE),
+        SetConfiguration(
+            types::kLeAudioDirectionSource, 1, 1,
+            codec_lc3_16_2(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration
+    kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2 = {
+        .name = "kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2",
+        .confs = {
+            SetConfiguration(
+                types::kLeAudioDirectionSink, 2, 4,
+                codec_lc3_16_2(
+                    codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel),
+                le_audio::types::LeAudioConfigurationStrategy::
+                    STEREO_TWO_CISES_PER_DEVICE),
+            SetConfiguration(
+                types::kLeAudioDirectionSource, 1, 1,
+                codec_lc3_16_2(
+                    codec_spec_caps::
+                        kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2 = {
+    .name = "kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2",
+    .confs = {
+        SetConfiguration(
+            types::kLeAudioDirectionSink, 1, 2,
+            codec_lc3_16_2(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel),
+            le_audio::types::LeAudioConfigurationStrategy::
+                STEREO_TWO_CISES_PER_DEVICE),
+        SetConfiguration(
+            types::kLeAudioDirectionSource, 1, 1,
+            codec_lc3_16_2(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_2 = {
+    .name = "kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_2",
+    .confs = {
+        SetConfiguration(
+            types::kLeAudioDirectionSink, 2, 2,
+            codec_lc3_16_2(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel)),
+        SetConfiguration(
+            types::kLeAudioDirectionSource, 1, 1,
+            codec_lc3_16_2(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1 = {
+    .name = "kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1",
+    .confs = {
+        SetConfiguration(
+            types::kLeAudioDirectionSink, 1, 1,
+            codec_lc3_16_1(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel),
+            le_audio::types::LeAudioConfigurationStrategy::
+                STEREO_ONE_CIS_PER_DEVICE),
+        SetConfiguration(
+            types::kLeAudioDirectionSource, 1, 1,
+            codec_lc3_16_1(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1 = {
+    .name = "kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1",
+    .confs = {
+        SetConfiguration(
+            types::kLeAudioDirectionSink, 1, 2,
+            codec_lc3_16_1(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel),
+            le_audio::types::LeAudioConfigurationStrategy::
+                STEREO_TWO_CISES_PER_DEVICE),
+        SetConfiguration(
+            types::kLeAudioDirectionSource, 1, 1,
+            codec_lc3_16_1(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_1 = {
+    .name = "kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_1",
+    .confs = {
+        SetConfiguration(
+            types::kLeAudioDirectionSink, 2, 2,
+            codec_lc3_16_1(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel)),
+        SetConfiguration(
+            types::kLeAudioDirectionSource, 1, 1,
+            codec_lc3_16_1(
+                codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+const AudioSetConfiguration
+    kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1 = {
+        .name = "kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1",
+        .confs = {
+            SetConfiguration(
+                types::kLeAudioDirectionSink, 2, 4,
+                codec_lc3_16_1(
+                    codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel),
+                le_audio::types::LeAudioConfigurationStrategy::
+                    STEREO_TWO_CISES_PER_DEVICE),
+            SetConfiguration(
+                types::kLeAudioDirectionSource, 1, 1,
+                codec_lc3_16_1(
+                    codec_spec_caps::
+                        kLeAudioCodecLC3ChannelCountSingleChannel))}};
+
+/* Defined audio scenario linked with context type, priority sorted */
+const AudioSetConfigurations audio_set_conf_ringtone = {
+    &kDualDev_OneChanStereoSnk_16_2,   &kDualDev_OneChanStereoSnk_16_1,
+    &kSingleDev_OneChanStereoSnk_16_2, &kSingleDev_OneChanStereoSnk_16_1,
+    &kSingleDev_TwoChanStereoSnk_16_2, &kSingleDev_TwoChanStereoSnk_16_1,
+    &kSingleDev_OneChanMonoSnk_16_2,   &kSingleDev_OneChanMonoSnk_16_1,
+};
+
+const AudioSetConfigurations audio_set_conf_conversational = {
+    &kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_2,
+    &kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_1,
+    &kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2,
+    &kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1,
+    &kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2,
+    &kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1,
+    &kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2,
+    &kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1,
+    &kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2,
+    &kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1,
+};
+
+const AudioSetConfigurations audio_set_conf_media = {
+    &kDualDev_OneChanStereoSnk_48_4,   &kDualDev_OneChanStereoSnk_16_2,
+    &kDualDev_OneChanStereoSnk_16_1,   &kSingleDev_OneChanStereoSnk_48_4,
+    &kSingleDev_OneChanStereoSnk_16_2, &kSingleDev_OneChanStereoSnk_16_1,
+    &kSingleDev_TwoChanStereoSnk_48_4, &kSingleDev_TwoChanStereoSnk_16_2,
+    &kSingleDev_TwoChanStereoSnk_16_1, &kSingleDev_OneChanMonoSnk_48_4,
+    &kSingleDev_OneChanMonoSnk_16_2,   &kSingleDev_OneChanMonoSnk_16_1,
+};
+
+const AudioSetConfigurations audio_set_conf_default = {
+    &kDualDev_OneChanStereoSnk_16_2,
+    &kSingleDev_OneChanStereoSnk_16_2,
+    &kSingleDev_TwoChanStereoSnk_16_2,
+    &kSingleDev_OneChanMonoSnk_16_2,
+};
+
+/* Declarations */
+bool check_if_may_cover_scenario(
+    const AudioSetConfigurations* audio_set_configurations, uint8_t group_size);
+bool check_if_may_cover_scenario(
+    const AudioSetConfiguration* audio_set_configuration, uint8_t group_size);
+bool IsCodecCapabilitySettingSupported(
+    const types::acs_ac_record& pac_record,
+    const CodecCapabilitySetting& codec_capability_setting);
+const AudioSetConfigurations* get_confs_by_type(types::LeAudioContextType type);
+}  // namespace set_configurations
+
+struct stream_configuration {
+  bool valid;
+
+  types::LeAudioCodecId id;
+
+  /* Pointer to chosen req */
+  const le_audio::set_configurations::AudioSetConfiguration* conf;
+
+  /* Sink configuration */
+  /* For now we have always same frequency for all the channels */
+  uint32_t sink_sample_frequency_hz;
+  uint32_t sink_frame_duration_us;
+  uint16_t sink_octets_per_codec_frame;
+  /* Number of channels is what we will request from audio framework */
+  uint8_t sink_num_of_channels;
+  int sink_num_of_devices;
+  /* cis_handle, audio location*/
+  std::vector<std::pair<uint16_t, uint32_t>> sink_streams;
+
+  /* Source configuration */
+  /* For now we have always same frequency for all the channels */
+  uint32_t source_sample_frequency_hz;
+  uint32_t source_frame_duration_us;
+  uint16_t source_octets_per_codec_frame;
+  /* Number of channels is what we will request from audio framework */
+  uint8_t source_num_of_channels;
+  int source_num_of_devices;
+  /* cis_handle, audio location*/
+  std::vector<std::pair<uint16_t, uint32_t>> source_streams;
+};
+
+}  // namespace le_audio
+
+std::ostream& operator<<(std::ostream& os,
+                         const le_audio::types::LeAudioLc3Config& config);
+
+std::ostream& operator<<(std::ostream& os,
+                         const le_audio::types::AseState& state);
diff --git a/system/bta/le_audio/le_audio_types_test.cc b/system/bta/le_audio/le_audio_types_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0d6f1e475267f67923d5e22b2f2f0b64772859c8
--- /dev/null
+++ b/system/bta/le_audio/le_audio_types_test.cc
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "le_audio_types.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace le_audio {
+namespace types {
+
+using ::testing::AllOf;
+using ::testing::Contains;
+using ::testing::ElementsAre;
+using ::testing::ElementsAreArray;
+using ::testing::Eq;
+using ::testing::Pair;
+using ::testing::SizeIs;
+
+TEST(LeAudioLtvMapTest, test_serialization) {
+  // clang-format off
+  const std::vector<uint8_t> ltv_test_vec{
+      0x02, 0x01, 0x0a,
+      0x03, 0x02, 0xaa, 0xbb,
+      0x04, 0x03, 0xde, 0xc0, 0xde,
+      0x05, 0x04, 0xc0, 0xde, 0xc0, 0xde,
+  };
+  // clang-format on
+
+  // Parse
+  bool success;
+  LeAudioLtvMap ltv_map =
+      LeAudioLtvMap::Parse(ltv_test_vec.data(), ltv_test_vec.size(), success);
+  ASSERT_TRUE(success);
+  ASSERT_FALSE(ltv_map.IsEmpty());
+  ASSERT_EQ((size_t)4, ltv_map.Size());
+
+  ASSERT_TRUE(ltv_map.Find(0x01));
+  ASSERT_THAT(*(ltv_map.Find(0x01)), ElementsAre(0x0a));
+  ASSERT_TRUE(ltv_map.Find(0x02));
+  ASSERT_THAT(*(ltv_map.Find(0x02)), ElementsAre(0xaa, 0xbb));
+  ASSERT_TRUE(ltv_map.Find(0x03));
+  ASSERT_THAT(*(ltv_map.Find(0x03)), ElementsAre(0xde, 0xc0, 0xde));
+  ASSERT_TRUE(ltv_map.Find(0x04));
+  ASSERT_THAT(*(ltv_map.Find(0x04)), ElementsAre(0xc0, 0xde, 0xc0, 0xde));
+
+  // RawPacket
+  std::vector<uint8_t> serialized(ltv_map.RawPacketSize());
+  ASSERT_TRUE(ltv_map.RawPacket(serialized.data()));
+  ASSERT_THAT(serialized, ElementsAreArray(ltv_test_vec));
+}
+
+TEST(LeAudioLtvMapTest, test_serialization_ltv_len_is_zero) {
+  // clang-format off
+  const std::vector<uint8_t> ltv_test_vec{
+      0x02, 0x01, 0x0a,
+      0x03, 0x02, 0xaa, 0xbb,
+      0x00, 0x00, 0x00, 0x00, 0x00,       // ltv_len == 0
+      0x05, 0x04, 0xc0, 0xde, 0xc0, 0xde,
+  };
+  // clang-format on
+
+  // Parse
+  bool success;
+  LeAudioLtvMap ltv_map =
+      LeAudioLtvMap::Parse(ltv_test_vec.data(), ltv_test_vec.size(), success);
+  ASSERT_TRUE(success);
+  ASSERT_FALSE(ltv_map.IsEmpty());
+  ASSERT_EQ((size_t)3, ltv_map.Size());
+
+  ASSERT_TRUE(ltv_map.Find(0x01));
+  ASSERT_THAT(*(ltv_map.Find(0x01)), ElementsAre(0x0a));
+  ASSERT_TRUE(ltv_map.Find(0x02));
+  ASSERT_THAT(*(ltv_map.Find(0x02)), ElementsAre(0xaa, 0xbb));
+  ASSERT_TRUE(ltv_map.Find(0x04));
+  ASSERT_THAT(*(ltv_map.Find(0x04)), ElementsAre(0xc0, 0xde, 0xc0, 0xde));
+
+  // RawPacket
+  std::vector<uint8_t> serialized(ltv_map.RawPacketSize());
+  ASSERT_TRUE(ltv_map.RawPacket(serialized.data()));
+  ASSERT_THAT(serialized, ElementsAre(0x02, 0x01, 0x0a, 0x03, 0x02, 0xaa, 0xbb,
+                                      0x05, 0x04, 0xc0, 0xde, 0xc0, 0xde));
+}
+
+TEST(LeAudioLtvMapTest, test_serialization_ltv_len_is_one) {
+  // clang-format off
+  const std::vector<uint8_t> ltv_test_vec{
+    0x02, 0x01, 0x0a,
+    0x01, 0x02,
+  };
+  // clang-format on
+
+  // Parse
+  bool success;
+  LeAudioLtvMap ltv_map =
+      LeAudioLtvMap::Parse(ltv_test_vec.data(), ltv_test_vec.size(), success);
+  ASSERT_TRUE(success);
+  ASSERT_FALSE(ltv_map.IsEmpty());
+  ASSERT_EQ((size_t)2, ltv_map.Size());
+
+  ASSERT_TRUE(ltv_map.Find(0x01));
+  ASSERT_THAT(*(ltv_map.Find(0x01)), ElementsAre(0x0a));
+  ASSERT_TRUE(ltv_map.Find(0x02));
+  ASSERT_THAT(*(ltv_map.Find(0x02)), SizeIs(0));
+
+  // RawPacket
+  std::vector<uint8_t> serialized(ltv_map.RawPacketSize());
+  ASSERT_TRUE(ltv_map.RawPacket(serialized.data()));
+  ASSERT_THAT(serialized, ElementsAreArray(ltv_test_vec));
+}
+
+TEST(LeAudioLtvMapTest, test_serialization_ltv_len_is_invalid) {
+  // clang-format off
+  const std::vector<uint8_t> ltv_test_vec_1{
+      0x02, 0x01, 0x0a,
+      0x04, 0x02, 0xaa, 0xbb, // one byte missing
+  };
+  const std::vector<uint8_t> ltv_test_vec_2{
+      0x02, 0x01, 0x0a,
+      0x03, 0x02, 0xaa, 0xbb,
+      0x01,
+  };
+  const std::vector<uint8_t> ltv_test_vec_3{
+      0x02, 0x01, 0x0a,
+      0x03, 0x02, 0xaa, 0xbb,
+      0x02, 0x03,
+  };
+  // clang-format on
+
+  // Parse
+  bool success = true;
+  LeAudioLtvMap ltv_map;
+
+  ltv_map = LeAudioLtvMap::Parse(ltv_test_vec_1.data(), ltv_test_vec_1.size(),
+                                 success);
+  ASSERT_FALSE(success);
+
+  ltv_map = LeAudioLtvMap::Parse(ltv_test_vec_2.data(), ltv_test_vec_2.size(),
+                                 success);
+  ASSERT_FALSE(success);
+
+  ltv_map = LeAudioLtvMap::Parse(ltv_test_vec_3.data(), ltv_test_vec_3.size(),
+                                 success);
+  ASSERT_FALSE(success);
+}
+
+}  // namespace types
+}  // namespace le_audio
diff --git a/system/bta/le_audio/mock_iso_manager.cc b/system/bta/le_audio/mock_iso_manager.cc
new file mode 100644
index 0000000000000000000000000000000000000000..89579e7108ce2689b08baa153afe2b0fd06b5a25
--- /dev/null
+++ b/system/bta/le_audio/mock_iso_manager.cc
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mock_iso_manager.h"
+
+MockIsoManager* mock_pimpl_;
+MockIsoManager* MockIsoManager::GetInstance() {
+  bluetooth::hci::IsoManager::GetInstance();
+  return mock_pimpl_;
+}
+
+namespace bluetooth {
+namespace hci {
+
+struct IsoManager::impl : public MockIsoManager {
+ public:
+  impl() = default;
+  ~impl() = default;
+};
+
+IsoManager::IsoManager() {}
+
+void IsoManager::RegisterCigCallbacks(
+    iso_manager::CigCallbacks* callbacks) const {
+  if (!pimpl_) return;
+  pimpl_->RegisterCigCallbacks(callbacks);
+}
+
+void IsoManager::RegisterBigCallbacks(
+    iso_manager::BigCallbacks* callbacks) const {
+  if (!pimpl_) return;
+  pimpl_->RegisterBigCallbacks(callbacks);
+}
+
+void IsoManager::CreateCig(uint8_t cig_id,
+                           struct iso_manager::cig_create_params cig_params) {
+  if (!pimpl_) return;
+  pimpl_->CreateCig(cig_id, std::move(cig_params));
+}
+
+void IsoManager::ReconfigureCig(
+    uint8_t cig_id, struct iso_manager::cig_create_params cig_params) {
+  if (!pimpl_) return;
+  pimpl_->ReconfigureCig(cig_id, std::move(cig_params));
+}
+
+void IsoManager::RemoveCig(uint8_t cig_id) { pimpl_->RemoveCig(cig_id); }
+
+void IsoManager::EstablishCis(
+    struct iso_manager::cis_establish_params conn_params) {
+  if (!pimpl_) return;
+  pimpl_->EstablishCis(std::move(conn_params));
+}
+
+void IsoManager::DisconnectCis(uint16_t cis_handle, uint8_t reason) {
+  if (!pimpl_) return;
+  pimpl_->DisconnectCis(cis_handle, reason);
+}
+
+void IsoManager::SetupIsoDataPath(
+    uint16_t iso_handle, struct iso_manager::iso_data_path_params path_params) {
+  if (!pimpl_) return;
+  pimpl_->SetupIsoDataPath(iso_handle, std::move(path_params));
+}
+
+void IsoManager::RemoveIsoDataPath(uint16_t iso_handle, uint8_t data_path_dir) {
+  if (!pimpl_) return;
+  pimpl_->RemoveIsoDataPath(iso_handle, data_path_dir);
+}
+
+void IsoManager::ReadIsoLinkQuality(uint16_t iso_handle) {
+  if (!pimpl_) return;
+  pimpl_->ReadIsoLinkQuality(iso_handle);
+}
+
+void IsoManager::SendIsoData(uint16_t iso_handle, const uint8_t* data,
+                             uint16_t data_len) {
+  if (!pimpl_) return;
+  pimpl_->SendIsoData(iso_handle, data, data_len);
+}
+
+void IsoManager::CreateBig(uint8_t big_id,
+                           struct iso_manager::big_create_params big_params) {
+  if (!pimpl_) return;
+  pimpl_->CreateBig(big_id, std::move(big_params));
+}
+
+void IsoManager::TerminateBig(uint8_t big_id, uint8_t reason) {
+  if (!pimpl_) return;
+  pimpl_->TerminateBig(big_id, reason);
+}
+
+void IsoManager::HandleIsoData(void* p_msg) {
+  if (!pimpl_) return;
+  pimpl_->HandleIsoData(static_cast<BT_HDR*>(p_msg));
+}
+
+void IsoManager::HandleDisconnect(uint16_t handle, uint8_t reason) {
+  if (!pimpl_) return;
+  pimpl_->HandleDisconnect(handle, reason);
+}
+
+void IsoManager::HandleNumComplDataPkts(uint8_t* p, uint8_t evt_len) {
+  if (!pimpl_) return;
+  pimpl_->HandleNumComplDataPkts(p, evt_len);
+}
+
+void IsoManager::HandleGdNumComplDataPkts(uint16_t handle, uint16_t credits) {}
+
+void IsoManager::HandleHciEvent(uint8_t sub_code, uint8_t* params,
+                                uint16_t length) {
+  if (!pimpl_) return;
+  pimpl_->HandleHciEvent(sub_code, params, length);
+}
+
+void IsoManager::Start() {
+  // It is needed here as IsoManager which is a singleton creates it, but in
+  // this mock we want to destroy and recreate the mock on each test case.
+  if (!pimpl_) {
+    pimpl_ = std::make_unique<impl>();
+  }
+
+  mock_pimpl_ = pimpl_.get();
+  pimpl_->Start();
+}
+
+void IsoManager::Stop() {
+  // It is needed here as IsoManager which is a singleton creates it, but in
+  // this mock we want to destroy and recreate the mock on each test case.
+  if (pimpl_) {
+    pimpl_->Stop();
+    pimpl_.reset();
+  }
+
+  mock_pimpl_ = nullptr;
+}
+
+IsoManager::~IsoManager() = default;
+
+}  // namespace hci
+}  // namespace bluetooth
diff --git a/system/bta/le_audio/mock_iso_manager.h b/system/bta/le_audio/mock_iso_manager.h
new file mode 100644
index 0000000000000000000000000000000000000000..e4a10abccbd33cf158e5e9bd507f21eeb4df319e
--- /dev/null
+++ b/system/bta/le_audio/mock_iso_manager.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "btm_iso_api.h"
+
+struct MockIsoManager {
+ public:
+  static MockIsoManager* GetInstance();
+
+  MockIsoManager() = default;
+  virtual ~MockIsoManager() = default;
+
+  MOCK_METHOD((void), RegisterCigCallbacks,
+              (bluetooth::hci::iso_manager::CigCallbacks * callbacks), (const));
+  MOCK_METHOD((void), RegisterBigCallbacks,
+              (bluetooth::hci::iso_manager::BigCallbacks * callbacks), (const));
+  MOCK_METHOD(
+      (void), CreateCig,
+      (uint8_t cig_id,
+       struct bluetooth::hci::iso_manager::cig_create_params cig_params));
+  MOCK_METHOD(
+      (void), ReconfigureCig,
+      (uint8_t cig_id,
+       struct bluetooth::hci::iso_manager::cig_create_params cig_params));
+  MOCK_METHOD((void), RemoveCig, (uint8_t cig_id));
+  MOCK_METHOD(
+      (void), EstablishCis,
+      (struct bluetooth::hci::iso_manager::cis_establish_params conn_params));
+  MOCK_METHOD((void), DisconnectCis, (uint16_t cis_handle, uint8_t reason));
+  MOCK_METHOD(
+      (void), SetupIsoDataPath,
+      (uint16_t iso_handle,
+       struct bluetooth::hci::iso_manager::iso_data_path_params path_params));
+  MOCK_METHOD((void), RemoveIsoDataPath,
+              (uint16_t iso_handle, uint8_t data_path_dir));
+  MOCK_METHOD((void), SendIsoData,
+              (uint16_t iso_handle, const uint8_t* data, uint16_t data_len));
+  MOCK_METHOD((void), ReadIsoLinkQuality, (uint16_t iso_handle));
+  MOCK_METHOD(
+      (void), CreateBig,
+      (uint8_t big_id,
+       struct bluetooth::hci::iso_manager::big_create_params big_params));
+  MOCK_METHOD((void), TerminateBig, (uint8_t big_id, uint8_t reason));
+  MOCK_METHOD((void), HandleIsoData, (void* p_msg));
+  MOCK_METHOD((void), HandleDisconnect, (uint16_t handle, uint8_t reason));
+  MOCK_METHOD((void), HandleNumComplDataPkts, (uint8_t * p, uint8_t evt_len));
+  MOCK_METHOD((void), HandleGdNumComplDataPkts, (uint8_t * p, uint8_t evt_len));
+  MOCK_METHOD((void), HandleHciEvent,
+              (uint8_t sub_code, uint8_t* params, uint16_t length));
+
+  MOCK_METHOD((void), Start, ());
+  MOCK_METHOD((void), Stop, ());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockIsoManager);
+};
diff --git a/system/bta/le_audio/mock_le_audio_client_audio.cc b/system/bta/le_audio/mock_le_audio_client_audio.cc
new file mode 100644
index 0000000000000000000000000000000000000000..5279b4c79dc721567eb5436074efc13a36624d36
--- /dev/null
+++ b/system/bta/le_audio/mock_le_audio_client_audio.cc
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mock_le_audio_client_audio.h"
+
+#include <base/logging.h>
+
+/* Source mock */
+static MockLeAudioClientAudioSource* source_instance = nullptr;
+void MockLeAudioClientAudioSource::SetMockInstanceForTesting(
+    MockLeAudioClientAudioSource* mock) {
+  source_instance = mock;
+}
+
+bool LeAudioClientAudioSource::Start(
+    const LeAudioCodecConfiguration& codecConfiguration,
+    LeAudioClientAudioSinkReceiver* audioReceiver) {
+  LOG_ASSERT(source_instance)
+      << "Mock LeAudioClientAudioSource interface not set!";
+  return source_instance->Start(codecConfiguration, audioReceiver);
+}
+
+void LeAudioClientAudioSource::Stop() {
+  LOG_ASSERT(source_instance)
+      << "Mock LeAudioClientAudioSource interface not set!";
+  source_instance->Stop();
+}
+
+// FIXME: This is wrong! we will return a different class object - not even in
+// inheritance hierarchy
+const void* LeAudioClientAudioSource::Acquire() {
+  LOG_ASSERT(source_instance)
+      << "Mock LeAudioClientAudioSource interface not set!";
+  return source_instance->Acquire();
+}
+
+void LeAudioClientAudioSource::Release(const void* inst) {
+  LOG_ASSERT(source_instance)
+      << "Mock LeAudioClientAudioSource interface not set!";
+  source_instance->Release(inst);
+}
+
+void LeAudioClientAudioSource::ConfirmStreamingRequest() {
+  LOG_ASSERT(source_instance)
+      << "Mock LeAudioClientAudioSink interface not set!";
+  source_instance->ConfirmStreamingRequest();
+}
+
+void LeAudioClientAudioSource::CancelStreamingRequest() {
+  LOG_ASSERT(source_instance)
+      << "Mock LeAudioClientAudioSink interface not set!";
+  source_instance->CancelStreamingRequest();
+}
+
+void LeAudioClientAudioSource::UpdateRemoteDelay(uint16_t delay) {
+  LOG_ASSERT(source_instance)
+      << "Mock LeAudioClientAudioSource interface not set!";
+  source_instance->UpdateRemoteDelay(delay);
+}
+
+void LeAudioClientAudioSource::DebugDump(int fd) {
+  LOG_ASSERT(source_instance)
+      << "Mock LeAudioClientAudioSource interface not set!";
+  source_instance->DebugDump(fd);
+}
+
+/* Sink mock */
+static MockLeAudioClientAudioSink* sink_instance = nullptr;
+void MockLeAudioClientAudioSink::SetMockInstanceForTesting(
+    MockLeAudioClientAudioSink* mock) {
+  sink_instance = mock;
+}
+
+bool LeAudioClientAudioSink::Start(
+    const LeAudioCodecConfiguration& codecConfiguration,
+    LeAudioClientAudioSourceReceiver* audioReceiver) {
+  LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!";
+  return sink_instance->Start(codecConfiguration, audioReceiver);
+}
+
+void LeAudioClientAudioSink::Stop() {
+  LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!";
+  sink_instance->Stop();
+}
+
+// FIXME: This is wrong! we will return a different class object - not even in
+// inheritance hierarchy
+const void* LeAudioClientAudioSink::Acquire() {
+  LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!";
+  return sink_instance->Acquire();
+}
+
+void LeAudioClientAudioSink::Release(const void* inst) {
+  LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!";
+  sink_instance->Release(inst);
+}
+
+void LeAudioClientAudioSink::UpdateRemoteDelay(uint16_t delay) {
+  LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!";
+  sink_instance->UpdateRemoteDelay(delay);
+}
+
+void LeAudioClientAudioSink::DebugDump(int fd) {
+  LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!";
+  sink_instance->DebugDump(fd);
+}
+
+size_t LeAudioClientAudioSink::SendData(uint8_t* data, uint16_t size) {
+  LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!";
+  return sink_instance->SendData(data, size);
+}
+
+void LeAudioClientAudioSink::ConfirmStreamingRequest() {
+  LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!";
+  sink_instance->ConfirmStreamingRequest();
+}
+
+void LeAudioClientAudioSink::CancelStreamingRequest() {
+  LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!";
+  sink_instance->CancelStreamingRequest();
+}
diff --git a/system/bta/le_audio/mock_le_audio_client_audio.h b/system/bta/le_audio/mock_le_audio_client_audio.h
new file mode 100644
index 0000000000000000000000000000000000000000..fbae8f70c0a09aeac153a10c87c32f3ea220b7b3
--- /dev/null
+++ b/system/bta/le_audio/mock_le_audio_client_audio.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "client_audio.h"
+
+class MockLeAudioClientAudioSource {
+ public:
+  static void SetMockInstanceForTesting(MockLeAudioClientAudioSource* mock);
+  MOCK_METHOD((bool), Start,
+              (const LeAudioCodecConfiguration& codecConfiguration,
+               LeAudioClientAudioSinkReceiver* audioReceiver));
+  MOCK_METHOD((void), Stop, ());
+  MOCK_METHOD((const void*), Acquire, ());
+  MOCK_METHOD((void), Release, (const void*));
+  MOCK_METHOD((void), ConfirmStreamingRequest, ());
+  MOCK_METHOD((void), CancelStreamingRequest, ());
+  MOCK_METHOD((void), UpdateRemoteDelay, (uint16_t delay));
+  MOCK_METHOD((void), DebugDump, (int fd));
+};
+
+class MockLeAudioClientAudioSink {
+ public:
+  static void SetMockInstanceForTesting(MockLeAudioClientAudioSink* mock);
+  MOCK_METHOD((bool), Start,
+              (const LeAudioCodecConfiguration& codecConfiguration,
+               LeAudioClientAudioSourceReceiver* audioReceiver));
+  MOCK_METHOD((void), Stop, ());
+  MOCK_METHOD((const void*), Acquire, ());
+  MOCK_METHOD((void), Release, (const void*));
+  MOCK_METHOD((size_t), SendData, (uint8_t * data, uint16_t size));
+  MOCK_METHOD((void), ConfirmStreamingRequest, ());
+  MOCK_METHOD((void), CancelStreamingRequest, ());
+  MOCK_METHOD((void), UpdateRemoteDelay, (uint16_t delay));
+  MOCK_METHOD((void), DebugDump, (int fd));
+};
diff --git a/system/bta/le_audio/mock_le_audio_client_audio_source.cc b/system/bta/le_audio/mock_le_audio_client_audio_source.cc
new file mode 100644
index 0000000000000000000000000000000000000000..014d74be0777722bcbf25fca631ee938f522c720
--- /dev/null
+++ b/system/bta/le_audio/mock_le_audio_client_audio_source.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mock_le_audio_client_audio_source.h"
+
+static MockLeAudioClientAudioSource* instance;
+void MockLeAudioClientAudioSource::SetMockInstanceForTesting(
+    MockLeAudioClientAudioSource* mock) {
+  instance = mock;
+}
+
+bool LeAudioClientAudioSource::Start(
+    const LeAudioCodecConfiguration& codecConfiguration,
+    LeAudioClientAudioSinkReceiver* audioReceiver, uint16_t remote_delay_ms) {
+  return instance->Start(codecConfiguration, audioReceiver, remote_delay_ms);
+}
+
+void LeAudioClientAudioSource::Stop() { instance->Stop(); }
+
+// FIXME: This is wrong! we will return a different class object - not even in
+// inheritance hierarchy
+const void* LeAudioClientAudioSource::Acquire() { return instance->Acquire(); }
+
+void LeAudioClientAudioSource::Release(const void* inst) {
+  instance->Release(inst);
+}
+
+void LeAudioClientAudioSource::ConfirmStreamingRequest() {
+  instance->ConfirmStreamingRequest();
+}
+
+void LeAudioClientAudioSource::CancelStreamingRequest() {
+  instance->CancelStreamingRequest();
+}
+
+void LeAudioClientAudioSource::DebugDump(int fd) { instance->DebugDump(fd); }
diff --git a/system/bta/le_audio/mock_le_audio_client_audio_source.h b/system/bta/le_audio/mock_le_audio_client_audio_source.h
new file mode 100644
index 0000000000000000000000000000000000000000..59646a56671273d6bf378cc7bb28bf80163c4eec
--- /dev/null
+++ b/system/bta/le_audio/mock_le_audio_client_audio_source.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "client_audio.h"
+
+class MockLeAudioClientAudioSource {
+ public:
+  static void SetMockInstanceForTesting(MockLeAudioClientAudioSource* mock);
+  MOCK_METHOD((bool), Start,
+              (const LeAudioCodecConfiguration& codecConfiguration,
+               LeAudioClientAudioSinkReceiver* audioReceiver,
+               uint16_t remote_delay_ms));
+  MOCK_METHOD((void), Stop, ());
+  MOCK_METHOD((const void*), Acquire, ());
+  MOCK_METHOD((void), Release, (const void*));
+  MOCK_METHOD((void), DebugDump, (int fd));
+};
diff --git a/system/bta/le_audio/mock_state_machine.cc b/system/bta/le_audio/mock_state_machine.cc
new file mode 100644
index 0000000000000000000000000000000000000000..dbff4f427005c0df2c062a97ee0e9569afad147c
--- /dev/null
+++ b/system/bta/le_audio/mock_state_machine.cc
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mock_state_machine.h"
+
+#include <base/logging.h>
+
+static MockLeAudioGroupStateMachine* mock_machine = nullptr;
+
+void le_audio::LeAudioGroupStateMachine::Initialize(
+    le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks) {
+  LOG_ASSERT(mock_machine) << "Mock State Machine not set!";
+  mock_machine->Initialize(state_machine_callbacks);
+}
+
+void le_audio::LeAudioGroupStateMachine::Cleanup(void) {
+  LOG_ASSERT(mock_machine) << "Mock State Machine not set!";
+  mock_machine->Cleanup();
+}
+
+le_audio::LeAudioGroupStateMachine* le_audio::LeAudioGroupStateMachine::Get(
+    void) {
+  return mock_machine;
+}
+
+void MockLeAudioGroupStateMachine::SetMockInstanceForTesting(
+    MockLeAudioGroupStateMachine* machine) {
+  mock_machine = machine;
+}
diff --git a/system/bta/le_audio/mock_state_machine.h b/system/bta/le_audio/mock_state_machine.h
new file mode 100644
index 0000000000000000000000000000000000000000..61a0b4f0b9f613249a6bbdd08b4222115f3556d2
--- /dev/null
+++ b/system/bta/le_audio/mock_state_machine.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "state_machine.h"
+
+class MockLeAudioGroupStateMachine : public le_audio::LeAudioGroupStateMachine {
+ public:
+  MOCK_METHOD((bool), StartStream,
+              (le_audio::LeAudioDeviceGroup * group,
+               le_audio::types::LeAudioContextType context_type),
+              (override));
+  MOCK_METHOD((bool), AttachToStream,
+              (le_audio::LeAudioDeviceGroup * group,
+               le_audio::LeAudioDevice* leAudioDevice),
+              (override));
+  MOCK_METHOD((void), SuspendStream, (le_audio::LeAudioDeviceGroup * group),
+              (override));
+  MOCK_METHOD((void), StopStream, (le_audio::LeAudioDeviceGroup * group),
+              (override));
+  MOCK_METHOD((void), ProcessGattNotifEvent,
+              (uint8_t * value, uint16_t len, le_audio::types::ase* ase,
+               le_audio::LeAudioDevice* leAudioDevice,
+               le_audio::LeAudioDeviceGroup* group),
+              (override));
+
+  MOCK_METHOD((void), ProcessHciNotifOnCigCreate,
+              (le_audio::LeAudioDeviceGroup * group, uint8_t status,
+               uint8_t cig_id, std::vector<uint16_t> conn_handles),
+              (override));
+  MOCK_METHOD((void), ProcessHciNotifOnCigRemove,
+              (uint8_t status, le_audio::LeAudioDeviceGroup* group),
+              (override));
+  MOCK_METHOD(
+      (void), ProcessHciNotifCisEstablished,
+      (le_audio::LeAudioDeviceGroup * group,
+       le_audio::LeAudioDevice* leAudioDevice,
+       const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event),
+      (override));
+  MOCK_METHOD((void), ProcessHciNotifCisDisconnected,
+              (le_audio::LeAudioDeviceGroup * group,
+               le_audio::LeAudioDevice* leAudioDevice,
+               const bluetooth::hci::iso_manager::cis_disconnected_evt* event),
+              (override));
+  MOCK_METHOD((void), ProcessHciNotifSetupIsoDataPath,
+              (le_audio::LeAudioDeviceGroup * group,
+               le_audio::LeAudioDevice* leAudioDevice, uint8_t status,
+               uint16_t conn_hdl),
+              (override));
+  MOCK_METHOD((void), ProcessHciNotifRemoveIsoDataPath,
+              (le_audio::LeAudioDeviceGroup * group,
+               le_audio::LeAudioDevice* leAudioDevice, uint8_t status,
+               uint16_t conn_hdl),
+              (override));
+  MOCK_METHOD((void), Initialize,
+              (le_audio::LeAudioGroupStateMachine::Callbacks *
+               state_machine_callbacks));
+  MOCK_METHOD((void), Cleanup, ());
+  MOCK_METHOD((void), ProcessHciNotifIsoLinkQualityRead,
+              (le_audio::LeAudioDeviceGroup * group,
+               le_audio::LeAudioDevice* leAudioDevice, uint8_t conn_handle,
+               uint32_t txUnackedPackets, uint32_t txFlushedPackets,
+               uint32_t txLastSubeventPackets, uint32_t retransmittedPackets,
+               uint32_t crcErrorPackets, uint32_t rxUnreceivedPackets,
+               uint32_t duplicatePackets),
+              (override));
+  MOCK_METHOD((void), ProcessHciNotifAclDisconnected,
+              (le_audio::LeAudioDeviceGroup * group,
+               le_audio::LeAudioDevice* leAudioDevice),
+              (override));
+
+  static void SetMockInstanceForTesting(MockLeAudioGroupStateMachine* machine);
+};
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6529dcdea752d966165beeb3eb808f79acb8d03e
--- /dev/null
+++ b/system/bta/le_audio/state_machine.cc
@@ -0,0 +1,1832 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "state_machine.h"
+
+#include <base/bind.h>
+#include <base/callback.h>
+
+#include <map>
+
+#include "bt_types.h"
+#include "bta_gatt_queue.h"
+#include "bta_le_audio_api.h"
+#include "btm_iso_api.h"
+#include "client_parser.h"
+#include "devices.h"
+#include "hcimsgs.h"
+#include "le_audio_types.h"
+#include "osi/include/alarm.h"
+#include "osi/include/osi.h"
+#include "osi/include/properties.h"
+
+// clang-format off
+/* ASCS state machine 1.0
+ *
+ * State machine manages group of ASEs to make transition from one state to
+ * another according to specification and keeping involved necessary externals
+ * like: ISO, CIG, ISO data path, audio path form/to upper layer.
+ *
+ * GroupStream (API): GroupStream method of this le audio implementation class
+ *                    object should allow transition from Idle (No Caching),
+ *                    Codec Configured (Caching after release) state to
+ *                    Streaming for all ASEs in group within time limit. Time
+ *                    limit should keep safe whole state machine from being
+ *                    stucked in any in-middle state, which is not a destination
+ *                    state.
+ *
+ *                    TODO Second functionality of streaming should be switch
+ *                    context which will base on previous state, context type.
+ *
+ * GroupStop (API): GroupStop method of this le audio implementation class
+ *                  object should allow safe transition from any state to Idle
+ *                  or Codec Configured (if caching supported).
+ *
+ * ╔══════════════════╦═════════════════════════════╦══════════════╦══════════════════╦══════╗
+ * â•‘  Current State   â•‘ ASE Control Point Operation â•‘    Result    â•‘    Next State    â•‘ Note â•‘
+ * ╠══════════════════╬═════════════════════════════╬══════════════╬══════════════════╬══════╣
+ * â•‘ Idle             â•‘ Config Codec                â•‘ Success      â•‘ Codec Configured â•‘  +   â•‘
+ * â•‘ Codec Configured â•‘ Config Codec                â•‘ Success      â•‘ Codec Configured â•‘  -   â•‘
+ * â•‘ Codec Configured â•‘ Release                     â•‘ Success      â•‘ Releasing        â•‘  +   â•‘
+ * â•‘ Codec Configured â•‘ Config QoS                  â•‘ Success      â•‘ QoS Configured   â•‘  +   â•‘
+ * â•‘ QoS Configured   â•‘ Config Codec                â•‘ Success      â•‘ Codec Configured â•‘  -   â•‘
+ * â•‘ QoS Configured   â•‘ Config QoS                  â•‘ Success      â•‘ QoS Configured   â•‘  -   â•‘
+ * â•‘ QoS Configured   â•‘ Release                     â•‘ Success      â•‘ Releasing        â•‘  +   â•‘
+ * â•‘ QoS Configured   â•‘ Enable                      â•‘ Success      â•‘ Enabling         â•‘  +   â•‘
+ * â•‘ Enabling         â•‘ Release                     â•‘ Success      â•‘ Releasing        â•‘  +   â•‘
+ * â•‘ Enabling         â•‘ Update Metadata             â•‘ Success      â•‘ Enabling         â•‘  -   â•‘
+ * â•‘ Enabling         â•‘ Disable                     â•‘ Success      â•‘ Disabling        â•‘  -   â•‘
+ * â•‘ Enabling         â•‘ Receiver Start Ready        â•‘ Success      â•‘ Streaming        â•‘  +   â•‘
+ * â•‘ Streaming        â•‘ Update Metadata             â•‘ Success      â•‘ Streaming        â•‘  -   â•‘
+ * â•‘ Streaming        â•‘ Disable                     â•‘ Success      â•‘ Disabling        â•‘  +   â•‘
+ * â•‘ Streaming        â•‘ Release                     â•‘ Success      â•‘ Releasing        â•‘  +   â•‘
+ * â•‘ Disabling        â•‘ Receiver Stop Ready         â•‘ Success      â•‘ QoS Configured   â•‘  +   â•‘
+ * â•‘ Disabling        â•‘ Release                     â•‘ Success      â•‘ Releasing        â•‘  +   â•‘
+ * â•‘ Releasing        â•‘ Released (no caching)       â•‘ Success      â•‘ Idle             â•‘  +   â•‘
+ * â•‘ Releasing        â•‘ Released (caching)          â•‘ Success      â•‘ Codec Configured â•‘  -   â•‘
+ * ╚══════════════════╩═════════════════════════════╩══════════════╩══════════════════╩══════╝
+ *
+ * + - supported transition
+ * - - not supported
+ */
+// clang-format on
+
+using bluetooth::hci::IsoManager;
+using bluetooth::le_audio::GroupStreamStatus;
+using le_audio::LeAudioDevice;
+using le_audio::LeAudioDeviceGroup;
+using le_audio::LeAudioGroupStateMachine;
+
+using le_audio::types::ase;
+using le_audio::types::AseState;
+using le_audio::types::AudioStreamDataPathState;
+
+namespace {
+
+constexpr int linkQualityCheckInterval = 4000;
+
+static void link_quality_cb(void* data) {
+  // very ugly, but we need to pass just two bytes
+  uint16_t cis_conn_handle = *((uint16_t*)data);
+
+  IsoManager::GetInstance()->ReadIsoLinkQuality(cis_conn_handle);
+}
+
+class LeAudioGroupStateMachineImpl;
+LeAudioGroupStateMachineImpl* instance;
+
+class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
+ public:
+  LeAudioGroupStateMachineImpl(Callbacks* state_machine_callbacks_)
+      : state_machine_callbacks_(state_machine_callbacks_),
+        watchdog_(alarm_new("LeAudioStateMachineTimer")) {}
+
+  ~LeAudioGroupStateMachineImpl() {
+    alarm_free(watchdog_);
+    watchdog_ = nullptr;
+  }
+
+  bool AttachToStream(LeAudioDeviceGroup* group,
+                      LeAudioDevice* leAudioDevice) override {
+    LOG(INFO) << __func__ << " group id: " << group->group_id_
+              << " device: " << leAudioDevice->address_;
+
+    /* This function is used to attach the device to the stream.
+     * Limitation here is that device should be previously in the streaming
+     * group and just got reconnected.
+     */
+    if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+      LOG(ERROR) << __func__
+                 << " group not in the streaming state: " << group->GetState();
+      return false;
+    }
+
+    PrepareAndSendCodecConfigure(group, leAudioDevice);
+    return true;
+  }
+
+  bool StartStream(LeAudioDeviceGroup* group,
+                   le_audio::types::LeAudioContextType context_type) override {
+    LOG(INFO) << __func__ << " current state: " << group->GetState();
+
+    switch (group->GetState()) {
+      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
+      case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
+        if (!group->Configure(context_type)) {
+          LOG(ERROR) << __func__ << ", failed to set ASE configuration";
+          return false;
+        }
+
+        /* All ASEs should aim to achieve target state */
+        SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+        PrepareAndSendCodecConfigure(group, group->GetFirstActiveDevice());
+        break;
+
+      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: {
+        LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
+        if (!leAudioDevice) {
+          LOG(ERROR) << __func__ << ", group has no active devices";
+          return false;
+        }
+
+        /* All ASEs should aim to achieve target state */
+        SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+        PrepareAndSendEnable(leAudioDevice);
+        break;
+      }
+
+      case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
+        if (group->GetContextType() != context_type) {
+          /* TODO: Switch context of group */
+          group->SetContextType(context_type);
+        }
+        return true;
+
+      default:
+        LOG(ERROR) << "Unable to transit from " << group->GetState();
+        return false;
+    }
+
+    return true;
+  }
+
+  void SuspendStream(LeAudioDeviceGroup* group) override {
+    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
+    LOG_ASSERT(leAudioDevice)
+        << __func__ << " Shouldn't be called without an active device.";
+
+    /* All ASEs should aim to achieve target state */
+    SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+    PrepareAndSendDisable(leAudioDevice);
+  }
+
+  void StopStream(LeAudioDeviceGroup* group) override {
+    if (group->IsReleasing()) {
+      LOG(INFO) << __func__ << ", group already in releasing process";
+      return;
+    }
+
+    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
+    if (leAudioDevice == nullptr) {
+      LOG(ERROR) << __func__
+                 << " Shouldn't be called without an active device.";
+      return;
+    }
+
+    /* All Ases should aim to achieve target state */
+    SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+    PrepareAndSendRelease(leAudioDevice);
+  }
+
+  void ProcessGattNotifEvent(uint8_t* value, uint16_t len, struct ase* ase,
+                             LeAudioDevice* leAudioDevice,
+                             LeAudioDeviceGroup* group) override {
+    struct le_audio::client_parser::ascs::ase_rsp_hdr arh;
+
+    ParseAseStatusHeader(arh, len, value);
+
+    LOG(INFO) << __func__ << " " << leAudioDevice->address_
+              << ", ASE id: " << +ase->id << " state changed " << ase->state
+              << " -> " << AseState(arh.state);
+
+    switch (static_cast<AseState>(arh.state)) {
+      case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
+        AseStateMachineProcessIdle(arh, ase, group, leAudioDevice);
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
+        AseStateMachineProcessCodecConfigured(
+            arh, ase, value + le_audio::client_parser::ascs::kAseRspHdrMinLen,
+            len - le_audio::client_parser::ascs::kAseRspHdrMinLen, group,
+            leAudioDevice);
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
+        AseStateMachineProcessQosConfigured(arh, ase, group, leAudioDevice);
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
+        AseStateMachineProcessEnabling(arh, ase, group, leAudioDevice);
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
+        AseStateMachineProcessStreaming(arh, ase, group, leAudioDevice);
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING:
+        AseStateMachineProcessDisabling(arh, ase, group, leAudioDevice);
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING:
+        AseStateMachineProcessReleasing(arh, ase, group, leAudioDevice);
+        break;
+      default:
+        LOG(ERROR) << __func__
+                   << ", Wrong AES status: " << static_cast<int>(arh.state);
+        StopStream(group);
+        break;
+    }
+  }
+
+  void ProcessHciNotifOnCigCreate(LeAudioDeviceGroup* group, uint8_t status,
+                                  uint8_t cig_id,
+                                  std::vector<uint16_t> conn_handles) override {
+    uint8_t i = 0;
+    LeAudioDevice* leAudioDevice;
+    struct le_audio::types::ase* ase;
+
+    /* TODO: What if not all cises will be configured ?
+     * conn_handle.size() != active ases in group
+     */
+
+    if (!group) {
+      LOG(ERROR) << __func__ << ", invalid cig";
+      return;
+    }
+
+    if (status != HCI_SUCCESS) {
+      LOG(ERROR) << __func__
+                 << ", failed to create CIG, reason: " << loghex(status);
+      StopStream(group);
+      return;
+    }
+
+    group->cig_created_ = true;
+
+    LOG(INFO) << __func__ << "Group id: " << +group->group_id_
+              << " conn_handle size " << +conn_handles.size();
+
+    /* Assign all connection handles to ases. CIS ID order is represented by the
+     * order of active ASEs in active leAudioDevices
+     */
+
+    leAudioDevice = group->GetFirstActiveDevice();
+    LOG_ASSERT(leAudioDevice)
+        << __func__ << " Shouldn't be called without an active device.";
+
+    /* Assign all connection handles to ases */
+    do {
+      ase = leAudioDevice->GetFirstActiveAseByDataPathState(
+          AudioStreamDataPathState::IDLE);
+      LOG_ASSERT(ase) << __func__
+                      << " shouldn't be called without an active ASE";
+      do {
+        auto ases_pair = leAudioDevice->GetAsesByCisId(ase->cis_id);
+
+        if (ases_pair.sink) {
+          ases_pair.sink->cis_conn_hdl = conn_handles[i];
+          ases_pair.sink->data_path_state =
+              AudioStreamDataPathState::CIS_ASSIGNED;
+        }
+        if (ases_pair.source) {
+          ases_pair.source->cis_conn_hdl = conn_handles[i];
+          ases_pair.source->data_path_state =
+              AudioStreamDataPathState::CIS_ASSIGNED;
+        }
+        i++;
+      } while ((ase = leAudioDevice->GetFirstActiveAseByDataPathState(
+                    AudioStreamDataPathState::IDLE)) &&
+               (i < conn_handles.size()));
+    } while ((leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) &&
+             (i < conn_handles.size()));
+
+    /* Last node configured, process group to codec configured state */
+    group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+
+    if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+      leAudioDevice = group->GetFirstActiveDevice();
+      LOG_ASSERT(leAudioDevice)
+          << __func__ << " Shouldn't be called without an active device.";
+      PrepareAndSendEnable(leAudioDevice);
+    } else {
+      LOG(ERROR) << __func__
+                 << ", invalid state transition, from: " << group->GetState()
+                 << ", to: " << group->GetTargetState();
+      StopStream(group);
+      return;
+    }
+  }
+
+  void ProcessHciNotifOnCigRemove(uint8_t status,
+                                  LeAudioDeviceGroup* group) override {
+    if (status) {
+      LOG(ERROR) << __func__
+                 << ", failed to remove cig, id: " << loghex(group->group_id_)
+                 << ", status: " << loghex(status);
+      return;
+    }
+
+    group->cig_created_ = false;
+
+    LeAudioDevice* leAudioDevice = group->GetFirstDevice();
+    if (!leAudioDevice) return;
+
+    do {
+      alarm_free(leAudioDevice->link_quality_timer);
+      leAudioDevice->link_quality_timer = nullptr;
+
+      for (auto& ase : leAudioDevice->ases_) {
+        ase.data_path_state = AudioStreamDataPathState::IDLE;
+        ase.cis_id = le_audio::kInvalidCisId;
+      }
+    } while ((leAudioDevice = group->GetNextDevice(leAudioDevice)));
+  }
+
+  void ProcessHciNotifSetupIsoDataPath(LeAudioDeviceGroup* group,
+                                       LeAudioDevice* leAudioDevice,
+                                       uint8_t status,
+                                       uint16_t conn_handle) override {
+    if (status) {
+      LOG(ERROR) << __func__ << ", failed to setup data path";
+      StopStream(group);
+
+      return;
+    }
+
+    /* Update state for the given cis.*/
+    auto ase = leAudioDevice->GetFirstActiveAseByDataPathState(
+        AudioStreamDataPathState::CIS_ESTABLISHED);
+
+    if (ase->cis_conn_hdl != conn_handle) {
+      LOG(ERROR) << __func__ << " Cannot find ase by handle " << +conn_handle;
+      return;
+    }
+
+    ase->data_path_state = AudioStreamDataPathState::DATA_PATH_ESTABLISHED;
+
+    if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+      LOG(WARNING) << __func__ << " Group " << group->group_id_
+                   << " is not targeting streaming state any more";
+      return;
+    }
+
+    ase = leAudioDevice->GetNextActiveAse(ase);
+    if (!ase) {
+      leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
+
+      if (!leAudioDevice) {
+        state_machine_callbacks_->StatusReportCb(group->group_id_,
+                                                 GroupStreamStatus::STREAMING);
+        return;
+      }
+
+      ase = leAudioDevice->GetFirstActiveAse();
+    }
+
+    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
+    if (ase->data_path_state == AudioStreamDataPathState::CIS_ESTABLISHED)
+      PrepareDataPath(ase);
+    else
+      LOG(ERROR) << __func__
+                 << " CIS got disconnected? handle: " << +ase->cis_conn_hdl;
+  }
+
+  void ProcessHciNotifRemoveIsoDataPath(LeAudioDeviceGroup* group,
+                                        LeAudioDevice* leAudioDevice,
+                                        uint8_t status,
+                                        uint16_t conn_hdl) override {
+    if (status != HCI_SUCCESS) {
+      LOG(ERROR) << __func__ << ", failed to remove ISO data path, reason: "
+                 << loghex(status);
+      StopStream(group);
+
+      return;
+    }
+
+    bool do_disconnect = false;
+
+    auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(conn_hdl);
+    if (ases_pair.sink && (ases_pair.sink->data_path_state ==
+                           AudioStreamDataPathState::DATA_PATH_ESTABLISHED)) {
+      ases_pair.sink->data_path_state =
+          AudioStreamDataPathState::CIS_DISCONNECTING;
+      do_disconnect = true;
+    }
+
+    if (ases_pair.source &&
+        ases_pair.source->data_path_state ==
+            AudioStreamDataPathState::DATA_PATH_ESTABLISHED) {
+      ases_pair.source->data_path_state =
+          AudioStreamDataPathState::CIS_DISCONNECTING;
+      do_disconnect = true;
+    }
+
+    if (do_disconnect)
+      IsoManager::GetInstance()->DisconnectCis(conn_hdl, HCI_ERR_PEER_USER);
+  }
+
+  void ProcessHciNotifIsoLinkQualityRead(
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
+      uint8_t conn_handle, uint32_t txUnackedPackets, uint32_t txFlushedPackets,
+      uint32_t txLastSubeventPackets, uint32_t retransmittedPackets,
+      uint32_t crcErrorPackets, uint32_t rxUnreceivedPackets,
+      uint32_t duplicatePackets) {
+    LOG(INFO) << "conn_handle: " << loghex(conn_handle)
+              << ", txUnackedPackets: " << loghex(txUnackedPackets)
+              << ", txFlushedPackets: " << loghex(txFlushedPackets)
+              << ", txLastSubeventPackets: " << loghex(txLastSubeventPackets)
+              << ", retransmittedPackets: " << loghex(retransmittedPackets)
+              << ", crcErrorPackets: " << loghex(crcErrorPackets)
+              << ", rxUnreceivedPackets: " << loghex(rxUnreceivedPackets)
+              << ", duplicatePackets: " << loghex(duplicatePackets);
+  }
+
+  void ProcessHciNotifAclDisconnected(LeAudioDeviceGroup* group,
+                                      LeAudioDevice* leAudioDevice) {
+    if (leAudioDevice->link_quality_timer) {
+      alarm_free(leAudioDevice->link_quality_timer);
+      leAudioDevice->link_quality_timer = nullptr;
+    }
+
+    leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
+
+    if (!group) {
+      LOG(ERROR) << __func__
+                 << " group is null for device: " << leAudioDevice->address_
+                 << " group_id: " << leAudioDevice->group_id_;
+      return;
+    }
+
+    auto* stream_conf = &group->stream_conf;
+    if (stream_conf->valid) {
+      stream_conf->sink_streams.erase(
+          std::remove_if(stream_conf->sink_streams.begin(),
+                         stream_conf->sink_streams.end(),
+                         [leAudioDevice](auto& pair) {
+                           auto ases =
+                               leAudioDevice->GetAsesByCisConnHdl(pair.first);
+                           return ases.sink;
+                         }),
+          stream_conf->sink_streams.end());
+
+      stream_conf->source_streams.erase(
+          std::remove_if(stream_conf->source_streams.begin(),
+                         stream_conf->source_streams.end(),
+                         [leAudioDevice](auto& pair) {
+                           auto ases =
+                               leAudioDevice->GetAsesByCisConnHdl(pair.first);
+                           return ases.source;
+                         }),
+          stream_conf->source_streams.end());
+
+      if (stream_conf->sink_streams.empty() &&
+          stream_conf->source_streams.empty()) {
+        LOG(INFO) << __func__ << " stream stopped ";
+        stream_conf->valid = false;
+      }
+    }
+
+    /* mark ASEs as not used. */
+    leAudioDevice->DeactivateAllAses();
+
+    DLOG(INFO) << __func__ << " device: " << leAudioDevice->address_
+               << " group connected: " << group->IsAnyDeviceConnected()
+               << " all active ase disconnected: "
+               << group->HaveAllActiveDevicesCisDisc();
+
+    /* Group has changed. Lets update available contexts */
+    group->UpdateActiveContextsMap();
+
+    /* ACL of one of the device has been dropped.
+     * If there is active CIS, do nothing here. Just update the active contexts
+     * table
+     */
+    if (group->IsAnyDeviceConnected() &&
+        !group->HaveAllActiveDevicesCisDisc()) {
+      return;
+    }
+
+    /* Group is not connected and all the CISes are down.
+     * If group is in Idle there is nothing to do here */
+    if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
+      LOG(INFO) << __func__ << " group: " << group->group_id_ << " is in IDLE";
+      return;
+    }
+
+    /* Clean states and destroy HCI group */
+    group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+    group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+    if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
+    state_machine_callbacks_->StatusReportCb(group->group_id_,
+                                             GroupStreamStatus::IDLE);
+
+    if (!group->cig_created_) return;
+
+    IsoManager::GetInstance()->RemoveCig(group->group_id_);
+  }
+
+  void ProcessHciNotifCisEstablished(
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
+      const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event)
+      override {
+    std::vector<uint8_t> value;
+
+    auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl);
+
+    if (event->status) {
+      if (ases_pair.sink)
+        ases_pair.sink->data_path_state =
+            AudioStreamDataPathState::CIS_ASSIGNED;
+      if (ases_pair.source)
+        ases_pair.source->data_path_state =
+            AudioStreamDataPathState::CIS_ASSIGNED;
+
+      /* CIS establishment failed. Remove CIG if no other CIS is already created
+       * or pending. If CIS is established, this will be handled in disconnected
+       * complete event
+       */
+      if (group->HaveAllActiveDevicesCisDisc()) {
+        IsoManager::GetInstance()->RemoveCig(group->group_id_);
+      }
+
+      LOG(ERROR) << __func__
+                 << ", failed to create CIS, status: " << loghex(event->status);
+
+      StopStream(group);
+      return;
+    }
+
+    if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+      LOG(ERROR) << __func__ << ", Unintended CIS establishement event came";
+      StopStream(group);
+      return;
+    }
+
+    if (ases_pair.sink)
+      ases_pair.sink->data_path_state =
+          AudioStreamDataPathState::CIS_ESTABLISHED;
+    if (ases_pair.source)
+      ases_pair.source->data_path_state =
+          AudioStreamDataPathState::CIS_ESTABLISHED;
+
+    leAudioDevice->link_quality_timer =
+        alarm_new_periodic("le_audio_cis_link_quality");
+    leAudioDevice->link_quality_timer_data = event->cis_conn_hdl;
+    alarm_set_on_mloop(leAudioDevice->link_quality_timer,
+                       linkQualityCheckInterval, link_quality_cb,
+                       &leAudioDevice->link_quality_timer_data);
+
+    if (!leAudioDevice->HaveAllActiveAsesCisEst()) {
+      /* More cis established event has to come */
+      return;
+    }
+
+    std::vector<uint8_t> ids;
+
+    /* All CISes created. Send start ready for source ASE before we can go
+     * to streaming state.
+     */
+    struct ase* ase = leAudioDevice->GetFirstActiveAse();
+    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
+    do {
+      if (ase->direction == le_audio::types::kLeAudioDirectionSource)
+        ids.push_back(ase->id);
+    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+
+    if (ids.size() > 0) {
+      le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady(
+          ids, value);
+
+      BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
+                                        leAudioDevice->ctp_hdls_.val_hdl, value,
+                                        GATT_WRITE_NO_RSP, NULL, NULL);
+
+      return;
+    }
+
+    /* Cis establishment may came after setting group state to streaming, e.g.
+     * for autonomous scenario when ase is sink */
+    if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
+        group->IsGroupStreamReady()) {
+      /* No more transition for group */
+      alarm_cancel(watchdog_);
+      PrepareDataPath(group);
+    }
+  }
+
+  void ProcessHciNotifCisDisconnected(
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
+      const bluetooth::hci::iso_manager::cis_disconnected_evt* event) override {
+    /* Reset the disconnected CIS states */
+
+    alarm_free(leAudioDevice->link_quality_timer);
+    leAudioDevice->link_quality_timer = nullptr;
+    auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl);
+    if (ases_pair.sink) {
+      ases_pair.sink->data_path_state = AudioStreamDataPathState::CIS_ASSIGNED;
+    }
+    if (ases_pair.source) {
+      ases_pair.source->data_path_state =
+          AudioStreamDataPathState::CIS_ASSIGNED;
+    }
+
+    /* Invalidate stream configuration if needed */
+    auto* stream_conf = &group->stream_conf;
+    if (stream_conf->valid) {
+      if (ases_pair.sink) {
+        stream_conf->sink_streams.erase(
+            std::remove_if(stream_conf->sink_streams.begin(),
+                           stream_conf->sink_streams.end(),
+                           [&event](auto& pair) {
+                             return event->cis_conn_hdl == pair.first;
+                           }),
+            stream_conf->sink_streams.end());
+      }
+
+      if (ases_pair.source) {
+        stream_conf->source_streams.erase(
+            std::remove_if(stream_conf->source_streams.begin(),
+                           stream_conf->source_streams.end(),
+                           [&event](auto& pair) {
+                             return event->cis_conn_hdl == pair.first;
+                           }),
+            stream_conf->source_streams.end());
+      }
+
+      if (stream_conf->sink_streams.empty() &&
+          stream_conf->source_streams.empty()) {
+        LOG(INFO) << __func__ << " stream stopped ";
+        stream_conf->valid = false;
+      }
+    }
+
+    auto target_state = group->GetTargetState();
+    switch (target_state) {
+      case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
+        /* Something wrong happen when streaming or when creating stream.
+         * If there is other device connected and streaming, just leave it as it
+         * is, otherwise stop the stream.
+         */
+        if (!group->HaveAllActiveDevicesCisDisc()) {
+          /* TODO: Reconfigure LC3 codec from here or maybe other place?*/
+          return;
+        }
+
+        /*If there is no more ase to stream. Suspend audio and clear state
+         * machine -> go to Idle */
+        state_machine_callbacks_->StatusReportCb(group->group_id_,
+                                                 GroupStreamStatus::SUSPENDED);
+        StopStream(group);
+        return;
+
+      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
+        /* Intentional group disconnect has finished, but the last CIS in the
+         * event came after the ASE notification.
+         * If group is already suspended and all CIS are disconnected, we can
+         * report SUSPENDED state.
+         */
+        if ((group->GetState() ==
+             AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) &&
+            group->HaveAllActiveDevicesCisDisc()) {
+          /* No more transition for group */
+          alarm_cancel(watchdog_);
+
+          state_machine_callbacks_->StatusReportCb(
+              group->group_id_, GroupStreamStatus::SUSPENDED);
+          return;
+        }
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
+      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
+        /* Those two are used when closing the stream and CIS disconnection is
+         * expected */
+        if (group->HaveAllActiveDevicesCisDisc()) {
+          IsoManager::GetInstance()->RemoveCig(group->group_id_);
+          return;
+        }
+
+        break;
+      default:
+        break;
+    }
+
+    /* We should send Receiver Stop Ready when acting as a source */
+    if (ases_pair.source &&
+        ases_pair.source->state == AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING) {
+      std::vector<uint8_t> ids = {ases_pair.source->id};
+      std::vector<uint8_t> value;
+
+      le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStopReady(ids,
+                                                                         value);
+      BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
+                                        leAudioDevice->ctp_hdls_.val_hdl, value,
+                                        GATT_WRITE_NO_RSP, NULL, NULL);
+    }
+
+    /* Tear down CIS's data paths within the group */
+    struct ase* ase = leAudioDevice->GetFirstActiveAseByDataPathState(
+        AudioStreamDataPathState::DATA_PATH_ESTABLISHED);
+    if (!ase) {
+      leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
+      /* No more ASEs to disconnect their CISes */
+      if (!leAudioDevice) return;
+
+      ase = leAudioDevice->GetFirstActiveAse();
+    }
+
+    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
+    ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
+
+    if (ase->data_path_state ==
+        AudioStreamDataPathState::DATA_PATH_ESTABLISHED) {
+      IsoManager::GetInstance()->RemoveIsoDataPath(
+          ase->cis_conn_hdl,
+          (ases_pair.sink
+               ? bluetooth::hci::iso_manager::kIsoDataPathDirectionOut
+               : 0x00) |
+              (ases_pair.source
+                   ? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn
+                   : 0x00));
+    }
+  }
+
+ private:
+  static constexpr uint64_t kStateTransitionTimeoutMs = 5000;
+  static constexpr char kStateTransitionTimeoutMsProp[] =
+      "persist.bluetooth.leaudio.device.set.state.timeoutms";
+  Callbacks* state_machine_callbacks_;
+  alarm_t* watchdog_;
+
+  /* This callback is called on timeout during transition to target state */
+  void OnStateTransitionTimeout(int group_id) {
+    state_machine_callbacks_->OnStateTransitionTimeout(group_id);
+  }
+
+  void SetTargetState(LeAudioDeviceGroup* group, AseState state) {
+    group->SetTargetState(state);
+
+    /* Group should tie in time to get requested status */
+    uint64_t timeoutMs = kStateTransitionTimeoutMs;
+    timeoutMs =
+        osi_property_get_int32(kStateTransitionTimeoutMsProp, timeoutMs);
+
+    if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
+
+    alarm_set_on_mloop(
+        watchdog_, timeoutMs,
+        [](void* data) {
+          if (instance) instance->OnStateTransitionTimeout(PTR_TO_INT(data));
+        },
+        INT_TO_PTR(group->group_id_));
+  }
+
+  void CigCreate(LeAudioDeviceGroup* group) {
+    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
+    struct ase* ase;
+    uint32_t sdu_interval_mtos, sdu_interval_stom;
+    uint8_t packing, framing, sca;
+    std::vector<EXT_CIS_CFG> cis_cfgs;
+
+    if (group->cig_created_) {
+      LOG(ERROR) << __func__ << " group id " << group->group_id_
+                 << " is already created  in the controller. ";
+      return;
+    }
+
+    if (!leAudioDevice) {
+      LOG(ERROR) << __func__ << ", no active devices in group";
+
+      return;
+    }
+
+    sdu_interval_mtos =
+        group->GetSduInterval(le_audio::types::kLeAudioDirectionSink);
+    sdu_interval_stom =
+        group->GetSduInterval(le_audio::types::kLeAudioDirectionSource);
+    sca = group->GetSCA();
+    packing = group->GetPacking();
+    framing = group->GetFraming();
+    uint16_t max_trans_lat_mtos = group->GetMaxTransportLatencyMtos();
+    uint16_t max_trans_lat_stom = group->GetMaxTransportLatencyStom();
+
+    do {
+      ase = leAudioDevice->GetFirstActiveAse();
+      LOG_ASSERT(ase) << __func__
+                      << " shouldn't be called without an active ASE";
+      do {
+        auto& cis = ase->cis_id;
+        auto iter =
+            find_if(cis_cfgs.begin(), cis_cfgs.end(),
+                    [&cis](auto const& cfg) { return cis == cfg.cis_id; });
+
+        /* CIS configuration already on list */
+        if (iter != cis_cfgs.end()) continue;
+
+        auto ases_pair = leAudioDevice->GetAsesByCisId(cis);
+        EXT_CIS_CFG cis_cfg;
+        cis_cfg.cis_id = ase->cis_id;
+        cis_cfg.phy_mtos =
+            group->GetPhyBitmask(le_audio::types::kLeAudioDirectionSink);
+        cis_cfg.phy_stom =
+            group->GetPhyBitmask(le_audio::types::kLeAudioDirectionSource);
+
+        if (ases_pair.sink) {
+          /* TODO: config should be previously adopted */
+          cis_cfg.max_sdu_size_mtos = ase->max_sdu_size;
+          cis_cfg.rtn_mtos = ase->retrans_nb;
+        }
+        if (ases_pair.source) {
+          /* TODO: config should be previously adopted */
+          cis_cfg.max_sdu_size_stom = ase->max_sdu_size;
+          cis_cfg.rtn_stom = ase->retrans_nb;
+        }
+
+        cis_cfgs.push_back(cis_cfg);
+      } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+    } while ((leAudioDevice = group->GetNextActiveDevice(leAudioDevice)));
+
+    bluetooth::hci::iso_manager::cig_create_params param = {
+        .sdu_itv_mtos = sdu_interval_mtos,
+        .sdu_itv_stom = sdu_interval_stom,
+        .sca = sca,
+        .packing = packing,
+        .framing = framing,
+        .max_trans_lat_stom = max_trans_lat_stom,
+        .max_trans_lat_mtos = max_trans_lat_mtos,
+        .cis_cfgs = std::move(cis_cfgs),
+    };
+    IsoManager::GetInstance()->CreateCig(group->group_id_, std::move(param));
+  }
+
+  static void CisCreateForDevice(LeAudioDevice* leAudioDevice) {
+    std::vector<EXT_CIS_CREATE_CFG> conn_pairs;
+    struct ase* ase = leAudioDevice->GetFirstActiveAse();
+    do {
+      /* First in ase pair is Sink, second Source */
+      auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
+
+      /* Already in pending state - bi-directional CIS */
+      if (ase->data_path_state == AudioStreamDataPathState::CIS_PENDING)
+        continue;
+
+      if (ases_pair.sink)
+        ases_pair.sink->data_path_state = AudioStreamDataPathState::CIS_PENDING;
+      if (ases_pair.source)
+        ases_pair.source->data_path_state =
+            AudioStreamDataPathState::CIS_PENDING;
+
+      uint16_t acl_handle =
+          BTM_GetHCIConnHandle(leAudioDevice->address_, BT_TRANSPORT_LE);
+      conn_pairs.push_back({.cis_conn_handle = ase->cis_conn_hdl,
+                            .acl_conn_handle = acl_handle});
+      LOG(INFO) << __func__ << " cis handle: " << +ase->cis_conn_hdl
+                << " acl handle : " << loghex(+acl_handle);
+
+    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+
+    IsoManager::GetInstance()->EstablishCis(
+        {.conn_pairs = std::move(conn_pairs)});
+  }
+
+  static void CisCreate(LeAudioDeviceGroup* group) {
+    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
+    struct ase* ase;
+    std::vector<EXT_CIS_CREATE_CFG> conn_pairs;
+
+    LOG_ASSERT(leAudioDevice)
+        << __func__ << " Shouldn't be called without an active device.";
+
+    do {
+      ase = leAudioDevice->GetFirstActiveAse();
+      LOG_ASSERT(ase) << __func__
+                      << " shouldn't be called without an active ASE";
+      do {
+        /* First is ase pair is Sink, second Source */
+        auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
+
+        /* Already in pending state - bi-directional CIS */
+        if (ase->data_path_state == AudioStreamDataPathState::CIS_PENDING)
+          continue;
+
+        if (ases_pair.sink)
+          ases_pair.sink->data_path_state =
+              AudioStreamDataPathState::CIS_PENDING;
+        if (ases_pair.source)
+          ases_pair.source->data_path_state =
+              AudioStreamDataPathState::CIS_PENDING;
+
+        uint16_t acl_handle =
+            BTM_GetHCIConnHandle(leAudioDevice->address_, BT_TRANSPORT_LE);
+        conn_pairs.push_back({.cis_conn_handle = ase->cis_conn_hdl,
+                              .acl_conn_handle = acl_handle});
+        DLOG(INFO) << __func__ << " cis handle: " << +ase->cis_conn_hdl
+                   << " acl handle : " << loghex(+acl_handle);
+
+      } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+    } while ((leAudioDevice = group->GetNextActiveDevice(leAudioDevice)));
+
+    IsoManager::GetInstance()->EstablishCis(
+        {.conn_pairs = std::move(conn_pairs)});
+  }
+
+  static void PrepareDataPath(const struct ase* ase) {
+    /* TODO Handle HW offloading as we handle here only HCI for now */
+    bluetooth::hci::iso_manager::iso_data_path_params param = {
+        .data_path_dir =
+            ase->direction == le_audio::types::kLeAudioDirectionSink
+                ? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn
+                : bluetooth::hci::iso_manager::kIsoDataPathDirectionOut,
+        .data_path_id = bluetooth::hci::iso_manager::kIsoDataPathHci,
+        .codec_id_format = ase->codec_id.coding_format,
+        .codec_id_company = ase->codec_id.vendor_company_id,
+        .codec_id_vendor = ase->codec_id.vendor_codec_id,
+        .controller_delay = 0x00000000,
+        .codec_conf = std::vector<uint8_t>(),
+    };
+    IsoManager::GetInstance()->SetupIsoDataPath(ase->cis_conn_hdl,
+                                                std::move(param));
+  }
+
+  static inline void PrepareDataPath(LeAudioDeviceGroup* group) {
+    auto* leAudioDevice = group->GetFirstActiveDevice();
+    LOG_ASSERT(leAudioDevice)
+        << __func__ << " Shouldn't be called without an active device.";
+
+    auto* ase = leAudioDevice->GetFirstActiveAse();
+    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
+    PrepareDataPath(ase);
+  }
+
+  static void ReleaseDataPath(LeAudioDeviceGroup* group) {
+    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
+    LOG_ASSERT(leAudioDevice)
+        << __func__ << " Shouldn't be called without an active device.";
+
+    auto ase = leAudioDevice->GetFirstActiveAseByDataPathState(
+        AudioStreamDataPathState::DATA_PATH_ESTABLISHED);
+    LOG_ASSERT(ase) << __func__
+                    << " Shouldn't be called without an active ASE.";
+
+    auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
+
+    IsoManager::GetInstance()->RemoveIsoDataPath(
+        ase->cis_conn_hdl,
+        (ases_pair.sink ? bluetooth::hci::iso_manager::kIsoDataPathDirectionOut
+                        : 0x00) |
+            (ases_pair.source
+                 ? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn
+                 : 0x00));
+  }
+
+  void AseStateMachineProcessIdle(
+      struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
+    switch (ase->state) {
+      case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
+        if (ase->id == 0x00) {
+          /* Initial state of Ase - update id */
+          LOG(INFO) << __func__
+                    << ", discovered ase id: " << static_cast<int>(arh.id);
+          ase->id = arh.id;
+        }
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING: {
+        LeAudioDevice* leAudioDeviceNext;
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
+        ase->active = false;
+
+        if (!leAudioDevice->HaveAllActiveAsesSameState(
+                AseState::BTA_LE_AUDIO_ASE_STATE_IDLE)) {
+          /* More ASEs notification from this device has to come for this group
+           */
+          return;
+        }
+
+        leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice);
+
+        /* Configure ASEs for next device in group */
+        if (leAudioDeviceNext) {
+          PrepareAndSendRelease(leAudioDeviceNext);
+        } else {
+          /* Last node is in releasing state*/
+          if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
+
+          group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+
+          state_machine_callbacks_->StatusReportCb(group->group_id_,
+                                                   GroupStreamStatus::IDLE);
+        }
+        break;
+      }
+      default:
+        LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                   << static_cast<int>(ase->state) << ", to: "
+                   << static_cast<int>(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+        StopStream(group);
+        break;
+    }
+  }
+
+  void StartConfigQoSForTheGroup(LeAudioDeviceGroup* group) {
+    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
+    if (!leAudioDevice) {
+      LOG(ERROR) << __func__ << ", no active devices in group";
+      StopStream(group);
+      return;
+    }
+
+    PrepareAndSendConfigQos(group, leAudioDevice);
+  }
+
+  void PrepareAndSendCodecConfigure(LeAudioDeviceGroup* group,
+                                    LeAudioDevice* leAudioDevice) {
+    struct le_audio::client_parser::ascs::ctp_codec_conf conf;
+    std::vector<struct le_audio::client_parser::ascs::ctp_codec_conf> confs;
+    struct ase* ase;
+
+    ase = leAudioDevice->GetFirstActiveAse();
+    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
+    do {
+      conf.ase_id = ase->id;
+      conf.target_latency = group->GetTargetLatency();
+      conf.target_phy = group->GetTargetPhy(ase->direction);
+      conf.codec_id = ase->codec_id;
+      conf.codec_config = ase->codec_config;
+      confs.push_back(conf);
+    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+
+    std::vector<uint8_t> value;
+    le_audio::client_parser::ascs::PrepareAseCtpCodecConfig(confs, value);
+    BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
+                                      leAudioDevice->ctp_hdls_.val_hdl, value,
+                                      GATT_WRITE_NO_RSP, NULL, NULL);
+  }
+
+  void AseStateMachineProcessCodecConfigured(
+      struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
+      uint8_t* data, uint16_t len, LeAudioDeviceGroup* group,
+      LeAudioDevice* leAudioDevice) {
+    if (!group) {
+      LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
+
+      return;
+    }
+
+    /* ase contain current ASE state. New state is in "arh" */
+    switch (ase->state) {
+      case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: {
+        if (ase->id == 0x00) {
+          /* Initial state of Ase - update id */
+          LOG(INFO) << __func__
+                    << ", discovered ase id: " << static_cast<int>(arh.id);
+          ase->id = arh.id;
+        }
+
+        LeAudioDevice* leAudioDeviceNext;
+
+        struct le_audio::client_parser::ascs::ase_codec_configured_state_params
+            rsp;
+
+        /* Cache codec configured status values for further
+         * configuration/reconfiguration
+         */
+        if (!ParseAseStatusCodecConfiguredStateParams(rsp, len, data)) {
+          StopStream(group);
+          return;
+        }
+        ase->framing = rsp.framing;
+        ase->preferred_phy = rsp.preferred_phy;
+        ase->max_transport_latency = rsp.max_transport_latency;
+        ase->pres_delay_min = rsp.pres_delay_min;
+        ase->pres_delay_max = rsp.pres_delay_max;
+        ase->preferred_pres_delay_min = rsp.preferred_pres_delay_min;
+        ase->preferred_pres_delay_max = rsp.preferred_pres_delay_max;
+        ase->retrans_nb = rsp.preferred_retrans_nb;
+
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED;
+
+        if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
+          /* This is autonomus change of the remote device */
+          LOG(INFO) << __func__ << " Autonomus change. Just store it. ";
+          return;
+        }
+
+        if (leAudioDevice->HaveAnyUnconfiguredAses()) {
+          /* More ASEs notification from this device has to come for this group
+           */
+          return;
+        }
+
+        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+          /* We are here because of the reconnection of the single device. */
+          PrepareAndSendConfigQos(group, leAudioDevice);
+          return;
+        }
+
+        leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice);
+
+        /* Configure ASEs for next device in group */
+        if (leAudioDeviceNext) {
+          PrepareAndSendCodecConfigure(group, leAudioDeviceNext);
+        } else {
+          /* Last node configured, process group to codec configured state */
+          group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+          if (group->GetTargetState() ==
+              AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+            StartConfigQoSForTheGroup(group);
+            return;
+          } else {
+            LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                       << group->GetState()
+                       << ", to: " << group->GetTargetState();
+            StopStream(group);
+            return;
+          }
+        }
+
+        break;
+      }
+      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: {
+        /* Received Configured in Configured state. This could be done
+         * autonomously because of the reconfiguration done by us
+         */
+
+        struct le_audio::client_parser::ascs::ase_codec_configured_state_params
+            rsp;
+
+        /* Cache codec configured status values for further
+         * configuration/reconfiguration
+         */
+        if (!ParseAseStatusCodecConfiguredStateParams(rsp, len, data)) {
+          StopStream(group);
+          return;
+        }
+
+        ase->framing = rsp.framing;
+        ase->preferred_phy = rsp.preferred_phy;
+        ase->max_transport_latency = rsp.max_transport_latency;
+        ase->pres_delay_min = rsp.pres_delay_min;
+        ase->pres_delay_max = rsp.pres_delay_max;
+        ase->preferred_pres_delay_min = rsp.preferred_pres_delay_min;
+        ase->preferred_pres_delay_max = rsp.preferred_pres_delay_max;
+        ase->retrans_nb = rsp.preferred_retrans_nb;
+
+        /* This may be a notification from a re-configured ASE */
+        ase->reconfigure = false;
+
+        if (leAudioDevice->HaveAnyUnconfiguredAses()) {
+          /* Waiting for others to be reconfigured */
+          return;
+        }
+
+        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+          /* We are here because of the reconnection of the single device. */
+          PrepareAndSendConfigQos(group, leAudioDevice);
+          return;
+        }
+
+        LeAudioDevice* leAudioDeviceNext =
+            group->GetNextActiveDevice(leAudioDevice);
+
+        /* Configure ASEs for next device in group */
+        if (leAudioDeviceNext) {
+          PrepareAndSendCodecConfigure(group, leAudioDeviceNext);
+        } else {
+          /* Last node configured, process group to codec configured state */
+          group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+          if (group->GetTargetState() ==
+              AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+            StartConfigQoSForTheGroup(group);
+            return;
+          } else {
+            LOG(ERROR) << __func__
+                       << ", Autonomouse change ?: " << group->GetState()
+                       << ", to: " << group->GetTargetState();
+          }
+        }
+
+        break;
+      }
+      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
+        /* TODO: Config Codec */
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING:
+        LeAudioDevice* leAudioDeviceNext;
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED;
+        ase->active = false;
+
+        if (!leAudioDevice->HaveAllActiveAsesSameState(
+                AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)) {
+          /* More ASEs notification from this device has to come for this group
+           */
+          return;
+        }
+
+        leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice);
+
+        /* Configure ASEs for next device in group */
+        if (leAudioDeviceNext) {
+          PrepareAndSendRelease(leAudioDeviceNext);
+        } else {
+          /* Last node is in releasing state*/
+          if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
+
+          group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+          /* Remote device has cache and keep staying in configured state after
+           * release. Therefore, we assume this is a target state requested by
+           * remote device.
+           */
+          group->SetTargetState(group->GetState());
+          state_machine_callbacks_->StatusReportCb(group->group_id_,
+                                                   GroupStreamStatus::IDLE);
+        }
+        break;
+      default:
+        LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                   << static_cast<int>(ase->state) << ", to: "
+                   << static_cast<int>(
+                          AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+        StopStream(group);
+        break;
+    }
+  }
+
+  void AseStateMachineProcessQosConfigured(
+      struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
+    if (!group) {
+      LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
+
+      return;
+    }
+
+    switch (ase->state) {
+      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: {
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED;
+
+        if (!leAudioDevice->HaveAllActiveAsesSameState(
+                AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)) {
+          /* More ASEs notification from this device has to come for this group
+           */
+          return;
+        }
+
+        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+          /* We are here because of the reconnection of the single device. */
+          PrepareAndSendEnable(leAudioDevice);
+          return;
+        }
+
+        LeAudioDevice* leAudioDeviceNext =
+            group->GetNextActiveDevice(leAudioDevice);
+
+        /* Configure ASEs qos for next device in group */
+        if (leAudioDeviceNext) {
+          PrepareAndSendConfigQos(group, leAudioDeviceNext);
+        } else {
+          CigCreate(group);
+        }
+
+        break;
+      }
+      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
+        /* TODO: Config Codec error/Config Qos/Config QoS error/Enable error */
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
+        if (ase->direction == le_audio::types::kLeAudioDirectionSource) {
+          /* Source ASE cannot go from Streaming to QoS Configured state */
+          LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                     << static_cast<int>(ase->state) << ", to: "
+                     << static_cast<int>(
+                            AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+          StopStream(group);
+          return;
+        }
+
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED;
+
+        /* Process the Disable Transition of the rest of group members if no
+         * more ASE notifications has to come from this device. */
+        if (leAudioDevice->IsReadyToSuspendStream())
+          ProcessGroupDisable(group, leAudioDevice);
+
+        break;
+
+      case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING: {
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED;
+
+        /* More ASEs notification from this device has to come for this group */
+        if (!group->HaveAllActiveDevicesAsesTheSameState(
+                AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED))
+          return;
+
+        group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+
+        if (!group->HaveAllActiveDevicesCisDisc()) return;
+
+        if (group->GetTargetState() ==
+            AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
+          /* No more transition for group */
+          alarm_cancel(watchdog_);
+
+          state_machine_callbacks_->StatusReportCb(
+              group->group_id_, GroupStreamStatus::SUSPENDED);
+        } else {
+          LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                     << group->GetState()
+                     << ", to: " << group->GetTargetState();
+          StopStream(group);
+          return;
+        }
+        break;
+      }
+      default:
+        LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                   << static_cast<int>(ase->state) << ", to: "
+                   << static_cast<int>(
+                          AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+        StopStream(group);
+        break;
+    }
+  }
+
+  void PrepareAndSendEnable(LeAudioDevice* leAudioDevice) {
+    struct le_audio::client_parser::ascs::ctp_enable conf;
+    std::vector<struct le_audio::client_parser::ascs::ctp_enable> confs;
+    std::vector<uint8_t> value;
+    struct ase* ase;
+
+    ase = leAudioDevice->GetFirstActiveAse();
+    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
+    do {
+      conf.ase_id = ase->id;
+      conf.metadata = ase->metadata;
+      confs.push_back(conf);
+    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+
+    le_audio::client_parser::ascs::PrepareAseCtpEnable(confs, value);
+
+    BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
+                                      leAudioDevice->ctp_hdls_.val_hdl, value,
+                                      GATT_WRITE_NO_RSP, NULL, NULL);
+  }
+
+  void PrepareAndSendDisable(LeAudioDevice* leAudioDevice) {
+    ase* ase = leAudioDevice->GetFirstActiveAse();
+    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
+
+    std::vector<uint8_t> ids;
+    do {
+      ids.push_back(ase->id);
+    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+
+    std::vector<uint8_t> value;
+    le_audio::client_parser::ascs::PrepareAseCtpDisable(ids, value);
+
+    BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
+                                      leAudioDevice->ctp_hdls_.val_hdl, value,
+                                      GATT_WRITE_NO_RSP, NULL, NULL);
+  }
+
+  void PrepareAndSendRelease(LeAudioDevice* leAudioDevice) {
+    ase* ase = leAudioDevice->GetFirstActiveAse();
+    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
+
+    std::vector<uint8_t> ids;
+    do {
+      ids.push_back(ase->id);
+    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+
+    std::vector<uint8_t> value;
+    le_audio::client_parser::ascs::PrepareAseCtpRelease(ids, value);
+
+    BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
+                                      leAudioDevice->ctp_hdls_.val_hdl, value,
+                                      GATT_WRITE_NO_RSP, NULL, NULL);
+  }
+
+  void PrepareAndSendConfigQos(LeAudioDeviceGroup* group,
+                               LeAudioDevice* leAudioDevice) {
+    std::vector<struct le_audio::client_parser::ascs::ctp_qos_conf> confs;
+
+    for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
+         ase = leAudioDevice->GetNextActiveAse(ase)) {
+      /* Get completive (to be bi-directional CIS) CIS ID for ASE */
+      uint8_t cis_id = leAudioDevice->GetMatchingBidirectionCisId(ase);
+      if (cis_id == le_audio::kInvalidCisId) {
+        /* Get next free CIS ID for group */
+        cis_id = group->GetFirstFreeCisId();
+        if (cis_id == le_audio::kInvalidCisId) {
+          LOG(ERROR) << __func__ << ", failed to get free CIS ID";
+          StopStream(group);
+          return;
+        }
+      }
+
+      ase->cis_id = cis_id;
+
+      /* TODO: Configure first ASE qos according to context type */
+      struct le_audio::client_parser::ascs::ctp_qos_conf conf;
+      conf.ase_id = ase->id;
+      conf.cig = group->group_id_;
+      conf.cis = ase->cis_id;
+      conf.framing = group->GetFraming();
+      conf.phy = group->GetPhyBitmask(ase->direction);
+      conf.max_sdu = ase->max_sdu_size;
+      conf.retrans_nb = ase->retrans_nb;
+      if (!group->GetPresentationDelay(&conf.pres_delay, ase->direction)) {
+        LOG(ERROR) << __func__ << ", inconsistent presentation delay for group";
+        StopStream(group);
+        return;
+      }
+
+      conf.sdu_interval = group->GetSduInterval(ase->direction);
+      if (!conf.sdu_interval) {
+        LOG(ERROR) << __func__ << ", unsupported SDU interval for group";
+        StopStream(group);
+        return;
+      }
+
+      if (ase->direction == le_audio::types::kLeAudioDirectionSink) {
+        conf.max_transport_latency = group->GetMaxTransportLatencyMtos();
+      } else {
+        conf.max_transport_latency = group->GetMaxTransportLatencyStom();
+      }
+      confs.push_back(conf);
+    }
+
+    LOG_ASSERT(confs.size() > 0)
+        << __func__ << " shouldn't be called without an active ASE";
+
+    std::vector<uint8_t> value;
+    le_audio::client_parser::ascs::PrepareAseCtpConfigQos(confs, value);
+
+    BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
+                                      leAudioDevice->ctp_hdls_.val_hdl, value,
+                                      GATT_WRITE_NO_RSP, NULL, NULL);
+  }
+
+  void AseStateMachineProcessEnabling(
+      struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
+    if (!group) {
+      LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
+      return;
+    }
+
+    if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+      /* We are here because of the reconnection of the single device. */
+      CisCreateForDevice(leAudioDevice);
+      return;
+    }
+
+    switch (ase->state) {
+      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING;
+
+        if (leAudioDevice->IsReadyToCreateStream())
+          ProcessGroupEnable(group, leAudioDevice);
+
+        break;
+
+      case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
+        /* Enable/Switch Content */
+        break;
+      default:
+        LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                   << static_cast<int>(ase->state) << ", to: "
+                   << static_cast<int>(
+                          AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING);
+        StopStream(group);
+        break;
+    }
+  }
+
+  void AseStateMachineProcessStreaming(
+      struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
+    if (!group) {
+      LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
+
+      return;
+    }
+
+    switch (ase->state) {
+      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
+        /* As per ASCS 1.0 :
+         * If a CIS has been established and the server is acting as Audio Sink
+         * for the ASE, and if the server is ready to receive audio data
+         * transmitted by the client, the server may autonomously initiate the
+         * Receiver Start Ready, as defined in Section 5.4, without first
+         * sending a notification of the ASE characteristic value in the
+         * Enabling state.
+         */
+        if (ase->direction != le_audio::types::kLeAudioDirectionSink) {
+          LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                     << static_cast<int>(ase->state) << ", to: "
+                     << static_cast<int>(
+                            AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+          StopStream(group);
+          return;
+        }
+
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING;
+
+        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+          /* We are here because of the reconnection of the single device. */
+          CisCreateForDevice(leAudioDevice);
+          return;
+        }
+
+        if (leAudioDevice->IsReadyToCreateStream())
+          ProcessGroupEnable(group, leAudioDevice);
+
+        break;
+
+      case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING: {
+        std::vector<uint8_t> value;
+
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING;
+
+        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+          /* We are here because of the reconnection of the single device. */
+          auto* stream_conf = &group->stream_conf;
+          if (ase->direction == le_audio::types::kLeAudioDirectionSource) {
+            stream_conf->source_streams.emplace_back(std::make_pair(
+                ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation));
+          } else {
+            stream_conf->sink_streams.emplace_back(std::make_pair(
+                ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation));
+          }
+        }
+
+        if (!group->HaveAllActiveDevicesAsesTheSameState(
+                AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
+          /* More ASEs notification form this device has to come for this group
+           */
+
+          return;
+        }
+
+        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+          /* We are here because of the reconnection of the single device. */
+          return;
+        }
+
+        /* Last node is in streaming state */
+        group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+        /* Not all CISes establish evens came */
+        if (!group->IsGroupStreamReady()) return;
+
+        if (group->GetTargetState() ==
+            AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+          /* No more transition for group */
+          alarm_cancel(watchdog_);
+          PrepareDataPath(group);
+
+          return;
+        } else {
+          LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                     << group->GetState()
+                     << ", to: " << group->GetTargetState();
+          StopStream(group);
+          return;
+        }
+
+        break;
+      }
+      case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
+        /* TODO: Update metadata/Enable */
+        break;
+      default:
+        LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                   << static_cast<int>(ase->state) << ", to: "
+                   << static_cast<int>(
+                          AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+        StopStream(group);
+        break;
+    }
+  }
+
+  void AseStateMachineProcessDisabling(
+      struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
+    if (!group) {
+      LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
+
+      return;
+    }
+
+    if (ase->direction == le_audio::types::kLeAudioDirectionSink) {
+      /* Sink ASE state machine does not have Disabling state */
+      LOG(ERROR) << __func__
+                 << ", invalid state transition, from: " << group->GetState()
+                 << ", to: " << group->GetTargetState();
+      StopStream(group);
+      return;
+    }
+
+    switch (ase->state) {
+      case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
+        /* TODO: Disable */
+        break;
+      case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING;
+
+        /* Process the Disable Transition of the rest of group members if no
+         * more ASE notifications has to come from this device. */
+        if (leAudioDevice->IsReadyToSuspendStream())
+          ProcessGroupDisable(group, leAudioDevice);
+
+        break;
+
+      default:
+        LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                   << static_cast<int>(ase->state) << ", to: "
+                   << static_cast<int>(
+                          AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING);
+        StopStream(group);
+        break;
+    }
+  }
+
+  void AseStateMachineProcessReleasing(
+      struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
+    if (!group) {
+      LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
+
+      return;
+    }
+
+    switch (ase->state) {
+      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
+      case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING: {
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING;
+        break;
+      }
+      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
+        /* At this point all of the active ASEs within group are released. */
+        if (group->cig_created_)
+          IsoManager::GetInstance()->RemoveCig(group->group_id_);
+
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING;
+        if (group->HaveAllActiveDevicesAsesTheSameState(
+                AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING))
+          group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING);
+
+        break;
+
+      case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
+      case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: {
+        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING;
+
+        /* Since single ase gets into Releasing state, lets assume our new
+         * target state is IDLE
+         */
+        SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+
+        /* Happens when bi-directional completive ASE releasing state came */
+        if (ase->data_path_state == AudioStreamDataPathState::CIS_DISCONNECTING)
+          break;
+
+        if (ase->data_path_state ==
+            AudioStreamDataPathState::DATA_PATH_ESTABLISHED) {
+          auto ases_pair =
+              leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
+          IsoManager::GetInstance()->RemoveIsoDataPath(
+              ase->cis_conn_hdl,
+              (ases_pair.sink
+                   ? bluetooth::hci::iso_manager::kIsoDataPathDirectionOut
+                   : 0x00) |
+                  (ases_pair.source
+                       ? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn
+                       : 0x00));
+        } else if (ase->data_path_state ==
+                       AudioStreamDataPathState::CIS_ESTABLISHED ||
+                   ase->data_path_state ==
+                       AudioStreamDataPathState::CIS_PENDING) {
+          IsoManager::GetInstance()->DisconnectCis(ase->cis_conn_hdl,
+                                                   HCI_ERR_PEER_USER);
+        } else {
+          DLOG(INFO) << __func__ << ", Nothing to do ase data path state: "
+                    << static_cast<int>(ase->data_path_state);
+        }
+        break;
+      }
+      default:
+        LOG(ERROR) << __func__ << ", invalid state transition, from: "
+                   << static_cast<int>(ase->state) << ", to: "
+                   << static_cast<int>(
+                          AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING);
+        break;
+    }
+  }
+
+  void ProcessGroupEnable(LeAudioDeviceGroup* group, LeAudioDevice* device) {
+    /* Enable ASEs for next device in group. */
+    LeAudioDevice* deviceNext = group->GetNextActiveDevice(device);
+    if (deviceNext) {
+      PrepareAndSendEnable(deviceNext);
+      return;
+    }
+
+    /* At this point all of the active ASEs within group are enabled. The server
+     * might perform autonomous state transition for Sink ASE and skip Enabling
+     * state notification and transit to Streaming directly. So check the group
+     * state, because we might be ready to create CIS. */
+    if (group->HaveAllActiveDevicesAsesTheSameState(
+            AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
+      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+    } else {
+      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING);
+    }
+
+    if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+      CisCreate(group);
+    } else {
+      LOG(ERROR) << __func__
+                 << ", invalid state transition, from: " << group->GetState()
+                 << ", to: " << group->GetTargetState();
+      StopStream(group);
+    }
+  }
+
+  void ProcessGroupDisable(LeAudioDeviceGroup* group, LeAudioDevice* device) {
+    /* Disable ASEs for next device in group. */
+    LeAudioDevice* deviceNext = group->GetNextActiveDevice(device);
+    if (deviceNext) {
+      PrepareAndSendDisable(deviceNext);
+      return;
+    }
+
+    /* At this point all of the active ASEs within group are disabled. As there
+     * is no Disabling state for Sink ASE, it might happen that all of the
+     * active ASEs are Sink ASE and will transit to QoS state. So check
+     * the group state, because we might be ready to release data path. */
+    if (group->HaveAllActiveDevicesAsesTheSameState(
+            AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)) {
+      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+    } else {
+      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING);
+    }
+
+    /* Transition to QoS configured is done by CIS disconnection */
+    if (group->GetTargetState() ==
+        AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
+      ReleaseDataPath(group);
+    } else {
+      LOG(ERROR) << __func__
+                 << ", invalid state transition, from: " << group->GetState()
+                 << ", to: " << group->GetTargetState();
+      StopStream(group);
+    }
+  }
+};
+}  // namespace
+
+namespace le_audio {
+void LeAudioGroupStateMachine::Initialize(Callbacks* state_machine_callbacks_) {
+  if (instance) {
+    LOG(ERROR) << "Already initialized";
+    return;
+  }
+
+  instance = new LeAudioGroupStateMachineImpl(state_machine_callbacks_);
+}
+
+void LeAudioGroupStateMachine::Cleanup() {
+  if (!instance) return;
+
+  LeAudioGroupStateMachineImpl* ptr = instance;
+  instance = nullptr;
+
+  delete ptr;
+}
+
+LeAudioGroupStateMachine* LeAudioGroupStateMachine::Get() {
+  CHECK(instance);
+  return instance;
+}
+}  // namespace le_audio
diff --git a/system/bta/le_audio/state_machine.h b/system/bta/le_audio/state_machine.h
new file mode 100644
index 0000000000000000000000000000000000000000..21365e89e4207a038c022fff3a5d9b8c8d29f500
--- /dev/null
+++ b/system/bta/le_audio/state_machine.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include "btm_iso_api.h"
+#include "btm_iso_api_types.h"
+#include "devices.h"
+#include "hardware/bt_le_audio.h"
+#include "le_audio_types.h"
+
+namespace le_audio {
+
+/* State machine interface */
+class LeAudioGroupStateMachine {
+ public:
+  class Callbacks {
+   public:
+    virtual ~Callbacks() = default;
+
+    virtual void StatusReportCb(
+        int group_id, bluetooth::le_audio::GroupStreamStatus status) = 0;
+    virtual void OnStateTransitionTimeout(int group_id) = 0;
+  };
+
+  virtual ~LeAudioGroupStateMachine() = default;
+
+  static void Initialize(Callbacks* state_machine_callbacks);
+  static void Cleanup(void);
+  static LeAudioGroupStateMachine* Get(void);
+
+  virtual bool AttachToStream(LeAudioDeviceGroup* group,
+                              LeAudioDevice* leAudioDevice) = 0;
+  virtual bool StartStream(LeAudioDeviceGroup* group,
+                           types::LeAudioContextType context_type) = 0;
+  virtual void SuspendStream(LeAudioDeviceGroup* group) = 0;
+  virtual void StopStream(LeAudioDeviceGroup* group) = 0;
+  virtual void ProcessGattNotifEvent(uint8_t* value, uint16_t len,
+                                     struct types::ase* ase,
+                                     LeAudioDevice* leAudioDevice,
+                                     LeAudioDeviceGroup* group) = 0;
+
+  virtual void ProcessHciNotifOnCigCreate(
+      LeAudioDeviceGroup* group, uint8_t status, uint8_t cig_id,
+      std::vector<uint16_t> conn_handles) = 0;
+  virtual void ProcessHciNotifOnCigRemove(uint8_t status,
+                                          LeAudioDeviceGroup* group) = 0;
+  virtual void ProcessHciNotifCisEstablished(
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
+      const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event) = 0;
+  virtual void ProcessHciNotifCisDisconnected(
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
+      const bluetooth::hci::iso_manager::cis_disconnected_evt* event) = 0;
+  virtual void ProcessHciNotifSetupIsoDataPath(LeAudioDeviceGroup* group,
+                                               LeAudioDevice* leAudioDevice,
+                                               uint8_t status,
+                                               uint16_t conn_hdl) = 0;
+  virtual void ProcessHciNotifRemoveIsoDataPath(LeAudioDeviceGroup* group,
+                                                LeAudioDevice* leAudioDevice,
+                                                uint8_t status,
+                                                uint16_t conn_hdl) = 0;
+  virtual void ProcessHciNotifIsoLinkQualityRead(
+      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
+      uint8_t conn_handle, uint32_t txUnackedPackets, uint32_t txFlushedPackets,
+      uint32_t txLastSubeventPackets, uint32_t retransmittedPackets,
+      uint32_t crcErrorPackets, uint32_t rxUnreceivedPackets,
+      uint32_t duplicatePackets) = 0;
+  virtual void ProcessHciNotifAclDisconnected(LeAudioDeviceGroup* group,
+                                              LeAudioDevice* leAudioDevice) = 0;
+};
+}  // namespace le_audio
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9bd339bc6570cbaea5bf78b79a22dc4e2bd9ccf5
--- /dev/null
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -0,0 +1,2108 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
+ * www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "state_machine.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <functional>
+
+#include "bta_gatt_api_mock.h"
+#include "bta_gatt_queue_mock.h"
+#include "btm_api_mock.h"
+#include "client_parser.h"
+#include "fake_osi.h"
+#include "mock_controller.h"
+#include "mock_iso_manager.h"
+#include "types/bt_transport.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::SaveArg;
+using ::testing::Test;
+
+std::map<std::string, int> mock_function_count_map;
+
+extern struct fake_osi_alarm_set_on_mloop fake_osi_alarm_set_on_mloop_;
+
+namespace le_audio {
+namespace internal {
+
+// Just some arbitrary initial handles - it has no real meaning
+#define ATTR_HANDLE_ASCS_POOL_START (0x0000 | 32)
+#define ATTR_HANDLE_PACS_POOL_START (0xFF00 | 64)
+
+constexpr uint16_t kContextTypeUnspecified = 0x0001;
+constexpr uint16_t kContextTypeConversational = 0x0002;
+constexpr uint16_t kContextTypeMedia = 0x0004;
+// constexpr uint16_t kContextTypeInstructional = 0x0008;
+// constexpr uint16_t kContextTypeAttentionSeeking = 0x0010;
+// constexpr uint16_t kContextTypeImmediateAllert = 0x0020;
+// constexpr uint16_t kContextTypeManMachine = 0x0040;
+// constexpr uint16_t kContextTypeEmergencyAlert = 0x0080;
+constexpr uint16_t kContextTypeRingtone = 0x0100;
+// constexpr uint16_t kContextTypeTV = 0x0200;
+// constexpr uint16_t kContextTypeRFULive = 0x0400;
+
+namespace codec_specific {
+
+constexpr uint8_t kLc3CodingFormat = 0x06;
+
+// Reference Codec Capabilities values to test against
+constexpr uint8_t kCapTypeSupportedSamplingFrequencies = 0x01;
+constexpr uint8_t kCapTypeSupportedFrameDurations = 0x02;
+constexpr uint8_t kCapTypeAudioChannelCount = 0x03;
+constexpr uint8_t kCapTypeSupportedOctetsPerCodecFrame = 0x04;
+// constexpr uint8_t kCapTypeSupportedLc3CodecFramesPerSdu = 0x05;
+
+// constexpr uint8_t kCapSamplingFrequency8000Hz = 0x0001;
+// constexpr uint8_t kCapSamplingFrequency11025Hz = 0x0002;
+constexpr uint8_t kCapSamplingFrequency16000Hz = 0x0004;
+// constexpr uint8_t kCapSamplingFrequency22050Hz = 0x0008;
+// constexpr uint8_t kCapSamplingFrequency24000Hz = 0x0010;
+// constexpr uint8_t kCapSamplingFrequency32000Hz = 0x0020;
+// constexpr uint8_t kCapSamplingFrequency44100Hz = 0x0040;
+// constexpr uint8_t kCapSamplingFrequency48000Hz = 0x0080;
+// constexpr uint8_t kCapSamplingFrequency88200Hz = 0x0100;
+// constexpr uint8_t kCapSamplingFrequency96000Hz = 0x0200;
+// constexpr uint8_t kCapSamplingFrequency176400Hz = 0x0400;
+// constexpr uint8_t kCapSamplingFrequency192000Hz = 0x0800;
+// constexpr uint8_t kCapSamplingFrequency384000Hz = 0x1000;
+
+constexpr uint8_t kCapFrameDuration7p5ms = 0x01;
+constexpr uint8_t kCapFrameDuration10ms = 0x02;
+// constexpr uint8_t kCapFrameDuration7p5msPreferred = 0x10;
+constexpr uint8_t kCapFrameDuration10msPreferred = 0x20;
+}  // namespace codec_specific
+
+namespace ascs {
+constexpr uint8_t kAseStateIdle = 0x00;
+constexpr uint8_t kAseStateCodecConfigured = 0x01;
+constexpr uint8_t kAseStateQoSConfigured = 0x02;
+constexpr uint8_t kAseStateEnabling = 0x03;
+constexpr uint8_t kAseStateStreaming = 0x04;
+constexpr uint8_t kAseStateDisabling = 0x05;
+constexpr uint8_t kAseStateReleasing = 0x06;
+
+// constexpr uint8_t kAseParamDirectionServerIsAudioSink = 0x01;
+// constexpr uint8_t kAseParamDirectionServerIsAudioSource = 0x02;
+
+constexpr uint8_t kAseParamFramingUnframedSupported = 0x00;
+// constexpr uint8_t kAseParamFramingUnframedNotSupported = 0x01;
+
+// constexpr uint8_t kAseParamPreferredPhy1M = 0x01;
+// constexpr uint8_t kAseParamPreferredPhy2M = 0x02;
+// constexpr uint8_t kAseParamPreferredPhyCoded = 0x04;
+
+constexpr uint8_t kAseCtpOpcodeConfigureCodec = 0x01;
+constexpr uint8_t kAseCtpOpcodeConfigureQos = 0x02;
+constexpr uint8_t kAseCtpOpcodeEnable = 0x03;
+constexpr uint8_t kAseCtpOpcodeReceiverStartReady = 0x04;
+constexpr uint8_t kAseCtpOpcodeDisable = 0x05;
+constexpr uint8_t kAseCtpOpcodeReceiverStopReady = 0x06;
+// constexpr uint8_t kAseCtpOpcodeUpdateMetadata = 0x07;
+constexpr uint8_t kAseCtpOpcodeRelease = 0x08;
+constexpr uint8_t kAseCtpOpcodeMaxVal = kAseCtpOpcodeRelease;
+
+}  // namespace ascs
+
+static RawAddress GetTestAddress(uint8_t index) {
+  return {{0xC0, 0xDE, 0xC0, 0xDE, 0x00, index}};
+}
+
+static uint8_t ase_id_last_assigned;
+
+class MockLeAudioGroupStateMachineCallbacks
+    : public LeAudioGroupStateMachine::Callbacks {
+ public:
+  MockLeAudioGroupStateMachineCallbacks() = default;
+  ~MockLeAudioGroupStateMachineCallbacks() override = default;
+  MOCK_METHOD((void), StatusReportCb,
+              (int group_id, bluetooth::le_audio::GroupStreamStatus status),
+              (override));
+  MOCK_METHOD((void), OnStateTransitionTimeout, (int group_id), (override));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockLeAudioGroupStateMachineCallbacks);
+};
+
+class StateMachineTest : public Test {
+ protected:
+  void SetUp() override {
+    mock_function_count_map.clear();
+    controller::SetMockControllerInterface(&mock_controller_);
+    bluetooth::manager::SetMockBtmInterface(&btm_interface);
+    gatt::SetMockBtaGattInterface(&gatt_interface);
+    gatt::SetMockBtaGattQueue(&gatt_queue);
+
+    ase_id_last_assigned = types::ase::kAseIdInvalid;
+    LeAudioGroupStateMachine::Initialize(&mock_callbacks_);
+
+    // Support 2M Phy
+    ON_CALL(mock_controller_, SupportsBle2mPhy()).WillByDefault(Return(true));
+    ON_CALL(btm_interface, IsPhy2mSupported(_, _)).WillByDefault(Return(true));
+    ON_CALL(btm_interface, GetHCIConnHandle(_, _))
+        .WillByDefault(
+            Invoke([](RawAddress const& remote_bda, tBT_TRANSPORT transport) {
+              return remote_bda.IsEmpty()
+                         ? HCI_INVALID_HANDLE
+                         : ((uint16_t)(remote_bda.address[0] ^
+                                       remote_bda.address[1] ^
+                                       remote_bda.address[2]))
+                                   << 8 ||
+                               (remote_bda.address[3] ^ remote_bda.address[4] ^
+                                remote_bda.address[5]);
+            }));
+
+    ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, GATT_WRITE_NO_RSP, _, _))
+        .WillByDefault(Invoke([this](uint16_t conn_id, uint16_t handle,
+                                     std::vector<uint8_t> value,
+                                     tGATT_WRITE_TYPE write_type,
+                                     GATT_WRITE_OP_CB cb, void* cb_data) {
+          for (auto& dev : le_audio_devices_) {
+            if (dev->conn_id_ == conn_id) {
+              // Control point write handler
+              if (dev->ctp_hdls_.val_hdl == handle) {
+                HandleCtpOperation(dev.get(), value, cb, cb_data);
+              }
+              break;
+            }
+          }
+        }));
+
+    ConfigureIsoManagerMock();
+  }
+
+  void HandleCtpOperation(LeAudioDevice* device, std::vector<uint8_t> value,
+                          GATT_WRITE_OP_CB cb, void* cb_data) {
+    auto opcode = value[0];
+
+    // Verify against valid opcode range
+    ASSERT_LT(opcode, ascs::kAseCtpOpcodeMaxVal + 1);
+    ASSERT_NE(opcode, 0);
+
+    if (ase_ctp_handlers[opcode])
+      ase_ctp_handlers[opcode](device, std::move(value), cb, cb_data);
+  }
+
+/* Helper function to make a deterministic (and unique on the entire device)
+ * connection handle for a given cis.
+ */
+#define UNIQUE_CIS_CONN_HANDLE(cig_id, cis_index) (cig_id << 8 | cis_index)
+
+  void ConfigureIsoManagerMock() {
+    iso_manager_ = bluetooth::hci::IsoManager::GetInstance();
+    ASSERT_NE(iso_manager_, nullptr);
+    iso_manager_->Start();
+
+    mock_iso_manager_ = MockIsoManager::GetInstance();
+    ASSERT_NE(mock_iso_manager_, nullptr);
+
+    ON_CALL(*mock_iso_manager_, CreateCig)
+        .WillByDefault(
+            [this](uint8_t cig_id,
+                   bluetooth::hci::iso_manager::cig_create_params p) {
+              DLOG(INFO) << "CreateCig";
+
+              auto& group = le_audio_device_groups_[cig_id];
+              if (group) {
+                std::vector<uint16_t> conn_handles;
+                // Fake connection ID for each cis in a request
+                for (auto i = 0u; i < p.cis_cfgs.size(); ++i) {
+                  conn_handles.push_back(UNIQUE_CIS_CONN_HANDLE(cig_id, i));
+                }
+                LeAudioGroupStateMachine::Get()->ProcessHciNotifOnCigCreate(
+                    group.get(), 0, cig_id, conn_handles);
+              }
+            });
+
+    ON_CALL(*mock_iso_manager_, RemoveCig)
+        .WillByDefault([this](uint8_t cig_id) {
+          DLOG(INFO) << "CreateRemove";
+
+          auto& group = le_audio_device_groups_[cig_id];
+          if (group) {
+            // Fake connection ID for each cis in a request
+            LeAudioGroupStateMachine::Get()->ProcessHciNotifOnCigRemove(
+                0, group.get());
+          }
+        });
+
+    ON_CALL(*mock_iso_manager_, SetupIsoDataPath)
+        .WillByDefault([this](uint16_t conn_handle,
+                              bluetooth::hci::iso_manager::iso_data_path_params
+                                  p) {
+          DLOG(INFO) << "SetupIsoDataPath";
+
+          auto dev_it =
+              std::find_if(le_audio_devices_.begin(), le_audio_devices_.end(),
+                           [&conn_handle](auto& dev) {
+                             auto ases = dev->GetAsesByCisConnHdl(conn_handle);
+                             return (ases.sink || ases.source);
+                           });
+          if (dev_it == le_audio_devices_.end()) {
+            DLOG(ERROR) << "Device not found";
+            return;
+          }
+
+          for (auto& kv_pair : le_audio_device_groups_) {
+            auto& group = kv_pair.second;
+            if (group->IsDeviceInTheGroup(dev_it->get())) {
+              LeAudioGroupStateMachine::Get()->ProcessHciNotifSetupIsoDataPath(
+                  group.get(), dev_it->get(), 0, conn_handle);
+              return;
+            }
+          }
+        });
+
+    ON_CALL(*mock_iso_manager_, RemoveIsoDataPath)
+        .WillByDefault([this](uint16_t conn_handle, uint8_t iso_direction) {
+          DLOG(INFO) << "RemoveIsoDataPath";
+
+          auto dev_it =
+              std::find_if(le_audio_devices_.begin(), le_audio_devices_.end(),
+                           [&conn_handle](auto& dev) {
+                             auto ases = dev->GetAsesByCisConnHdl(conn_handle);
+                             return (ases.sink || ases.source);
+                           });
+          if (dev_it == le_audio_devices_.end()) {
+            DLOG(ERROR) << "Device not found";
+            return;
+          }
+
+          for (auto& kv_pair : le_audio_device_groups_) {
+            auto& group = kv_pair.second;
+            if (group->IsDeviceInTheGroup(dev_it->get())) {
+              LeAudioGroupStateMachine::Get()->ProcessHciNotifRemoveIsoDataPath(
+                  group.get(), dev_it->get(), 0, conn_handle);
+              return;
+            }
+          }
+        });
+
+    ON_CALL(*mock_iso_manager_, EstablishCis)
+        .WillByDefault([this](bluetooth::hci::iso_manager::cis_establish_params
+                                  conn_params) {
+          DLOG(INFO) << "EstablishCis";
+
+          for (auto& pair : conn_params.conn_pairs) {
+            auto dev_it = std::find_if(
+                le_audio_devices_.begin(), le_audio_devices_.end(),
+                [&pair](auto& dev) {
+                  auto ases = dev->GetAsesByCisConnHdl(pair.cis_conn_handle);
+                  return (ases.sink || ases.source);
+                });
+            if (dev_it == le_audio_devices_.end()) {
+              DLOG(ERROR) << "Device not found";
+              return;
+            }
+
+            for (auto& kv_pair : le_audio_device_groups_) {
+              auto& group = kv_pair.second;
+              if (group->IsDeviceInTheGroup(dev_it->get())) {
+                bluetooth::hci::iso_manager::cis_establish_cmpl_evt evt;
+
+                // Fill proper values if needed
+                evt.status = 0x00;
+                evt.cig_id = group->group_id_;
+                evt.cis_conn_hdl = pair.cis_conn_handle;
+                evt.cig_sync_delay = 0;
+                evt.cis_sync_delay = 0;
+                evt.trans_lat_mtos = 0;
+                evt.trans_lat_stom = 0;
+                evt.phy_mtos = 0;
+                evt.phy_stom = 0;
+                evt.nse = 0;
+                evt.bn_mtos = 0;
+                evt.bn_stom = 0;
+                evt.ft_mtos = 0;
+                evt.ft_stom = 0;
+                evt.max_pdu_mtos = 0;
+                evt.max_pdu_stom = 0;
+                evt.iso_itv = 0;
+
+                LeAudioGroupStateMachine::Get()->ProcessHciNotifCisEstablished(
+                    group.get(), dev_it->get(), &evt);
+                break;
+              }
+            }
+          }
+        });
+
+    ON_CALL(*mock_iso_manager_, DisconnectCis)
+        .WillByDefault([this](uint16_t cis_handle, uint8_t reason) {
+          DLOG(INFO) << "DisconnectCis";
+
+          auto dev_it =
+              std::find_if(le_audio_devices_.begin(), le_audio_devices_.end(),
+                           [&cis_handle](auto& dev) {
+                             auto ases = dev->GetAsesByCisConnHdl(cis_handle);
+                             return (ases.sink || ases.source);
+                           });
+          if (dev_it == le_audio_devices_.end()) {
+            DLOG(ERROR) << "Device not found";
+            return;
+          }
+
+          for (auto& kv_pair : le_audio_device_groups_) {
+            auto& group = kv_pair.second;
+            if (group->IsDeviceInTheGroup(dev_it->get())) {
+              bluetooth::hci::iso_manager::cis_disconnected_evt evt{
+                  .reason = reason,
+                  .cig_id = static_cast<uint8_t>(group->group_id_),
+                  .cis_conn_hdl = cis_handle,
+              };
+              LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected(
+                  group.get(), dev_it->get(), &evt);
+              return;
+            }
+          }
+        });
+  }
+
+  void TearDown() override {
+    iso_manager_->Stop();
+    mock_iso_manager_ = nullptr;
+
+    gatt::SetMockBtaGattQueue(nullptr);
+    gatt::SetMockBtaGattInterface(nullptr);
+    bluetooth::manager::SetMockBtmInterface(nullptr);
+    controller::SetMockControllerInterface(nullptr);
+
+    for (auto i = 0u; i <= ascs::kAseCtpOpcodeMaxVal; ++i)
+      ase_ctp_handlers[i] = nullptr;
+
+    le_audio_devices_.clear();
+    cached_codec_configuration_map_.clear();
+    LeAudioGroupStateMachine::Cleanup();
+  }
+
+  std::shared_ptr<LeAudioDevice> PrepareConnectedDevice(uint8_t id,
+                                                        bool first_connection,
+                                                        uint8_t num_ase_snk,
+                                                        uint8_t num_ase_src) {
+    auto leAudioDevice =
+        std::make_shared<LeAudioDevice>(GetTestAddress(id), first_connection);
+    leAudioDevice->conn_id_ = id;
+
+    uint16_t attr_handle = ATTR_HANDLE_ASCS_POOL_START;
+    leAudioDevice->snk_audio_locations_hdls_.val_hdl = attr_handle++;
+    leAudioDevice->snk_audio_locations_hdls_.ccc_hdl = attr_handle++;
+    leAudioDevice->src_audio_locations_hdls_.val_hdl = attr_handle++;
+    leAudioDevice->src_audio_locations_hdls_.ccc_hdl = attr_handle++;
+    leAudioDevice->audio_avail_hdls_.val_hdl = attr_handle++;
+    leAudioDevice->audio_avail_hdls_.ccc_hdl = attr_handle++;
+    leAudioDevice->audio_supp_cont_hdls_.val_hdl = attr_handle++;
+    leAudioDevice->audio_supp_cont_hdls_.ccc_hdl = attr_handle++;
+    leAudioDevice->ctp_hdls_.val_hdl = attr_handle++;
+    leAudioDevice->ctp_hdls_.ccc_hdl = attr_handle++;
+
+    // Add some Sink ASEs
+    while (num_ase_snk) {
+      types::ase ase(0, 0, 0x01);
+      ase.hdls.val_hdl = attr_handle++;
+      ase.hdls.ccc_hdl = attr_handle++;
+
+      leAudioDevice->ases_.emplace_back(std::move(ase));
+      num_ase_snk--;
+    }
+
+    // Add some Source ASEs
+    while (num_ase_src) {
+      types::ase ase(0, 0, 0x02);
+      ase.hdls.val_hdl = attr_handle++;
+      ase.hdls.ccc_hdl = attr_handle++;
+
+      leAudioDevice->ases_.emplace_back(std::move(ase));
+      num_ase_src--;
+    }
+
+    le_audio_devices_.push_back(leAudioDevice);
+
+    return std::move(leAudioDevice);
+  }
+
+  LeAudioDeviceGroup* GroupTheDevice(
+      int group_id, const std::shared_ptr<LeAudioDevice>& leAudioDevice) {
+    if (le_audio_device_groups_.count(group_id) == 0) {
+      le_audio_device_groups_[group_id] =
+          std::make_unique<LeAudioDeviceGroup>(group_id);
+    }
+
+    auto& group = le_audio_device_groups_[group_id];
+
+    group->AddNode(leAudioDevice);
+    if (group->IsEmpty()) return nullptr;
+
+    return &(*group);
+  }
+
+  static void InjectAseStateNotification(types::ase* ase, LeAudioDevice* device,
+                                         LeAudioDeviceGroup* group,
+                                         uint8_t new_state,
+                                         void* new_state_params) {
+    // Prepare additional params
+    switch (new_state) {
+      case ascs::kAseStateCodecConfigured: {
+        client_parser::ascs::ase_codec_configured_state_params* conf =
+            static_cast<
+                client_parser::ascs::ase_codec_configured_state_params*>(
+                new_state_params);
+        std::vector<uint8_t> notif_value(25 + conf->codec_spec_conf.size());
+        auto* p = notif_value.data();
+
+        UINT8_TO_STREAM(p, ase->id == types::ase::kAseIdInvalid
+                               ? ++ase_id_last_assigned
+                               : ase->id);
+        UINT8_TO_STREAM(p, new_state);
+
+        UINT8_TO_STREAM(p, conf->framing);
+        UINT8_TO_STREAM(p, conf->preferred_phy);
+        UINT8_TO_STREAM(p, conf->preferred_retrans_nb);
+        UINT16_TO_STREAM(p, conf->max_transport_latency);
+        UINT24_TO_STREAM(p, conf->pres_delay_min);
+        UINT24_TO_STREAM(p, conf->pres_delay_max);
+        UINT24_TO_STREAM(p, conf->preferred_pres_delay_min);
+        UINT24_TO_STREAM(p, conf->preferred_pres_delay_max);
+
+        // CodecID:
+        UINT8_TO_STREAM(p, conf->codec_id.coding_format);
+        UINT16_TO_STREAM(p, conf->codec_id.vendor_company_id);
+        UINT16_TO_STREAM(p, conf->codec_id.vendor_codec_id);
+
+        // Codec Spec. Conf. Length and Data
+        UINT8_TO_STREAM(p, conf->codec_spec_conf.size());
+        memcpy(p, conf->codec_spec_conf.data(), conf->codec_spec_conf.size());
+
+        LeAudioGroupStateMachine::Get()->ProcessGattNotifEvent(
+            notif_value.data(), notif_value.size(), ase, device, group);
+      } break;
+
+      case ascs::kAseStateQoSConfigured: {
+        client_parser::ascs::ase_qos_configured_state_params* conf =
+            static_cast<client_parser::ascs::ase_qos_configured_state_params*>(
+                new_state_params);
+        std::vector<uint8_t> notif_value(17);
+        auto* p = notif_value.data();
+
+        // Prepare header
+        UINT8_TO_STREAM(p, ase->id);
+        UINT8_TO_STREAM(p, new_state);
+
+        UINT8_TO_STREAM(p, conf->cig_id);
+        UINT8_TO_STREAM(p, conf->cis_id);
+        UINT24_TO_STREAM(p, conf->sdu_interval);
+        UINT8_TO_STREAM(p, conf->framing);
+        UINT8_TO_STREAM(p, conf->phy);
+        UINT16_TO_STREAM(p, conf->max_sdu);
+        UINT8_TO_STREAM(p, conf->retrans_nb);
+        UINT16_TO_STREAM(p, conf->max_transport_latency);
+        UINT24_TO_STREAM(p, conf->pres_delay);
+
+        LeAudioGroupStateMachine::Get()->ProcessGattNotifEvent(
+            notif_value.data(), notif_value.size(), ase, device, group);
+      } break;
+
+      case ascs::kAseStateEnabling:
+        // fall-through
+      case ascs::kAseStateStreaming:
+        // fall-through
+      case ascs::kAseStateDisabling: {
+        client_parser::ascs::ase_transient_state_params* params =
+            static_cast<client_parser::ascs::ase_transient_state_params*>(
+                new_state_params);
+        std::vector<uint8_t> notif_value(5 + params->metadata.size());
+        auto* p = notif_value.data();
+
+        // Prepare header
+        UINT8_TO_STREAM(p, ase->id);
+        UINT8_TO_STREAM(p, new_state);
+
+        UINT8_TO_STREAM(p, group->group_id_);
+        UINT8_TO_STREAM(p, ase->cis_id);
+        UINT8_TO_STREAM(p, params->metadata.size());
+        memcpy(p, params->metadata.data(), params->metadata.size());
+
+        LeAudioGroupStateMachine::Get()->ProcessGattNotifEvent(
+            notif_value.data(), notif_value.size(), ase, device, group);
+      } break;
+
+      case ascs::kAseStateReleasing:
+        // fall-through
+      case ascs::kAseStateIdle: {
+        std::vector<uint8_t> notif_value(2);
+        auto* p = notif_value.data();
+
+        // Prepare header
+        UINT8_TO_STREAM(p, ase->id == types::ase::kAseIdInvalid
+                               ? ++ase_id_last_assigned
+                               : ase->id);
+        UINT8_TO_STREAM(p, new_state);
+
+        LeAudioGroupStateMachine::Get()->ProcessGattNotifEvent(
+            notif_value.data(), notif_value.size(), ase, device, group);
+      } break;
+
+      default:
+        break;
+    };
+  }
+
+  static void InsertPacRecord(
+      std::vector<types::acs_ac_record>& recs,
+      uint16_t sampling_frequencies_bitfield,
+      uint8_t supported_frame_durations_bitfield,
+      uint8_t audio_channel_count_bitfield,
+      uint16_t supported_octets_per_codec_frame_min,
+      uint16_t supported_octets_per_codec_frame_max,
+      uint8_t coding_format = codec_specific::kLc3CodingFormat,
+      uint16_t vendor_company_id = 0x0000, uint16_t vendor_codec_id = 0x0000,
+      std::vector<uint8_t> metadata = {}) {
+    recs.push_back({
+        .codec_id =
+            {
+                .coding_format = coding_format,
+                .vendor_company_id = vendor_company_id,
+                .vendor_codec_id = vendor_codec_id,
+            },
+        .codec_spec_caps = types::LeAudioLtvMap({
+            {codec_specific::kCapTypeSupportedSamplingFrequencies,
+             {(uint8_t)(sampling_frequencies_bitfield),
+              (uint8_t)(sampling_frequencies_bitfield >> 8)}},
+            {codec_specific::kCapTypeSupportedFrameDurations,
+             {supported_frame_durations_bitfield}},
+            {codec_specific::kCapTypeAudioChannelCount,
+             {audio_channel_count_bitfield}},
+            {codec_specific::kCapTypeSupportedOctetsPerCodecFrame,
+             {
+                 // Min
+                 (uint8_t)(supported_octets_per_codec_frame_min),
+                 (uint8_t)(supported_octets_per_codec_frame_min >> 8),
+                 // Max
+                 (uint8_t)(supported_octets_per_codec_frame_max),
+                 (uint8_t)(supported_octets_per_codec_frame_max >> 8),
+             }},
+        }),
+        .metadata = std::move(metadata),
+    });
+  }
+
+  static void InjectInitialIdleNotification(LeAudioDeviceGroup* group) {
+    for (auto* device = group->GetFirstDevice(); device != nullptr;
+         device = group->GetNextDevice(device)) {
+      for (auto& ase : device->ases_) {
+        InjectAseStateNotification(&ase, device, group, ascs::kAseStateIdle,
+                                   nullptr);
+      }
+    }
+  }
+
+  void MultipleTestDevicePrepare(int leaudio_group_id, uint16_t context_type,
+                                 uint16_t device_cnt,
+                                 bool insert_default_pac_records = true) {
+    // Prepare fake connected device group
+    bool first_connections = true;
+    int total_devices = device_cnt;
+    le_audio::LeAudioDeviceGroup* group = nullptr;
+
+    uint8_t num_ase_snk;
+    uint8_t num_ase_src;
+    switch (context_type) {
+      case kContextTypeRingtone:
+        num_ase_snk = 1;
+        num_ase_src = 0;
+        break;
+
+      case kContextTypeMedia:
+        num_ase_snk = 2;
+        num_ase_src = 0;
+        break;
+
+      case kContextTypeConversational:
+        num_ase_snk = 1;
+        num_ase_src = 1;
+        break;
+
+      default:
+        ASSERT_TRUE(false);
+    }
+
+    while (device_cnt) {
+      auto leAudioDevice = PrepareConnectedDevice(
+          device_cnt--, first_connections, num_ase_snk, num_ase_src);
+
+      if (insert_default_pac_records) {
+        uint16_t attr_handle = ATTR_HANDLE_PACS_POOL_START;
+
+        /* As per spec, unspecified shall be supported */
+        types::AudioContexts snk_context_type = kContextTypeUnspecified;
+        types::AudioContexts src_context_type = kContextTypeUnspecified;
+
+        // Prepare Sink Published Audio Capability records
+        if ((context_type & kContextTypeRingtone) ||
+            (context_type & kContextTypeMedia) ||
+            (context_type & kContextTypeConversational)) {
+          // Set target ASE configurations
+          std::vector<types::acs_ac_record> pac_recs;
+
+          InsertPacRecord(pac_recs,
+                          codec_specific::kCapSamplingFrequency16000Hz,
+                          codec_specific::kCapFrameDuration10ms |
+                              codec_specific::kCapFrameDuration7p5ms |
+                              codec_specific::kCapFrameDuration10msPreferred,
+                          0b00000001, 30, 120);
+
+          types::hdl_pair handle_pair;
+          handle_pair.val_hdl = attr_handle++;
+          handle_pair.ccc_hdl = attr_handle++;
+
+          leAudioDevice->snk_pacs_.emplace_back(
+              std::make_tuple(std::move(handle_pair), pac_recs));
+
+          snk_context_type |= context_type;
+          leAudioDevice->snk_audio_locations_ =
+              ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft |
+              ::le_audio::codec_spec_conf::kLeAudioLocationFrontRight;
+        }
+
+        // Prepare Source Published Audio Capability records
+        if (context_type & kContextTypeConversational) {
+          // Set target ASE configurations
+          std::vector<types::acs_ac_record> pac_recs;
+
+          InsertPacRecord(pac_recs,
+                          codec_specific::kCapSamplingFrequency16000Hz,
+                          codec_specific::kCapFrameDuration10ms |
+                              codec_specific::kCapFrameDuration7p5ms |
+                              codec_specific::kCapFrameDuration10msPreferred,
+                          0b00000001, 30, 120);
+
+          types::hdl_pair handle_pair;
+          handle_pair.val_hdl = attr_handle++;
+          handle_pair.ccc_hdl = attr_handle++;
+
+          leAudioDevice->src_pacs_.emplace_back(
+              std::make_tuple(std::move(handle_pair), pac_recs));
+          src_context_type |= kContextTypeConversational;
+
+          leAudioDevice->src_audio_locations_ =
+              ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft;
+        }
+
+        leAudioDevice->SetSupportedContexts(snk_context_type, src_context_type);
+        leAudioDevice->SetAvailableContexts(snk_context_type, src_context_type);
+      }
+
+      group = GroupTheDevice(leaudio_group_id, std::move(leAudioDevice));
+    }
+
+    /* Stimulate update of active context map */
+    types::AudioContexts type_set = static_cast<uint16_t>(context_type);
+    group->UpdateActiveContextsMap(type_set);
+
+    ASSERT_NE(group, nullptr);
+    ASSERT_EQ(group->Size(), total_devices);
+  }
+
+  LeAudioDeviceGroup* PrepareSingleTestDeviceGroup(int leaudio_group_id,
+                                                   uint16_t context_type,
+                                                   uint16_t device_cnt = 1) {
+    MultipleTestDevicePrepare(leaudio_group_id, context_type, device_cnt);
+    return le_audio_device_groups_.count(leaudio_group_id)
+               ? le_audio_device_groups_[leaudio_group_id].get()
+               : nullptr;
+  }
+
+  void PrepareConfigureCodecHandler(LeAudioDeviceGroup* group,
+                                    int verify_ase_count = 0,
+                                    bool caching = false) {
+    ase_ctp_handlers[ascs::kAseCtpOpcodeConfigureCodec] =
+        [group, verify_ase_count, caching, this](
+            LeAudioDevice* device, std::vector<uint8_t> value,
+            GATT_WRITE_OP_CB cb, void* cb_data) {
+          auto num_ase = value[1];
+
+          // Verify ase count if needed
+          if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase);
+
+          // Inject Configured ASE state notification for each requested ASE
+          auto* ase_p = &value[2];
+          for (auto i = 0u; i < num_ase; ++i) {
+            client_parser::ascs::ase_codec_configured_state_params
+                codec_configured_state_params;
+
+            /* Check if this is a valid ASE ID  */
+            auto ase_id = *ase_p++;
+            auto it = std::find_if(
+                device->ases_.begin(), device->ases_.end(),
+                [ase_id](auto& ase) { return (ase.id == ase_id); });
+            ASSERT_NE(it, device->ases_.end());
+            const auto ase = &(*it);
+
+            // Skip target latency param
+            ase_p++;
+
+            codec_configured_state_params.preferred_phy = *ase_p++;
+            codec_configured_state_params.codec_id.coding_format = ase_p[0];
+            codec_configured_state_params.codec_id.vendor_company_id =
+                (uint16_t)(ase_p[1] << 8 | ase_p[2]),
+            codec_configured_state_params.codec_id.vendor_codec_id =
+                (uint16_t)(ase_p[3] << 8 | ase_p[4]),
+            ase_p += 5;
+
+            auto codec_spec_param_len = *ase_p++;
+            auto num_handled_bytes = ase_p - value.data();
+            codec_configured_state_params.codec_spec_conf =
+                std::vector<uint8_t>(
+                    value.begin() + num_handled_bytes,
+                    value.begin() + num_handled_bytes + codec_spec_param_len);
+            ase_p += codec_spec_param_len;
+
+            // Some initial QoS settings
+            codec_configured_state_params.framing =
+                ascs::kAseParamFramingUnframedSupported;
+            codec_configured_state_params.preferred_retrans_nb = 0x04;
+            codec_configured_state_params.max_transport_latency = 0x0005;
+            codec_configured_state_params.pres_delay_min = 0xABABAB;
+            codec_configured_state_params.pres_delay_max = 0xCDCDCD;
+            codec_configured_state_params.preferred_pres_delay_min =
+                types::kPresDelayNoPreference;
+            codec_configured_state_params.preferred_pres_delay_max =
+                types::kPresDelayNoPreference;
+
+            if (caching) {
+              cached_codec_configuration_map_[ase_id] =
+                  codec_configured_state_params;
+            }
+            InjectAseStateNotification(ase, device, group,
+                                       ascs::kAseStateCodecConfigured,
+                                       &codec_configured_state_params);
+          }
+        };
+  }
+
+  void PrepareConfigureQosHandler(LeAudioDeviceGroup* group,
+                                  int verify_ase_count = 0) {
+    ase_ctp_handlers[ascs::kAseCtpOpcodeConfigureQos] =
+        [group, verify_ase_count](LeAudioDevice* device,
+                                  std::vector<uint8_t> value,
+                                  GATT_WRITE_OP_CB cb, void* cb_data) {
+          auto num_ase = value[1];
+
+          // Verify ase count if needed
+          if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase);
+
+          // Inject Configured QoS state notification for each requested ASE
+          auto* ase_p = &value[2];
+          for (auto i = 0u; i < num_ase; ++i) {
+            client_parser::ascs::ase_qos_configured_state_params
+                qos_configured_state_params;
+
+            /* Check if this is a valid ASE ID  */
+            auto ase_id = *ase_p++;
+            auto it = std::find_if(
+                device->ases_.begin(), device->ases_.end(),
+                [ase_id](auto& ase) { return (ase.id == ase_id); });
+            ASSERT_NE(it, device->ases_.end());
+            const auto ase = &(*it);
+
+            qos_configured_state_params.cig_id = *ase_p++;
+            qos_configured_state_params.cis_id = *ase_p++;
+
+            qos_configured_state_params.sdu_interval =
+                (uint32_t)((ase_p[0] << 16) | (ase_p[1] << 8) | ase_p[2]);
+            ase_p += 3;
+
+            qos_configured_state_params.framing = *ase_p++;
+            qos_configured_state_params.phy = *ase_p++;
+            qos_configured_state_params.max_sdu =
+                (uint16_t)((ase_p[0] << 8) | ase_p[1]);
+            ase_p += 2;
+
+            qos_configured_state_params.retrans_nb = *ase_p++;
+            qos_configured_state_params.max_transport_latency =
+                (uint16_t)((ase_p[0] << 8) | ase_p[1]);
+            ase_p += 2;
+
+            qos_configured_state_params.pres_delay =
+                (uint16_t)((ase_p[0] << 16) | (ase_p[1] << 8) | ase_p[2]);
+            ase_p += 3;
+
+            InjectAseStateNotification(ase, device, group,
+                                       ascs::kAseStateQoSConfigured,
+                                       &qos_configured_state_params);
+          }
+        };
+  }
+
+  void PrepareEnableHandler(LeAudioDeviceGroup* group, int verify_ase_count = 0,
+                            bool inject_enabling = true) {
+    ase_ctp_handlers[ascs::kAseCtpOpcodeEnable] =
+        [group, verify_ase_count, inject_enabling](
+            LeAudioDevice* device, std::vector<uint8_t> value,
+            GATT_WRITE_OP_CB cb, void* cb_data) {
+          auto num_ase = value[1];
+
+          // Verify ase count if needed
+          if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase);
+
+          // Inject Streaming ASE state notification for each requested ASE
+          auto* ase_p = &value[2];
+          for (auto i = 0u; i < num_ase; ++i) {
+            /* Check if this is a valid ASE ID  */
+            auto ase_id = *ase_p++;
+            auto it = std::find_if(
+                device->ases_.begin(), device->ases_.end(),
+                [ase_id](auto& ase) { return (ase.id == ase_id); });
+            ASSERT_NE(it, device->ases_.end());
+            const auto ase = &(*it);
+
+            auto meta_len = *ase_p++;
+            auto num_handled_bytes = ase_p - value.data();
+            ase_p += num_handled_bytes;
+
+            client_parser::ascs::ase_transient_state_params enable_params = {
+                .metadata = std::vector<uint8_t>(
+                    value.begin() + num_handled_bytes,
+                    value.begin() + num_handled_bytes + meta_len)};
+
+            // Server does the 'ReceiverStartReady' on its own - goes to
+            // Streaming, when in Sink role
+            if (ase->direction & le_audio::types::kLeAudioDirectionSink) {
+              if (inject_enabling)
+                InjectAseStateNotification(ase, device, group,
+                                           ascs::kAseStateEnabling,
+                                           &enable_params);
+              InjectAseStateNotification(
+                  ase, device, group, ascs::kAseStateStreaming, &enable_params);
+            } else {
+              InjectAseStateNotification(
+                  ase, device, group, ascs::kAseStateEnabling, &enable_params);
+            }
+          }
+        };
+  }
+
+  void PrepareDisableHandler(LeAudioDeviceGroup* group,
+                             int verify_ase_count = 0) {
+    ase_ctp_handlers[ascs::kAseCtpOpcodeDisable] =
+        [group, verify_ase_count](LeAudioDevice* device,
+                                  std::vector<uint8_t> value,
+                                  GATT_WRITE_OP_CB cb, void* cb_data) {
+          auto num_ase = value[1];
+
+          // Verify ase count if needed
+          if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase);
+          ASSERT_EQ(value.size(), 2ul + num_ase);
+
+          // Inject Disabling & QoS Conf. ASE state notification for each ASE
+          auto* ase_p = &value[2];
+          for (auto i = 0u; i < num_ase; ++i) {
+            /* Check if this is a valid ASE ID  */
+            auto ase_id = *ase_p++;
+            auto it = std::find_if(
+                device->ases_.begin(), device->ases_.end(),
+                [ase_id](auto& ase) { return (ase.id == ase_id); });
+            ASSERT_NE(it, device->ases_.end());
+            const auto ase = &(*it);
+
+            // The Disabling state is present for Source ASE
+            if (ase->direction & le_audio::types::kLeAudioDirectionSource) {
+              client_parser::ascs::ase_transient_state_params disabling_params =
+                  {.metadata = {}};
+              InjectAseStateNotification(ase, device, group,
+                                         ascs::kAseStateDisabling,
+                                         &disabling_params);
+            }
+
+            // Server does the 'ReceiverStopReady' on its own - goes to
+            // Streaming, when in Sink role
+            if (ase->direction & le_audio::types::kLeAudioDirectionSink) {
+              // FIXME: For now our fake peer does not remember qos params
+              client_parser::ascs::ase_qos_configured_state_params
+                  qos_configured_state_params;
+              InjectAseStateNotification(ase, device, group,
+                                         ascs::kAseStateQoSConfigured,
+                                         &qos_configured_state_params);
+            }
+          }
+        };
+  }
+
+  void PrepareReceiverStartReady(LeAudioDeviceGroup* group,
+                                 int verify_ase_count = 0) {
+    ase_ctp_handlers[ascs::kAseCtpOpcodeReceiverStartReady] =
+        [group, verify_ase_count](LeAudioDevice* device,
+                                  std::vector<uint8_t> value,
+                                  GATT_WRITE_OP_CB cb, void* cb_data) {
+          auto num_ase = value[1];
+
+          // Verify ase count if needed
+          if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase);
+
+          // Inject Streaming ASE state notification for each Source ASE
+          auto* ase_p = &value[2];
+          for (auto i = 0u; i < num_ase; ++i) {
+            /* Check if this is a valid ASE ID  */
+            auto ase_id = *ase_p++;
+            auto it = std::find_if(
+                device->ases_.begin(), device->ases_.end(),
+                [ase_id](auto& ase) { return (ase.id == ase_id); });
+            ASSERT_NE(it, device->ases_.end());
+
+            // Once we did the 'ReceiverStartReady' the server goes to
+            // Streaming, when in Source role
+            auto meta_len = *ase_p++;
+            auto num_handled_bytes = ase_p - value.data();
+            ase_p += num_handled_bytes;
+
+            const auto& ase = &(*it);
+            client_parser::ascs::ase_transient_state_params enable_params = {
+                .metadata = std::vector<uint8_t>(
+                    value.begin() + num_handled_bytes,
+                    value.begin() + num_handled_bytes + meta_len)};
+            InjectAseStateNotification(
+                ase, device, group, ascs::kAseStateStreaming, &enable_params);
+          }
+        };
+  }
+
+  void PrepareReceiverStopReady(LeAudioDeviceGroup* group,
+                                int verify_ase_count = 0) {
+    ase_ctp_handlers[ascs::kAseCtpOpcodeReceiverStopReady] =
+        [group, verify_ase_count](LeAudioDevice* device,
+                                  std::vector<uint8_t> value,
+                                  GATT_WRITE_OP_CB cb, void* cb_data) {
+          auto num_ase = value[1];
+
+          // Verify ase count if needed
+          if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase);
+
+          // Inject QoS configured ASE state notification for each Source ASE
+          auto* ase_p = &value[2];
+          for (auto i = 0u; i < num_ase; ++i) {
+            /* Check if this is a valid ASE ID  */
+            auto ase_id = *ase_p++;
+            auto it = std::find_if(
+                device->ases_.begin(), device->ases_.end(),
+                [ase_id](auto& ase) { return (ase.id == ase_id); });
+            ASSERT_NE(it, device->ases_.end());
+
+            const auto& ase = &(*it);
+
+            // FIXME: For now our fake peer does not remember qos params
+            client_parser::ascs::ase_qos_configured_state_params
+                qos_configured_state_params;
+            InjectAseStateNotification(ase, device, group,
+                                       ascs::kAseStateQoSConfigured,
+                                       &qos_configured_state_params);
+          }
+        };
+  }
+
+  void PrepareReleaseHandler(LeAudioDeviceGroup* group,
+                             int verify_ase_count = 0) {
+    ase_ctp_handlers[ascs::kAseCtpOpcodeRelease] =
+        [group, verify_ase_count, this](LeAudioDevice* device,
+                                        std::vector<uint8_t> value,
+                                        GATT_WRITE_OP_CB cb, void* cb_data) {
+          auto num_ase = value[1];
+
+          // Verify ase count if needed
+          if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase);
+          ASSERT_EQ(value.size(), 2ul + num_ase);
+
+          // Inject Releasing & Idle ASE state notification for each ASE
+          auto* ase_p = &value[2];
+          for (auto i = 0u; i < num_ase; ++i) {
+            /* Check if this is a valid ASE ID  */
+            auto ase_id = *ase_p++;
+            auto it = std::find_if(
+                device->ases_.begin(), device->ases_.end(),
+                [ase_id](auto& ase) { return (ase.id == ase_id); });
+            ASSERT_NE(it, device->ases_.end());
+            const auto ase = &(*it);
+
+            InjectAseStateNotification(ase, device, group,
+                                       ascs::kAseStateReleasing, nullptr);
+
+            /* Check if codec configuration is cached */
+            if (cached_codec_configuration_map_.count(ase_id) > 0) {
+              InjectAseStateNotification(
+                  ase, device, group, ascs::kAseStateCodecConfigured,
+                  &cached_codec_configuration_map_[ase_id]);
+            } else {
+              // Release - no caching
+              InjectAseStateNotification(ase, device, group,
+                                         ascs::kAseStateIdle, nullptr);
+            }
+          }
+        };
+  }
+
+  controller::MockControllerInterface mock_controller_;
+  bluetooth::manager::MockBtmInterface btm_interface;
+  gatt::MockBtaGattInterface gatt_interface;
+  gatt::MockBtaGattQueue gatt_queue;
+
+  bluetooth::hci::IsoManager* iso_manager_;
+  MockIsoManager* mock_iso_manager_;
+
+  std::function<void(LeAudioDevice* device, std::vector<uint8_t> value,
+                     GATT_WRITE_OP_CB cb, void* cb_data)>
+      ase_ctp_handlers[ascs::kAseCtpOpcodeMaxVal + 1] = {nullptr};
+  std::map<int, client_parser::ascs::ase_codec_configured_state_params>
+      cached_codec_configuration_map_;
+
+  MockLeAudioGroupStateMachineCallbacks mock_callbacks_;
+  std::vector<std::shared_ptr<LeAudioDevice>> le_audio_devices_;
+  std::map<uint8_t, std::unique_ptr<LeAudioDeviceGroup>>
+      le_audio_device_groups_;
+};
+
+TEST_F(StateMachineTest, testInit) {
+  ASSERT_NE(LeAudioGroupStateMachine::Get(), nullptr);
+}
+
+TEST_F(StateMachineTest, testCleanup) {
+  ASSERT_NE(LeAudioGroupStateMachine::Get(), nullptr);
+  LeAudioGroupStateMachine::Cleanup();
+  EXPECT_DEATH(LeAudioGroupStateMachine::Get(), "");
+}
+
+TEST_F(StateMachineTest, testConfigureCodecSingle) {
+  const auto context_type = kContextTypeRingtone;
+  const int leaudio_group_id = 2;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Ringtone context in mind, only one ASE
+   * should have been configured.
+   */
+  auto* leAudioDevice = group->GetFirstDevice();
+  PrepareConfigureCodecHandler(group, 1);
+
+  // Start the configuration and stream Media content
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(2);
+
+  InjectInitialIdleNotification(group);
+
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+}
+
+TEST_F(StateMachineTest, testConfigureCodecMulti) {
+  const auto context_type = kContextTypeMedia;
+  const auto leaudio_group_id = 2;
+  const auto num_devices = 2;
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group);
+
+  auto expected_devices_written = 0;
+  auto* leAudioDevice = group->GetFirstDevice();
+  while (leAudioDevice) {
+    EXPECT_CALL(gatt_queue,
+                WriteCharacteristic(leAudioDevice->conn_id_,
+                                    leAudioDevice->ctp_hdls_.val_hdl, _,
+                                    GATT_WRITE_NO_RSP, _, _))
+        .Times(AtLeast(1));
+    expected_devices_written++;
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream the content
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+}
+
+TEST_F(StateMachineTest, testConfigureQosSingle) {
+  const auto context_type = kContextTypeRingtone;
+  const int leaudio_group_id = 3;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Ringtone context in mind, only one ASE
+   * should have been configured.
+   */
+  auto* leAudioDevice = group->GetFirstDevice();
+  PrepareConfigureCodecHandler(group, 1);
+  PrepareConfigureQosHandler(group, 1);
+
+  // Start the configuration and stream Media content
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(3);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  InjectInitialIdleNotification(group);
+
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+}
+
+TEST_F(StateMachineTest, testConfigureQosMultiple) {
+  const auto context_type = kContextTypeMedia;
+  const auto leaudio_group_id = 3;
+  const auto num_devices = 2;
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group);
+  PrepareConfigureQosHandler(group);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  auto expected_devices_written = 0;
+  while (leAudioDevice) {
+    EXPECT_CALL(gatt_queue,
+                WriteCharacteristic(leAudioDevice->conn_id_,
+                                    leAudioDevice->ctp_hdls_.val_hdl, _,
+                                    GATT_WRITE_NO_RSP, _, _))
+        .Times(AtLeast(2));
+    expected_devices_written++;
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream Media content
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+}
+
+TEST_F(StateMachineTest, testStreamSingle) {
+  const auto context_type = kContextTypeRingtone;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Ringtone context in mind, only one ASE
+   * should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 1);
+  PrepareConfigureQosHandler(group, 1);
+  PrepareEnableHandler(group, 1);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(3);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  InjectInitialIdleNotification(group);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+  // Start the configuration and stream Media content
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+}
+
+TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
+  const auto context_type = kContextTypeRingtone;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Ringtone context in mind, only one ASE
+   * should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 1);
+  PrepareConfigureQosHandler(group, 1);
+  PrepareEnableHandler(group, 1, false);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(3);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  InjectInitialIdleNotification(group);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+  // Start the configuration and stream Media content
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+}
+
+TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
+  const auto context_type = kContextTypeConversational;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Conversional context in mind, one Sink ASE
+   * and one Source ASE should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 2);
+  PrepareConfigureQosHandler(group, 2);
+  PrepareEnableHandler(group, 2, false);
+  PrepareReceiverStartReady(group, 1);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(4);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  InjectInitialIdleNotification(group);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+  // Start the configuration and stream Media content
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+}
+
+TEST_F(StateMachineTest, testStreamMultipleConversational) {
+  const auto context_type = kContextTypeConversational;
+  const auto leaudio_group_id = 4;
+  const auto num_devices = 2;
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group);
+  PrepareConfigureQosHandler(group);
+  PrepareEnableHandler(group);
+  PrepareReceiverStartReady(group, 1);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(AtLeast(1));
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  InjectInitialIdleNotification(group);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  auto expected_devices_written = 0;
+  while (leAudioDevice) {
+    EXPECT_CALL(gatt_queue,
+                WriteCharacteristic(leAudioDevice->conn_id_,
+                                    leAudioDevice->ctp_hdls_.val_hdl, _,
+                                    GATT_WRITE_NO_RSP, _, _))
+        .Times(AtLeast(3));
+    expected_devices_written++;
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+  // Start the configuration and stream Media content
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+}
+
+TEST_F(StateMachineTest, testStreamMultiple) {
+  const auto context_type = kContextTypeMedia;
+  const auto leaudio_group_id = 4;
+  const auto num_devices = 2;
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group);
+  PrepareConfigureQosHandler(group);
+  PrepareEnableHandler(group);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(AtLeast(1));
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  InjectInitialIdleNotification(group);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  auto expected_devices_written = 0;
+  while (leAudioDevice) {
+    EXPECT_CALL(gatt_queue,
+                WriteCharacteristic(leAudioDevice->conn_id_,
+                                    leAudioDevice->ctp_hdls_.val_hdl, _,
+                                    GATT_WRITE_NO_RSP, _, _))
+        .Times(AtLeast(3));
+    expected_devices_written++;
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+  // Start the configuration and stream Media content
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+}
+
+TEST_F(StateMachineTest, testDisableSingle) {
+  const auto context_type = kContextTypeRingtone;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Ringtone context in mind, only one ASE
+   * should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 1);
+  PrepareConfigureQosHandler(group, 1);
+  PrepareEnableHandler(group, 1);
+  PrepareDisableHandler(group, 1);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(4);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::SUSPENDED));
+
+  // Suspend the stream
+  LeAudioGroupStateMachine::Get()->SuspendStream(group);
+
+  // Check if group has transition to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+}
+
+TEST_F(StateMachineTest, testDisableMultiple) {
+  const auto context_type = kContextTypeMedia;
+  const auto leaudio_group_id = 4;
+  const auto num_devices = 2;
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group);
+  PrepareConfigureQosHandler(group);
+  PrepareEnableHandler(group);
+  PrepareDisableHandler(group);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  auto expected_devices_written = 0;
+  while (leAudioDevice) {
+    EXPECT_CALL(gatt_queue,
+                WriteCharacteristic(leAudioDevice->conn_id_,
+                                    leAudioDevice->ctp_hdls_.val_hdl, _,
+                                    GATT_WRITE_NO_RSP, _, _))
+        .Times(AtLeast(4));
+    expected_devices_written++;
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::SUSPENDED));
+
+  // Suspend the stream
+  LeAudioGroupStateMachine::Get()->SuspendStream(group);
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+}
+
+TEST_F(StateMachineTest, testDisableBidirectional) {
+  const auto context_type = kContextTypeConversational;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Conversional context in mind, Sink and Source
+   * ASEs should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 2);
+  PrepareConfigureQosHandler(group, 2);
+  PrepareEnableHandler(group, 2);
+  PrepareDisableHandler(group, 2);
+  PrepareReceiverStartReady(group, 1);
+  PrepareReceiverStopReady(group, 1);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(AtLeast(4));
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  // Suspend the stream
+  LeAudioGroupStateMachine::Get()->SuspendStream(group);
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+}
+
+TEST_F(StateMachineTest, testReleaseSingle) {
+  const auto context_type = kContextTypeRingtone;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Ringtone context in mind, only one ASE
+   * should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 1);
+  PrepareConfigureQosHandler(group, 1);
+  PrepareEnableHandler(group, 1);
+  PrepareDisableHandler(group, 1);
+  PrepareReleaseHandler(group, 1);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(4);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(mock_callbacks_,
+              StatusReportCb(leaudio_group_id,
+                             bluetooth::le_audio::GroupStreamStatus::IDLE));
+
+  // Stop the stream
+  LeAudioGroupStateMachine::Get()->StopStream(group);
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+}
+
+TEST_F(StateMachineTest, testReleaseCachingSingle) {
+  const auto context_type = kContextTypeRingtone;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Ringtone context in mind, only one ASE
+   * should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 1, true);
+  PrepareConfigureQosHandler(group, 1);
+  PrepareEnableHandler(group, 1);
+  PrepareDisableHandler(group, 1);
+  PrepareReleaseHandler(group, 1);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(4);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+
+  InjectInitialIdleNotification(group);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(mock_callbacks_,
+              StatusReportCb(leaudio_group_id,
+                             bluetooth::le_audio::GroupStreamStatus::IDLE));
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  // Stop the stream
+  LeAudioGroupStateMachine::Get()->StopStream(group);
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+}
+
+TEST_F(StateMachineTest, testStreamCachingSingle) {
+  const auto context_type = kContextTypeRingtone;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Ringtone context in mind, only one ASE
+   * should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 1, true);
+  PrepareConfigureQosHandler(group, 1);
+  PrepareEnableHandler(group, 1);
+  PrepareDisableHandler(group, 1);
+  PrepareReleaseHandler(group, 1);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(4 + 3);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+
+  InjectInitialIdleNotification(group);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(mock_callbacks_,
+              StatusReportCb(leaudio_group_id,
+                             bluetooth::le_audio::GroupStreamStatus::IDLE));
+
+  EXPECT_CALL(mock_callbacks_,
+              StatusReportCb(leaudio_group_id,
+                             bluetooth::le_audio::GroupStreamStatus::STREAMING))
+      .Times(2);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  // Stop the stream
+  LeAudioGroupStateMachine::Get()->StopStream(group);
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+}
+
+TEST_F(StateMachineTest, testReleaseMultiple) {
+  const auto context_type = kContextTypeMedia;
+  const auto leaudio_group_id = 6;
+  const auto num_devices = 2;
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group);
+  PrepareConfigureQosHandler(group);
+  PrepareEnableHandler(group);
+  PrepareDisableHandler(group);
+  PrepareReleaseHandler(group);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  auto expected_devices_written = 0;
+  while (leAudioDevice) {
+    EXPECT_CALL(gatt_queue,
+                WriteCharacteristic(leAudioDevice->conn_id_,
+                                    leAudioDevice->ctp_hdls_.val_hdl, _,
+                                    GATT_WRITE_NO_RSP, _, _))
+        .Times(AtLeast(4));
+    expected_devices_written++;
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(mock_callbacks_,
+              StatusReportCb(leaudio_group_id,
+                             bluetooth::le_audio::GroupStreamStatus::IDLE));
+
+  // Stop the stream
+  LeAudioGroupStateMachine::Get()->StopStream(group);
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+}
+
+TEST_F(StateMachineTest, testReleaseBidirectional) {
+  const auto context_type = kContextTypeConversational;
+  const auto leaudio_group_id = 6;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Conversional context in mind, Sink and Source
+   * ASEs should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 2);
+  PrepareConfigureQosHandler(group, 2);
+  PrepareEnableHandler(group, 2);
+  PrepareDisableHandler(group, 2);
+  PrepareReceiverStartReady(group, 1);
+  PrepareReleaseHandler(group, 2);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(AtLeast(4));
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  // Stop the stream
+  LeAudioGroupStateMachine::Get()->StopStream(group);
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+}
+
+TEST_F(StateMachineTest, testDisableAndReleaseBidirectional) {
+  const auto context_type = kContextTypeConversational;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Conversional context in mind, Sink and Source
+   * ASEs should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 2);
+  PrepareConfigureQosHandler(group, 2);
+  PrepareEnableHandler(group, 2);
+  PrepareDisableHandler(group, 2);
+  PrepareReceiverStartReady(group, 1);
+  PrepareReceiverStopReady(group, 1);
+  PrepareReleaseHandler(group, 2);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(AtLeast(4));
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type));
+
+  // Suspend the stream
+  LeAudioGroupStateMachine::Get()->SuspendStream(group);
+
+  // Stop the stream
+  LeAudioGroupStateMachine::Get()->StopStream(group);
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+}
+
+TEST_F(StateMachineTest, testAseIdAssignmentIdle) {
+  const auto context_type = kContextTypeConversational;
+  const auto leaudio_group_id = 6;
+  const auto num_devices = 1;
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  // Should not trigger any action on our side
+  EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  for (auto* device = group->GetFirstDevice(); device != nullptr;
+       device = group->GetNextDevice(device)) {
+    for (auto& ase : device->ases_) {
+      ASSERT_EQ(ase.id, le_audio::types::ase::kAseIdInvalid);
+      InjectAseStateNotification(&ase, device, group, ascs::kAseStateIdle,
+                                 nullptr);
+      ASSERT_EQ(ase.id, ase_id_last_assigned);
+    }
+  }
+}
+
+TEST_F(StateMachineTest, testAseIdAssignmentCodecConfigured) {
+  const auto context_type = kContextTypeConversational;
+  const auto leaudio_group_id = 6;
+  const auto num_devices = 1;
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  // Should not trigger any action on our side
+  EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+
+  for (auto* device = group->GetFirstDevice(); device != nullptr;
+       device = group->GetNextDevice(device)) {
+    for (auto& ase : device->ases_) {
+      client_parser::ascs::ase_codec_configured_state_params
+          codec_configured_state_params;
+
+      ASSERT_EQ(ase.id, le_audio::types::ase::kAseIdInvalid);
+      InjectAseStateNotification(&ase, device, group,
+                                 ascs::kAseStateCodecConfigured,
+                                 &codec_configured_state_params);
+      ASSERT_EQ(ase.id, ase_id_last_assigned);
+    }
+  }
+}
+
+TEST_F(StateMachineTest, testAseAutonomousRelease) {
+  const auto context_type = kContextTypeConversational;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Conversional context in mind, Sink and Source
+   * ASEs should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 2);
+  PrepareConfigureQosHandler(group, 2);
+  PrepareEnableHandler(group, 2);
+  PrepareDisableHandler(group, 2);
+  PrepareReceiverStartReady(group, 1);
+  PrepareReceiverStopReady(group, 1);
+  PrepareReleaseHandler(group, 2);
+
+  InjectInitialIdleNotification(group);
+
+  // Validate initial GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+  // Start the configuration and stream Media content
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Validate new GroupStreamStatus
+  EXPECT_CALL(mock_callbacks_,
+              StatusReportCb(leaudio_group_id,
+                             bluetooth::le_audio::GroupStreamStatus::IDLE));
+
+  for (auto* device = group->GetFirstDevice(); device != nullptr;
+       device = group->GetNextDevice(device)) {
+    for (auto& ase : device->ases_) {
+      client_parser::ascs::ase_codec_configured_state_params
+          codec_configured_state_params;
+
+      ASSERT_EQ(ase.state, types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+      // Each one does the autonomous release
+      InjectAseStateNotification(&ase, device, group, ascs::kAseStateReleasing,
+                                 &codec_configured_state_params);
+      InjectAseStateNotification(&ase, device, group, ascs::kAseStateIdle,
+                                 &codec_configured_state_params);
+    }
+  }
+
+  // Verify we've handled the release and updated all states
+  for (auto* device = group->GetFirstDevice(); device != nullptr;
+       device = group->GetNextDevice(device)) {
+    for (auto& ase : device->ases_) {
+      ASSERT_EQ(ase.state, types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+    }
+  }
+}
+
+TEST_F(StateMachineTest, testStateTransitionTimeout) {
+  const auto context_type = kContextTypeRingtone;
+  const int leaudio_group_id = 4;
+
+  // Prepare fake connected device group
+  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+  /* Since we prepared device with Ringtone context in mind, only one ASE
+   * should have been configured.
+   */
+  PrepareConfigureCodecHandler(group, 1);
+  PrepareConfigureQosHandler(group, 1);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+                                  GATT_WRITE_NO_RSP, _, _))
+      .Times(3);
+
+  // Start the configuration and stream Media content
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<types::LeAudioContextType>(context_type)));
+
+  // Check if timeout is fired
+  EXPECT_CALL(mock_callbacks_, OnStateTransitionTimeout(leaudio_group_id));
+
+  // simulate timeout seconds passed, alarm executing
+  fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data);
+  ASSERT_EQ(1, mock_function_count_map["alarm_set_on_mloop"]);
+}
+}  // namespace internal
+}  // namespace le_audio
diff --git a/system/bta/test/common/btif_storage_mock.cc b/system/bta/test/common/btif_storage_mock.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1d389c68050e2cdd3b52ec85804e60315579cff2
--- /dev/null
+++ b/system/bta/test/common/btif_storage_mock.cc
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "btif_storage_mock.h"
+
+#include <base/logging.h>
+
+static bluetooth::storage::MockBtifStorageInterface* btif_storage_interface =
+    nullptr;
+
+void bluetooth::storage::SetMockBtifStorageInterface(
+    MockBtifStorageInterface* mock_btif_storage_interface) {
+  btif_storage_interface = mock_btif_storage_interface;
+}
+
+void btif_storage_set_leaudio_autoconnect(RawAddress const& addr,
+                                          bool autoconnect) {
+  LOG_ASSERT(btif_storage_interface) << "Mock storage module not set!";
+  btif_storage_interface->AddLeaudioAutoconnect(addr, autoconnect);
+}
+
+void btif_storage_remove_leaudio(RawAddress const& addr) {
+  LOG_ASSERT(btif_storage_interface) << "Mock storage module not set!";
+  btif_storage_interface->RemoveLeaudio(addr);
+}
\ No newline at end of file
diff --git a/system/bta/test/common/btif_storage_mock.h b/system/bta/test/common/btif_storage_mock.h
new file mode 100644
index 0000000000000000000000000000000000000000..1087bc9c594509b74254dcd0a4c421f74b65a9f6
--- /dev/null
+++ b/system/bta/test/common/btif_storage_mock.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "types/raw_address.h"
+
+namespace bluetooth {
+namespace storage {
+
+class BtifStorageInterface {
+ public:
+  virtual void AddLeaudioAutoconnect(RawAddress const& addr,
+                                     bool autoconnect) = 0;
+  virtual void RemoveLeaudio(RawAddress const& addr) = 0;
+  virtual ~BtifStorageInterface() = default;
+};
+
+class MockBtifStorageInterface : public BtifStorageInterface {
+ public:
+  MOCK_METHOD((void), AddLeaudioAutoconnect,
+              (RawAddress const& addr, bool autoconnect), (override));
+  MOCK_METHOD((void), RemoveLeaudio, (RawAddress const& addr), (override));
+};
+
+/**
+ * Set the {@link MockBifStorageInterface} for testing
+ *
+ * @param mock_btif_storage_interface pointer to mock btm security
+ * internal interface, could be null
+ */
+void SetMockBtifStorageInterface(
+    MockBtifStorageInterface* mock_btif_storage_interface);
+
+}  // namespace storage
+}  // namespace bluetooth
diff --git a/system/btif/Android.bp b/system/btif/Android.bp
index a1cd9759752478bba86ebdd9b0106959e21d8472..d541ee2318503f9a300f2fc7c34c3f9e6be9b36f 100644
--- a/system/btif/Android.bp
+++ b/system/btif/Android.bp
@@ -178,7 +178,10 @@ cc_library_static {
             srcs: ["src/btif_avrcp_audio_track.cc"],
         },
         host: {
-            srcs: ["src/btif_avrcp_audio_track_linux.cc"],
+            srcs: [
+                     "src/btif_avrcp_audio_track_linux.cc",
+                     "src/btif_leaudio_hal_version_host.cc",
+                  ],
         },
     },
     whole_static_libs: [
@@ -228,6 +231,7 @@ cc_test {
         "libbt-utils",
         "libFraunhoferAAC",
         "libg722codec",
+        "liblc3codec",
         "libbtdevice",
         "libbt-hci",
         "libudrv-uipc",
@@ -405,6 +409,7 @@ cc_test {
           ":TestMockBtaHf",
           ":TestMockBtaHh",
           ":TestMockBtaJv",
+          ":TestMockBtaLeAudio",
           ":TestMockBtaPan",
           ":TestMockBtaSdp",
           ":TestMockBtaSys",
diff --git a/system/btif/include/btif_storage.h b/system/btif/include/btif_storage.h
index c5aee96f9c97d8ffd96c520f6fcf7ef6f9fbcd23..a86ad609071f66abaf09cb7ed683e91b5072581b 100644
--- a/system/btif/include/btif_storage.h
+++ b/system/btif/include/btif_storage.h
@@ -264,6 +264,16 @@ bool btif_storage_get_hearing_aid_prop(
     const RawAddress& address, uint8_t* capabilities, uint64_t* hi_sync_id,
     uint16_t* render_delay, uint16_t* preparation_delay, uint16_t* codecs);
 
+/** Store Le Audio device autoconnect flag */
+void btif_storage_set_leaudio_autoconnect(const RawAddress& addr,
+                                          bool autoconnect);
+
+/** Remove Le Audio device from the storage */
+void btif_storage_remove_leaudio(const RawAddress& address);
+
+/** Load bonded Le Audio devices */
+void btif_storage_load_bonded_leaudio(void);
+
 /*******************************************************************************
  *
  * Function         btif_storage_is_retricted_device
diff --git a/system/btif/src/bluetooth.cc b/system/btif/src/bluetooth.cc
index 8df55805dc372edf28929957d565fa01e5523996..1fec5b54aad97ea00b161d840b5985221d71fd00 100644
--- a/system/btif/src/bluetooth.cc
+++ b/system/btif/src/bluetooth.cc
@@ -51,6 +51,7 @@
 #include "bta/include/bta_csis_api.h"
 #include "bta/include/bta_hearing_aid_api.h"
 #include "bta/include/bta_hf_client_api.h"
+#include "bta/include/bta_le_audio_api.h"
 #include "btif/avrcp/avrcp_service.h"
 #include "btif/include/stack_manager.h"
 #include "btif_a2dp.h"
@@ -403,6 +404,7 @@ static void dump(int fd, const char** arguments) {
   alarm_debug_dump(fd);
   bluetooth::csis::CsisClient::DebugDump(fd);
   HearingAid::DebugDump(fd);
+  LeAudioClient::DebugDump(fd);
   connection_manager::dump(fd);
   bluetooth::bqr::DebugDump(fd);
   if (bluetooth::shim::is_any_gd_enabled()) {
diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc
index 51600ed090ec9a98dce2f48266d4a0c3c189a1cd..f938987c591f7e662c3872cc6d1d400955714be4 100644
--- a/system/btif/src/btif_dm.cc
+++ b/system/btif/src/btif_dm.cc
@@ -35,6 +35,7 @@
 #include <hardware/bluetooth.h>
 #include <hardware/bt_csis.h>
 #include <hardware/bt_hearing_aid.h>
+#include <hardware/bt_le_audio.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -49,6 +50,7 @@
 #include "bta_csis_api.h"
 #include "bta_dm_int.h"
 #include "bta_gatt_api.h"
+#include "bta_le_audio_api.h"
 #include "btif/include/stack_manager.h"
 #include "btif_api.h"
 #include "btif_av.h"
@@ -86,6 +88,7 @@ using bluetooth::Uuid;
 const Uuid UUID_HEARING_AID = Uuid::FromString("FDF0");
 const Uuid UUID_VC = Uuid::FromString("1844");
 const Uuid UUID_CSIS = Uuid::FromString("1846");
+const Uuid UUID_LE_AUDIO = Uuid::FromString("184E");
 
 #define COD_MASK 0x07FF
 
@@ -244,6 +247,8 @@ extern bt_status_t btif_hd_execute_service(bool b_enable);
 extern bluetooth::hearing_aid::HearingAidInterface*
 btif_hearing_aid_get_interface();
 extern bluetooth::csis::CsisClientInterface* btif_csis_client_get_interface();
+extern bluetooth::le_audio::LeAudioClientInterface*
+btif_le_audio_get_interface();
 
 /******************************************************************************
  *  Functions
@@ -1304,7 +1309,7 @@ static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event,
 /* Returns true if |uuid| should be passed as device property */
 static bool btif_is_interesting_le_service(bluetooth::Uuid uuid) {
   return (uuid.As16Bit() == UUID_SERVCLASS_LE_HID || uuid == UUID_HEARING_AID ||
-          uuid == UUID_VC || uuid == UUID_CSIS);
+          uuid == UUID_VC || uuid == UUID_CSIS || uuid == UUID_LE_AUDIO);
 }
 
 /*******************************************************************************
@@ -1592,6 +1597,9 @@ static void btif_dm_upstreams_evt(uint16_t event, char* p_param) {
       if (bluetooth::csis::CsisClient::IsCsisClientRunning())
         btif_csis_client_get_interface()->RemoveDevice(bd_addr);
 
+      if (LeAudioClient::IsLeAudioClientRunning())
+        btif_le_audio_get_interface()->RemoveDevice(bd_addr);
+
       btif_storage_remove_bonded_device(&bd_addr);
       bond_state_changed(BT_STATUS_SUCCESS, bd_addr, BT_BOND_STATE_NONE);
       break;
diff --git a/system/btif/src/btif_le_audio.cc b/system/btif/src/btif_le_audio.cc
index dea691b3e90abad2e7fb0b3012441e6c675da84f..73a8e0fb4651e512e9f9de6b67be88ea914ebae9 100644
--- a/system/btif/src/btif_le_audio.cc
+++ b/system/btif/src/btif_le_audio.cc
@@ -20,10 +20,11 @@
 
 #include <vector>
 
+#include "audio_hal_interface/hal_version_manager.h"
+#include "bta_le_audio_api.h"
 #include "btif_common.h"
 #include "btif_storage.h"
-#include "hardware/bt_le_audio.h"
-#include "types/raw_address.h"
+#include "stack/include/btu.h"
 
 using base::Bind;
 using base::Unretained;
@@ -70,21 +71,69 @@ class LeAudioClientInterfaceImpl : public LeAudioClientInterface,
 
   void Initialize(LeAudioClientCallbacks* callbacks) override {
     this->callbacks = callbacks;
+    do_in_main_thread(
+        FROM_HERE,
+        Bind(&LeAudioClient::Initialize, this,
+             jni_thread_wrapper(FROM_HERE,
+                                Bind(&btif_storage_load_bonded_leaudio)),
+             base::Bind([]() -> bool {
+               return bluetooth::audio::HalVersionManager::GetHalVersion() ==
+                      bluetooth::audio::BluetoothAudioHalVersion::VERSION_2_1;
+             })));
   }
 
-  void Cleanup(void) override {}
+  void Cleanup(void) override {
+    DVLOG(2) << __func__;
+    do_in_main_thread(FROM_HERE, Bind(&LeAudioClient::Cleanup));
+  }
+
+  void RemoveDevice(const RawAddress& address) override {
+    DVLOG(2) << __func__ << " address: " << address;
+    do_in_main_thread(FROM_HERE,
+                      Bind(&LeAudioClient::RemoveDevice,
+                           Unretained(LeAudioClient::Get()), address));
 
-  void RemoveDevice(const RawAddress& address) override {}
+    do_in_jni_thread(FROM_HERE, Bind(&btif_storage_remove_leaudio, address));
+  }
 
-  void Connect(const RawAddress& address) override {}
+  void Connect(const RawAddress& address) override {
+    DVLOG(2) << __func__ << " address: " << address;
+    do_in_main_thread(FROM_HERE,
+                      Bind(&LeAudioClient::Connect,
+                           Unretained(LeAudioClient::Get()), address));
+  }
 
-  void GroupAddNode(const int group_id, const RawAddress& addr) override {}
+  void Disconnect(const RawAddress& address) override {
+    DVLOG(2) << __func__ << " address: " << address;
+    do_in_main_thread(FROM_HERE,
+                      Bind(&LeAudioClient::Disconnect,
+                           Unretained(LeAudioClient::Get()), address));
+    do_in_jni_thread(
+        FROM_HERE, Bind(&btif_storage_set_leaudio_autoconnect, address, false));
+  }
 
-  void Disconnect(const RawAddress& address) override {}
+  void GroupAddNode(const int group_id, const RawAddress& address) override {
+    DVLOG(2) << __func__ << " group_id: " << group_id
+             << " address: " << address;
+    do_in_main_thread(
+        FROM_HERE, Bind(&LeAudioClient::GroupAddNode,
+                        Unretained(LeAudioClient::Get()), group_id, address));
+  }
 
-  void GroupRemoveNode(const int group_id, const RawAddress& addr) override {}
+  void GroupRemoveNode(const int group_id, const RawAddress& address) override {
+    DVLOG(2) << __func__ << " group_id: " << group_id
+             << " address: " << address;
+    do_in_main_thread(
+        FROM_HERE, Bind(&LeAudioClient::GroupRemoveNode,
+                        Unretained(LeAudioClient::Get()), group_id, address));
+  }
 
-  void GroupSetActive(const int group_id) override {}
+  void GroupSetActive(const int group_id) override {
+    DVLOG(2) << __func__ << " group_id: " << group_id;
+    do_in_main_thread(FROM_HERE,
+                      Bind(&LeAudioClient::GroupSetActive,
+                           Unretained(LeAudioClient::Get()), group_id));
+  }
 
  private:
   LeAudioClientCallbacks* callbacks;
diff --git a/system/btif/src/btif_leaudio_hal_version_host.cc b/system/btif/src/btif_leaudio_hal_version_host.cc
new file mode 100644
index 0000000000000000000000000000000000000000..a093f80ad3f93f58d77de226498c26edd787faca
--- /dev/null
+++ b/system/btif/src/btif_leaudio_hal_version_host.cc
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+
+#include "embdrv/lc3/Api/Lc3Encoder.hpp"
+
+namespace bluetooth {
+namespace audio {
+class HalVersionManager {
+  static std::unique_ptr<HalVersionManager> instance_ptr;
+};
+}  // namespace audio
+}  // namespace bluetooth
+
+std::unique_ptr<bluetooth::audio::HalVersionManager>
+    bluetooth::audio::HalVersionManager::instance_ptr;
diff --git a/system/btif/src/btif_storage.cc b/system/btif/src/btif_storage.cc
index 572005c8a0dbca5f61da2643a3398aa048f86c6d..38640e235d9d070cdd827f23b6774a8425826696 100644
--- a/system/btif/src/btif_storage.cc
+++ b/system/btif/src/btif_storage.cc
@@ -44,6 +44,7 @@
 #include "bta_hd_api.h"
 #include "bta_hearing_aid_api.h"
 #include "bta_hh_api.h"
+#include "bta_le_audio_api.h"
 #include "btif_api.h"
 #include "btif_config.h"
 #include "btif_hd.h"
@@ -94,6 +95,7 @@ using bluetooth::groups::DeviceGroups;
 #define BTIF_STORAGE_DEVICE_GROUP_BIN "DeviceGroupBin"
 #define BTIF_STORAGE_CSIS_AUTOCONNECT "CsisAutoconnect"
 #define BTIF_STORAGE_CSIS_SET_INFO_BIN "CsisSetInfoBin"
+#define BTIF_STORAGE_LEAUDIO_AUTOCONNECT "LeAudioAutoconnect"
 
 /* This is a local property to add a device found */
 #define BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP 0xFF
@@ -1735,6 +1737,64 @@ uint8_t btif_storage_get_sr_supp_feat(const RawAddress& bd_addr) {
   return value;
 }
 
+/** Set autoconnect information for LeAudio device */
+void btif_storage_set_leaudio_autoconnect(const RawAddress& addr,
+                                          bool autoconnect) {
+  do_in_jni_thread(FROM_HERE, Bind(
+                                  [](const RawAddress& addr, bool autoconnect) {
+                                    std::string bdstr = addr.ToString();
+                                    VLOG(2)
+                                        << "saving le audio device: " << bdstr;
+                                    btif_config_set_int(
+                                        bdstr, BTIF_STORAGE_LEAUDIO_AUTOCONNECT,
+                                        autoconnect);
+                                    btif_config_save();
+                                  },
+                                  addr, autoconnect));
+}
+
+/** Loads information about bonded Le Audio devices */
+void btif_storage_load_bonded_leaudio() {
+  for (const auto& bd_addr : btif_config_get_paired_devices()) {
+    auto name = bd_addr.ToString();
+
+    int size = STORAGE_UUID_STRING_SIZE * BT_MAX_NUM_UUIDS;
+    char uuid_str[size];
+    bool isLeAudioDevice = false;
+    if (btif_config_get_str(name, BTIF_STORAGE_PATH_REMOTE_SERVICE, uuid_str,
+                            &size)) {
+      Uuid p_uuid[BT_MAX_NUM_UUIDS];
+      size_t num_uuids =
+          btif_split_uuids_string(uuid_str, p_uuid, BT_MAX_NUM_UUIDS);
+      for (size_t i = 0; i < num_uuids; i++) {
+        if (p_uuid[i] == Uuid::FromString("184E")) {
+          isLeAudioDevice = true;
+          break;
+        }
+      }
+    }
+    if (!isLeAudioDevice) {
+      continue;
+    }
+
+    BTIF_TRACE_DEBUG("Remote device:%s", name.c_str());
+
+    int value;
+    bool autoconnect = false;
+    if (btif_config_get_int(name, BTIF_STORAGE_LEAUDIO_AUTOCONNECT, &value))
+      autoconnect = !!value;
+
+    do_in_main_thread(
+        FROM_HERE, Bind(&LeAudioClient::AddFromStorage, bd_addr, autoconnect));
+  }
+}
+
+/** Remove the Le Audio device from storage */
+void btif_storage_remove_leaudio(const RawAddress& address) {
+  std::string addrstr = address.ToString();
+  btif_config_set_int(addrstr, BTIF_STORAGE_LEAUDIO_AUTOCONNECT, false);
+}
+
 /** Adds the bonded Le Audio device grouping info into the NVRAM */
 void btif_storage_add_groups(const RawAddress& addr) {
   std::vector<uint8_t> group_info;
diff --git a/system/embdrv/lc3/Android.bp b/system/embdrv/lc3/Android.bp
index 2ba76485cb6424f32fb1598dd964bf2dcb5a8ad7..ce1302f85df71e5379de05f1eee4c30b0cbf8fd5 100644
--- a/system/embdrv/lc3/Android.bp
+++ b/system/embdrv/lc3/Android.bp
@@ -33,13 +33,17 @@ cc_library_static {
         "-Wno-self-assign",
         "-Wno-implicit-fallthrough",
     ],
-    sanitize: {
-        misc_undefined:[
-           "unsigned-integer-overflow",
-           "signed-integer-overflow",
-           "bounds",
-        ],
-        cfi: true,
+    target: {
+       android: {
+            sanitize: {
+                misc_undefined:[
+                   "unsigned-integer-overflow",
+                   "signed-integer-overflow",
+                   "bounds",
+                ],
+                cfi: true,
+            },
+       },
     },
     shared_libs: [
         "liblog",
diff --git a/system/gd/rust/topshim/facade/Android.bp b/system/gd/rust/topshim/facade/Android.bp
index d7d046cc59fdd36bdb474705d675b551320b528a..8afff7e493da8f0088d49411f9545b746c3845e5 100644
--- a/system/gd/rust/topshim/facade/Android.bp
+++ b/system/gd/rust/topshim/facade/Android.bp
@@ -43,6 +43,7 @@ rust_binary_host {
         "libbt-sbc-encoder",
         "libFraunhoferAAC",
         "libg722codec",
+        "liblc3codec",
         "libudrv-uipc",
         "libbluetooth_gd", // Gabeldorsche
         "libbluetooth_rust_interop",
diff --git a/system/include/hardware/bluetooth.h b/system/include/hardware/bluetooth.h
index 0810af92f5860d1f07a49cf38d5e9af9fff49e17..28069dbeeaeafa266d438e6d7d5fb1330f942f5c 100644
--- a/system/include/hardware/bluetooth.h
+++ b/system/include/hardware/bluetooth.h
@@ -54,6 +54,7 @@
 #define BT_ACTIVITY_ATTRIBUTION_ID "activity_attribution"
 #define BT_PROFILE_VC_ID "volume_control"
 #define BT_PROFILE_CSIS_CLIENT_ID "csis_client"
+#define BT_PROFILE_LE_AUDIO_ID "le_audio"
 
 /** Bluetooth Device Name */
 typedef struct { uint8_t name[249]; } __attribute__((packed)) bt_bdname_t;
diff --git a/system/main/Android.bp b/system/main/Android.bp
index 93e3387edb4e51d80e3adcc436f030ec48acfa83..a03b10d3184056836989b1ce76015e8707fe83db 100644
--- a/system/main/Android.bp
+++ b/system/main/Android.bp
@@ -103,7 +103,7 @@ cc_library_shared {
         "libbt-sbc-encoder",
         "libFraunhoferAAC",
         "libg722codec",
-	"liblc3codec",
+        "liblc3codec",
         "libudrv-uipc",
         "libprotobuf-cpp-lite",
         "libbluetooth_gd", // Gabeldorsche
diff --git a/system/stack/test/fuzzers/Android.bp b/system/stack/test/fuzzers/Android.bp
index 56a8178d03fb8bac0c50e9f76a02b1687fe7ac2b..27debff9cdfe9389410523ad4f64a50c4ae37c17 100644
--- a/system/stack/test/fuzzers/Android.bp
+++ b/system/stack/test/fuzzers/Android.bp
@@ -34,6 +34,7 @@ cc_defaults {
         "libbt-hci",
         "libbtdevice",
         "libg722codec",
+        "liblc3codec",
         "libosi",
         "libudrv-uipc",
         "libbt-protos-lite",
diff --git a/system/test/Android.bp b/system/test/Android.bp
index a7806d638c156ecb2d980374db8e2939d5b7c52c..3f5fea75464247528be2e6118e895d0aff58c206 100644
--- a/system/test/Android.bp
+++ b/system/test/Android.bp
@@ -96,6 +96,13 @@ filegroup {
     ],
 }
 
+filegroup {
+    name: "TestMockBtaLeAudio",
+    srcs: [
+      "mock/mock_bta_leaudio*.cc",
+    ],
+}
+
 filegroup {
     name: "TestMockBtaPan",
     srcs: [
@@ -139,6 +146,7 @@ filegroup {
       ":TestMockBtaHf",
       ":TestMockBtaHh",
       ":TestMockBtaJv",
+      ":TestMockBtaLeAudio",
       ":TestMockBtaPan",
       ":TestMockBtaSdp",
       ":TestMockBtaSys",
diff --git a/system/test/mock/mock_bta_leaudio.cc b/system/test/mock/mock_bta_leaudio.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b44269709c95bd3b0e91fe4513800eaef2598a88
--- /dev/null
+++ b/system/test/mock/mock_bta_leaudio.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Generated mock file from original source file
+ *   Functions generated:7
+ */
+
+#include <map>
+#include <memory>
+#include <string>
+
+extern std::map<std::string, int> mock_function_count_map;
+
+#include <base/bind.h>
+#include <base/bind_helpers.h>
+#include <hardware/bt_le_audio.h>
+
+#include "bta/include/bta_le_audio_api.h"
+#include "types/raw_address.h"
+
+#ifndef UNUSED_ATTR
+#define UNUSED_ATTR
+#endif
+
+/* Empty class to satisfy compiler */
+namespace bluetooth {
+namespace audio {
+class HalVersionManager {
+  static std::unique_ptr<HalVersionManager> instance_ptr;
+};
+}  // namespace audio
+}  // namespace bluetooth
+
+void LeAudioClient::AddFromStorage(const RawAddress& address,
+                                   bool auto_connect) {
+  mock_function_count_map[__func__]++;
+}
+void LeAudioClient::Cleanup() { mock_function_count_map[__func__]++; }
+
+LeAudioClient* LeAudioClient::Get(void) {
+  mock_function_count_map[__func__]++;
+  return nullptr;
+}
+bool LeAudioClient::IsLeAudioClientRunning(void) {
+  mock_function_count_map[__func__]++;
+  return false;
+}
+void LeAudioClient::Initialize(
+    bluetooth::le_audio::LeAudioClientCallbacks* callbacks_,
+    base::Closure initCb, base::Callback<bool()> hal_2_1_verifier) {
+  mock_function_count_map[__func__]++;
+}
+void LeAudioClient::DebugDump(int fd) { mock_function_count_map[__func__]++; }
\ No newline at end of file