/*
 * Copyright 2023 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 "codec_manager.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>

#include "audio_hal_client/audio_hal_client.h"
#include "audio_hal_interface/le_audio_software.h"
#include "hci/controller_interface_mock.h"
#include "hci/hci_packets.h"
#include "internal_include/stack_config.h"
#include "le_audio/le_audio_types.h"
#include "le_audio_set_configuration_provider.h"
#include "test/mock/mock_legacy_hci_interface.h"
#include "test/mock/mock_main_shim_entry.h"

// TODO(b/369381361) Enfore -Wmissing-prototypes
#pragma GCC diagnostic ignored "-Wmissing-prototypes"

using ::testing::_;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::Test;

using bluetooth::hci::OpCode;
using bluetooth::hci::iso_manager::kIsoDataPathHci;
using bluetooth::hci::iso_manager::kIsoDataPathPlatformDefault;
using bluetooth::le_audio::set_configurations::AudioSetConfiguration;
using bluetooth::le_audio::types::CodecLocation;
using bluetooth::le_audio::types::kLeAudioDirectionSink;
using bluetooth::le_audio::types::kLeAudioDirectionSource;

void osi_property_set_bool(const char* key, bool value);

template <typename T>
T& bluetooth::le_audio::types::BidirectionalPair<T>::get(uint8_t direction) {
  return (direction == bluetooth::le_audio::types::kLeAudioDirectionSink) ? sink : source;
}

static const std::vector<AudioSetConfiguration> offload_capabilities_none(0);

const std::vector<AudioSetConfiguration>* offload_capabilities = &offload_capabilities_none;

const std::string kSmpOptions("mock smp options");
bool get_pts_avrcp_test(void) { return false; }
bool get_pts_secure_only_mode(void) { return false; }
bool get_pts_conn_updates_disabled(void) { return false; }
bool get_pts_crosskey_sdp_disable(void) { return false; }
const std::string* get_pts_smp_options(void) { return &kSmpOptions; }
int get_pts_smp_failure_case(void) { return 123; }
bool get_pts_force_eatt_for_notifications(void) { return false; }
bool get_pts_connect_eatt_unconditionally(void) { return false; }
bool get_pts_connect_eatt_before_encryption(void) { return false; }
bool get_pts_unencrypt_broadcast(void) { return false; }
bool get_pts_eatt_peripheral_collision_support(void) { return false; }
bool get_pts_force_le_audio_multiple_contexts_metadata(void) { return false; }
bool get_pts_le_audio_disable_ases_before_stopping(void) { return false; }
config_t* get_all(void) { return nullptr; }

stack_config_t mock_stack_config{
        .get_pts_avrcp_test = get_pts_avrcp_test,
        .get_pts_secure_only_mode = get_pts_secure_only_mode,
        .get_pts_conn_updates_disabled = get_pts_conn_updates_disabled,
        .get_pts_crosskey_sdp_disable = get_pts_crosskey_sdp_disable,
        .get_pts_smp_options = get_pts_smp_options,
        .get_pts_smp_failure_case = get_pts_smp_failure_case,
        .get_pts_force_eatt_for_notifications = get_pts_force_eatt_for_notifications,
        .get_pts_connect_eatt_unconditionally = get_pts_connect_eatt_unconditionally,
        .get_pts_connect_eatt_before_encryption = get_pts_connect_eatt_before_encryption,
        .get_pts_unencrypt_broadcast = get_pts_unencrypt_broadcast,
        .get_pts_eatt_peripheral_collision_support = get_pts_eatt_peripheral_collision_support,
        .get_pts_force_le_audio_multiple_contexts_metadata =
                get_pts_force_le_audio_multiple_contexts_metadata,
        .get_pts_le_audio_disable_ases_before_stopping =
                get_pts_le_audio_disable_ases_before_stopping,
        .get_all = get_all,
};

const stack_config_t* stack_config_get_interface(void) { return &mock_stack_config; }

namespace bluetooth {
namespace audio {
namespace le_audio {
OffloadCapabilities get_offload_capabilities() {
  return {*offload_capabilities, *offload_capabilities};
}
}  // namespace le_audio
}  // namespace audio
}  // namespace bluetooth

namespace bluetooth::le_audio {

class MockLeAudioSourceHalClient;
MockLeAudioSourceHalClient* mock_le_audio_source_hal_client_;
std::unique_ptr<LeAudioSourceAudioHalClient> owned_mock_le_audio_source_hal_client_;
bool is_audio_unicast_source_acquired;
bool is_audio_broadcast_source_acquired;

std::unique_ptr<LeAudioSourceAudioHalClient> LeAudioSourceAudioHalClient::AcquireUnicast() {
  if (is_audio_unicast_source_acquired) {
    return nullptr;
  }
  is_audio_unicast_source_acquired = true;
  return std::move(owned_mock_le_audio_source_hal_client_);
}

MockLeAudioSourceHalClient* mock_broadcast_le_audio_source_hal_client_;
std::unique_ptr<LeAudioSourceAudioHalClient> owned_mock_broadcast_le_audio_source_hal_client_;

std::unique_ptr<LeAudioSourceAudioHalClient> LeAudioSourceAudioHalClient::AcquireBroadcast() {
  if (is_audio_broadcast_source_acquired) {
    return nullptr;
  }
  is_audio_broadcast_source_acquired = true;
  return std::move(owned_mock_broadcast_le_audio_source_hal_client_);
}

void LeAudioSourceAudioHalClient::DebugDump(int /*fd*/) {}

class MockLeAudioSinkHalClient;
MockLeAudioSinkHalClient* mock_le_audio_sink_hal_client_;
std::unique_ptr<LeAudioSinkAudioHalClient> owned_mock_le_audio_sink_hal_client_;
bool is_audio_unicast_sink_acquired;

std::unique_ptr<LeAudioSinkAudioHalClient> LeAudioSinkAudioHalClient::AcquireUnicast() {
  if (is_audio_unicast_sink_acquired) {
    return nullptr;
  }
  is_audio_unicast_sink_acquired = true;
  return std::move(owned_mock_le_audio_sink_hal_client_);
}

class MockLeAudioSinkHalClient : public LeAudioSinkAudioHalClient {
public:
  MockLeAudioSinkHalClient() = default;
  MOCK_METHOD((bool), Start,
              (const LeAudioCodecConfiguration& codecConfiguration,
               LeAudioSinkAudioHalClient::Callbacks* audioReceiver, DsaModes dsa_modes),
              (override));
  MOCK_METHOD((void), Stop, (), (override));
  MOCK_METHOD((size_t), SendData, (uint8_t* data, uint16_t size), (override));
  MOCK_METHOD((void), ConfirmStreamingRequest, (), (override));
  MOCK_METHOD((void), CancelStreamingRequest, (), (override));
  MOCK_METHOD((void), UpdateRemoteDelay, (uint16_t delay), (override));
  MOCK_METHOD((void), UpdateAudioConfigToHal, (const ::bluetooth::le_audio::offload_config&),
              (override));
  MOCK_METHOD((void), SuspendedForReconfiguration, (), (override));
  MOCK_METHOD((void), ReconfigurationComplete, (), (override));

  MOCK_METHOD((std::optional<broadcaster::BroadcastConfiguration>), GetBroadcastConfig,
              ((const std::vector<std::pair<types::LeAudioContextType, uint8_t>>&),
               (const std::optional<std::vector<::bluetooth::le_audio::types::acs_ac_record>>&)),
              (const override));

  MOCK_METHOD((std::optional<::bluetooth::le_audio::set_configurations::AudioSetConfiguration>),
              GetUnicastConfig,
              (types::LeAudioContextType,
               std::optional<const ::bluetooth::le_audio::types::PublishedAudioCapabilities*>,
               std::optional<const ::bluetooth::le_audio::types::PublishedAudioCapabilities*>),
              (const override));

  MOCK_METHOD((void), OnDestroyed, ());
  virtual ~MockLeAudioSinkHalClient() override { OnDestroyed(); }
};

class MockLeAudioSourceHalClient : public LeAudioSourceAudioHalClient {
public:
  MockLeAudioSourceHalClient() = default;
  MOCK_METHOD((bool), Start,
              (const LeAudioCodecConfiguration& codecConfiguration,
               LeAudioSourceAudioHalClient::Callbacks* audioReceiver, DsaModes dsa_modes),
              (override));
  MOCK_METHOD((void), Stop, (), (override));
  MOCK_METHOD((void), ConfirmStreamingRequest, (), (override));
  MOCK_METHOD((void), CancelStreamingRequest, (), (override));
  MOCK_METHOD((void), UpdateRemoteDelay, (uint16_t delay), (override));
  MOCK_METHOD((void), UpdateAudioConfigToHal, (const ::bluetooth::le_audio::offload_config&),
              (override));
  MOCK_METHOD((void), UpdateBroadcastAudioConfigToHal,
              (const ::bluetooth::le_audio::broadcast_offload_config&), (override));
  MOCK_METHOD((void), SuspendedForReconfiguration, (), (override));
  MOCK_METHOD((void), ReconfigurationComplete, (), (override));

  MOCK_METHOD((std::optional<broadcaster::BroadcastConfiguration>), GetBroadcastConfig,
              ((const std::vector<std::pair<types::LeAudioContextType, uint8_t>>&),
               (const std::optional<std::vector<::bluetooth::le_audio::types::acs_ac_record>>&)),
              (const override));

  MOCK_METHOD((std::optional<::bluetooth::le_audio::set_configurations::AudioSetConfiguration>),
              GetUnicastConfig, (const CodecManager::UnicastConfigurationRequirements&),
              (const override));

  MOCK_METHOD((void), OnDestroyed, ());
  virtual ~MockLeAudioSourceHalClient() override { OnDestroyed(); }
};

static const types::LeAudioCodecId kLeAudioCodecIdLc3 = {
        .coding_format = types::kLeAudioCodingFormatLC3,
        .vendor_company_id = types::kLeAudioVendorCompanyIdUndefined,
        .vendor_codec_id = types::kLeAudioVendorCodecIdUndefined};

static const set_configurations::CodecConfigSetting lc3_16_2 = {
        .id = kLeAudioCodecIdLc3,
        .params = types::LeAudioLtvMap({
                LTV_ENTRY_SAMPLING_FREQUENCY(codec_spec_conf::kLeAudioSamplingFreq16000Hz),
                LTV_ENTRY_FRAME_DURATION(codec_spec_conf::kLeAudioCodecFrameDur10000us),
                LTV_ENTRY_AUDIO_CHANNEL_ALLOCATION(codec_spec_conf::kLeAudioLocationStereo),
                LTV_ENTRY_OCTETS_PER_CODEC_FRAME(40),
        }),
        .channel_count_per_iso_stream = 1,
};

static const set_configurations::CodecConfigSetting lc3_24_2 = {
        .id = kLeAudioCodecIdLc3,
        .params = types::LeAudioLtvMap({
                LTV_ENTRY_SAMPLING_FREQUENCY(codec_spec_conf::kLeAudioSamplingFreq24000Hz),
                LTV_ENTRY_FRAME_DURATION(codec_spec_conf::kLeAudioCodecFrameDur10000us),
                LTV_ENTRY_AUDIO_CHANNEL_ALLOCATION(codec_spec_conf::kLeAudioLocationStereo),
                LTV_ENTRY_OCTETS_PER_CODEC_FRAME(60),
        }),
        .channel_count_per_iso_stream = 1,
};

static const set_configurations::CodecConfigSetting lc3_32_2 = {
        .id = kLeAudioCodecIdLc3,
        .params = types::LeAudioLtvMap({
                LTV_ENTRY_SAMPLING_FREQUENCY(codec_spec_conf::kLeAudioSamplingFreq32000Hz),
                LTV_ENTRY_FRAME_DURATION(codec_spec_conf::kLeAudioCodecFrameDur10000us),
                LTV_ENTRY_AUDIO_CHANNEL_ALLOCATION(codec_spec_conf::kLeAudioLocationStereo),
                LTV_ENTRY_OCTETS_PER_CODEC_FRAME(80),
        }),
        .channel_count_per_iso_stream = 1,
};

static const set_configurations::CodecConfigSetting lc3_48_2 = {
        .id = kLeAudioCodecIdLc3,
        .params = types::LeAudioLtvMap({
                LTV_ENTRY_SAMPLING_FREQUENCY(codec_spec_conf::kLeAudioSamplingFreq48000Hz),
                LTV_ENTRY_FRAME_DURATION(codec_spec_conf::kLeAudioCodecFrameDur10000us),
                LTV_ENTRY_AUDIO_CHANNEL_ALLOCATION(codec_spec_conf::kLeAudioLocationStereo),
                LTV_ENTRY_OCTETS_PER_CODEC_FRAME(100),
        }),
        .channel_count_per_iso_stream = 1,
};

void set_mock_offload_capabilities(const std::vector<AudioSetConfiguration>& caps) {
  offload_capabilities = &caps;
}

static constexpr char kPropLeAudioOffloadSupported[] = "ro.bluetooth.leaudio_offload.supported";
static constexpr char kPropLeAudioCodecExtensibility[] =
        "bluetooth.core.le_audio.codec_extension_aidl.enabled";
static constexpr char kPropLeAudioOffloadDisabled[] = "persist.bluetooth.leaudio_offload.disabled";
static constexpr char kPropLeAudioBidirSwbSupported[] =
        "bluetooth.leaudio.dual_bidirection_swb.supported";

RawAddress GetTestAddress(uint8_t index) {
  EXPECT_LT(index, UINT8_MAX);
  RawAddress result = {{0xC0, 0xDE, 0xC0, 0xDE, 0x00, index}};
  return result;
}

class CodecManagerTestBase : public Test {
public:
  virtual void SetUp() override {
    __android_log_set_minimum_priority(ANDROID_LOG_VERBOSE);
    com::android::bluetooth::flags::provider_->reset_flags();
    set_mock_offload_capabilities(offload_capabilities_none);

    bluetooth::legacy::hci::testing::SetMock(legacy_hci_mock_);

    ON_CALL(controller_interface, SupportsBleIsochronousBroadcaster).WillByDefault(Return(true));
    ON_CALL(controller_interface, IsSupported(OpCode::CONFIGURE_DATA_PATH))
            .WillByDefault(Return(true));
    bluetooth::hci::testing::mock_controller_ = &controller_interface;

    codec_manager = CodecManager::GetInstance();

    RegisterSourceHalClientMock();
    RegisterSinkHalClientMock();
  }

  virtual void TearDown() override { codec_manager->Stop(); }

  NiceMock<bluetooth::hci::testing::MockControllerInterface> controller_interface;
  CodecManager* codec_manager;
  bluetooth::legacy::hci::testing::MockInterface legacy_hci_mock_;

protected:
  void RegisterSourceHalClientMock() {
    owned_mock_le_audio_source_hal_client_.reset(new NiceMock<MockLeAudioSourceHalClient>());
    mock_le_audio_source_hal_client_ =
            (MockLeAudioSourceHalClient*)owned_mock_le_audio_source_hal_client_.get();

    is_audio_unicast_source_acquired = false;

    owned_mock_broadcast_le_audio_source_hal_client_.reset(
            new NiceMock<MockLeAudioSourceHalClient>());
    mock_broadcast_le_audio_source_hal_client_ =
            (MockLeAudioSourceHalClient*)owned_mock_broadcast_le_audio_source_hal_client_.get();
    is_audio_broadcast_source_acquired = false;

    ON_CALL(*mock_le_audio_source_hal_client_, OnDestroyed).WillByDefault([]() {
      mock_le_audio_source_hal_client_ = nullptr;
      is_audio_unicast_source_acquired = false;
    });
  }

  void RegisterSinkHalClientMock() {
    owned_mock_le_audio_sink_hal_client_.reset(new NiceMock<MockLeAudioSinkHalClient>());
    mock_le_audio_sink_hal_client_ =
            (MockLeAudioSinkHalClient*)owned_mock_le_audio_sink_hal_client_.get();

    is_audio_unicast_sink_acquired = false;

    ON_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed).WillByDefault([]() {
      mock_le_audio_sink_hal_client_ = nullptr;
      is_audio_unicast_sink_acquired = false;
    });
  }
};

/*----------------- ADSP codec manager tests ------------------*/
class CodecManagerTestAdsp : public CodecManagerTestBase {
public:
  virtual void SetUp() override {
    // Enable the HW offloader
    osi_property_set_bool(kPropLeAudioOffloadSupported, true);
    osi_property_set_bool(kPropLeAudioOffloadDisabled, false);

    // Allow for bidir SWB configurations
    osi_property_set_bool(kPropLeAudioBidirSwbSupported, true);

    CodecManagerTestBase::SetUp();
  }
};

class CodecManagerTestAdspNoSwb : public CodecManagerTestBase {
public:
  virtual void SetUp() override {
    // Enable the HW offloader
    osi_property_set_bool(kPropLeAudioOffloadSupported, true);
    osi_property_set_bool(kPropLeAudioOffloadDisabled, false);

    // Allow for bidir SWB configurations
    osi_property_set_bool(kPropLeAudioBidirSwbSupported, false);

    CodecManagerTestBase::SetUp();
  }
};

TEST_F(CodecManagerTestAdsp, test_init) { ASSERT_EQ(codec_manager, CodecManager::GetInstance()); }

TEST_F(CodecManagerTestAdsp, test_start) {
  EXPECT_CALL(legacy_hci_mock_, ConfigureDataPath(hci_data_direction_t::HOST_TO_CONTROLLER,
                                                  kIsoDataPathPlatformDefault, _))
          .Times(1);
  EXPECT_CALL(legacy_hci_mock_, ConfigureDataPath(hci_data_direction_t::CONTROLLER_TO_HOST,
                                                  kIsoDataPathPlatformDefault, _))
          .Times(1);

  // Verify data path is reset on Stop()
  EXPECT_CALL(legacy_hci_mock_,
              ConfigureDataPath(hci_data_direction_t::HOST_TO_CONTROLLER, kIsoDataPathHci, _))
          .Times(1);
  EXPECT_CALL(legacy_hci_mock_,
              ConfigureDataPath(hci_data_direction_t::CONTROLLER_TO_HOST, kIsoDataPathHci, _))
          .Times(1);

  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference(0);
  codec_manager->Start(offloading_preference);

  ASSERT_EQ(codec_manager->GetCodecLocation(), CodecLocation::ADSP);
}

TEST_F(CodecManagerTestAdsp, testStreamConfigurationAdspDownMix) {
  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference(0);
  codec_manager->Start(offloading_preference);

  // Current CIS configuration for two earbuds
  std::vector<struct types::cis> cises{
          {
                  .id = 0x00,
                  .type = types::CisType::CIS_TYPE_BIDIRECTIONAL,
                  .conn_handle = 96,
          },
          {
                  .id = 0x01,
                  .type = types::CisType::CIS_TYPE_BIDIRECTIONAL,
                  .conn_handle = 97,
          },
  };

  // Stream parameters
  types::BidirectionalPair<stream_parameters> stream_params{
          .sink =
                  {
                          .sample_frequency_hz = 16000,
                          .frame_duration_us = 10000,
                          .octets_per_codec_frame = 40,
                          .audio_channel_allocation = codec_spec_conf::kLeAudioLocationFrontLeft,
                          .codec_frames_blocks_per_sdu = 1,
                          .num_of_channels = 1,
                          .num_of_devices = 1,
                          .stream_locations =
                                  {
                                          std::pair<uint16_t, uint32_t>{
                                                  97 /*conn_handle*/,
                                                  codec_spec_conf::kLeAudioLocationFrontLeft},
                                  },
                  },
          .source =
                  {
                          .sample_frequency_hz = 16000,
                          .frame_duration_us = 10000,
                          .octets_per_codec_frame = 40,
                          .audio_channel_allocation = codec_spec_conf::kLeAudioLocationFrontLeft,
                          .codec_frames_blocks_per_sdu = 1,
                          .num_of_channels = 1,
                          .num_of_devices = 1,
                          {
                                  std::pair<uint16_t, uint32_t>{
                                          97 /*conn_handle*/,
                                          codec_spec_conf::kLeAudioLocationBackLeft},
                          },
                  },
  };

  codec_manager->UpdateCisConfiguration(cises, stream_params.sink, kLeAudioDirectionSink);
  codec_manager->UpdateCisConfiguration(cises, stream_params.source, kLeAudioDirectionSource);

  // Verify the offloader config content
  types::BidirectionalPair<std::optional<offload_config>> out_offload_configs;
  codec_manager->UpdateActiveAudioConfig(
          stream_params, {.sink = 44, .source = 44},
          [&out_offload_configs](const offload_config& config, uint8_t direction) {
            out_offload_configs.get(direction) = config;
          });

  // Expect the same configuration for sink and source
  ASSERT_TRUE(out_offload_configs.sink.has_value());
  ASSERT_TRUE(out_offload_configs.source.has_value());
  for (auto direction : {bluetooth::le_audio::types::kLeAudioDirectionSink,
                         bluetooth::le_audio::types::kLeAudioDirectionSource}) {
    uint32_t allocation = 0;
    auto& config = out_offload_configs.get(direction).value();
    ASSERT_EQ(2lu, config.stream_map.size());
    for (const auto& info : config.stream_map) {
      if (info.stream_handle == 96) {
        ASSERT_EQ(codec_spec_conf::kLeAudioLocationFrontRight, info.audio_channel_allocation);
        // The disconnected should be inactive
        ASSERT_FALSE(info.is_stream_active);

      } else if (info.stream_handle == 97) {
        ASSERT_EQ(codec_spec_conf::kLeAudioLocationFrontLeft, info.audio_channel_allocation);
        // The connected should be active
        ASSERT_TRUE(info.is_stream_active);

      } else {
        ASSERT_EQ(97, info.stream_handle);
      }
      allocation |= info.audio_channel_allocation;
    }

    ASSERT_EQ(16, config.bits_per_sample);
    ASSERT_EQ(16000u, config.sampling_rate);
    ASSERT_EQ(10000u, config.frame_duration);
    ASSERT_EQ(40u, config.octets_per_frame);
    ASSERT_EQ(1, config.blocks_per_sdu);
    ASSERT_EQ(44, config.peer_delay_ms);
    ASSERT_EQ(codec_spec_conf::kLeAudioLocationStereo, allocation);
  }

  // Clear the CIS configuration map (no active CISes).
  codec_manager->ClearCisConfiguration(kLeAudioDirectionSink);
  codec_manager->ClearCisConfiguration(kLeAudioDirectionSource);
  out_offload_configs.sink = std::nullopt;
  out_offload_configs.source = std::nullopt;
  codec_manager->UpdateActiveAudioConfig(
          stream_params, {.sink = 44, .source = 44},
          [&out_offload_configs](const offload_config& config, uint8_t direction) {
            out_offload_configs.get(direction) = config;
          });

  // Expect sink & source configurations with empty CIS channel allocation map.
  ASSERT_TRUE(out_offload_configs.sink.has_value());
  ASSERT_TRUE(out_offload_configs.source.has_value());
  for (auto direction : {bluetooth::le_audio::types::kLeAudioDirectionSink,
                         bluetooth::le_audio::types::kLeAudioDirectionSource}) {
    auto& config = out_offload_configs.get(direction).value();
    ASSERT_EQ(0lu, config.stream_map.size());
    ASSERT_EQ(16, config.bits_per_sample);
    ASSERT_EQ(16000u, config.sampling_rate);
    ASSERT_EQ(10000u, config.frame_duration);
    ASSERT_EQ(40u, config.octets_per_frame);
    ASSERT_EQ(1, config.blocks_per_sdu);
    ASSERT_EQ(44, config.peer_delay_ms);
  }
}

TEST_F(CodecManagerTestAdsp, testStreamConfigurationMono) {
  com::android::bluetooth::flags::provider_->leaudio_mono_location_errata(true);
  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference(0);
  codec_manager->Start(offloading_preference);

  // Current CIS configuration for two earbuds
  std::vector<struct types::cis> cises{
          {
                  .id = 0x00,
                  .type = types::CisType::CIS_TYPE_BIDIRECTIONAL,
                  .conn_handle = 96,
                  .addr = RawAddress::kEmpty,  // Disconnected
          },
          {
                  .id = 0x01,
                  .type = types::CisType::CIS_TYPE_BIDIRECTIONAL,
                  .conn_handle = 97,
                  .addr = GetTestAddress(1),
          },
  };

  // Stream parameters
  types::BidirectionalPair<stream_parameters> stream_params{
          .sink =
                  {
                          .sample_frequency_hz = 16000,
                          .frame_duration_us = 10000,
                          .octets_per_codec_frame = 40,
                          .audio_channel_allocation = codec_spec_conf::kLeAudioLocationMonoAudio,
                          .codec_frames_blocks_per_sdu = 1,
                          .num_of_channels = 1,
                          .num_of_devices = 1,
                          .stream_locations =
                                  {
                                          std::pair<uint16_t, uint32_t>{
                                                  97 /*conn_handle*/,
                                                  codec_spec_conf::kLeAudioLocationMonoAudio},
                                  },
                  },
          .source =
                  {
                          .sample_frequency_hz = 16000,
                          .frame_duration_us = 10000,
                          .octets_per_codec_frame = 40,
                          .audio_channel_allocation = codec_spec_conf::kLeAudioLocationMonoAudio,
                          .codec_frames_blocks_per_sdu = 1,
                          .num_of_channels = 1,
                          .num_of_devices = 1,
                          {
                                  std::pair<uint16_t, uint32_t>{
                                          97 /*conn_handle*/,
                                          codec_spec_conf::kLeAudioLocationMonoAudio},
                          },
                  },
  };

  ASSERT_TRUE(
          codec_manager->UpdateCisConfiguration(cises, stream_params.sink, kLeAudioDirectionSink));
  ASSERT_TRUE(codec_manager->UpdateCisConfiguration(cises, stream_params.source,
                                                    kLeAudioDirectionSource));

  // Verify the offloader config content
  types::BidirectionalPair<std::optional<offload_config>> out_offload_configs;
  codec_manager->UpdateActiveAudioConfig(
          stream_params, {.sink = 44, .source = 44},
          [&out_offload_configs](const offload_config& config, uint8_t direction) {
            out_offload_configs.get(direction) = config;
          });

  // Expect the same configuration for sink and source
  ASSERT_TRUE(out_offload_configs.sink.has_value());
  ASSERT_TRUE(out_offload_configs.source.has_value());
  for (auto direction : {bluetooth::le_audio::types::kLeAudioDirectionSink,
                         bluetooth::le_audio::types::kLeAudioDirectionSource}) {
    uint32_t allocation = 0;
    auto& config = out_offload_configs.get(direction).value();
    ASSERT_EQ(2lu, config.stream_map.size());
    for (const auto& info : config.stream_map) {
      if (info.stream_handle == 96) {
        ASSERT_EQ(codec_spec_conf::kLeAudioLocationMonoAudio, info.audio_channel_allocation);
        // The disconnected should be inactive
        ASSERT_FALSE(info.is_stream_active);

      } else if (info.stream_handle == 97) {
        ASSERT_EQ(codec_spec_conf::kLeAudioLocationMonoAudio, info.audio_channel_allocation);
        // The connected should be active
        ASSERT_TRUE(info.is_stream_active);

      } else {
        ASSERT_EQ(97, info.stream_handle);
      }
      allocation |= info.audio_channel_allocation;
    }

    ASSERT_EQ(16, config.bits_per_sample);
    ASSERT_EQ(16000u, config.sampling_rate);
    ASSERT_EQ(10000u, config.frame_duration);
    ASSERT_EQ(40u, config.octets_per_frame);
    ASSERT_EQ(1, config.blocks_per_sdu);
    ASSERT_EQ(44, config.peer_delay_ms);
    ASSERT_EQ(codec_spec_conf::kLeAudioLocationMonoAudio, allocation);
  }

  // Clear the CIS configuration map (no active CISes).
  codec_manager->ClearCisConfiguration(kLeAudioDirectionSink);
  codec_manager->ClearCisConfiguration(kLeAudioDirectionSource);
  out_offload_configs.sink = std::nullopt;
  out_offload_configs.source = std::nullopt;
  codec_manager->UpdateActiveAudioConfig(
          stream_params, {.sink = 44, .source = 44},
          [&out_offload_configs](const offload_config& config, uint8_t direction) {
            out_offload_configs.get(direction) = config;
          });

  // Expect sink & source configurations with empty CIS channel allocation map.
  ASSERT_TRUE(out_offload_configs.sink.has_value());
  ASSERT_TRUE(out_offload_configs.source.has_value());
  for (auto direction : {bluetooth::le_audio::types::kLeAudioDirectionSink,
                         bluetooth::le_audio::types::kLeAudioDirectionSource}) {
    auto& config = out_offload_configs.get(direction).value();
    ASSERT_EQ(0lu, config.stream_map.size());
    ASSERT_EQ(16, config.bits_per_sample);
    ASSERT_EQ(16000u, config.sampling_rate);
    ASSERT_EQ(10000u, config.frame_duration);
    ASSERT_EQ(40u, config.octets_per_frame);
    ASSERT_EQ(1, config.blocks_per_sdu);
    ASSERT_EQ(44, config.peer_delay_ms);
  }
}

TEST_F(CodecManagerTestAdsp, test_capabilities_none) {
  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference(0);
  codec_manager->Start(offloading_preference);

  bool has_null_config = false;
  auto match_first_config =
          [&](const CodecManager::UnicastConfigurationRequirements& /*requirements*/,
              const set_configurations::AudioSetConfigurations* confs)
          -> std::unique_ptr<set_configurations::AudioSetConfiguration> {
    // Don't expect the matcher being called on nullptr
    if (confs == nullptr) {
      has_null_config = true;
    }
    if (confs && confs->size()) {
      // For simplicity return the first element, the real matcher should
      // check the group capabilities.
      return std::make_unique<AudioSetConfiguration>(*(confs->at(0)));
    }
    return nullptr;
  };

  // Verify every context
  for (::bluetooth::le_audio::types::LeAudioContextType ctx_type :
       ::bluetooth::le_audio::types::kLeAudioContextAllTypesArray) {
    has_null_config = false;
    CodecManager::UnicastConfigurationRequirements requirements = {
            .audio_context_type = ctx_type,
    };
    ASSERT_EQ(nullptr, codec_manager->GetCodecConfig(requirements, match_first_config));
    ASSERT_FALSE(has_null_config);
  }
}

TEST_F(CodecManagerTestAdsp, test_capabilities) {
  for (auto test_context : ::bluetooth::le_audio::types::kLeAudioContextAllTypesArray) {
    // Build the offloader capabilities vector using the configuration provider
    // in HOST mode to get all the .json filce configuration entries.
    std::vector<AudioSetConfiguration> offload_capabilities;
    AudioSetConfigurationProvider::Initialize(bluetooth::le_audio::types::CodecLocation::HOST);
    for (auto& cap : *AudioSetConfigurationProvider::Get()->GetConfigurations(test_context)) {
      offload_capabilities.push_back(*cap);
    }
    ASSERT_NE(0u, offload_capabilities.size());
    set_mock_offload_capabilities(offload_capabilities);
    // Clean up before the codec manager starts it in ADSP mode.
    AudioSetConfigurationProvider::Cleanup();

    const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {
            {.codec_type = bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3}};
    codec_manager->Start(offloading_preference);

    size_t available_configs_size = 0;
    auto match_first_config =
            [&available_configs_size](
                    const CodecManager::UnicastConfigurationRequirements& /*requirements*/,
                    const set_configurations::AudioSetConfigurations* confs)
            -> std::unique_ptr<set_configurations::AudioSetConfiguration> {
      if (confs && confs->size()) {
        available_configs_size = confs->size();
        // For simplicity return the first element, the real matcher should
        // check the group capabilities.
        return std::make_unique<AudioSetConfiguration>(*(confs->at(0)));
      }
      return nullptr;
    };

    CodecManager::UnicastConfigurationRequirements requirements = {
            .audio_context_type = test_context,
    };
    auto cfg = codec_manager->GetCodecConfig(requirements, match_first_config);
    ASSERT_NE(nullptr, cfg);
    ASSERT_EQ(offload_capabilities.size(), available_configs_size);

    // Clean up the before testing any other offload capabilities.
    codec_manager->Stop();
  }
}

TEST_F(CodecManagerTestAdsp, test_broadcast_config) {
  static const set_configurations::CodecConfigSetting bc_lc3_48_2 = {
          .id = kLeAudioCodecIdLc3,
          .params = types::LeAudioLtvMap({
                  LTV_ENTRY_SAMPLING_FREQUENCY(codec_spec_conf::kLeAudioSamplingFreq48000Hz),
                  LTV_ENTRY_FRAME_DURATION(codec_spec_conf::kLeAudioCodecFrameDur10000us),
                  LTV_ENTRY_AUDIO_CHANNEL_ALLOCATION(codec_spec_conf::kLeAudioLocationStereo),
                  LTV_ENTRY_OCTETS_PER_CODEC_FRAME(100),
          }),
          .channel_count_per_iso_stream = 2,
  };

  std::vector<AudioSetConfiguration> offload_capabilities = {{
          .name = "Test_Broadcast_Config_No_Dev_lc3_48_2",
          .confs = {.sink = {set_configurations::AseConfiguration(bc_lc3_48_2),
                             set_configurations::AseConfiguration(bc_lc3_48_2)},
                    .source = {}},
  }};
  set_mock_offload_capabilities(offload_capabilities);

  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {
          {.codec_type = bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3}};
  codec_manager->Start(offloading_preference);

  CodecManager::BroadcastConfigurationRequirements requirements = {
          .subgroup_quality = {{types::LeAudioContextType::MEDIA, 1}}};
  auto cfg = codec_manager->GetBroadcastConfig(requirements);
  ASSERT_EQ(2, cfg->GetNumBisTotal());
  ASSERT_EQ(2, cfg->GetNumChannelsMax());
  ASSERT_EQ(48000u, cfg->GetSamplingFrequencyHzMax());
  ASSERT_EQ(10000u, cfg->GetSduIntervalUs());
  ASSERT_EQ(100u, cfg->GetMaxSduOctets());
  ASSERT_EQ(1lu, cfg->subgroups.size());
  ASSERT_EQ(2lu, cfg->subgroups.at(0).GetNumBis());
  ASSERT_EQ(2lu, cfg->subgroups.at(0).GetNumChannelsTotal());

  ASSERT_EQ(2lu, cfg->subgroups.at(0).GetBisCodecConfigs().at(0).GetNumBis());
  ASSERT_EQ(2lu, cfg->subgroups.at(0).GetBisCodecConfigs().at(0).GetNumChannels());
  ASSERT_EQ(1lu, cfg->subgroups.at(0).GetBisCodecConfigs().at(0).GetNumChannelsPerBis());

  // Clean up the before testing any other offload capabilities.
  codec_manager->Stop();
}

TEST_F(CodecManagerTestAdsp, test_update_broadcast_offloader) {
  static const set_configurations::CodecConfigSetting bc_lc3_48_2 = {
          .id = kLeAudioCodecIdLc3,
          .params = types::LeAudioLtvMap({
                  LTV_ENTRY_SAMPLING_FREQUENCY(codec_spec_conf::kLeAudioSamplingFreq48000Hz),
                  LTV_ENTRY_FRAME_DURATION(codec_spec_conf::kLeAudioCodecFrameDur10000us),
                  LTV_ENTRY_AUDIO_CHANNEL_ALLOCATION(codec_spec_conf::kLeAudioLocationStereo),
                  LTV_ENTRY_OCTETS_PER_CODEC_FRAME(100),
          }),
          .channel_count_per_iso_stream = 2,
  };
  std::vector<AudioSetConfiguration> offload_capabilities = {{
          .name = "Test_Broadcast_Config_For_Offloader",
          .confs = {.sink = {set_configurations::AseConfiguration(bc_lc3_48_2),
                             set_configurations::AseConfiguration(bc_lc3_48_2)},
                    .source = {}},
  }};
  set_mock_offload_capabilities(offload_capabilities);

  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {
          {.codec_type = bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3}};
  codec_manager->Start(offloading_preference);

  CodecManager::BroadcastConfigurationRequirements requirements = {
          .subgroup_quality = {{types::LeAudioContextType::MEDIA, 1}}};
  codec_manager->GetBroadcastConfig(requirements);

  bool was_called = false;
  bluetooth::le_audio::broadcast_offload_config bcast_config;
  codec_manager->UpdateBroadcastConnHandle(
          {0x0001, 0x0002}, [&](const bluetooth::le_audio::broadcast_offload_config& config) {
            was_called = true;
            bcast_config = config;
          });

  // Expect a call for ADSP encoding
  ASSERT_TRUE(was_called);
  ASSERT_EQ(2lu, bcast_config.stream_map.size());
  ASSERT_EQ(16, bcast_config.bits_per_sample);
  ASSERT_EQ(48000lu, bcast_config.sampling_rate);
  ASSERT_EQ(10000lu, bcast_config.frame_duration);
  ASSERT_EQ(100u, bcast_config.octets_per_frame);
  ASSERT_EQ(1u, bcast_config.blocks_per_sdu);
  ASSERT_NE(0u, bcast_config.retransmission_number);
  ASSERT_NE(0u, bcast_config.max_transport_latency);
}

/*----------------- HOST codec manager tests ------------------*/
class CodecManagerTestHost : public CodecManagerTestBase {
public:
  virtual void SetUp() override {
    // Enable the HW offloader
    osi_property_set_bool(kPropLeAudioOffloadSupported, false);
    osi_property_set_bool(kPropLeAudioOffloadDisabled, false);

    // Allow for bidir SWB configurations
    osi_property_set_bool(kPropLeAudioBidirSwbSupported, true);

    // Codec extensibility disabled by default
    com::android::bluetooth::flags::provider_->leaudio_multicodec_aidl_support(false);
    osi_property_set_bool(kPropLeAudioCodecExtensibility, false);

    CodecManagerTestBase::SetUp();
  }
};

class CodecManagerTestHostNoSwb : public CodecManagerTestBase {
public:
  virtual void SetUp() override {
    // Enable the HW offloader
    osi_property_set_bool(kPropLeAudioOffloadSupported, true);
    osi_property_set_bool(kPropLeAudioOffloadDisabled, false);

    // Do not allow for bidir SWB configurations
    osi_property_set_bool(kPropLeAudioBidirSwbSupported, false);

    // Codec extensibility disabled by default
    com::android::bluetooth::flags::provider_->leaudio_multicodec_aidl_support(false);
    osi_property_set_bool(kPropLeAudioCodecExtensibility, false);

    CodecManagerTestBase::SetUp();
  }
};

TEST_F(CodecManagerTestHost, test_init) { ASSERT_EQ(codec_manager, CodecManager::GetInstance()); }

TEST_F(CodecManagerTestHost, test_audio_session_update) {
  ASSERT_EQ(codec_manager, CodecManager::GetInstance());

  auto unicast_source = LeAudioSourceAudioHalClient::AcquireUnicast();
  auto unicast_sink = LeAudioSinkAudioHalClient::AcquireUnicast();
  auto broadcast_source = LeAudioSourceAudioHalClient::AcquireBroadcast();

  // codec manager not started
  ASSERT_FALSE(codec_manager->UpdateActiveUnicastAudioHalClient(unicast_source.get(),
                                                                unicast_sink.get(), true));
  ASSERT_FALSE(codec_manager->UpdateActiveUnicastAudioHalClient(unicast_source.get(),
                                                                unicast_sink.get(), false));
  ASSERT_FALSE(codec_manager->UpdateActiveBroadcastAudioHalClient(broadcast_source.get(), true));
  ASSERT_FALSE(codec_manager->UpdateActiveBroadcastAudioHalClient(broadcast_source.get(), false));

  std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference(0);

  // Start codec manager
  codec_manager->Start(offloading_preference);

  ASSERT_TRUE(codec_manager->UpdateActiveUnicastAudioHalClient(unicast_source.get(),
                                                               unicast_sink.get(), true));
  ASSERT_FALSE(codec_manager->UpdateActiveUnicastAudioHalClient(unicast_source.get(),
                                                                unicast_sink.get(), true));
  ASSERT_TRUE(codec_manager->UpdateActiveUnicastAudioHalClient(unicast_source.get(),
                                                               unicast_sink.get(), false));
  ASSERT_TRUE(
          codec_manager->UpdateActiveUnicastAudioHalClient(unicast_source.get(), nullptr, true));
  ASSERT_TRUE(codec_manager->UpdateActiveUnicastAudioHalClient(nullptr, unicast_sink.get(), true));
  ASSERT_FALSE(codec_manager->UpdateActiveUnicastAudioHalClient(nullptr, nullptr, false));
  ASSERT_FALSE(codec_manager->UpdateActiveUnicastAudioHalClient(nullptr, nullptr, true));
  ASSERT_TRUE(codec_manager->UpdateActiveUnicastAudioHalClient(nullptr, unicast_sink.get(), false));
  ASSERT_TRUE(
          codec_manager->UpdateActiveUnicastAudioHalClient(unicast_source.get(), nullptr, false));

  ASSERT_TRUE(codec_manager->UpdateActiveBroadcastAudioHalClient(broadcast_source.get(), true));
  ASSERT_TRUE(codec_manager->UpdateActiveBroadcastAudioHalClient(broadcast_source.get(), false));
  ASSERT_TRUE(codec_manager->UpdateActiveBroadcastAudioHalClient(broadcast_source.get(), true));
  ASSERT_FALSE(codec_manager->UpdateActiveBroadcastAudioHalClient(broadcast_source.get(), true));
  ASSERT_FALSE(codec_manager->UpdateActiveBroadcastAudioHalClient(unicast_source.get(), true));
  ASSERT_FALSE(codec_manager->UpdateActiveBroadcastAudioHalClient(unicast_source.get(), false));
  ASSERT_FALSE(codec_manager->UpdateActiveBroadcastAudioHalClient(nullptr, false));
  ASSERT_FALSE(codec_manager->UpdateActiveBroadcastAudioHalClient(nullptr, true));
}

TEST_F(CodecManagerTestHost, test_start) {
  EXPECT_CALL(legacy_hci_mock_, ConfigureDataPath(hci_data_direction_t::HOST_TO_CONTROLLER,
                                                  kIsoDataPathPlatformDefault, _))
          .Times(0);
  EXPECT_CALL(legacy_hci_mock_, ConfigureDataPath(hci_data_direction_t::CONTROLLER_TO_HOST,
                                                  kIsoDataPathPlatformDefault, _))
          .Times(0);

  // Verify data path is NOT reset on Stop() for the Host encoding session
  EXPECT_CALL(legacy_hci_mock_,
              ConfigureDataPath(hci_data_direction_t::HOST_TO_CONTROLLER, kIsoDataPathHci, _))
          .Times(0);
  EXPECT_CALL(legacy_hci_mock_,
              ConfigureDataPath(hci_data_direction_t::CONTROLLER_TO_HOST, kIsoDataPathHci, _))
          .Times(0);

  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference(0);
  codec_manager->Start(offloading_preference);

  ASSERT_EQ(codec_manager->GetCodecLocation(), CodecLocation::HOST);
}

TEST_F(CodecManagerTestHost, test_non_bidir_swb) {
  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {
          {.codec_type = bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3}};
  codec_manager->Start(offloading_preference);

  // NON-SWB configs
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_16_2),
                             set_configurations::AseConfiguration(lc3_16_2)},
                    .source = {set_configurations::AseConfiguration(lc3_16_2),
                               set_configurations::AseConfiguration(lc3_16_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_24_2),
                             set_configurations::AseConfiguration(lc3_24_2)},
                    .source = {set_configurations::AseConfiguration(lc3_16_2),
                               set_configurations::AseConfiguration(lc3_16_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_16_2),
                             set_configurations::AseConfiguration(lc3_16_2)},
                    .source = {set_configurations::AseConfiguration(lc3_24_2),
                               set_configurations::AseConfiguration(lc3_24_2)}},
  }));

  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_16_2),
                             set_configurations::AseConfiguration(lc3_16_2)},
                    .source = {set_configurations::AseConfiguration(lc3_32_2),
                               set_configurations::AseConfiguration(lc3_32_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_32_2),
                             set_configurations::AseConfiguration(lc3_32_2)},
                    .source = {set_configurations::AseConfiguration(lc3_16_2),
                               set_configurations::AseConfiguration(lc3_16_2)}},
  }));

  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_24_2),
                             set_configurations::AseConfiguration(lc3_24_2)},
                    .source = {set_configurations::AseConfiguration(lc3_24_2),
                               set_configurations::AseConfiguration(lc3_24_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_24_2),
                             set_configurations::AseConfiguration(lc3_24_2)},
                    .source = {set_configurations::AseConfiguration(lc3_32_2),
                               set_configurations::AseConfiguration(lc3_32_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_32_2),
                             set_configurations::AseConfiguration(lc3_32_2)},
                    .source = {set_configurations::AseConfiguration(lc3_24_2),
                               set_configurations::AseConfiguration(lc3_24_2)}},
  }));

  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_16_2),
                             set_configurations::AseConfiguration(lc3_16_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.source = {set_configurations::AseConfiguration(lc3_16_2),
                               set_configurations::AseConfiguration(lc3_16_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_24_2),
                             set_configurations::AseConfiguration(lc3_24_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.source = {set_configurations::AseConfiguration(lc3_24_2),
                               set_configurations::AseConfiguration(lc3_24_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_32_2),
                             set_configurations::AseConfiguration(lc3_32_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.source = {set_configurations::AseConfiguration(lc3_32_2),
                               set_configurations::AseConfiguration(lc3_32_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_48_2),
                             set_configurations::AseConfiguration(lc3_48_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsBiDirSwb({
          .confs = {.source = {set_configurations::AseConfiguration(lc3_48_2),
                               set_configurations::AseConfiguration(lc3_48_2)}},
  }));

  // NON-DUAL-SWB configs
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_16_2),
                             set_configurations::AseConfiguration(lc3_16_2)},
                    .source = {set_configurations::AseConfiguration(lc3_16_2),
                               set_configurations::AseConfiguration(lc3_16_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_24_2),
                             set_configurations::AseConfiguration(lc3_24_2)},
                    .source = {set_configurations::AseConfiguration(lc3_16_2),
                               set_configurations::AseConfiguration(lc3_16_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_16_2),
                             set_configurations::AseConfiguration(lc3_16_2)},
                    .source = {set_configurations::AseConfiguration(lc3_24_2),
                               set_configurations::AseConfiguration(lc3_24_2)}},
  }));

  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_16_2),
                             set_configurations::AseConfiguration(lc3_16_2)},
                    .source = {set_configurations::AseConfiguration(lc3_32_2),
                               set_configurations::AseConfiguration(lc3_32_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_32_2),
                             set_configurations::AseConfiguration(lc3_32_2)},
                    .source = {set_configurations::AseConfiguration(lc3_16_2),
                               set_configurations::AseConfiguration(lc3_16_2)}},
  }));

  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_24_2),
                             set_configurations::AseConfiguration(lc3_24_2)},
                    .source = {set_configurations::AseConfiguration(lc3_24_2),
                               set_configurations::AseConfiguration(lc3_24_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_24_2),
                             set_configurations::AseConfiguration(lc3_24_2)},
                    .source = {set_configurations::AseConfiguration(lc3_32_2),
                               set_configurations::AseConfiguration(lc3_32_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_32_2),
                             set_configurations::AseConfiguration(lc3_32_2)},
                    .source = {set_configurations::AseConfiguration(lc3_24_2),
                               set_configurations::AseConfiguration(lc3_24_2)}},
  }));

  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_16_2),
                             set_configurations::AseConfiguration(lc3_16_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.source = {set_configurations::AseConfiguration(lc3_16_2),
                               set_configurations::AseConfiguration(lc3_16_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_24_2),
                             set_configurations::AseConfiguration(lc3_24_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.source = {set_configurations::AseConfiguration(lc3_24_2),
                               set_configurations::AseConfiguration(lc3_24_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_32_2),
                             set_configurations::AseConfiguration(lc3_32_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.source = {set_configurations::AseConfiguration(lc3_32_2),
                               set_configurations::AseConfiguration(lc3_32_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_48_2),
                             set_configurations::AseConfiguration(lc3_48_2)}},
  }));
  ASSERT_FALSE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.source = {set_configurations::AseConfiguration(lc3_48_2),
                               set_configurations::AseConfiguration(lc3_48_2)}},
  }));
}

TEST_F(CodecManagerTestHost, test_dual_bidir_swb) {
  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {
          {.codec_type = bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3}};
  codec_manager->Start(offloading_preference);

  // Single Dev BiDir SWB configs
  ASSERT_TRUE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_32_2),
                             set_configurations::AseConfiguration(lc3_32_2)},
                    .source = {set_configurations::AseConfiguration(lc3_32_2),
                               set_configurations::AseConfiguration(lc3_32_2)}},
  }));
  ASSERT_TRUE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_48_2),
                             set_configurations::AseConfiguration(lc3_48_2)},
                    .source = {set_configurations::AseConfiguration(lc3_32_2),
                               set_configurations::AseConfiguration(lc3_32_2)}},
  }));
  ASSERT_TRUE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_32_2),
                             set_configurations::AseConfiguration(lc3_32_2)},
                    .source = {set_configurations::AseConfiguration(lc3_48_2),
                               set_configurations::AseConfiguration(lc3_48_2)}},
  }));
  ASSERT_TRUE(codec_manager->CheckCodecConfigIsDualBiDirSwb({
          .confs = {.sink = {set_configurations::AseConfiguration(lc3_48_2),
                             set_configurations::AseConfiguration(lc3_48_2)},
                    .source = {set_configurations::AseConfiguration(lc3_48_2),
                               set_configurations::AseConfiguration(lc3_48_2)}},
  }));
}

TEST_F(CodecManagerTestHost, test_dual_bidir_swb_supported) {
  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {
          {.codec_type = bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3}};
  codec_manager->Start(offloading_preference);

  int num_of_dual_bidir_swb_configs = 0;
  for (auto context : types::kLeAudioContextAllTypesArray) {
    bool got_null_cfgs_container = false;
    auto ptr = codec_manager->GetCodecConfig(
            {.audio_context_type = context},
            [&](const CodecManager::UnicastConfigurationRequirements& /*requirements*/,
                const set_configurations::AudioSetConfigurations* confs)
                    -> std::unique_ptr<set_configurations::AudioSetConfiguration> {
              if (confs == nullptr) {
                got_null_cfgs_container = true;
              } else {
                num_of_dual_bidir_swb_configs +=
                        std::count_if(confs->begin(), confs->end(), [&](auto const& cfg) {
                          bool is_bidir = codec_manager->CheckCodecConfigIsDualBiDirSwb(*cfg);
                          return codec_manager->CheckCodecConfigIsDualBiDirSwb(*cfg);
                        });
              }
              // In this case the chosen configuration doesn't matter - select none
              return nullptr;
            });
    ASSERT_FALSE(got_null_cfgs_container);
  }

  // Make sure some dual bidir SWB configs were returned
  ASSERT_NE(0, num_of_dual_bidir_swb_configs);
}

TEST_F(CodecManagerTestAdsp, test_dual_bidir_swb_supported) {
  // Set the offloader capabilities
  std::vector<AudioSetConfiguration> offload_capabilities = {
          {
                  .name = "Test_Bidir_SWB_Config_No_Dev_lc3_32_2",
                  .confs = {.sink = {set_configurations::AseConfiguration(lc3_32_2),
                                     set_configurations::AseConfiguration(lc3_32_2)},
                            .source = {set_configurations::AseConfiguration(lc3_32_2),
                                       set_configurations::AseConfiguration(lc3_32_2)}},
          },
          {
                  .name = "Test_Bidir_Non_SWB_Config_No_Dev_lc3_16_2",
                  .confs = {.sink = {set_configurations::AseConfiguration(lc3_16_2),
                                     set_configurations::AseConfiguration(lc3_16_2)},
                            .source = {set_configurations::AseConfiguration(lc3_16_2),
                                       set_configurations::AseConfiguration(lc3_16_2)}},
          }};
  set_mock_offload_capabilities(offload_capabilities);

  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {
          {.codec_type = bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3}};
  codec_manager->Start(offloading_preference);

  int num_of_dual_bidir_swb_configs = 0;
  for (auto context : types::kLeAudioContextAllTypesArray) {
    bool got_null_cfgs_container = false;
    auto ptr = codec_manager->GetCodecConfig(
            {.audio_context_type = context},
            [&](const CodecManager::UnicastConfigurationRequirements& /*requirements*/,
                const set_configurations::AudioSetConfigurations* confs)
                    -> std::unique_ptr<set_configurations::AudioSetConfiguration> {
              if (confs == nullptr) {
                got_null_cfgs_container = true;
              } else {
                num_of_dual_bidir_swb_configs +=
                        std::count_if(confs->begin(), confs->end(), [&](auto const& cfg) {
                          bool is_bidir = codec_manager->CheckCodecConfigIsDualBiDirSwb(*cfg);
                          return codec_manager->CheckCodecConfigIsDualBiDirSwb(*cfg);
                        });
              }
              // In this case the chosen configuration doesn't matter - select none
              return nullptr;
            });
    ASSERT_FALSE(got_null_cfgs_container);
  }

  // Make sure some dual bidir SWB configs were returned
  ASSERT_NE(0, num_of_dual_bidir_swb_configs);
}

TEST_F(CodecManagerTestHostNoSwb, test_dual_bidir_swb_not_supported) {
  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {
          {.codec_type = bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3}};
  codec_manager->Start(offloading_preference);

  int num_of_dual_bidir_swb_configs = 0;
  for (auto context : types::kLeAudioContextAllTypesArray) {
    bool got_null_cfgs_container = false;
    auto ptr = codec_manager->GetCodecConfig(
            {.audio_context_type = context},
            [&](const CodecManager::UnicastConfigurationRequirements& /*requirements*/,
                const set_configurations::AudioSetConfigurations* confs)
                    -> std::unique_ptr<set_configurations::AudioSetConfiguration> {
              if (confs == nullptr) {
                got_null_cfgs_container = true;
              } else {
                num_of_dual_bidir_swb_configs +=
                        std::count_if(confs->begin(), confs->end(), [&](auto const& cfg) {
                          return codec_manager->CheckCodecConfigIsDualBiDirSwb(*cfg);
                        });
              }
              // In this case the chosen configuration doesn't matter - select none
              return nullptr;
            });
    ASSERT_FALSE(got_null_cfgs_container);
  }

  // Make sure no dual bidir SWB configs were returned
  ASSERT_EQ(0, num_of_dual_bidir_swb_configs);
}

TEST_F(CodecManagerTestAdspNoSwb, test_dual_bidir_swb_not_supported) {
  // Set the offloader capabilities
  std::vector<AudioSetConfiguration> offload_capabilities = {
          {
                  .name = "Test_Bidir_SWB_Config_No_Dev_lc3_32_2",
                  .confs = {.sink = {set_configurations::AseConfiguration(lc3_32_2),
                                     set_configurations::AseConfiguration(lc3_32_2)},
                            .source = {set_configurations::AseConfiguration(lc3_32_2),
                                       set_configurations::AseConfiguration(lc3_32_2)}},
          },
          {
                  .name = "Test_Bidir_Non_SWB_Config_No_Dev_lc3_16_2",
                  .confs = {.sink = {set_configurations::AseConfiguration(lc3_16_2),
                                     set_configurations::AseConfiguration(lc3_16_2)},
                            .source = {set_configurations::AseConfiguration(lc3_16_2),
                                       set_configurations::AseConfiguration(lc3_16_2)}},
          }};
  set_mock_offload_capabilities(offload_capabilities);

  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {
          {.codec_type = bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3}};
  codec_manager->Start(offloading_preference);

  int num_of_dual_bidir_swb_configs = 0;
  for (auto context : types::kLeAudioContextAllTypesArray) {
    bool got_null_cfgs_container = false;
    auto ptr = codec_manager->GetCodecConfig(
            {.audio_context_type = context},
            [&](const CodecManager::UnicastConfigurationRequirements& /*requirements*/,
                const set_configurations::AudioSetConfigurations* confs)
                    -> std::unique_ptr<set_configurations::AudioSetConfiguration> {
              if (confs == nullptr) {
                got_null_cfgs_container = true;
              } else {
                num_of_dual_bidir_swb_configs +=
                        std::count_if(confs->begin(), confs->end(), [&](auto const& cfg) {
                          return codec_manager->CheckCodecConfigIsDualBiDirSwb(*cfg);
                        });
              }
              // In this case the chosen configuration doesn't matter - select none
              return nullptr;
            });
    ASSERT_FALSE(got_null_cfgs_container);
  }

  // Make sure no dual bidir SWB configs were returned
  ASSERT_EQ(0, num_of_dual_bidir_swb_configs);
}

TEST_F(CodecManagerTestHost, test_dont_update_broadcast_offloader) {
  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {
          {.codec_type = bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3}};
  codec_manager->Start(offloading_preference);

  bool was_called = false;
  codec_manager->UpdateBroadcastConnHandle(
          {0x0001, 0x0002}, [&](const bluetooth::le_audio::broadcast_offload_config& /*config*/) {
            was_called = true;
          });

  // Expect no call for HOST encoding
  ASSERT_FALSE(was_called);
}

TEST_F(CodecManagerTestHost, test_dont_call_hal_for_config) {
  com::android::bluetooth::flags::provider_->leaudio_multicodec_aidl_support(true);
  osi_property_set_bool(kPropLeAudioCodecExtensibility, true);

  // Set the offloader capabilities
  std::vector<AudioSetConfiguration> offload_capabilities;
  set_mock_offload_capabilities(offload_capabilities);

  const std::vector<bluetooth::le_audio::btle_audio_codec_config_t> offloading_preference = {};
  codec_manager->Start(offloading_preference);
  codec_manager->UpdateActiveUnicastAudioHalClient(mock_le_audio_source_hal_client_,
                                                   mock_le_audio_sink_hal_client_, true);

  EXPECT_CALL(*mock_le_audio_source_hal_client_, GetUnicastConfig(_)).Times(0);
  codec_manager->GetCodecConfig(
          {.audio_context_type = types::LeAudioContextType::MEDIA},
          [&](const CodecManager::UnicastConfigurationRequirements& /*requirements*/,
              const set_configurations::AudioSetConfigurations* /*confs*/)
                  -> std::unique_ptr<set_configurations::AudioSetConfiguration> {
            // In this case the chosen configuration doesn't matter - select none
            return nullptr;
          });
}

}  // namespace bluetooth::le_audio
