blob: dc5cfe9cfed3853ff9ed08ac6c836da29f459606 [file] [log] [blame]
/*
* 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/le_audio/content_control_id_keeper.h"
#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 "gd/common/init_flags.h"
#include "le_audio_set_configuration_provider.h"
#include "mock_codec_manager.h"
#include "mock_controller.h"
#include "mock_csis_client.h"
#include "mock_iso_manager.h"
#include "types/bt_transport.h"
using ::le_audio::DeviceConnectState;
using ::le_audio::codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel;
using ::le_audio::codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel;
using ::le_audio::types::LeAudioContextType;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::NiceMock;
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_;
void osi_property_set_bool(const char* key, bool value);
static const char* test_flags[] = {
"INIT_logging_debug_enabled_for_all=true",
nullptr,
};
constexpr uint8_t media_ccid = 0xC0;
constexpr auto media_context =
static_cast<std::underlying_type<LeAudioContextType>::type>(
LeAudioContextType::MEDIA);
constexpr uint8_t call_ccid = 0xD0;
constexpr auto call_context =
static_cast<std::underlying_type<LeAudioContextType>::type>(
LeAudioContextType::CONVERSATIONAL);
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 LeAudioContextType kContextTypeUnspecified =
static_cast<LeAudioContextType>(0x0001);
constexpr LeAudioContextType kContextTypeConversational =
static_cast<LeAudioContextType>(0x0002);
constexpr LeAudioContextType kContextTypeMedia =
static_cast<LeAudioContextType>(0x0004);
constexpr LeAudioContextType kContextTypeSoundEffects =
static_cast<LeAudioContextType>(0x0080);
constexpr LeAudioContextType kContextTypeRingtone =
static_cast<LeAudioContextType>(0x0200);
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}};
}
class MockLeAudioGroupStateMachineCallbacks
: public LeAudioGroupStateMachine::Callbacks {
public:
MockLeAudioGroupStateMachineCallbacks() = default;
MockLeAudioGroupStateMachineCallbacks(
const MockLeAudioGroupStateMachineCallbacks&) = delete;
MockLeAudioGroupStateMachineCallbacks& operator=(
const MockLeAudioGroupStateMachineCallbacks&) = delete;
~MockLeAudioGroupStateMachineCallbacks() override = default;
MOCK_METHOD((void), StatusReportCb,
(int group_id, bluetooth::le_audio::GroupStreamStatus status),
(override));
MOCK_METHOD((void), OnStateTransitionTimeout, (int group_id), (override));
};
class StateMachineTest : public Test {
protected:
uint8_t ase_id_last_assigned = types::ase::kAseIdInvalid;
uint8_t additional_snk_ases = 0;
uint8_t additional_src_ases = 0;
uint8_t channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel;
uint16_t sample_freq_ = codec_specific::kCapSamplingFrequency16000Hz;
void SetUp() override {
bluetooth::common::InitFlags::Load(test_flags);
mock_function_count_map.clear();
controller::SetMockControllerInterface(&mock_controller_);
bluetooth::manager::SetMockBtmInterface(&btm_interface);
gatt::SetMockBtaGattInterface(&gatt_interface);
gatt::SetMockBtaGattQueue(&gatt_queue);
::le_audio::AudioSetConfigurationProvider::Initialize();
LeAudioGroupStateMachine::Initialize(&mock_callbacks_);
ContentControlIdKeeper::GetInstance()->Start();
MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_);
ON_CALL(mock_csis_client_module_, Get())
.WillByDefault(Return(&mock_csis_client_module_));
ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
.WillByDefault(Return(true));
ON_CALL(mock_csis_client_module_, GetDeviceList(_))
.WillByDefault(Invoke([this](int group_id) { return addresses_; }));
ON_CALL(mock_csis_client_module_, GetDesiredSize(_))
.WillByDefault(
Invoke([this](int group_id) { return (int)(addresses_.size()); }));
// 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();
ConfigCodecManagerMock();
}
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));
}
auto status = HCI_SUCCESS;
if (group_create_command_disallowed_) {
group_create_command_disallowed_ = false;
status = HCI_ERR_COMMAND_DISALLOWED;
}
LeAudioGroupStateMachine::Get()->ProcessHciNotifOnCigCreate(
group.get(), status, cig_id, conn_handles);
}
});
ON_CALL(*mock_iso_manager_, RemoveCig)
.WillByDefault([this](uint8_t cig_id, bool force) {
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;
}
// When we disconnect the remote with HCI_ERR_PEER_USER, we
// should be getting HCI_ERR_CONN_CAUSE_LOCAL_HOST from HCI.
if (reason == HCI_ERR_PEER_USER) {
reason = HCI_ERR_CONN_CAUSE_LOCAL_HOST;
}
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 ConfigCodecManagerMock() {
codec_manager_ = le_audio::CodecManager::GetInstance();
ASSERT_NE(codec_manager_, nullptr);
std::vector<bluetooth::le_audio::btle_audio_codec_config_t>
mock_offloading_preference(0);
codec_manager_->Start(mock_offloading_preference);
mock_codec_manager_ = MockCodecManager::GetInstance();
ASSERT_NE(mock_codec_manager_, nullptr);
ON_CALL(*mock_codec_manager_, GetCodecLocation())
.WillByDefault(Return(types::CodecLocation::HOST));
}
void TearDown() override {
/* Clear the alarm on tear down in case test case ends when the
* alarm is scheduled
*/
alarm_cancel(nullptr);
iso_manager_->Stop();
mock_iso_manager_ = nullptr;
codec_manager_->Stop();
mock_codec_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();
addresses_.clear();
cached_codec_configuration_map_.clear();
cached_ase_to_cis_id_map_.clear();
LeAudioGroupStateMachine::Cleanup();
::le_audio::AudioSetConfigurationProvider::Cleanup();
}
std::shared_ptr<LeAudioDevice> PrepareConnectedDevice(
uint8_t id, DeviceConnectState initial_connect_state, uint8_t num_ase_snk,
uint8_t num_ase_src) {
auto leAudioDevice = std::make_shared<LeAudioDevice>(GetTestAddress(id),
initial_connect_state);
leAudioDevice->conn_id_ = id;
leAudioDevice->SetConnectionState(DeviceConnectState::CONNECTED);
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);
addresses_.push_back(leAudioDevice->address_);
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);
}
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),
});
}
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,
LeAudioContextType context_type,
uint16_t device_cnt,
types::AudioContexts update_contexts,
bool insert_default_pac_records = true) {
// Prepare fake connected device group
DeviceConnectState initial_connect_state =
DeviceConnectState::CONNECTING_BY_USER;
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 + additional_snk_ases;
num_ase_src = 0 + additional_src_ases;
break;
case kContextTypeMedia:
num_ase_snk = 2 + additional_snk_ases;
num_ase_src = 0 + additional_src_ases;
break;
case kContextTypeConversational:
num_ase_snk = 1 + additional_snk_ases;
num_ase_src = 1 + additional_src_ases;
break;
default:
ASSERT_TRUE(false);
}
while (device_cnt) {
auto leAudioDevice = PrepareConnectedDevice(
device_cnt--, initial_connect_state, 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 */
auto snk_context_type = kContextTypeUnspecified | update_contexts;
auto src_context_type = kContextTypeUnspecified | update_contexts;
// Prepare Sink Published Audio Capability records
if ((kContextTypeRingtone | kContextTypeMedia |
kContextTypeConversational)
.test(context_type)) {
// Set target ASE configurations
std::vector<types::acs_ac_record> pac_recs;
InsertPacRecord(pac_recs, sample_freq_,
codec_specific::kCapFrameDuration10ms |
codec_specific::kCapFrameDuration7p5ms |
codec_specific::kCapFrameDuration10msPreferred,
channel_count_, 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.set(static_cast<LeAudioContextType>(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.set(
static_cast<LeAudioContextType>(kContextTypeConversational));
leAudioDevice->src_audio_locations_ =
::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft |
::le_audio::codec_spec_conf::kLeAudioLocationFrontRight;
}
leAudioDevice->SetSupportedContexts(snk_context_type, src_context_type);
leAudioDevice->SetAvailableContexts(snk_context_type, src_context_type);
}
group = GroupTheDevice(leaudio_group_id, std::move(leAudioDevice));
/* Set the location and direction to the group (done in client.cc)*/
group->ReloadAudioLocations();
group->ReloadAudioDirections();
}
/* Stimulate update of available context map */
auto types_set = update_contexts.any() ? context_type | update_contexts
: types::AudioContexts(context_type);
group->UpdateAudioContextTypeAvailability(types_set);
ASSERT_NE(group, nullptr);
ASSERT_EQ(group->Size(), total_devices);
}
LeAudioDeviceGroup* PrepareSingleTestDeviceGroup(
int leaudio_group_id, LeAudioContextType context_type,
uint16_t device_cnt = 1,
types::AudioContexts update_contexts = types::AudioContexts()) {
MultipleTestDevicePrepare(leaudio_group_id, context_type, device_cnt,
update_contexts);
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 = 0x0010;
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,
bool caching = false) {
ase_ctp_handlers[ascs::kAseCtpOpcodeConfigureQos] =
[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 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;
if (caching) {
LOG(INFO) << __func__ << " Device: " << device->address_;
if (cached_ase_to_cis_id_map_.count(device->address_) > 0) {
auto ase_list = cached_ase_to_cis_id_map_.at(device->address_);
if (ase_list.count(ase_id) > 0) {
auto cis_id = ase_list.at(ase_id);
ASSERT_EQ(cis_id, qos_configured_state_params.cis_id);
} else {
ase_list[ase_id] = qos_configured_state_params.cis_id;
}
} else {
std::map<int, int> ase_map;
ase_map[ase_id] = qos_configured_state_params.cis_id;
cached_ase_to_cis_id_map_[device->address_] = ase_map;
}
}
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, 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 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 += meta_len;
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, 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 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, 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 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, 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 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);
}
}
};
}
MockCsisClient mock_csis_client_module_;
NiceMock<controller::MockControllerInterface> mock_controller_;
NiceMock<bluetooth::manager::MockBtmInterface> btm_interface;
gatt::MockBtaGattInterface gatt_interface;
gatt::MockBtaGattQueue gatt_queue;
bluetooth::hci::IsoManager* iso_manager_;
MockIsoManager* mock_iso_manager_;
le_audio::CodecManager* codec_manager_;
MockCodecManager* mock_codec_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_;
std::map<RawAddress, std::map<int, int>> cached_ase_to_cis_id_map_;
MockLeAudioGroupStateMachineCallbacks mock_callbacks_;
std::vector<std::shared_ptr<LeAudioDevice>> le_audio_devices_;
std::vector<RawAddress> addresses_;
std::map<uint8_t, std::unique_ptr<LeAudioDeviceGroup>>
le_audio_device_groups_;
bool group_create_command_disallowed_ = false;
};
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) {
/* Device is banded headphones with 1x snk + 0x src ase
* (1xunidirectional CIS) with channel count 2 (for stereo
*/
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 2;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
// 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 1 time for the Codec Config call only. */
EXPECT_CALL(gatt_queue,
WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(1);
/* Do nothing on the CigCreate, so the state machine stays in the configure
* state */
ON_CALL(*mock_iso_manager_, CreateCig).WillByDefault(Return());
EXPECT_CALL(*mock_iso_manager_, CreateCig).Times(1);
InjectInitialIdleNotification(group);
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
/* Cancel is called when group goes to streaming. */
ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
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);
/* Do nothing on the CigCreate, so the state machine stays in the configure
* state */
ON_CALL(*mock_iso_manager_, CreateCig).WillByDefault(Return());
EXPECT_CALL(*mock_iso_manager_, CreateCig).Times(1);
// Start the configuration and stream the content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
/* Cancel is called when group goes to streaming. */
ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testConfigureQosSingle) {
/* Device is banded headphones with 2x snk + 1x src ase
* (1x bidirectional + 1xunidirectional CIS)
*/
additional_snk_ases = 1;
additional_src_ases = 1;
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, 2);
PrepareConfigureQosHandler(group, 2);
// 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, context_type, types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testConfigureQosSingleRecoverCig) {
/* Device is banded headphones with 2x snk + 1x src ase
* (1x bidirectional + 1xunidirectional CIS)
*/
additional_snk_ases = 1;
additional_src_ases = 1;
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 3;
/* Assume that on previous BT OFF CIG was not removed */
group_create_command_disallowed_ = true;
// 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, 2);
PrepareConfigureQosHandler(group, 2);
// 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(2);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).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);
InjectInitialIdleNotification(group);
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
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<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testStreamSingle) {
/* Device is banded headphones with 1x snk + 0x src ase
* (1xunidirectional CIS) with channel count 2 (for stereo
*/
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
/* Ringtone with channel count 1 for single device and 1 ASE sink will
* end up with 1 Sink ASE being 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<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
/* Device is banded headphones with 2x snk + none src ase
* (2x unidirectional CIS)
*/
const auto context_type = kContextTypeMedia;
const int leaudio_group_id = 4;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
/* For Media context type with channel count 1 and two ASEs,
* there should have be 2 Ases configured configured.
*/
PrepareConfigureCodecHandler(group, 2);
PrepareConfigureQosHandler(group, 2);
PrepareEnableHandler(group, 2, 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(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<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
/* Device is banded headphones with 2x snk + 1x src ase
* (1x bidirectional CIS)
*/
const auto context_type = kContextTypeConversational;
const int leaudio_group_id = 4;
additional_snk_ases = 1;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
/* Since we prepared device with Conversional context in mind,
* 2 Sink ASEs and 1 Source ASE should have been configured.
*/
PrepareConfigureCodecHandler(group, 3);
PrepareConfigureQosHandler(group, 3);
PrepareEnableHandler(group, 3, 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(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);
// 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<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
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);
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(AtLeast(1));
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4);
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(4);
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<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
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<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testUpdateMetadataMultiple) {
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<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
testing::Mock::VerifyAndClearExpectations(&gatt_queue);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Make sure all devices get the metadata update
leAudioDevice = group->GetFirstDevice();
expected_devices_written = 0;
while (leAudioDevice) {
EXPECT_CALL(gatt_queue,
WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(1);
expected_devices_written++;
leAudioDevice = group->GetNextDevice(leAudioDevice);
}
ASSERT_EQ(expected_devices_written, num_devices);
const auto metadata_context_type =
kContextTypeMedia | kContextTypeSoundEffects;
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
metadata_context_type));
/* This is just update metadata - watchdog is not used */
ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testDisableSingle) {
/* Device is banded headphones with 2x snk + 0x src ase
* (2xunidirectional CIS)
*/
additional_snk_ases = 1;
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
/* Ringtone context plus additional ASE with channel count 1
* gives us 2 ASE which should have been configured.
*/
PrepareConfigureCodecHandler(group, 2);
PrepareConfigureQosHandler(group, 2);
PrepareEnableHandler(group, 2);
PrepareDisableHandler(group, 2);
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(
_, bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput))
.Times(2);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
InjectInitialIdleNotification(group);
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<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::SUSPENDING));
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);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
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(
_, bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput))
.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<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::SUSPENDING));
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);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testDisableBidirectional) {
/* Device is banded headphones with 2x snk + 1x src ase
* (1x bidirectional + 1xunidirectional CIS)
*/
additional_snk_ases = 1;
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, 3);
PrepareConfigureQosHandler(group, 3);
PrepareEnableHandler(group, 3);
PrepareDisableHandler(group, 3);
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(3);
bool removed_bidirectional = false;
bool removed_unidirectional = false;
/* Check data path removal */
ON_CALL(*mock_iso_manager_, RemoveIsoDataPath)
.WillByDefault(Invoke([&removed_bidirectional, &removed_unidirectional,
this](uint16_t conn_handle,
uint8_t data_path_dir) {
/* Set flags for verification */
if (data_path_dir ==
(bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput |
bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput)) {
removed_bidirectional = true;
} else if (data_path_dir == bluetooth::hci::iso_manager::
kRemoveIsoDataPathDirectionInput) {
removed_unidirectional = true;
}
/* Copied from default handler of 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()) {
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;
}
}
/* End of copy */
}));
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::SUSPENDING));
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);
ASSERT_EQ(removed_bidirectional, true);
ASSERT_EQ(removed_unidirectional, true);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testReleaseSingle) {
/* Device is banded headphones with 1x snk + 0x src ase
* (1xunidirectional CIS) with channel count 2 (for stereo)
*/
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
// 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<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::RELEASING));
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);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testReleaseCachingSingle) {
/* Device is banded headphones with 1x snk + 0x src ase
* (1xunidirectional CIS)
*/
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
// 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::RELEASING));
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(
leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
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<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// 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);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest,
testStreamCaching_NoReconfigurationNeeded_SingleDevice) {
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
additional_snk_ases = 2;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
/* Since we prepared device with Ringtone context in mind and with no Source
* ASEs, therefor only one ASE should have been configured.
*/
PrepareConfigureCodecHandler(group, 1, true);
PrepareConfigureQosHandler(group, 1, true);
PrepareEnableHandler(group, 1);
PrepareDisableHandler(group, 1);
PrepareReleaseHandler(group, 1);
/* Ctp messages we expect:
* 1. Codec Config
* 2. QoS Config
* 3. Enable
* 4. Release
* 5. QoS Config (because device stays in Configured state)
* 6. Enable
*/
auto* leAudioDevice = group->GetFirstDevice();
EXPECT_CALL(gatt_queue,
WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(6);
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::RELEASING));
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(
leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
EXPECT_CALL(mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::STREAMING))
.Times(2);
// Start the configuration and stream Ringtone content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// 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);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
}
TEST_F(StateMachineTest,
test_StreamCaching_ReconfigureForContextChange_SingleDevice) {
auto context_type = kContextTypeConversational;
const int leaudio_group_id = 4;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
additional_snk_ases = 2;
/* Prepare fake connected device group with update of Media and Conversational
* contexts
*/
auto* group = PrepareSingleTestDeviceGroup(
leaudio_group_id, context_type, 1,
kContextTypeConversational | kContextTypeMedia);
/* Don't validate ASE here, as after reconfiguration different ASE number
* will be used.
* For the first configuration (CONVERSTATIONAL) there will be 2 ASEs (Sink
* and Source) After reconfiguration (MEDIA) there will be single ASE.
*/
PrepareConfigureCodecHandler(group, 0, true);
PrepareConfigureQosHandler(group, 0, true);
PrepareEnableHandler(group);
PrepareReceiverStartReady(group);
PrepareReleaseHandler(group);
/* Ctp messages we expect:
* 1. Codec Config
* 2. QoS Config
* 3. Enable
* 4. Release
* 5. Codec Config
* 6. QoS Config
* 7. Enable
*/
auto* leAudioDevice = group->GetFirstDevice();
EXPECT_CALL(gatt_queue,
WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(8);
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(2);
/* 2 times for first configuration (1 Sink, 1 Source), 1 time for second
* configuration (1 Sink)*/
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3);
uint8_t value =
bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput |
bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput;
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, value)).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::RELEASING));
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(
leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
EXPECT_CALL(mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::STREAMING))
.Times(2);
// Start the configuration and stream Conversational content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// 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);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Start the configuration and stream Media content
context_type = kContextTypeMedia;
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
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<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::RELEASING));
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);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testReleaseBidirectional) {
/* Device is banded headphones with 2x snk + 1x src ase
* (1x bidirectional + 1xunidirectional CIS)
*/
additional_snk_ases = 1;
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, 3);
PrepareConfigureQosHandler(group, 3);
PrepareEnableHandler(group, 3);
PrepareDisableHandler(group, 3);
PrepareReceiverStartReady(group, 1);
PrepareReleaseHandler(group, 3);
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(3);
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<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// 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);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
}
TEST_F(StateMachineTest, testDisableAndReleaseBidirectional) {
/* Device is banded headphones with 2x snk + 1x src ase
* (1x bidirectional + 1xunidirectional CIS)
*/
additional_snk_ases = 1;
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, 3);
PrepareConfigureQosHandler(group, 3);
PrepareEnableHandler(group, 3);
PrepareDisableHandler(group, 3);
PrepareReceiverStartReady(group, 1);
PrepareReceiverStopReady(group, 1);
PrepareReleaseHandler(group, 3);
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(3);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(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) {
/* Device is banded headphones with 2x snk + 1x src ase
* (1x bidirectional + 1xunidirectional CIS)
*/
additional_snk_ases = 1;
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, 3);
PrepareConfigureQosHandler(group, 3);
PrepareEnableHandler(group, 3);
PrepareDisableHandler(group, 3);
PrepareReceiverStartReady(group, 1);
PrepareReceiverStopReady(group, 1);
PrepareReleaseHandler(group, 3);
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<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Validate new GroupStreamStatus
EXPECT_CALL(mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::IDLE))
.Times(AtLeast(1));
/* Single disconnect as it is bidirectional Cis*/
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 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.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);
}
}
ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testAseAutonomousRelease2Devices) {
const auto context_type = kContextTypeConversational;
const int leaudio_group_id = 4;
const int num_of_devices = 2;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type,
num_of_devices);
/* Since we prepared device with Conversional context in mind, Sink and Source
* ASEs should have been configured.
*/
PrepareConfigureCodecHandler(group);
PrepareConfigureQosHandler(group);
PrepareEnableHandler(group);
PrepareDisableHandler(group);
PrepareReceiverStartReady(group);
PrepareReceiverStopReady(group);
PrepareReleaseHandler(group);
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<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check streaming will continue
EXPECT_CALL(mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::IDLE))
.Times(0);
/* Single disconnect as it is bidirectional Cis*/
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
auto device = group->GetFirstDevice();
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);
// Simulate autonomus release for one device.
InjectAseStateNotification(&ase, device, group, ascs::kAseStateReleasing,
&codec_configured_state_params);
InjectAseStateNotification(&ase, device, group, ascs::kAseStateIdle,
&codec_configured_state_params);
}
}
TEST_F(StateMachineTest, testStateTransitionTimeoutOnIdleState) {
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
auto* leAudioDevice = group->GetFirstDevice();
EXPECT_CALL(gatt_queue,
WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(1);
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Disconnect device
LeAudioGroupStateMachine::Get()->ProcessHciNotifAclDisconnected(
group, leAudioDevice);
// Make sure timeout is cleared
ASSERT_TRUE(fake_osi_alarm_set_on_mloop_.cb == nullptr);
}
TEST_F(StateMachineTest, testStateTransitionTimeout) {
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
// 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<LeAudioContextType>(context_type),
types::AudioContexts(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"]);
}
MATCHER_P(dataPathIsEq, expected, "") { return (arg.data_path_id == expected); }
TEST_F(StateMachineTest, testConfigureDataPathForHost) {
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
/* Should be called 3 times because
* 1 - calling GetConfigurations just after connection
* (UpdateAudioContextTypeAvailability)
* 2 - when doing configuration of the context type
* 3 - AddCisToStreamConfiguration -> CreateStreamVectorForOffloader
* 4 - Data Path
*/
EXPECT_CALL(*mock_codec_manager_, GetCodecLocation())
.Times(4)
.WillRepeatedly(Return(types::CodecLocation::HOST));
// 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);
EXPECT_CALL(
*mock_iso_manager_,
SetupIsoDataPath(
_, dataPathIsEq(bluetooth::hci::iso_manager::kIsoDataPathHci)))
.Times(1);
InjectInitialIdleNotification(group);
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
}
TEST_F(StateMachineTest, testConfigureDataPathForAdsp) {
const auto context_type = kContextTypeRingtone;
const int leaudio_group_id = 4;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
/* Should be called 3 times because
* 1 - calling GetConfigurations just after connection
* (UpdateAudioContextTypeAvailability)
* 2 - when doing configuration of the context type
* 3 - AddCisToStreamConfiguration -> CreateStreamVectorForOffloader
* 4 - data path
*/
EXPECT_CALL(*mock_codec_manager_, GetCodecLocation())
.Times(4)
.WillRepeatedly(Return(types::CodecLocation::ADSP));
// 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);
EXPECT_CALL(
*mock_iso_manager_,
SetupIsoDataPath(
_, dataPathIsEq(
bluetooth::hci::iso_manager::kIsoDataPathPlatformDefault)))
.Times(1);
InjectInitialIdleNotification(group);
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
}
static void InjectAclDisconnected(LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice) {
LeAudioGroupStateMachine::Get()->ProcessHciNotifAclDisconnected(
group, leAudioDevice);
}
TEST_F(StateMachineTest, testStreamConfigurationAdspDownMix) {
const auto context_type = kContextTypeConversational;
const int leaudio_group_id = 4;
const int num_devices = 2;
// Prepare fake connected device group
auto* group = PrepareSingleTestDeviceGroup(
leaudio_group_id, context_type, num_devices,
types::AudioContexts(kContextTypeConversational));
/* Should be called 5 times because
* 1 - calling GetConfigurations just after connection
* (UpdateAudioContextTypeAvailability),
* 2 - when doing configuration of the context type
* 3 - AddCisToStreamConfiguration -> CreateStreamVectorForOffloader (sink)
* 4 - AddCisToStreamConfiguration -> CreateStreamVectorForOffloader (source)
* 5,6 - Data Path
*/
EXPECT_CALL(*mock_codec_manager_, GetCodecLocation())
.Times(6)
.WillRepeatedly(Return(types::CodecLocation::ADSP));
PrepareConfigureCodecHandler(group);
PrepareConfigureQosHandler(group);
PrepareEnableHandler(group);
PrepareReceiverStartReady(group);
InjectInitialIdleNotification(group);
auto* leAudioDevice = group->GetFirstDevice();
InjectAclDisconnected(group, leAudioDevice);
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
ASSERT_EQ(
static_cast<int>(
group->stream_conf.sink_offloader_streams_target_allocation.size()),
2);
ASSERT_EQ(
static_cast<int>(
group->stream_conf.source_offloader_streams_target_allocation.size()),
2);
ASSERT_EQ(
static_cast<int>(
group->stream_conf.sink_offloader_streams_current_allocation.size()),
2);
ASSERT_EQ(
static_cast<int>(group->stream_conf
.source_offloader_streams_current_allocation.size()),
2);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
uint32_t allocation = 0;
for (const auto& s :
group->stream_conf.sink_offloader_streams_target_allocation) {
allocation |= s.second;
ASSERT_FALSE(allocation == 0);
}
ASSERT_TRUE(allocation == codec_spec_conf::kLeAudioLocationStereo);
allocation = 0;
for (const auto& s :
group->stream_conf.source_offloader_streams_target_allocation) {
allocation |= s.second;
ASSERT_FALSE(allocation == 0);
}
ASSERT_TRUE(allocation == codec_spec_conf::kLeAudioLocationStereo);
for (const auto& s :
group->stream_conf.sink_offloader_streams_current_allocation) {
ASSERT_TRUE((s.second == 0) ||
(s.second == codec_spec_conf::kLeAudioLocationStereo));
}
for (const auto& s :
group->stream_conf.source_offloader_streams_current_allocation) {
ASSERT_TRUE((s.second == 0) ||
(s.second == codec_spec_conf::kLeAudioLocationStereo));
}
}
static void InjectCisDisconnected(LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice,
uint8_t reason) {
bluetooth::hci::iso_manager::cis_disconnected_evt event;
for (auto const ase : leAudioDevice->ases_) {
if (ase.data_path_state != types::AudioStreamDataPathState::CIS_ASSIGNED &&
ase.data_path_state != types::AudioStreamDataPathState::IDLE) {
event.reason = reason;
event.cig_id = group->group_id_;
event.cis_conn_hdl = ase.cis_conn_hdl;
LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected(
group, leAudioDevice, &event);
}
}
}
TEST_F(StateMachineTest, testAttachDeviceToTheStream) {
const auto context_type = kContextTypeMedia;
const auto leaudio_group_id = 6;
const auto num_devices = 2;
ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
// 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();
LeAudioDevice* lastDevice;
LeAudioDevice* fistDevice = leAudioDevice;
auto expected_devices_written = 0;
while (leAudioDevice) {
/* Three Writes:
* 1: Codec Config
* 2: Codec QoS
* 3: Enabling
*/
lastDevice = 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);
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
InjectInitialIdleNotification(group);
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
// Inject CIS and ACL disconnection of first device
InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT);
InjectAclDisconnected(group, lastDevice);
// Check if group keeps streaming
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
lastDevice->conn_id_ = 3;
group->UpdateAudioContextTypeAvailability();
// Make sure ASE with disconnected CIS are not left in STREAMING
ASSERT_EQ(lastDevice->GetFirstAseWithState(
::le_audio::types::kLeAudioDirectionSink,
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
nullptr);
ASSERT_EQ(lastDevice->GetFirstAseWithState(
::le_audio::types::kLeAudioDirectionSource,
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
nullptr);
EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_,
lastDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(AtLeast(3));
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice);
// Check if group keeps streaming
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
// Verify that the joining device receives the right CCID list
auto lastMeta = lastDevice->GetFirstActiveAse()->metadata;
bool parsedOk = false;
auto ltv = le_audio::types::LeAudioLtvMap::Parse(lastMeta.data(),
lastMeta.size(), parsedOk);
ASSERT_TRUE(parsedOk);
auto ccids = ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList);
ASSERT_TRUE(ccids.has_value());
ASSERT_NE(std::find(ccids->begin(), ccids->end(), media_ccid), ccids->end());
/* Verify that ASE of first device are still good*/
auto ase = fistDevice->GetFirstActiveAse();
ASSERT_NE(ase->max_transport_latency, 0);
ASSERT_NE(ase->retrans_nb, 0);
}
TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) {
const auto context_type = kContextTypeConversational;
const auto leaudio_group_id = 6;
const auto num_devices = 2;
ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_ccid);
// 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);
PrepareDisableHandler(group);
PrepareReleaseHandler(group);
auto* leAudioDevice = group->GetFirstDevice();
LeAudioDevice* lastDevice;
LeAudioDevice* fistDevice = leAudioDevice;
auto expected_devices_written = 0;
while (leAudioDevice) {
/* Three Writes:
* 1: Codec Config
* 2: Codec QoS
* 3: Enabling
*/
lastDevice = 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);
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4);
InjectInitialIdleNotification(group);
// Start the configuration and stream Conversational content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
// Inject CIS and ACL disconnection of first device
InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT);
InjectAclDisconnected(group, lastDevice);
// Check if group keeps streaming
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
lastDevice->conn_id_ = 3;
group->UpdateAudioContextTypeAvailability();
// Make sure ASE with disconnected CIS are not left in STREAMING
ASSERT_EQ(lastDevice->GetFirstAseWithState(
::le_audio::types::kLeAudioDirectionSink,
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
nullptr);
ASSERT_EQ(lastDevice->GetFirstAseWithState(
::le_audio::types::kLeAudioDirectionSource,
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
nullptr);
EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_,
lastDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(AtLeast(3));
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice);
// Check if group keeps streaming
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
// Verify that the joining device receives the right CCID list
auto lastMeta = lastDevice->GetFirstActiveAse()->metadata;
bool parsedOk = false;
auto ltv = le_audio::types::LeAudioLtvMap::Parse(lastMeta.data(),
lastMeta.size(), parsedOk);
ASSERT_TRUE(parsedOk);
auto ccids = ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList);
ASSERT_TRUE(ccids.has_value());
ASSERT_NE(std::find(ccids->begin(), ccids->end(), call_ccid), ccids->end());
/* Verify that ASE of first device are still good*/
auto ase = fistDevice->GetFirstActiveAse();
ASSERT_NE(ase->max_transport_latency, 0);
ASSERT_NE(ase->retrans_nb, 0);
// Make sure ASEs with reconnected CIS are in STREAMING state
ASSERT_TRUE(lastDevice->HaveAllActiveAsesSameState(
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING));
}
TEST_F(StateMachineTest, StartStreamAfterConfigure) {
const auto context_type = kContextTypeMedia;
const auto leaudio_group_id = 6;
const auto num_devices = 2;
ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
// 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, 0, true);
PrepareConfigureQosHandler(group);
PrepareEnableHandler(group);
PrepareDisableHandler(group);
PrepareReleaseHandler(group);
InjectInitialIdleNotification(group);
auto* leAudioDevice = group->GetFirstDevice();
auto expected_devices_written = 0;
while (leAudioDevice) {
/* Three Writes:
* 1. Codec configure
* 2: Codec QoS
* 3: Enabling
*/
EXPECT_CALL(gatt_queue,
WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(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::CONFIGURED_BY_USER));
// Start the configuration and stream Media content
group->SetPendingConfiguration();
LeAudioGroupStateMachine::Get()->ConfigureStream(
group, context_type, types::AudioContexts(context_type));
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
group->ClearPendingConfiguration();
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::STREAMING));
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, context_type, types::AudioContexts(context_type));
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
}
TEST_F(StateMachineTest, StartStreamCachedConfig) {
const auto context_type = kContextTypeMedia;
const auto leaudio_group_id = 6;
const auto num_devices = 2;
ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
// 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, 0, true);
PrepareConfigureQosHandler(group);
PrepareEnableHandler(group);
PrepareDisableHandler(group);
PrepareReleaseHandler(group);
InjectInitialIdleNotification(group);
auto* leAudioDevice = group->GetFirstDevice();
auto expected_devices_written = 0;
while (leAudioDevice) {
/* Three Writes:
* 1: Codec config
* 2: Codec QoS (+1 after restart)
* 3: Enabling (+1 after restart)
* 4: Release (1)
*/
EXPECT_CALL(gatt_queue,
WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(6);
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
LeAudioGroupStateMachine::Get()->StartStream(
group, context_type, types::AudioContexts(context_type));
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::RELEASING));
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(
leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StopStream(group);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Restart stream
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::STREAMING));
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, context_type, types::AudioContexts(context_type));
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, BoundedHeadphonesConversationalToMediaChannelCount_2) {
const auto initial_context_type = kContextTypeConversational;
const auto new_context_type = kContextTypeMedia;
const auto leaudio_group_id = 6;
const auto num_devices = 1;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel |
kLeAudioCodecLC3ChannelCountTwoChannel;
sample_freq_ |= codec_specific::kCapSamplingFrequency48000Hz |
codec_specific::kCapSamplingFrequency32000Hz;
additional_snk_ases = 3;
additional_src_ases = 1;
ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_ccid);
// Prepare multiple fake connected devices in a group
auto* group = PrepareSingleTestDeviceGroup(
leaudio_group_id, initial_context_type, num_devices,
kContextTypeConversational | kContextTypeMedia);
ASSERT_EQ(group->Size(), num_devices);
PrepareConfigureCodecHandler(group, 0, true);
PrepareConfigureQosHandler(group);
PrepareEnableHandler(group);
PrepareDisableHandler(group);
PrepareReleaseHandler(group);
PrepareReceiverStartReady(group);
InjectInitialIdleNotification(group);
auto* leAudioDevice = group->GetFirstDevice();
auto expected_devices_written = 0;
while (leAudioDevice) {
/* 8 Writes:
* 1: Codec config (+1 after reconfig)
* 2: Codec QoS (+1 after reconfig)
* 3: Enabling (+1 after reconfig)
* 4: ReceiverStartReady (only for conversational)
* 5: Release
*/
EXPECT_CALL(gatt_queue,
WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(8);
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
LeAudioGroupStateMachine::Get()->StartStream(
group, initial_context_type, types::AudioContexts(initial_context_type));
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::RELEASING));
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(
leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StopStream(group);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
// Restart stream
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::STREAMING));
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, new_context_type, types::AudioContexts(new_context_type));
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
}
TEST_F(StateMachineTest, BoundedHeadphonesConversationalToMediaChannelCount_1) {
const auto initial_context_type = kContextTypeConversational;
const auto new_context_type = kContextTypeMedia;
const auto leaudio_group_id = 6;
const auto num_devices = 1;
channel_count_ = kLeAudioCodecLC3ChannelCountSingleChannel;
sample_freq_ |= codec_specific::kCapSamplingFrequency48000Hz |
codec_specific::kCapSamplingFrequency32000Hz;
additional_snk_ases = 3;
additional_src_ases = 1;
ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_ccid);
// Prepare one fake connected devices in a group
auto* group = PrepareSingleTestDeviceGroup(
leaudio_group_id, initial_context_type, num_devices,
kContextTypeConversational | kContextTypeMedia);
ASSERT_EQ(group->Size(), num_devices);
// Cannot verify here as we will change the number of ases on reconfigure
PrepareConfigureCodecHandler(group, 0, true);
PrepareConfigureQosHandler(group);
PrepareEnableHandler(group);
PrepareDisableHandler(group);
PrepareReleaseHandler(group);
PrepareReceiverStartReady(group);
InjectInitialIdleNotification(group);
auto* leAudioDevice = group->GetFirstDevice();
auto expected_devices_written = 0;
while (leAudioDevice) {
/* 8 Writes:
* 1: Codec config (+1 after reconfig)
* 2: Codec QoS (+1 after reconfig)
* 3: Enabling (+1 after reconfig)
* 4: ReceiverStartReady (only for conversational)
* 5: Release
*/
EXPECT_CALL(gatt_queue,
WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(8);
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
LeAudioGroupStateMachine::Get()->StartStream(
group, initial_context_type, types::AudioContexts(initial_context_type));
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::RELEASING));
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(
leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StopStream(group);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
// Restart stream
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::STREAMING));
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, new_context_type, types::AudioContexts(new_context_type));
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, lateCisDisconnectedEvent_ConfiguredByUser) {
const auto context_type = kContextTypeMedia;
const auto leaudio_group_id = 6;
const auto num_devices = 1;
ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
// 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, 0, true);
PrepareConfigureQosHandler(group);
PrepareEnableHandler(group);
PrepareDisableHandler(group);
PrepareReleaseHandler(group);
auto* leAudioDevice = group->GetFirstDevice();
auto expected_devices_written = 0;
/* Three Writes:
* 1: Codec Config
* 2: Codec QoS
* 3: Enabling
*/
EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl,
_, GATT_WRITE_NO_RSP, _, _))
.Times(AtLeast(3));
expected_devices_written++;
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);
InjectInitialIdleNotification(group);
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
/* Prepare DisconnectCis mock to not symulate CisDisconnection */
ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
/* Do reconfiguration */
group->SetPendingConfiguration();
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::RELEASING));
EXPECT_CALL(mock_callbacks_,
StatusReportCb(
leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER))
.Times(0);
LeAudioGroupStateMachine::Get()->StopStream(group);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
EXPECT_CALL(mock_callbacks_,
StatusReportCb(
leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER));
// Inject CIS and ACL disconnection of first device
InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, lateCisDisconnectedEvent_AutonomousConfigured) {
const auto context_type = kContextTypeMedia;
const auto leaudio_group_id = 6;
const auto num_devices = 1;
ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
// 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, 0, true);
PrepareConfigureQosHandler(group);
PrepareEnableHandler(group);
PrepareDisableHandler(group);
PrepareReleaseHandler(group);
auto* leAudioDevice = group->GetFirstDevice();
auto expected_devices_written = 0;
/* Three Writes:
* 1: Codec Config
* 2: Codec QoS
* 3: Enabling
*/
EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl,
_, GATT_WRITE_NO_RSP, _, _))
.Times(AtLeast(3));
expected_devices_written++;
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);
InjectInitialIdleNotification(group);
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
/* Prepare DisconnectCis mock to not symulate CisDisconnection */
ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::RELEASING));
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(
leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS))
.Times(0);
// 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);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(
leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
// Inject CIS and ACL disconnection of first device
InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, lateCisDisconnectedEvent_Idle) {
const auto context_type = kContextTypeMedia;
const auto leaudio_group_id = 6;
const auto num_devices = 1;
ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
// 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;
/* Three Writes:
* 1: Codec Config
* 2: Codec QoS
* 3: Enabling
*/
EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl,
_, GATT_WRITE_NO_RSP, _, _))
.Times(AtLeast(3));
expected_devices_written++;
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);
InjectInitialIdleNotification(group);
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
mock_function_count_map["alarm_cancel"] = 0;
/* Prepare DisconnectCis mock to not symulate CisDisconnection */
ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::RELEASING));
EXPECT_CALL(mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::IDLE))
.Times(0);
// 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);
ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
EXPECT_CALL(mock_callbacks_,
StatusReportCb(leaudio_group_id,
bluetooth::le_audio::GroupStreamStatus::IDLE));
// Inject CIS and ACL disconnection of first device
InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, StreamReconfigureAfterCisLostTwoDevices) {
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,
kContextTypeConversational | kContextTypeMedia);
ASSERT_EQ(group->Size(), num_devices);
PrepareConfigureCodecHandler(group);
PrepareConfigureQosHandler(group);
PrepareEnableHandler(group);
PrepareReceiverStartReady(group);
/* Prepare DisconnectCis mock to not symulate CisDisconnection */
ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(2);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(6);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
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(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
context_type = kContextTypeMedia;
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
testing::Mock::VerifyAndClearExpectations(&gatt_queue);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
// Device disconnects due to timeout of CIS
leAudioDevice = group->GetFirstDevice();
while (leAudioDevice) {
InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
// Disconnect device
LeAudioGroupStateMachine::Get()->ProcessHciNotifAclDisconnected(
group, leAudioDevice);
leAudioDevice = group->GetNextDevice(leAudioDevice);
}
LOG(INFO) << "GK A1";
group->ReloadAudioLocations();
group->ReloadAudioDirections();
group->UpdateAudioContextTypeAvailability();
// Start conversational scenario
leAudioDevice = group->GetFirstDevice();
int device_cnt = num_devices;
while (leAudioDevice) {
LOG(INFO) << "GK A11";
leAudioDevice->conn_id_ = device_cnt--;
leAudioDevice->SetConnectionState(DeviceConnectState::CONNECTED);
leAudioDevice = group->GetNextDevice(leAudioDevice);
}
LOG(INFO) << "GK A2";
InjectInitialIdleNotification(group);
group->ReloadAudioLocations();
group->ReloadAudioDirections();
group->UpdateAudioContextTypeAvailability(kContextTypeConversational |
kContextTypeMedia);
leAudioDevice = group->GetFirstDevice();
expected_devices_written = 0;
while (leAudioDevice) {
EXPECT_CALL(gatt_queue,
WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, _,
GATT_WRITE_NO_RSP, _, _))
.Times(4);
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 Conversational content
context_type = kContextTypeConversational;
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
types::AudioContexts(context_type)));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
ASSERT_EQ(2, mock_function_count_map["alarm_cancel"]);
testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
testing::Mock::VerifyAndClearExpectations(&gatt_queue);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
}
} // namespace internal
} // namespace le_audio