/* * 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 #include #include #include #include #include #include #include "bta/csis/csis_types.h" #include "bta_gatt_api_mock.h" #include "bta_gatt_queue_mock.h" #include "bta_groups.h" #include "bta_le_audio_api.h" #include "bta_le_audio_broadcaster_api.h" #include "btif_storage_mock.h" #include "btm_api_mock.h" #include "btm_iso_api.h" #include "common/message_loop_thread.h" #include "fake_osi.h" #include "gatt/database_builder.h" #include "gmock/gmock.h" #include "hardware/bt_gatt_types.h" #include "hci/controller_interface_mock.h" #include "internal_include/stack_config.h" #include "le_audio/codec_manager.h" #include "le_audio_health_status.h" #include "le_audio_set_configuration_provider.h" #include "le_audio_types.h" #include "mock_codec_manager.h" #include "mock_csis_client.h" #include "mock_device_groups.h" #include "mock_state_machine.h" #include "test/common/mock_functions.h" #include "test/mock/mock_main_shim_entry.h" #include "test/mock/mock_stack_btm_iso.h" #define TEST_BT com::android::bluetooth::flags using testing::_; using testing::AnyNumber; using testing::AtLeast; using testing::AtMost; using testing::DoAll; using testing::Expectation; using testing::InSequence; using testing::Invoke; using testing::Matcher; using testing::Mock; using testing::MockFunction; using testing::NiceMock; using testing::NotNull; using testing::Return; using testing::SaveArg; using testing::SetArgPointee; using testing::Test; using testing::WithArg; using bluetooth::Uuid; using namespace bluetooth::le_audio; using bluetooth::le_audio::LeAudioCodecConfiguration; using bluetooth::le_audio::LeAudioDeviceGroup; using bluetooth::le_audio::LeAudioHealthStatus; using bluetooth::le_audio::LeAudioSinkAudioHalClient; using bluetooth::le_audio::LeAudioSourceAudioHalClient; using bluetooth::le_audio::DsaMode; using bluetooth::le_audio::DsaModes; using bluetooth::le_audio::types::AudioContexts; using bluetooth::le_audio::types::BidirectionalPair; using bluetooth::le_audio::types::LeAudioContextType; extern struct fake_osi_alarm_set_on_mloop fake_osi_alarm_set_on_mloop_; constexpr int max_num_of_ases = 5; constexpr bluetooth::le_audio::types::LeAudioContextType kLeAudioDefaultConfigurationContext = bluetooth::le_audio::types::LeAudioContextType::UNSPECIFIED; static constexpr char kNotifyUpperLayerAboutGroupBeingInIdleDuringCall[] = "persist.bluetooth.leaudio.notify.idle.during.call"; const char* test_flags[] = { "INIT_default_log_level_str=LOG_VERBOSE", "INIT_leaudio_targeted_announcement_reconnection_mode=true", "INIT_leaudio_enable_health_based_actions=false", "INIT_leaudio_broadcast_audio_handover_policies=false", nullptr, }; const char* test_flags_with_health_status[] = { "INIT_default_log_level_str=LOG_VERBOSE", "INIT_leaudio_targeted_announcement_reconnection_mode=true", "INIT_leaudio_enable_health_based_actions=true", "INIT_leaudio_broadcast_audio_handover_policies=false", nullptr, }; const char* test_flags_with_handover_mode[] = { "INIT_default_log_level_str=LOG_VERBOSE", "INIT_leaudio_targeted_announcement_reconnection_mode=true", "INIT_leaudio_enable_health_based_actions=false", "INIT_leaudio_broadcast_audio_handover_policies=true", nullptr, }; void osi_property_set_bool(const char* key, bool value); // Disables most likely false-positives from base::SplitString() extern "C" const char* __asan_default_options() { return "detect_container_overflow=0"; } std::atomic num_async_tasks; static base::MessageLoop* message_loop_; bluetooth::common::MessageLoopThread message_loop_thread("test message loop"); bluetooth::common::MessageLoopThread* get_main_thread() { return &message_loop_thread; } bt_status_t do_in_main_thread(const base::Location& from_here, base::OnceClosure task) { if (!message_loop_) return BT_STATUS_FAIL; // Wrap the task with task counter so we could later know if there are // any callbacks scheduled and we should wait before performing some actions if (!message_loop_thread.DoInThread( from_here, base::BindOnce( [](base::OnceClosure task, std::atomic& num_async_tasks) { std::move(task).Run(); num_async_tasks--; }, std::move(task), std::ref(num_async_tasks)))) { bluetooth::log::error("failed from {}", from_here.ToString()); return BT_STATUS_FAIL; } num_async_tasks++; return BT_STATUS_SUCCESS; } bt_status_t do_in_main_thread_delayed(const base::Location& from_here, base::OnceClosure task, std::chrono::microseconds delay) { /* For testing purpose it is ok to just skip delay */ return do_in_main_thread(from_here, std::move(task)); } base::MessageLoop* get_main_message_loop() { return message_loop_; } static void init_message_loop_thread() { num_async_tasks = 0; message_loop_thread.StartUp(); if (!message_loop_thread.IsRunning()) { FAIL() << "unable to create message loop thread."; } if (!message_loop_thread.EnableRealTimeScheduling()) bluetooth::log::error("Unable to set real time scheduling"); message_loop_ = message_loop_thread.message_loop(); if (message_loop_ == nullptr) FAIL() << "unable to get message loop."; } static void cleanup_message_loop_thread() { message_loop_ = nullptr; message_loop_thread.ShutDown(); } void invoke_switch_codec_cb(bool is_low_latency_buffer_size) {} void invoke_switch_buffer_size_cb(bool is_low_latency_buffer_size) {} const std::string kSmpOptions("mock smp options"); bool get_pts_avrcp_test(void) { return false; } bool get_pts_secure_only_mode(void) { return false; } bool get_pts_conn_updates_disabled(void) { return false; } bool get_pts_crosskey_sdp_disable(void) { return false; } const std::string* get_pts_smp_options(void) { return &kSmpOptions; } int get_pts_smp_failure_case(void) { return 123; } bool get_pts_force_eatt_for_notifications(void) { return false; } bool get_pts_connect_eatt_unconditionally(void) { return false; } bool get_pts_connect_eatt_before_encryption(void) { return false; } bool get_pts_unencrypt_broadcast(void) { return false; } bool get_pts_eatt_peripheral_collision_support(void) { return false; } bool get_pts_force_le_audio_multiple_contexts_metadata(void) { return false; } bool get_pts_le_audio_disable_ases_before_stopping(void) { return false; } config_t* get_all(void) { return nullptr; } stack_config_t mock_stack_config{ .get_pts_avrcp_test = get_pts_avrcp_test, .get_pts_secure_only_mode = get_pts_secure_only_mode, .get_pts_conn_updates_disabled = get_pts_conn_updates_disabled, .get_pts_crosskey_sdp_disable = get_pts_crosskey_sdp_disable, .get_pts_smp_options = get_pts_smp_options, .get_pts_smp_failure_case = get_pts_smp_failure_case, .get_pts_force_eatt_for_notifications = get_pts_force_eatt_for_notifications, .get_pts_connect_eatt_unconditionally = get_pts_connect_eatt_unconditionally, .get_pts_connect_eatt_before_encryption = get_pts_connect_eatt_before_encryption, .get_pts_unencrypt_broadcast = get_pts_unencrypt_broadcast, .get_pts_eatt_peripheral_collision_support = get_pts_eatt_peripheral_collision_support, .get_pts_force_le_audio_multiple_contexts_metadata = get_pts_force_le_audio_multiple_contexts_metadata, .get_pts_le_audio_disable_ases_before_stopping = get_pts_le_audio_disable_ases_before_stopping, .get_all = get_all, }; const stack_config_t* stack_config_get_interface(void) { return &mock_stack_config; } bool LeAudioBroadcaster::IsLeAudioBroadcasterRunning() { return false; } namespace server_configurable_flags { std::string GetServerConfigurableFlag( const std::string& experiment_category_name, const std::string& experiment_flag_name, const std::string& default_value) { return ""; } } // namespace server_configurable_flags namespace bluetooth::le_audio { class MockLeAudioSourceHalClient; MockLeAudioSourceHalClient* mock_le_audio_source_hal_client_; std::unique_ptr owned_mock_le_audio_source_hal_client_; bool is_audio_unicast_source_acquired; std::unique_ptr LeAudioSourceAudioHalClient::AcquireUnicast() { if (is_audio_unicast_source_acquired) return nullptr; is_audio_unicast_source_acquired = true; return std::move(owned_mock_le_audio_source_hal_client_); } void LeAudioSourceAudioHalClient::DebugDump(int fd) {} class MockLeAudioSinkHalClient; MockLeAudioSinkHalClient* mock_le_audio_sink_hal_client_; std::unique_ptr owned_mock_le_audio_sink_hal_client_; bool is_audio_unicast_sink_acquired; std::unique_ptr LeAudioSinkAudioHalClient::AcquireUnicast() { if (is_audio_unicast_sink_acquired) return nullptr; is_audio_unicast_sink_acquired = true; return std::move(owned_mock_le_audio_sink_hal_client_); } void LeAudioSinkAudioHalClient::DebugDump(int fd) {} class MockAudioHalClientCallbacks : public bluetooth::le_audio::LeAudioClientCallbacks { public: MOCK_METHOD((void), OnInitialized, (), (override)); MOCK_METHOD((void), OnConnectionState, (ConnectionState state, const RawAddress& address), (override)); MOCK_METHOD((void), OnGroupStatus, (int group_id, GroupStatus group_status), (override)); MOCK_METHOD((void), OnGroupStreamStatus, (int group_id, GroupStreamStatus group_stream_status), (override)); MOCK_METHOD((void), OnGroupNodeStatus, (const RawAddress& bd_addr, int group_id, GroupNodeStatus node_status), (override)); MOCK_METHOD((void), OnAudioConf, (uint8_t direction, int group_id, uint32_t snk_audio_location, uint32_t src_audio_location, uint16_t avail_cont), (override)); MOCK_METHOD((void), OnSinkAudioLocationAvailable, (const RawAddress& bd_addr, uint32_t snk_audio_location), (override)); MOCK_METHOD( (void), OnAudioLocalCodecCapabilities, (std::vector local_input_capa_codec_conf, std::vector local_output_capa_codec_conf), (override)); MOCK_METHOD((void), OnAudioGroupCurrentCodecConf, (int group_id, btle_audio_codec_config_t input_codec_conf, btle_audio_codec_config_t output_codec_conf), (override)); MOCK_METHOD( (void), OnAudioGroupSelectableCodecConf, (int group_id, std::vector input_selectable_codec_conf, std::vector output_selectable_codec_conf), (override)); MOCK_METHOD((void), OnHealthBasedRecommendationAction, (const RawAddress& address, LeAudioHealthBasedAction action), (override)); MOCK_METHOD((void), OnHealthBasedGroupRecommendationAction, (int group_id, LeAudioHealthBasedAction action), (override)); MOCK_METHOD((void), OnUnicastMonitorModeStatus, (uint8_t direction, UnicastMonitorModeStatus status)); }; class MockLeAudioSinkHalClient : public LeAudioSinkAudioHalClient { public: MockLeAudioSinkHalClient() = default; MOCK_METHOD((bool), Start, (const LeAudioCodecConfiguration& codecConfiguration, LeAudioSinkAudioHalClient::Callbacks* audioReceiver, DsaModes dsa_modes), (override)); MOCK_METHOD((void), Stop, (), (override)); MOCK_METHOD((size_t), SendData, (uint8_t * data, uint16_t size), (override)); MOCK_METHOD((void), ConfirmStreamingRequest, (), (override)); MOCK_METHOD((void), CancelStreamingRequest, (), (override)); MOCK_METHOD((void), UpdateRemoteDelay, (uint16_t delay), (override)); MOCK_METHOD((void), UpdateAudioConfigToHal, (const ::bluetooth::le_audio::offload_config&), (override)); MOCK_METHOD((void), SuspendedForReconfiguration, (), (override)); MOCK_METHOD((void), ReconfigurationComplete, (), (override)); MOCK_METHOD((void), OnDestroyed, ()); virtual ~MockLeAudioSinkHalClient() override { OnDestroyed(); } }; class MockLeAudioSourceHalClient : public LeAudioSourceAudioHalClient { public: MockLeAudioSourceHalClient() = default; MOCK_METHOD((bool), Start, (const LeAudioCodecConfiguration& codecConfiguration, LeAudioSourceAudioHalClient::Callbacks* audioReceiver, DsaModes dsa_modes), (override)); MOCK_METHOD((void), Stop, (), (override)); MOCK_METHOD((void), ConfirmStreamingRequest, (), (override)); MOCK_METHOD((void), CancelStreamingRequest, (), (override)); MOCK_METHOD((void), UpdateRemoteDelay, (uint16_t delay), (override)); MOCK_METHOD((void), UpdateAudioConfigToHal, (const ::bluetooth::le_audio::offload_config&), (override)); MOCK_METHOD( (std::optional), GetBroadcastConfig, ((const std::vector>&), (const std::optional< std::vector<::bluetooth::le_audio::types::acs_ac_record>>&)), (const override)); MOCK_METHOD( (std::optional< ::bluetooth::le_audio::set_configurations::AudioSetConfiguration>), GetUnicastConfig, (const CodecManager::UnicastConfigurationRequirements&), (const override)); MOCK_METHOD((void), UpdateBroadcastAudioConfigToHal, (const ::bluetooth::le_audio::broadcast_offload_config&), (override)); MOCK_METHOD((void), SuspendedForReconfiguration, (), (override)); MOCK_METHOD((void), ReconfigurationComplete, (), (override)); MOCK_METHOD((void), OnDestroyed, ()); virtual ~MockLeAudioSourceHalClient() override { OnDestroyed(); } }; class UnicastTestNoInit : public Test { public: bool use_health_status = false; bool use_handover_mode = false; protected: void RegisterSourceHalClientMock() { owned_mock_le_audio_source_hal_client_.reset( new NiceMock()); mock_le_audio_source_hal_client_ = (MockLeAudioSourceHalClient*) owned_mock_le_audio_source_hal_client_.get(); is_audio_unicast_source_acquired = false; ON_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)) .WillByDefault( [this](const LeAudioCodecConfiguration& codec_configuration, LeAudioSourceAudioHalClient::Callbacks* audioReceiver, DsaModes dsa_modes) { unicast_source_hal_cb_ = audioReceiver; return true; }); ON_CALL(*mock_le_audio_source_hal_client_, OnDestroyed).WillByDefault([]() { mock_le_audio_source_hal_client_ = nullptr; is_audio_unicast_source_acquired = false; }); } void RegisterSinkHalClientMock() { owned_mock_le_audio_sink_hal_client_.reset( new NiceMock()); mock_le_audio_sink_hal_client_ = (MockLeAudioSinkHalClient*)owned_mock_le_audio_sink_hal_client_.get(); is_audio_unicast_sink_acquired = false; ON_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)) .WillByDefault( [this](const LeAudioCodecConfiguration& codec_configuration, LeAudioSinkAudioHalClient::Callbacks* audioReceiver, DsaModes dsa_modes) { unicast_sink_hal_cb_ = audioReceiver; return true; }); ON_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed).WillByDefault([]() { mock_le_audio_sink_hal_client_ = nullptr; is_audio_unicast_sink_acquired = false; }); } void SetUpMockAudioHal() { if (use_health_status) { bluetooth::common::InitFlags::Load(test_flags_with_health_status); } else if (use_handover_mode) { bluetooth::common::InitFlags::Load(test_flags_with_handover_mode); } else { bluetooth::common::InitFlags::Load(test_flags); } /* Since these are returned by the Acquire() methods as unique_ptrs, we * will not free them manually. */ RegisterSourceHalClientMock(); owned_mock_le_audio_sink_hal_client_.reset( new NiceMock()); mock_le_audio_sink_hal_client_ = (MockLeAudioSinkHalClient*)owned_mock_le_audio_sink_hal_client_.get(); owned_mock_le_audio_source_hal_client_.reset( new NiceMock()); mock_le_audio_source_hal_client_ = (MockLeAudioSourceHalClient*) owned_mock_le_audio_source_hal_client_.get(); is_audio_unicast_source_acquired = false; ON_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)) .WillByDefault( [this](const LeAudioCodecConfiguration& codec_configuration, LeAudioSourceAudioHalClient::Callbacks* audioReceiver, DsaModes dsa_modes) { unicast_source_hal_cb_ = audioReceiver; return true; }); ON_CALL(*mock_le_audio_source_hal_client_, OnDestroyed).WillByDefault([]() { mock_le_audio_source_hal_client_ = nullptr; is_audio_unicast_source_acquired = false; }); is_audio_unicast_sink_acquired = false; ON_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)) .WillByDefault( [this](const LeAudioCodecConfiguration& codec_configuration, LeAudioSinkAudioHalClient::Callbacks* audioReceiver, DsaModes dsa_modes) { unicast_sink_hal_cb_ = audioReceiver; return true; }); ON_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed).WillByDefault([]() { mock_le_audio_sink_hal_client_ = nullptr; is_audio_unicast_sink_acquired = false; }); ON_CALL(*mock_le_audio_sink_hal_client_, SendData) .WillByDefault([](uint8_t* data, uint16_t size) { return size; }); // HAL ON_CALL(mock_hal_2_1_verifier, Call()).WillByDefault([]() -> bool { return true; }); } void InjectGroupDeviceRemoved(const RawAddress& address, int group_id) { group_callbacks_->OnGroupMemberRemoved(address, group_id); } void InjectGroupDeviceAdded(const RawAddress& address, int group_id) { bluetooth::Uuid uuid = bluetooth::le_audio::uuid::kCapServiceUuid; int group_members_num = 0; for (const auto& [addr, id] : groups) { if (id == group_id) group_members_num++; } bool first_device = (group_members_num == 1); do_in_main_thread( FROM_HERE, base::BindOnce( [](const RawAddress& addr, int group_id, bluetooth::Uuid uuid, bluetooth::groups::DeviceGroupsCallbacks* group_callbacks, bool first_device) { if (first_device) { group_callbacks->OnGroupAdded(addr, uuid, group_id); } else { group_callbacks->OnGroupMemberAdded(addr, group_id); } }, address, group_id, uuid, base::Unretained(this->group_callbacks_), first_device)); } void InjectServiceChangedEvent(const RawAddress& address, uint16_t conn_id) { tBTA_GATTC_SERVICE_CHANGED event_data = {.remote_bda = address, .conn_id = conn_id}; do_in_main_thread(FROM_HERE, base::BindOnce( [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_SERVICE_CHANGED event_data) { gatt_callback(BTA_GATTC_SRVC_CHG_EVT, (tBTA_GATTC*)&event_data); }, base::Unretained(this->gatt_callback), event_data)); } void InjectConnectedEvent(const RawAddress& address, uint16_t conn_id, tGATT_STATUS status = GATT_SUCCESS) { ASSERT_NE(conn_id, GATT_INVALID_CONN_ID); tBTA_GATTC_OPEN event_data = { .status = status, .conn_id = conn_id, .client_if = gatt_if, .remote_bda = address, .transport = GATT_TRANSPORT_LE, .mtu = 240, }; if (status == GATT_SUCCESS) { ASSERT_NE(peer_devices.count(conn_id), 0u); peer_devices.at(conn_id)->connected = true; } do_in_main_thread( FROM_HERE, base::BindOnce( [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_OPEN event_data) { gatt_callback(BTA_GATTC_OPEN_EVT, (tBTA_GATTC*)&event_data); }, base::Unretained(this->gatt_callback), event_data)); } void InjectEncryptionChangedEvent(const RawAddress& address) { tBTA_GATTC_ENC_CMPL_CB event_data = { .client_if = gatt_if, .remote_bda = address, }; do_in_main_thread(FROM_HERE, base::BindOnce( [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_ENC_CMPL_CB event_data) { gatt_callback(BTA_GATTC_ENC_CMPL_CB_EVT, (tBTA_GATTC*)&event_data); }, base::Unretained(this->gatt_callback), event_data)); } void InjectDisconnectedEvent( uint16_t conn_id, tGATT_DISCONN_REASON reason = GATT_CONN_TERMINATE_LOCAL_HOST) { ASSERT_NE(conn_id, GATT_INVALID_CONN_ID); ASSERT_NE(peer_devices.count(conn_id), 0u); tBTA_GATTC_CLOSE event_data = { .conn_id = conn_id, .status = GATT_SUCCESS, .client_if = gatt_if, .remote_bda = peer_devices.at(conn_id)->addr, .reason = reason, }; peer_devices.at(conn_id)->connected = false; do_in_main_thread( FROM_HERE, base::BindOnce( [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_CLOSE event_data) { gatt_callback(BTA_GATTC_CLOSE_EVT, (tBTA_GATTC*)&event_data); }, base::Unretained(this->gatt_callback), event_data)); } void InjectPhyChangedEvent(uint16_t conn_id, uint8_t tx_phy, uint8_t rx_phy, tGATT_STATUS status) { ASSERT_NE(conn_id, GATT_INVALID_CONN_ID); tBTA_GATTC_PHY_UPDATE event_data = { .conn_id = conn_id, .tx_phy = tx_phy, .rx_phy = rx_phy, .status = status, }; do_in_main_thread(FROM_HERE, base::BindOnce( [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_PHY_UPDATE event_data) { gatt_callback(BTA_GATTC_PHY_UPDATE_EVT, (tBTA_GATTC*)&event_data); }, base::Unretained(this->gatt_callback), event_data)); } void InjectSearchCompleteEvent(uint16_t conn_id) { ASSERT_NE(conn_id, GATT_INVALID_CONN_ID); tBTA_GATTC_SEARCH_CMPL event_data = { .conn_id = conn_id, .status = GATT_SUCCESS, }; do_in_main_thread(FROM_HERE, base::BindOnce( [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_SEARCH_CMPL event_data) { gatt_callback(BTA_GATTC_SEARCH_CMPL_EVT, (tBTA_GATTC*)&event_data); }, base::Unretained(this->gatt_callback), event_data)); } void InjectNotificationEvent(const RawAddress& test_address, uint16_t conn_id, uint16_t handle, std::vector value) { ASSERT_NE(conn_id, GATT_INVALID_CONN_ID); tBTA_GATTC_NOTIFY event_data = { .conn_id = conn_id, .bda = test_address, .handle = handle, .len = (uint8_t)value.size(), .is_notify = true, }; std::copy(value.begin(), value.end(), event_data.value); do_in_main_thread( FROM_HERE, base::BindOnce( [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_NOTIFY event_data) { gatt_callback(BTA_GATTC_NOTIF_EVT, (tBTA_GATTC*)&event_data); }, base::Unretained(this->gatt_callback), event_data)); } void InjectContextTypes(const RawAddress& test_address, uint16_t conn_id, uint16_t handle, AudioContexts sink_ctxs, AudioContexts source_ctxs) { std::vector contexts = { (uint8_t)(sink_ctxs.value()), (uint8_t)(sink_ctxs.value() >> 8), (uint8_t)(source_ctxs.value()), (uint8_t)(source_ctxs.value() >> 8)}; InjectNotificationEvent(test_address, conn_id, handle, contexts); } void InjectSupportedContextTypes(const RawAddress& test_address, uint16_t conn_id, AudioContexts sink_ctxs, AudioContexts source_ctxs) { /* 0x0077 pacs->supp_contexts_char + 1 */ InjectContextTypes(test_address, conn_id, 0x0077, sink_ctxs, source_ctxs); SyncOnMainLoop(); } void InjectAvailableContextTypes(const RawAddress& test_address, uint16_t conn_id, AudioContexts sink_ctxs, AudioContexts source_ctxs, bool sync_on_mainloop = true) { /* 0x0074 is pacs->avail_contexts_char + 1 */ InjectContextTypes(test_address, conn_id, 0x0074, sink_ctxs, source_ctxs); if (sync_on_mainloop) { SyncOnMainLoop(); } } void SetUpMockGatt() { // default action for GetCharacteristic function call ON_CALL(mock_gatt_interface_, GetCharacteristic(_, _)) .WillByDefault( Invoke([&](uint16_t conn_id, uint16_t handle) -> const gatt::Characteristic* { std::list& services = peer_devices.at(conn_id)->services; for (auto const& service : services) { for (auto const& characteristic : service.characteristics) { if (characteristic.value_handle == handle) { return &characteristic; } } } return nullptr; })); // default action for GetOwningService function call ON_CALL(mock_gatt_interface_, GetOwningService(_, _)) .WillByDefault(Invoke( [&](uint16_t conn_id, uint16_t handle) -> const gatt::Service* { std::list& services = peer_devices.at(conn_id)->services; for (auto const& service : services) { if (service.handle <= handle && service.end_handle >= handle) { return &service; } } return nullptr; })); // default action for ServiceSearchRequest function call ON_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _)) .WillByDefault(WithArg<0>(Invoke( [&](uint16_t conn_id) { InjectSearchCompleteEvent(conn_id); }))); // default action for GetServices function call ON_CALL(mock_gatt_interface_, GetServices(_)) .WillByDefault(WithArg<0>( Invoke([&](uint16_t conn_id) -> std::list* { return &peer_devices.at(conn_id)->services; }))); // default action for RegisterForNotifications function call ON_CALL(mock_gatt_interface_, RegisterForNotifications(gatt_if, _, _)) .WillByDefault(Return(GATT_SUCCESS)); // default action for DeregisterForNotifications function call ON_CALL(mock_gatt_interface_, DeregisterForNotifications(gatt_if, _, _)) .WillByDefault(Return(GATT_SUCCESS)); // default action for WriteDescriptor function call ON_CALL(mock_gatt_queue_, WriteDescriptor(_, _, _, _, _, _)) .WillByDefault(Invoke([this](uint16_t conn_id, uint16_t handle, std::vector value, tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, void* cb_data) -> void { auto& ascs = peer_devices.at(conn_id)->ascs; uint8_t idx; if (handle == ascs->ctp_ccc) { value = UINT16_TO_VEC_UINT8(ascs->ctp_ccc_val); } else { for (idx = 0; idx < max_num_of_ases; idx++) { if (handle == ascs->sink_ase_ccc[idx] + 1) { value = UINT16_TO_VEC_UINT8(ascs->sink_ase_ccc_val[idx]); break; } if (handle == ascs->source_ase_char[idx] + 1) { value = UINT16_TO_VEC_UINT8(ascs->source_ase_ccc_val[idx]); break; } } } if (cb) do_in_main_thread( FROM_HERE, base::BindOnce( [](GATT_WRITE_OP_CB cb, uint16_t conn_id, uint16_t handle, uint16_t len, uint8_t* value, void* cb_data) { cb(conn_id, GATT_SUCCESS, handle, len, value, cb_data); }, cb, conn_id, handle, value.size(), value.data(), cb_data)); })); global_conn_id = 1; ON_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault( Invoke([&](tGATT_IF client_if, const RawAddress& remote_bda, bool is_direct, bool opportunistic) { InjectConnectedEvent(remote_bda, global_conn_id++); })); ON_CALL(mock_gatt_interface_, Close(_)) .WillByDefault(Invoke([&](uint16_t conn_id) { ASSERT_NE(conn_id, GATT_INVALID_CONN_ID); InjectDisconnectedEvent(conn_id); })); // default Characteristic read handler dispatches requests to service mocks ON_CALL(mock_gatt_queue_, ReadCharacteristic(_, _, _, _)) .WillByDefault(Invoke([&](uint16_t conn_id, uint16_t handle, GATT_READ_OP_CB cb, void* cb_data) { do_in_main_thread( FROM_HERE, base::BindOnce( [](std::map>>* peer_devices, uint16_t conn_id, uint16_t handle, GATT_READ_OP_CB cb, void* cb_data) -> void { if (peer_devices->count(conn_id)) { auto& device = peer_devices->at(conn_id); auto svc = std::find_if( device->services.begin(), device->services.end(), [handle](const gatt::Service& svc) { return (handle >= svc.handle) && (handle <= svc.end_handle); }); if (svc == device->services.end()) return; // Dispatch to mockable handler functions if (svc->handle == device->csis->start) { device->csis->OnReadCharacteristic(handle, cb, cb_data); } else if (svc->handle == device->cas->start) { device->cas->OnReadCharacteristic(handle, cb, cb_data); } else if (svc->handle == device->ascs->start) { device->ascs->OnReadCharacteristic(handle, cb, cb_data); } else if (svc->handle == device->pacs->start) { device->pacs->OnReadCharacteristic(handle, cb, cb_data); } } }, &peer_devices, conn_id, handle, cb, cb_data)); })); } void SetUpMockGroups() { MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_); MockDeviceGroups::SetMockInstanceForTesting(&mock_groups_module_); MockLeAudioGroupStateMachine::SetMockInstanceForTesting( &mock_state_machine_); ON_CALL(mock_csis_client_module_, Get()) .WillByDefault(Return(&mock_csis_client_module_)); // Store group callbacks so that we could inject grouping events group_callbacks_ = nullptr; ON_CALL(mock_groups_module_, Initialize(_)) .WillByDefault(SaveArg<0>(&group_callbacks_)); ON_CALL(mock_groups_module_, GetGroupId(_, _)) .WillByDefault([this](const RawAddress& addr, bluetooth::Uuid uuid) { if (groups.find(addr) != groups.end()) return groups.at(addr); return bluetooth::groups::kGroupUnknown; }); ON_CALL(mock_groups_module_, RemoveDevice(_, _)) .WillByDefault([this](const RawAddress& addr, int group_id_) { int group_id = -1; if (groups.find(addr) != groups.end()) { group_id = groups[addr]; groups.erase(addr); } if (group_id < 0) return; do_in_main_thread( FROM_HERE, base::BindOnce( [](const RawAddress& address, int group_id, bluetooth::groups::DeviceGroupsCallbacks* group_callbacks) { group_callbacks->OnGroupMemberRemoved(address, group_id); }, addr, group_id, base::Unretained(group_callbacks_))); }); // Our test devices have unique LSB - use it for unique grouping when // devices added with a non-CIS context and no grouping info ON_CALL(mock_groups_module_, AddDevice(_, bluetooth::le_audio::uuid::kCapServiceUuid, _)) .WillByDefault( [this](const RawAddress& addr, bluetooth::Uuid uuid = bluetooth::le_audio::uuid::kCapServiceUuid, int group_id = bluetooth::groups::kGroupUnknown) -> int { if (group_id == bluetooth::groups::kGroupUnknown) { /* Generate group id from address */ groups[addr] = addr.address[RawAddress::kLength - 1]; group_id = groups[addr]; } else { groups[addr] = group_id; } InjectGroupDeviceAdded(addr, groups[addr]); return addr.address[RawAddress::kLength - 1]; }); ON_CALL(mock_state_machine_, Initialize(_)) .WillByDefault(SaveArg<0>(&state_machine_callbacks_)); ON_CALL(mock_state_machine_, ConfigureStream(_, _, _, _)) .WillByDefault( [this](LeAudioDeviceGroup* group, types::LeAudioContextType context_type, types::BidirectionalPair metadata_context_types, types::BidirectionalPair> ccid_lists) { bool isReconfiguration = group->IsPendingConfiguration(); /* This shall be called only for user reconfiguration */ if (!isReconfiguration) return false; /* Do what ReleaseCisIds(group) does: start */ LeAudioDevice* leAudioDevice = group->GetFirstDevice(); while (leAudioDevice != nullptr) { for (auto& ase : leAudioDevice->ases_) { ase.cis_id = bluetooth::le_audio::kInvalidCisId; } leAudioDevice = group->GetNextDevice(leAudioDevice); } group->ClearAllCises(); /* end */ if (!group->Configure(context_type, metadata_context_types, ccid_lists)) { log::error( "Could not configure ASEs for group {} content type {}", group->group_id_, int(context_type)); return false; } group->cig.GenerateCisIds(context_type); for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr; device = group->GetNextDevice(device)) { for (auto& ase : device->ases_) { ase.cis_state = types::CisState::IDLE; ase.data_path_state = types::DataPathState::IDLE; ase.active = false; ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED; } } // Inject the state group->SetTargetState( types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); group->SetState(group->GetTargetState()); group->ClearPendingConfiguration(); do_in_main_thread( FROM_HERE, base::BindOnce( [](int group_id, bluetooth::le_audio::LeAudioGroupStateMachine:: Callbacks* state_machine_callbacks) { state_machine_callbacks->StatusReportCb( group_id, GroupStreamStatus::CONFIGURED_BY_USER); }, group->group_id_, base::Unretained(this->state_machine_callbacks_))); return true; }); ON_CALL(mock_state_machine_, AttachToStream(_, _, _)) .WillByDefault([this](LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, types::BidirectionalPair> ccids) { if (group->GetState() != types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { if (group->GetTargetState() == types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { attach_to_stream_scheduled = true; } return false; } group->Configure(group->GetConfigurationContextType(), group->GetMetadataContexts(), ccids); if (!group->cig.AssignCisIds(leAudioDevice)) return false; group->AssignCisConnHandlesToAses(leAudioDevice); auto* stream_conf = &group->stream_conf; for (auto& ase : leAudioDevice->ases_) { if (!ase.active) continue; // And also skip the ase establishment procedure which should // be tested as part of the state machine unit tests ase.cis_state = types::CisState::CONNECTED; ase.data_path_state = types::DataPathState::CONFIGURED; ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING; uint16_t cis_conn_hdl = ase.cis_conn_hdl; auto core_config = ase.codec_config.GetAsCoreCodecConfig(); /* Copied from state_machine.cc ProcessHciNotifSetupIsoDataPath */ if (ase.direction == bluetooth::le_audio::types::kLeAudioDirectionSource) { auto iter = std::find_if( stream_conf->stream_params.source.stream_locations.begin(), stream_conf->stream_params.source.stream_locations.end(), [cis_conn_hdl](auto& pair) { return cis_conn_hdl == pair.first; }); if (iter == stream_conf->stream_params.source.stream_locations.end()) { stream_conf->stream_params.source.stream_locations.emplace_back( std::make_pair(ase.cis_conn_hdl, *core_config.audio_channel_allocation)); stream_conf->stream_params.source.num_of_devices++; stream_conf->stream_params.source.num_of_channels += ase.channel_count; log::info( "Added Source Stream Configuration. CIS Connection Handle: " "{}, Audio Channel Allocation: {}, Source Number Of " "Devices: {}, Source Number Of Channels: {}", ase.cis_conn_hdl, *core_config.audio_channel_allocation, stream_conf->stream_params.source.num_of_devices, stream_conf->stream_params.source.num_of_channels); } } else { auto iter = std::find_if( stream_conf->stream_params.sink.stream_locations.begin(), stream_conf->stream_params.sink.stream_locations.end(), [cis_conn_hdl](auto& pair) { return cis_conn_hdl == pair.first; }); if (iter == stream_conf->stream_params.sink.stream_locations.end()) { stream_conf->stream_params.sink.stream_locations.emplace_back( std::make_pair(ase.cis_conn_hdl, *core_config.audio_channel_allocation)); stream_conf->stream_params.sink.num_of_devices++; stream_conf->stream_params.sink.num_of_channels += ase.channel_count; log::info( "Added Sink Stream Configuration. CIS Connection Handle: " "{}, Audio Channel Allocation: {}, Sink Number Of Devices: " "{}, Sink Number Of Channels: {}", ase.cis_conn_hdl, *core_config.audio_channel_allocation, stream_conf->stream_params.sink.num_of_devices, stream_conf->stream_params.sink.num_of_channels); } } } return true; }); ON_CALL(mock_state_machine_, StartStream(_, _, _, _)) .WillByDefault([this](LeAudioDeviceGroup* group, types::LeAudioContextType context_type, types::BidirectionalPair metadata_context_types, types::BidirectionalPair> ccid_lists) { /* Do nothing if already streaming - the implementation would * probably update the metadata. */ if (group->GetState() == types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { return true; } /* Do what ReleaseCisIds(group) does: start */ LeAudioDevice* leAudioDevice = group->GetFirstDevice(); while (leAudioDevice != nullptr) { for (auto& ase : leAudioDevice->ases_) { ase.cis_id = bluetooth::le_audio::kInvalidCisId; } leAudioDevice = group->GetNextDevice(leAudioDevice); } group->ClearAllCises(); /* end */ if (!group->Configure(context_type, metadata_context_types, ccid_lists)) { log::error("failed to set ASE configuration"); return false; } if (group->GetState() == types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { group->cig.GenerateCisIds(context_type); std::vector conn_handles; for (uint8_t i = 0; i < (uint8_t)(group->cig.cises.size()); i++) { conn_handles.push_back(iso_con_counter_++); } group->cig.AssignCisConnHandles(conn_handles); for (LeAudioDevice* device = group->GetFirstActiveDevice(); device != nullptr; device = group->GetNextActiveDevice(device)) { if (!group->cig.AssignCisIds(device)) return false; group->AssignCisConnHandlesToAses(device); } } auto* stream_conf = &group->stream_conf; // Fake ASE configuration for (LeAudioDevice* device = group->GetFirstActiveDevice(); device != nullptr; device = group->GetNextActiveDevice(device)) { for (auto& ase : device->ases_) { if (!ase.active) continue; // And also skip the ase establishment procedure which should // be tested as part of the state machine unit tests ase.cis_state = types::CisState::CONNECTED; ase.data_path_state = types::DataPathState::CONFIGURED; ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING; ase.qos_preferences.pres_delay_min = 2500; ase.qos_preferences.pres_delay_max = 2500; ase.qos_preferences.preferred_pres_delay_min = 2500; ase.qos_preferences.preferred_pres_delay_max = 2500; auto core_config = ase.codec_config.GetAsCoreCodecConfig(); uint16_t cis_conn_hdl = ase.cis_conn_hdl; /* Copied from state_machine.cc ProcessHciNotifSetupIsoDataPath */ if (ase.direction == bluetooth::le_audio::types::kLeAudioDirectionSource) { auto iter = std::find_if( stream_conf->stream_params.source.stream_locations.begin(), stream_conf->stream_params.source.stream_locations.end(), [cis_conn_hdl](auto& pair) { return cis_conn_hdl == pair.first; }); if (iter == stream_conf->stream_params.source.stream_locations.end()) { stream_conf->stream_params.source.stream_locations .emplace_back(std::make_pair( ase.cis_conn_hdl, *core_config.audio_channel_allocation)); stream_conf->stream_params.source.num_of_devices++; stream_conf->stream_params.source.num_of_channels += ase.channel_count; stream_conf->stream_params.source.audio_channel_allocation |= *core_config.audio_channel_allocation; if (stream_conf->stream_params.source.sample_frequency_hz == 0) { stream_conf->stream_params.source.sample_frequency_hz = core_config.GetSamplingFrequencyHz(); } else { log::assert_that( stream_conf->stream_params.source.sample_frequency_hz == core_config.GetSamplingFrequencyHz(), "sample freq mismatch: {}!={}", stream_conf->stream_params.source.sample_frequency_hz, core_config.GetSamplingFrequencyHz()); } if (stream_conf->stream_params.source .octets_per_codec_frame == 0) { stream_conf->stream_params.source.octets_per_codec_frame = *core_config.octets_per_codec_frame; } else { log::assert_that(stream_conf->stream_params.source .octets_per_codec_frame == *core_config.octets_per_codec_frame, "octets per frame mismatch: {}!={}", stream_conf->stream_params.source .octets_per_codec_frame, *core_config.octets_per_codec_frame); } if (stream_conf->stream_params.source .codec_frames_blocks_per_sdu == 0) { stream_conf->stream_params.source .codec_frames_blocks_per_sdu = *core_config.codec_frames_blocks_per_sdu; } else { log::assert_that( stream_conf->stream_params.source .codec_frames_blocks_per_sdu == *core_config.codec_frames_blocks_per_sdu, "codec_frames_blocks_per_sdu: {}!={}", stream_conf->stream_params.source .codec_frames_blocks_per_sdu, *core_config.codec_frames_blocks_per_sdu); } log::info( "Added Source Stream Configuration. CIS Connection " "Handle: {}, Audio Channel Allocation: {}, Source Number " "Of Devices: {}, Source Number Of Channels: {}", ase.cis_conn_hdl, *core_config.audio_channel_allocation, stream_conf->stream_params.source.num_of_devices, stream_conf->stream_params.source.num_of_channels); } } else { auto iter = std::find_if( stream_conf->stream_params.sink.stream_locations.begin(), stream_conf->stream_params.sink.stream_locations.end(), [cis_conn_hdl](auto& pair) { return cis_conn_hdl == pair.first; }); if (iter == stream_conf->stream_params.sink.stream_locations.end()) { stream_conf->stream_params.sink.stream_locations.emplace_back( std::make_pair(ase.cis_conn_hdl, *core_config.audio_channel_allocation)); stream_conf->stream_params.sink.num_of_devices++; stream_conf->stream_params.sink.num_of_channels += ase.channel_count; stream_conf->stream_params.sink.audio_channel_allocation |= *core_config.audio_channel_allocation; if (stream_conf->stream_params.sink.sample_frequency_hz == 0) { stream_conf->stream_params.sink.sample_frequency_hz = core_config.GetSamplingFrequencyHz(); } else { log::assert_that( stream_conf->stream_params.sink.sample_frequency_hz == core_config.GetSamplingFrequencyHz(), "sample freq mismatch: {}!={}", stream_conf->stream_params.sink.sample_frequency_hz, core_config.GetSamplingFrequencyHz()); } if (stream_conf->stream_params.sink.octets_per_codec_frame == 0) { stream_conf->stream_params.sink.octets_per_codec_frame = *core_config.octets_per_codec_frame; } else { log::assert_that( stream_conf->stream_params.sink .octets_per_codec_frame == *core_config.octets_per_codec_frame, "octets per frame mismatch: {}!={}", stream_conf->stream_params.sink.octets_per_codec_frame, *core_config.octets_per_codec_frame); } if (stream_conf->stream_params.sink .codec_frames_blocks_per_sdu == 0) { stream_conf->stream_params.sink .codec_frames_blocks_per_sdu = *core_config.codec_frames_blocks_per_sdu; } else { log::assert_that( stream_conf->stream_params.sink .codec_frames_blocks_per_sdu == *core_config.codec_frames_blocks_per_sdu, "codec_frames_blocks_per_sdu: {}!={}", stream_conf->stream_params.sink .codec_frames_blocks_per_sdu, *core_config.codec_frames_blocks_per_sdu); } log::info( "Added Sink Stream Configuration. CIS Connection Handle: " "{}, Audio Channel Allocation: {}, Sink Number Of " "Devices: {}, Sink Number Of Channels: {}", ase.cis_conn_hdl, *core_config.audio_channel_allocation, stream_conf->stream_params.sink.num_of_devices, stream_conf->stream_params.sink.num_of_channels); } } } } // Inject the state group->SetTargetState( types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); group->SetState( types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); streaming_groups[group->group_id_] = group; /* Assume CIG is created */ group->cig.SetState(bluetooth::le_audio::types::CigState::CREATED); if (block_streaming_state_callback) return true; group->SetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); do_in_main_thread( FROM_HERE, base::BindOnce( [](int group_id, bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks) { state_machine_callbacks->StatusReportCb( group_id, GroupStreamStatus::STREAMING); }, group->group_id_, base::Unretained(this->state_machine_callbacks_))); return true; }); ON_CALL(mock_state_machine_, SuspendStream(_)) .WillByDefault([this](LeAudioDeviceGroup* group) { // Fake ASE state for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr; device = group->GetNextDevice(device)) { for (auto& ase : device->ases_) { ase.cis_state = types::CisState::CONNECTED; ase.active = false; ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED; } } // Inject the state group->SetTargetState( types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); group->SetState(group->GetTargetState()); state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::SUSPENDED); }); ON_CALL(mock_state_machine_, ProcessHciNotifAclDisconnected(_, _)) .WillByDefault([this](LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) { if (!group) return; auto* stream_conf = &group->stream_conf; if (!stream_conf->stream_params.sink.stream_locations.empty() || !stream_conf->stream_params.source.stream_locations.empty()) { stream_conf->stream_params.sink.stream_locations.erase( std::remove_if( stream_conf->stream_params.sink.stream_locations.begin(), stream_conf->stream_params.sink.stream_locations.end(), [leAudioDevice, &stream_conf](auto& pair) { auto ases = leAudioDevice->GetAsesByCisConnHdl(pair.first); if (ases.sink) { stream_conf->stream_params.sink.num_of_devices--; stream_conf->stream_params.sink.num_of_channels -= ases.sink->channel_count; log::info( ", Source Number Of Devices: {}" ", Source Number Of Channels: {}", stream_conf->stream_params.source.num_of_devices, stream_conf->stream_params.source.num_of_channels); } return ases.sink; }), stream_conf->stream_params.sink.stream_locations.end()); stream_conf->stream_params.source.stream_locations.erase( std::remove_if( stream_conf->stream_params.source.stream_locations.begin(), stream_conf->stream_params.source.stream_locations.end(), [leAudioDevice, &stream_conf](auto& pair) { auto ases = leAudioDevice->GetAsesByCisConnHdl(pair.first); if (ases.source) { stream_conf->stream_params.source.num_of_devices--; stream_conf->stream_params.source.num_of_channels -= ases.source->channel_count; log::info( ", Source Number Of Devices: {}, Source Number Of " "Channels: {}", stream_conf->stream_params.source.num_of_devices, stream_conf->stream_params.source.num_of_channels); } return ases.source; }), stream_conf->stream_params.source.stream_locations.end()); } group->cig.UnassignCis(leAudioDevice); if (group->IsEmpty()) { group->cig.SetState(bluetooth::le_audio::types::CigState::NONE); InjectCigRemoved(group->group_id_); } }); ON_CALL(mock_state_machine_, ProcessHciNotifCisDisconnected(_, _, _)) .WillByDefault([](LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, const bluetooth::hci::iso_manager:: cis_disconnected_evt* event) { if (!group) return; auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl); if (ases_pair.sink) { ases_pair.sink->cis_state = types::CisState::ASSIGNED; ases_pair.sink->active = false; } if (ases_pair.source) { ases_pair.source->active = false; ases_pair.source->cis_state = types::CisState::ASSIGNED; } /* Invalidate stream configuration if needed */ auto* stream_conf = &group->stream_conf; if (!stream_conf->stream_params.sink.stream_locations.empty() || !stream_conf->stream_params.source.stream_locations.empty()) { stream_conf->stream_params.sink.stream_locations.erase( std::remove_if( stream_conf->stream_params.sink.stream_locations.begin(), stream_conf->stream_params.sink.stream_locations.end(), [leAudioDevice, &stream_conf](auto& pair) { auto ases = leAudioDevice->GetAsesByCisConnHdl(pair.first); log::info( ", sink ase to delete. Cis handle: {}, ase pointer: " "{}", (int)(pair.first), fmt::ptr(+ases.sink)); if (ases.sink) { stream_conf->stream_params.sink.num_of_devices--; stream_conf->stream_params.sink.num_of_channels -= ases.sink->channel_count; log::info( "Sink Number Of Devices: {}, Sink Number Of " "Channels: {}", stream_conf->stream_params.sink.num_of_devices, stream_conf->stream_params.sink.num_of_channels); } return ases.sink; }), stream_conf->stream_params.sink.stream_locations.end()); stream_conf->stream_params.source.stream_locations.erase( std::remove_if( stream_conf->stream_params.source.stream_locations.begin(), stream_conf->stream_params.source.stream_locations.end(), [leAudioDevice, &stream_conf](auto& pair) { auto ases = leAudioDevice->GetAsesByCisConnHdl(pair.first); log::info( ", source to delete. Cis handle: {}, ase pointer: {}", (int)(pair.first), fmt::ptr(ases.source)); if (ases.source) { stream_conf->stream_params.source.num_of_devices--; stream_conf->stream_params.source.num_of_channels -= ases.source->channel_count; log::info( ", Source Number Of Devices: {}, Source Number Of " "Channels: {}", stream_conf->stream_params.source.num_of_devices, stream_conf->stream_params.source.num_of_channels); } return ases.source; }), stream_conf->stream_params.source.stream_locations.end()); } group->cig.UnassignCis(leAudioDevice); }); ON_CALL(mock_state_machine_, StopStream(_)) .WillByDefault([this](LeAudioDeviceGroup* group) { for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr; device = group->GetNextDevice(device)) { /* Invalidate stream configuration if needed */ auto* stream_conf = &group->stream_conf; if (!stream_conf->stream_params.sink.stream_locations.empty() || !stream_conf->stream_params.source.stream_locations.empty()) { stream_conf->stream_params.sink.stream_locations.erase( std::remove_if( stream_conf->stream_params.sink.stream_locations.begin(), stream_conf->stream_params.sink.stream_locations.end(), [device, &stream_conf](auto& pair) { auto ases = device->GetAsesByCisConnHdl(pair.first); log::info( ", sink ase to delete. Cis handle: {}, ase " "pointer: {}", (int)(pair.first), fmt::ptr(+ases.sink)); if (ases.sink) { stream_conf->stream_params.sink.num_of_devices--; stream_conf->stream_params.sink.num_of_channels -= ases.sink->channel_count; log::info( "Sink Number Of Devices: {}, Sink Number Of " "Channels: {}", stream_conf->stream_params.sink.num_of_devices, stream_conf->stream_params.sink.num_of_channels); } return ases.sink; }), stream_conf->stream_params.sink.stream_locations.end()); stream_conf->stream_params.source.stream_locations.erase( std::remove_if( stream_conf->stream_params.source.stream_locations .begin(), stream_conf->stream_params.source.stream_locations.end(), [device, &stream_conf](auto& pair) { auto ases = device->GetAsesByCisConnHdl(pair.first); log::info( ", source to delete. Cis handle: {}, ase pointer: " "{}", (int)(pair.first), fmt::ptr(+ases.source)); if (ases.source) { stream_conf->stream_params.source.num_of_devices--; stream_conf->stream_params.source.num_of_channels -= ases.source->channel_count; log::info( ", Source Number Of Devices: {}, Source Number " "Of Channels: {}", stream_conf->stream_params.source.num_of_devices, stream_conf->stream_params.source .num_of_channels); } return ases.source; }), stream_conf->stream_params.source.stream_locations.end()); } group->cig.UnassignCis(device); for (auto& ase : device->ases_) { ase.cis_state = types::CisState::IDLE; ase.data_path_state = types::DataPathState::IDLE; ase.active = false; ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; ase.cis_id = 0; ase.cis_conn_hdl = 0; } } // Inject the state group->SetTargetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); group->SetState(group->GetTargetState()); state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::RELEASING); do_in_main_thread( FROM_HERE, base::BindOnce( [](bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* cb, int group_id) { cb->StatusReportCb(group_id, GroupStreamStatus::IDLE); }, state_machine_callbacks_, group->group_id_)); }); } void SetUp() override { __android_log_set_minimum_priority(ANDROID_LOG_VERBOSE); init_message_loop_thread(); ON_CALL(controller_, SupportsBleConnectedIsochronousStreamCentral) .WillByDefault(Return(true)); ON_CALL(controller_, SupportsBleConnectedIsochronousStreamPeripheral) .WillByDefault(Return(true)); ON_CALL(controller_, SupportsBle2mPhy).WillByDefault(Return(true)); bluetooth::hci::testing::mock_controller_ = &controller_; bluetooth::manager::SetMockBtmInterface(&mock_btm_interface_); gatt::SetMockBtaGattInterface(&mock_gatt_interface_); gatt::SetMockBtaGattQueue(&mock_gatt_queue_); bluetooth::storage::SetMockBtifStorageInterface(&mock_btif_storage_); iso_manager_ = bluetooth::hci::IsoManager::GetInstance(); ASSERT_NE(iso_manager_, nullptr); iso_manager_->Start(); mock_iso_manager_ = MockIsoManager::GetInstance(); ON_CALL(*mock_iso_manager_, RegisterCigCallbacks(_)) .WillByDefault(SaveArg<0>(&cig_callbacks_)); ON_CALL(mock_btm_interface_, IsLinkKeyKnown(_, _)) .WillByDefault(DoAll(Return(true))); // Required since we call OnAudioDataReady() const auto codec_location = ::bluetooth::le_audio::types::CodecLocation::HOST; SetUpMockAudioHal(); SetUpMockGroups(); SetUpMockGatt(); SetUpMockCodecManager(codec_location); block_streaming_state_callback = false; available_snk_context_types_ = 0xffff; available_src_context_types_ = 0xffff; supported_snk_context_types_ = 0xffff; supported_src_context_types_ = 0xffff; empty_source_pack_ = false; empty_sink_pack_ = false; bluetooth::le_audio::AudioSetConfigurationProvider::Initialize( codec_location); ASSERT_FALSE(LeAudioClient::IsLeAudioClientRunning()); } void SetUpMockCodecManager(types::CodecLocation location) { codec_manager_ = bluetooth::le_audio::CodecManager::GetInstance(); ASSERT_NE(codec_manager_, nullptr); std::vector mock_offloading_preference(0); codec_manager_->Start(mock_offloading_preference); mock_codec_manager_ = MockCodecManager::GetInstance(); ASSERT_NE((void*)mock_codec_manager_, (void*)codec_manager_); ASSERT_NE(mock_codec_manager_, nullptr); ON_CALL(*mock_codec_manager_, GetCodecLocation()) .WillByDefault(Return(location)); ON_CALL(*mock_codec_manager_, UpdateActiveUnicastAudioHalClient(_, _, _)) .WillByDefault(Return(true)); ON_CALL(*mock_codec_manager_, UpdateActiveBroadcastAudioHalClient(_, _)) .WillByDefault(Return(true)); // Turn on the dual bidir SWB support ON_CALL(*mock_codec_manager_, IsDualBiDirSwbSupported) .WillByDefault(Return(true)); // Regardless of the codec location, return all the possible configurations ON_CALL(*mock_codec_manager_, GetCodecConfig) .WillByDefault( Invoke([](const CodecManager::UnicastConfigurationRequirements& requirements, CodecManager::UnicastConfigurationVerifier verifier) { auto filtered = *le_audio::AudioSetConfigurationProvider::Get() ->GetConfigurations(requirements.audio_context_type); // Filter out the dual bidir SWB configurations if (!bluetooth::le_audio::CodecManager::GetInstance() ->IsDualBiDirSwbSupported()) { filtered.erase( std::remove_if(filtered.begin(), filtered.end(), [](auto const& el) { if (el->confs.source.empty()) return false; return AudioSetConfigurationProvider::Get() ->CheckConfigurationIsDualBiDirSwb( *el); }), filtered.end()); } auto cptr = verifier(requirements, &filtered); return cptr ? std::make_unique< set_configurations::AudioSetConfiguration>(*cptr) : nullptr; })); } void TearDown() override { if (is_audio_unicast_source_acquired) { if (unicast_source_hal_cb_ != nullptr) { EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop).Times(1); } EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); } if (is_audio_unicast_sink_acquired) { if (unicast_sink_hal_cb_ != nullptr) { EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop).Times(1); } EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); } // Message loop cleanup should wait for all the 'till now' scheduled calls // so it should be called right at the very begginning of teardown. cleanup_message_loop_thread(); // This is required since Stop() and Cleanup() may trigger some callbacks or // drop unique pointers to mocks we have raw pointer for and we want to // verify them all. Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); if (LeAudioClient::IsLeAudioClientRunning()) { EXPECT_CALL(mock_gatt_interface_, AppDeregister(gatt_if)).Times(1); LeAudioClient::Cleanup(); ASSERT_FALSE(LeAudioClient::IsLeAudioClientRunning()); } owned_mock_le_audio_sink_hal_client_.reset(); owned_mock_le_audio_source_hal_client_.reset(); if (bluetooth::le_audio::AudioSetConfigurationProvider::Get()) bluetooth::le_audio::AudioSetConfigurationProvider::Cleanup(); iso_manager_->Stop(); bluetooth::hci::testing::mock_controller_ = nullptr; } protected: class MockDeviceWrapper { class IGattHandlers { public: // IGattHandlers() = default; virtual ~IGattHandlers() = default; virtual void OnReadCharacteristic(uint16_t handle, GATT_READ_OP_CB cb, void* cb_data) = 0; virtual void OnWriteCharacteristic(uint16_t handle, std::vector value, tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, void* cb_data) = 0; }; public: struct csis_mock : public IGattHandlers { uint16_t start = 0; uint16_t end = 0; uint16_t sirk_char = 0; uint16_t sirk_ccc = 0; uint16_t size_char = 0; uint16_t size_ccc = 0; uint16_t lock_char = 0; uint16_t lock_ccc = 0; uint16_t rank_char = 0; int rank = 0; int size = 0; MOCK_METHOD((void), OnReadCharacteristic, (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data), (override)); MOCK_METHOD((void), OnWriteCharacteristic, (uint16_t handle, std::vector value, tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, void* cb_data), (override)); }; struct cas_mock : public IGattHandlers { uint16_t start = 0; uint16_t end = 0; uint16_t csis_include = 0; MOCK_METHOD((void), OnReadCharacteristic, (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data), (override)); MOCK_METHOD((void), OnWriteCharacteristic, (uint16_t handle, std::vector value, tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, void* cb_data), (override)); }; struct pacs_mock : public IGattHandlers { uint16_t start = 0; uint16_t sink_pac_char = 0; uint16_t sink_pac_ccc = 0; uint16_t sink_audio_loc_char = 0; uint16_t sink_audio_loc_ccc = 0; uint16_t source_pac_char = 0; uint16_t source_pac_ccc = 0; uint16_t source_audio_loc_char = 0; uint16_t source_audio_loc_ccc = 0; uint16_t avail_contexts_char = 0; uint16_t avail_contexts_ccc = 0; uint16_t supp_contexts_char = 0; uint16_t supp_contexts_ccc = 0; uint16_t end = 0; MOCK_METHOD((void), OnReadCharacteristic, (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data), (override)); MOCK_METHOD((void), OnWriteCharacteristic, (uint16_t handle, std::vector value, tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, void* cb_data), (override)); }; struct ascs_mock : public IGattHandlers { uint16_t start = 0; uint16_t sink_ase_char[max_num_of_ases] = {0}; uint16_t sink_ase_ccc[max_num_of_ases] = {0}; uint16_t sink_ase_ccc_val[max_num_of_ases] = {0}; uint16_t source_ase_char[max_num_of_ases] = {0}; uint16_t source_ase_ccc[max_num_of_ases] = {0}; uint16_t source_ase_ccc_val[max_num_of_ases] = {0}; uint16_t ctp_char = 0; uint16_t ctp_ccc = 0; uint16_t ctp_ccc_val = 0; uint16_t end = 0; MOCK_METHOD((void), OnReadCharacteristic, (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data), (override)); MOCK_METHOD((void), OnWriteCharacteristic, (uint16_t handle, std::vector value, tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, void* cb_data), (override)); }; MockDeviceWrapper( RawAddress addr, const std::list& services, std::unique_ptr> csis, std::unique_ptr> cas, std::unique_ptr> ascs, std::unique_ptr> pacs) : addr(addr) { this->services = services; this->csis = std::move(csis); this->cas = std::move(cas); this->ascs = std::move(ascs); this->pacs = std::move(pacs); } ~MockDeviceWrapper() { Mock::VerifyAndClearExpectations(csis.get()); Mock::VerifyAndClearExpectations(cas.get()); Mock::VerifyAndClearExpectations(ascs.get()); Mock::VerifyAndClearExpectations(pacs.get()); } RawAddress addr; bool connected = false; // A list of services and their useful params std::list services; std::unique_ptr csis; std::unique_ptr cas; std::unique_ptr ascs; std::unique_ptr pacs; }; void SyncOnMainLoop() { // Wait for the main loop to flush // WARNING: Not tested with Timers pushing periodic tasks to the main loop while (num_async_tasks > 0) ; } void ConnectLeAudio(const RawAddress& address, bool isEncrypted = true, bool expect_connected_event = true) { // by default indicate link as encrypted ON_CALL(mock_btm_interface_, BTM_IsEncrypted(address, _)) .WillByDefault(DoAll(Return(isEncrypted))); ON_CALL(mock_btm_interface_, IsLinkKeyKnown(address, _)) .WillByDefault(DoAll(Return(true))); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, address, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); /* If connected event is not expected to arrive, don't test those two below */ if (expect_connected_event) { EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, address, false)); EXPECT_CALL( mock_gatt_interface_, Open(gatt_if, address, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); } do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::Connect, base::Unretained(LeAudioClient::Get()), address)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btm_interface_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } void DisconnectLeAudioWithGattClose( const RawAddress& address, uint16_t conn_id, tGATT_DISCONN_REASON reason = GATT_CONN_TERMINATE_LOCAL_HOST) { EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, address)) .Times(1); // For test purpose use the acl handle same as conn_id ON_CALL(mock_btm_interface_, GetHCIConnHandle(address, _)) .WillByDefault([conn_id](RawAddress const& bd_addr, tBT_TRANSPORT transport) { return conn_id; }); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(conn_id, _)) .Times(0); EXPECT_CALL(mock_gatt_interface_, Close(conn_id)).Times(1); do_in_main_thread( FROM_HERE, base::Bind(&LeAudioClient::Disconnect, base::Unretained(LeAudioClient::Get()), address)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btm_interface_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } void DisconnectLeAudioWithAclClose( const RawAddress& address, uint16_t conn_id, tGATT_DISCONN_REASON reason = GATT_CONN_TERMINATE_LOCAL_HOST) { EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, address)) .Times(1); // For test purpose use the acl handle same as conn_id ON_CALL(mock_btm_interface_, GetHCIConnHandle(address, _)) .WillByDefault([conn_id](RawAddress const& bd_addr, tBT_TRANSPORT transport) { return conn_id; }); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(conn_id, _)) .WillOnce([this, &reason](uint16_t handle, tHCI_STATUS rs) { InjectDisconnectedEvent(handle, reason); }); EXPECT_CALL(mock_gatt_interface_, Close(conn_id)).Times(0); do_in_main_thread( FROM_HERE, base::Bind(&LeAudioClient::Disconnect, base::Unretained(LeAudioClient::Get()), address)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btm_interface_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } void DisconnectLeAudioNoDisconnectedEvtExpected(const RawAddress& address, uint16_t conn_id) { EXPECT_CALL(mock_gatt_interface_, Close(conn_id)).Times(0); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(conn_id, _)) .Times(1); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::Disconnect, base::Unretained(LeAudioClient::Get()), address)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_btm_interface_); } void ConnectCsisDevice(const RawAddress& addr, uint16_t conn_id, uint32_t sink_audio_allocation, uint32_t source_audio_allocation, uint8_t group_size, int group_id, uint8_t rank, bool connect_through_csis = false, bool new_device = true) { SetSampleDatabaseEarbudsValid(conn_id, addr, sink_audio_allocation, source_audio_allocation, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ true, /*add_ascs*/ group_size, rank); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, addr)) .Times(1); if (new_device) { EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(addr, group_id, GroupNodeStatus::ADDED)) .Times(1); } if (connect_through_csis) { // Add it the way CSIS would do: add to group and then connect do_in_main_thread(FROM_HERE, base::BindOnce(&LeAudioClient::GroupAddNode, base::Unretained(LeAudioClient::Get()), group_id, addr)); ConnectLeAudio(addr); } else { // The usual connect // Since device has CSIS, lets add it here to groups already now groups[addr] = group_id; ConnectLeAudio(addr); InjectGroupDeviceAdded(addr, group_id); } } void ConnectNonCsisDevice(const RawAddress& addr, uint16_t conn_id, uint32_t sink_audio_allocation, uint32_t source_audio_allocation) { SetSampleDatabaseEarbudsValid( conn_id, addr, sink_audio_allocation, source_audio_allocation, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ true, /*add_ascs*/ 0, 0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, addr)) .Times(1); ConnectLeAudio(addr); } void UpdateLocalSourceMetadata( std::vector tracks, bool reconfigure_existing_stream = false) { std::vector tracks_vec; tracks_vec.reserve(tracks.size()); for (const auto& track : tracks) { playback_track_metadata_v7 desc_track = { .base = { .usage = static_cast(track.usage), .content_type = static_cast(track.content_type), .gain = track.gain, }, }; if (test_tags_ptr_) { memcpy(desc_track.tags, test_tags_ptr_, strlen(test_tags_ptr_)); } tracks_vec.push_back(desc_track); } ASSERT_NE(nullptr, mock_le_audio_source_hal_client_); /* Local Source may reconfigure once the metadata is updated */ if (reconfigure_existing_stream) { Expectation reconfigure = EXPECT_CALL(*mock_le_audio_source_hal_client_, SuspendedForReconfiguration()) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, CancelStreamingRequest()) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, ReconfigurationComplete()) .Times(1) .After(reconfigure); } else { EXPECT_CALL(*mock_le_audio_source_hal_client_, SuspendedForReconfiguration()) .Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, ReconfigurationComplete()) .Times(0); } ASSERT_NE(unicast_source_hal_cb_, nullptr); unicast_source_hal_cb_->OnAudioMetadataUpdate(std::move(tracks_vec), DsaMode::DISABLED); } void UpdateLocalSourceMetadata(audio_usage_t usage, audio_content_type_t content_type, bool reconfigure_existing_stream = false) { std::vector tracks = { {{AUDIO_USAGE_UNKNOWN, AUDIO_CONTENT_TYPE_UNKNOWN, 0}, {AUDIO_USAGE_UNKNOWN, AUDIO_CONTENT_TYPE_UNKNOWN, 0}}}; tracks[0].usage = usage; tracks[0].content_type = content_type; UpdateLocalSourceMetadata(tracks, reconfigure_existing_stream); } void UpdateLocalSinkMetadata(audio_source_t audio_source) { std::vector tracks = { {{AUDIO_SOURCE_INVALID, 0.5, AUDIO_DEVICE_NONE, "00:11:22:33:44:55"}, {AUDIO_SOURCE_MIC, 0.7, AUDIO_DEVICE_OUT_BLE_HEADSET, "AA:BB:CC:DD:EE:FF"}}}; tracks[1].source = audio_source; std::vector tracks_vec; tracks_vec.reserve(tracks.size()); for (const auto& track : tracks) { record_track_metadata_v7 desc_track = { .base = { .source = static_cast(track.source), .gain = track.gain, .dest_device = static_cast(track.dest_device), }, }; strcpy(desc_track.base.dest_device_address, track.dest_device_address); tracks_vec.push_back(desc_track); } ASSERT_NE(nullptr, unicast_sink_hal_cb_); unicast_sink_hal_cb_->OnAudioMetadataUpdate(std::move(tracks_vec)); } void LocalAudioSourceSuspend(void) { ASSERT_NE(unicast_source_hal_cb_, nullptr); unicast_source_hal_cb_->OnAudioSuspend(); SyncOnMainLoop(); } void LocalAudioSourceResume(bool expected_confirmation = true, bool expected_cancel = false) { ASSERT_NE(nullptr, mock_le_audio_source_hal_client_); if (expected_confirmation) { EXPECT_CALL(*mock_le_audio_source_hal_client_, ConfirmStreamingRequest()) .Times(1); } if (expected_cancel) { EXPECT_CALL(*mock_le_audio_source_hal_client_, CancelStreamingRequest()) .Times(1); } do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioSourceAudioHalClient::Callbacks* cb) { if (cb) cb->OnAudioResume(); }, unicast_source_hal_cb_)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&*mock_le_audio_source_hal_client_); } void LocalAudioSinkSuspend(void) { ASSERT_NE(unicast_sink_hal_cb_, nullptr); unicast_sink_hal_cb_->OnAudioSuspend(); SyncOnMainLoop(); } void LocalAudioSinkResume(void) { ASSERT_NE(unicast_sink_hal_cb_, nullptr); do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioSinkAudioHalClient::Callbacks* cb) { cb->OnAudioResume(); }, unicast_sink_hal_cb_)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); } void StartStreaming(audio_usage_t usage, audio_content_type_t content_type, int group_id, audio_source_t audio_source = AUDIO_SOURCE_INVALID, bool reconfigure_existing_stream = false, bool expected_resume_confirmation = true) { ASSERT_NE(unicast_source_hal_cb_, nullptr); UpdateLocalSourceMetadata(usage, content_type, reconfigure_existing_stream); if (audio_source != AUDIO_SOURCE_INVALID) { UpdateLocalSinkMetadata(audio_source); } /* Stream has been automatically restarted on UpdateLocalSourceMetadata */ if (reconfigure_existing_stream) return; LocalAudioSourceResume(expected_resume_confirmation); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); if (usage == AUDIO_USAGE_VOICE_COMMUNICATION || audio_source != AUDIO_SOURCE_INVALID) { ASSERT_NE(unicast_sink_hal_cb_, nullptr); do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioSinkAudioHalClient::Callbacks* cb) { cb->OnAudioResume(); }, unicast_sink_hal_cb_)); } SyncOnMainLoop(); } void StopStreaming(int group_id, bool suspend_source = false) { ASSERT_NE(unicast_source_hal_cb_, nullptr); /* TODO We should have a way to confirm Stop() otherwise, audio framework * might have different state that it is in the le_audio code - as tearing * down CISes might take some time */ /* It's enough to call only one resume even if it'll be bi-directional * streaming. First suspend will trigger GroupStop. * * There is no - 'only source receiver' scenario (e.g. single microphone). * If there will be such test oriented scenario, such resume choose logic * should be applied. */ unicast_source_hal_cb_->OnAudioSuspend(); if (suspend_source) { ASSERT_NE(unicast_sink_hal_cb_, nullptr); unicast_sink_hal_cb_->OnAudioSuspend(); } SyncOnMainLoop(); } void set_sample_database( uint16_t conn_id, RawAddress addr, std::unique_ptr> csis, std::unique_ptr> cas, std::unique_ptr> ascs, std::unique_ptr> pacs) { gatt::DatabaseBuilder bob; /* Generic Access Service */ bob.AddService(0x0001, 0x0003, Uuid::From16Bit(0x1800), true); /* Device Name Char. */ bob.AddCharacteristic(0x0002, 0x0003, Uuid::From16Bit(0x2a00), GATT_CHAR_PROP_BIT_READ); if (csis->start) { bool is_primary = true; bob.AddService(csis->start, csis->end, bluetooth::csis::kCsisServiceUuid, is_primary); if (csis->sirk_char) { bob.AddCharacteristic( csis->sirk_char, csis->sirk_char + 1, bluetooth::csis::kCsisSirkUuid, GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY); if (csis->sirk_ccc) bob.AddDescriptor(csis->sirk_ccc, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } if (csis->size_char) { bob.AddCharacteristic( csis->size_char, csis->size_char + 1, bluetooth::csis::kCsisSizeUuid, GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY); if (csis->size_ccc) bob.AddDescriptor(csis->size_ccc, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } if (csis->lock_char) { bob.AddCharacteristic(csis->lock_char, csis->lock_char + 1, bluetooth::csis::kCsisLockUuid, GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY | GATT_CHAR_PROP_BIT_WRITE); if (csis->lock_ccc) bob.AddDescriptor(csis->lock_ccc, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } if (csis->rank_char) bob.AddCharacteristic(csis->rank_char, csis->rank_char + 1, bluetooth::csis::kCsisRankUuid, GATT_CHAR_PROP_BIT_READ); } if (cas->start) { bool is_primary = true; bob.AddService(cas->start, cas->end, bluetooth::le_audio::uuid::kCapServiceUuid, is_primary); // Include CSIS service inside if (cas->csis_include) bob.AddIncludedService(cas->csis_include, bluetooth::csis::kCsisServiceUuid, csis->start, csis->end); } if (pacs->start) { bool is_primary = true; bob.AddService( pacs->start, pacs->end, bluetooth::le_audio::uuid::kPublishedAudioCapabilityServiceUuid, is_primary); if (pacs->sink_pac_char) { bob.AddCharacteristic( pacs->sink_pac_char, pacs->sink_pac_char + 1, bluetooth::le_audio::uuid:: kSinkPublishedAudioCapabilityCharacteristicUuid, GATT_CHAR_PROP_BIT_READ); if (pacs->sink_pac_ccc) bob.AddDescriptor(pacs->sink_pac_ccc, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } if (pacs->sink_audio_loc_char) { bob.AddCharacteristic( pacs->sink_audio_loc_char, pacs->sink_audio_loc_char + 1, bluetooth::le_audio::uuid::kSinkAudioLocationCharacteristicUuid, GATT_CHAR_PROP_BIT_READ); if (pacs->sink_audio_loc_ccc) bob.AddDescriptor(pacs->sink_audio_loc_ccc, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } if (pacs->source_pac_char) { bob.AddCharacteristic( pacs->source_pac_char, pacs->source_pac_char + 1, bluetooth::le_audio::uuid:: kSourcePublishedAudioCapabilityCharacteristicUuid, GATT_CHAR_PROP_BIT_READ); if (pacs->source_pac_ccc) bob.AddDescriptor(pacs->source_pac_ccc, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } if (pacs->source_audio_loc_char) { bob.AddCharacteristic( pacs->source_audio_loc_char, pacs->source_audio_loc_char + 1, bluetooth::le_audio::uuid::kSourceAudioLocationCharacteristicUuid, GATT_CHAR_PROP_BIT_READ); if (pacs->source_audio_loc_ccc) bob.AddDescriptor(pacs->source_audio_loc_ccc, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } if (pacs->avail_contexts_char) { bob.AddCharacteristic(pacs->avail_contexts_char, pacs->avail_contexts_char + 1, bluetooth::le_audio::uuid:: kAudioContextAvailabilityCharacteristicUuid, GATT_CHAR_PROP_BIT_READ); if (pacs->avail_contexts_ccc) bob.AddDescriptor(pacs->avail_contexts_ccc, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } if (pacs->supp_contexts_char) { bob.AddCharacteristic( pacs->supp_contexts_char, pacs->supp_contexts_char + 1, bluetooth::le_audio::uuid::kAudioSupportedContextCharacteristicUuid, GATT_CHAR_PROP_BIT_READ); if (pacs->supp_contexts_ccc) bob.AddDescriptor(pacs->supp_contexts_ccc, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } } if (ascs->start) { bool is_primary = true; bob.AddService(ascs->start, ascs->end, bluetooth::le_audio::uuid::kAudioStreamControlServiceUuid, is_primary); for (int i = 0; i < max_num_of_ases; i++) { if (ascs->sink_ase_char[i]) { bob.AddCharacteristic( ascs->sink_ase_char[i], ascs->sink_ase_char[i] + 1, bluetooth::le_audio::uuid::kSinkAudioStreamEndpointUuid, GATT_CHAR_PROP_BIT_READ); if (ascs->sink_ase_ccc[i]) bob.AddDescriptor(ascs->sink_ase_ccc[i], Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } if (ascs->source_ase_char[i]) { bob.AddCharacteristic( ascs->source_ase_char[i], ascs->source_ase_char[i] + 1, bluetooth::le_audio::uuid::kSourceAudioStreamEndpointUuid, GATT_CHAR_PROP_BIT_READ); if (ascs->source_ase_ccc[i]) bob.AddDescriptor(ascs->source_ase_ccc[i], Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } } if (ascs->ctp_char) { bob.AddCharacteristic( ascs->ctp_char, ascs->ctp_char + 1, bluetooth::le_audio::uuid:: kAudioStreamEndpointControlPointCharacteristicUuid, GATT_CHAR_PROP_BIT_READ); if (ascs->ctp_ccc) bob.AddDescriptor(ascs->ctp_ccc, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); } } // Assign conn_id to a certain device - this does not mean it is connected auto dev_wrapper = std::make_unique>( addr, bob.Build().Services(), std::move(csis), std::move(cas), std::move(ascs), std::move(pacs)); peer_devices.emplace(conn_id, std::move(dev_wrapper)); } void SetSampleDatabaseEmpty(uint16_t conn_id, RawAddress addr) { auto csis = std::make_unique>(); auto cas = std::make_unique>(); auto pacs = std::make_unique>(); auto ascs = std::make_unique>(); set_sample_database(conn_id, addr, std::move(csis), std::move(cas), std::move(ascs), std::move(pacs)); } void SetSampleDatabaseEarbudsValid( uint16_t conn_id, RawAddress addr, uint32_t sink_audio_allocation, uint32_t source_audio_allocation, uint8_t sink_channel_cnt = 0x03, uint8_t source_channel_cnt = 0x03, uint16_t sample_freq_mask = 0x0004, bool add_csis = true, bool add_cas = true, bool add_pacs = true, int add_ascs_cnt = 1, uint8_t set_size = 2, uint8_t rank = 1, GattStatus gatt_status = GATT_SUCCESS) { auto csis = std::make_unique>(); if (add_csis) { // attribute handles csis->start = 0x0010; csis->sirk_char = 0x0020; csis->sirk_ccc = 0x0022; csis->size_char = 0x0023; csis->size_ccc = 0x0025; csis->lock_char = 0x0026; csis->lock_ccc = 0x0028; csis->rank_char = 0x0029; csis->end = 0x0030; // other params csis->size = set_size; csis->rank = rank; } auto cas = std::make_unique>(); if (add_cas) { // attribute handles cas->start = 0x0040; if (add_csis) cas->csis_include = 0x0041; cas->end = 0x0050; // other params } auto pacs = std::make_unique>(); if (add_pacs) { // attribute handles pacs->start = 0x0060; pacs->sink_pac_char = 0x0061; pacs->sink_pac_ccc = 0x0063; pacs->sink_audio_loc_char = 0x0064; pacs->sink_audio_loc_ccc = 0x0066; pacs->source_pac_char = 0x0067; pacs->source_pac_ccc = 0x0069; pacs->source_audio_loc_char = 0x0070; pacs->source_audio_loc_ccc = 0x0072; pacs->avail_contexts_char = 0x0073; pacs->avail_contexts_ccc = 0x0075; pacs->supp_contexts_char = 0x0076; pacs->supp_contexts_ccc = 0x0078; pacs->end = 0x0080; // other params } auto ascs = std::make_unique>(); if (add_ascs_cnt > 0) { // attribute handles ascs->start = 0x0090; uint16_t handle = 0x0091; for (int i = 0; i < add_ascs_cnt; i++) { if (sink_audio_allocation != 0) { ascs->sink_ase_char[i] = handle; handle += 2; ascs->sink_ase_ccc[i] = handle; handle++; } if (source_audio_allocation != 0) { ascs->source_ase_char[i] = handle; handle += 2; ascs->source_ase_ccc[i] = handle; handle++; } } ascs->ctp_char = handle; handle += 2; ascs->ctp_ccc = handle; handle++; ascs->end = handle; // other params } set_sample_database(conn_id, addr, std::move(csis), std::move(cas), std::move(ascs), std::move(pacs)); if (add_pacs) { uint8_t snk_allocation[4]; uint8_t src_allocation[4]; snk_allocation[0] = (uint8_t)(sink_audio_allocation); snk_allocation[1] = (uint8_t)(sink_audio_allocation >> 8); snk_allocation[2] = (uint8_t)(sink_audio_allocation >> 16); snk_allocation[3] = (uint8_t)(sink_audio_allocation >> 24); src_allocation[0] = (uint8_t)(source_audio_allocation); src_allocation[1] = (uint8_t)(source_audio_allocation >> 8); src_allocation[2] = (uint8_t)(source_audio_allocation >> 16); src_allocation[3] = (uint8_t)(source_audio_allocation >> 24); uint8_t sample_freq[2]; sample_freq[0] = (uint8_t)(sample_freq_mask); sample_freq[1] = (uint8_t)(sample_freq_mask >> 8); // Set pacs default read values ON_CALL(*peer_devices.at(conn_id)->pacs, OnReadCharacteristic(_, _, _)) .WillByDefault([this, conn_id, snk_allocation, src_allocation, sample_freq, sink_channel_cnt, source_channel_cnt, gatt_status](uint16_t handle, GATT_READ_OP_CB cb, void* cb_data) { auto& pacs = peer_devices.at(conn_id)->pacs; std::vector value; if (gatt_status == GATT_SUCCESS) { if (handle == pacs->sink_pac_char + 1) { if (empty_sink_pack_) { value = {0x00}; } else { value = { // Num records 0x02, // Codec_ID 0x06, 0x00, 0x00, 0x00, 0x00, // Codec Spec. Caps. Len 0x10, 0x03, /* sample freq */ 0x01, sample_freq[0], sample_freq[1], 0x02, 0x02, /* frame duration */ 0x03, 0x02, /* channel count */ 0x03, sink_channel_cnt, 0x05, 0x04, 0x1E, 0x00, 0x78, 0x00, // Metadata Length 0x00, // Codec_ID 0x06, 0x00, 0x00, 0x00, 0x00, // Codec Spec. Caps. Len 0x10, 0x03, /* sample freq */ 0x01, 0x80, /* 48kHz */ 0x00, 0x02, /* frame duration */ 0x02, 0x03, 0x02, /* channel count */ 0x03, sink_channel_cnt, 0x05, /* octects per frame */ 0x04, 0x78, 0x00, 0x78, 0x00, // Metadata Length 0x00, }; } } else if (handle == pacs->sink_audio_loc_char + 1) { value = { // Audio Locations snk_allocation[0], snk_allocation[1], snk_allocation[2], snk_allocation[3], }; } else if (handle == pacs->source_pac_char + 1) { if (empty_source_pack_) { value = {0x00}; } else { value = { // Num records 0x02, // Codec_ID 0x06, 0x00, 0x00, 0x00, 0x00, // Codec Spec. Caps. Len 0x10, 0x03, 0x01, sample_freq[0], sample_freq[1], 0x02, 0x02, 0x03, 0x02, 0x03, source_channel_cnt, 0x05, 0x04, 0x1E, 0x00, 0x78, 0x00, // Metadata Length 0x00, // Codec_ID 0x06, 0x00, 0x00, 0x00, 0x00, // Codec Spec. Caps. Len 0x10, 0x03, 0x01, 0x24, 0x00, 0x02, 0x02, 0x03, 0x02, 0x03, source_channel_cnt, 0x05, 0x04, 0x1E, 0x00, 0x50, 0x00, // Metadata Length 0x00, }; } } else if (handle == pacs->source_audio_loc_char + 1) { value = { // Audio Locations src_allocation[0], src_allocation[1], src_allocation[2], src_allocation[3], }; } else if (handle == pacs->avail_contexts_char + 1) { value = { // Sink Avail Contexts (uint8_t)(available_snk_context_types_), (uint8_t)(available_snk_context_types_ >> 8), // Source Avail Contexts (uint8_t)(available_src_context_types_), (uint8_t)(available_src_context_types_ >> 8), }; } else if (handle == pacs->supp_contexts_char + 1) { value = { // Sink Supp Contexts (uint8_t)(supported_snk_context_types_), (uint8_t)(supported_snk_context_types_ >> 8), // Source Supp Contexts (uint8_t)(supported_src_context_types_), (uint8_t)(supported_src_context_types_ >> 8), }; } } cb(conn_id, gatt_status, handle, value.size(), value.data(), cb_data); }); } if (add_ascs_cnt > 0) { // Set ascs default read values ON_CALL(*peer_devices.at(conn_id)->ascs, OnReadCharacteristic(_, _, _)) .WillByDefault([this, conn_id, gatt_status](uint16_t handle, GATT_READ_OP_CB cb, void* cb_data) { auto& ascs = peer_devices.at(conn_id)->ascs; std::vector value; bool is_ase_sink_request = false; bool is_ase_src_request = false; uint8_t idx; if (handle == ascs->ctp_ccc && ccc_stored_byte_val_.has_value()) { value = {*ccc_stored_byte_val_, 00}; cb(conn_id, gatt_read_ctp_ccc_status_, handle, value.size(), value.data(), cb_data); return; } if (gatt_status == GATT_SUCCESS) { if (handle == ascs->ctp_ccc) { value = UINT16_TO_VEC_UINT8(ascs->ctp_ccc_val); } else { for (idx = 0; idx < max_num_of_ases; idx++) { if (handle == ascs->sink_ase_ccc[idx] + 1) { value = UINT16_TO_VEC_UINT8(ascs->sink_ase_ccc_val[idx]); break; } if (handle == ascs->source_ase_char[idx] + 1) { value = UINT16_TO_VEC_UINT8(ascs->source_ase_ccc_val[idx]); break; } } } for (idx = 0; idx < max_num_of_ases; idx++) { if (handle == ascs->sink_ase_char[idx] + 1) { is_ase_sink_request = true; break; } if (handle == ascs->source_ase_char[idx] + 1) { is_ase_src_request = true; break; } } if (is_ase_sink_request) { value = { // ASE ID static_cast(idx + 1), // State static_cast(bluetooth::le_audio::types::AseState:: BTA_LE_AUDIO_ASE_STATE_IDLE), // No Additional ASE params for IDLE state }; } else if (is_ase_src_request) { value = { // ASE ID static_cast(idx + 6), // State static_cast(bluetooth::le_audio::types::AseState:: BTA_LE_AUDIO_ASE_STATE_IDLE), // No Additional ASE params for IDLE state }; } } cb(conn_id, gatt_status, handle, value.size(), value.data(), cb_data); }); } } void TestAudioDataTransfer(int group_id, uint8_t cis_count_out, uint8_t cis_count_in, int data_len, int in_data_len = 40, uint16_t decoded_in_data_len = 0) { ASSERT_NE(unicast_source_hal_cb_, nullptr); ASSERT_NE(mock_le_audio_sink_hal_client_, nullptr); // Expect two channels ISO Data to be sent std::vector handles; if (cis_count_out) { EXPECT_CALL(*mock_iso_manager_, SendIsoData(_, _, _)) .Times(cis_count_out) .WillRepeatedly( [&handles](uint16_t iso_handle, const uint8_t* data, uint16_t data_len) { handles.push_back(iso_handle); }); } std::vector data(data_len); unicast_source_hal_cb_->OnAudioDataReady(data); // Inject microphone data from group (2 CISes - pass stereo data in 1 call) if (decoded_in_data_len) { EXPECT_CALL(*mock_le_audio_sink_hal_client_, SendData(_, decoded_in_data_len)) .Times(cis_count_in > 0 ? 1 : 0); } else { EXPECT_CALL(*mock_le_audio_sink_hal_client_, SendData(_, _)) .Times(cis_count_in > 0 ? 1 : 0); } ASSERT_EQ(streaming_groups.count(group_id), 1u); if (cis_count_in) { ASSERT_NE(unicast_sink_hal_cb_, nullptr); ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr; device = group->GetNextDevice(device)) { for (auto& ase : device->ases_) { if (ase.direction == bluetooth::le_audio::types::kLeAudioDirectionSource) { InjectIncomingIsoData(group_id, ase.cis_conn_hdl, in_data_len); --cis_count_in; if (!cis_count_in) break; } } if (!cis_count_in) break; } } SyncOnMainLoop(); std::sort(handles.begin(), handles.end()); ASSERT_EQ(cis_count_in, 0); handles.clear(); Mock::VerifyAndClearExpectations(mock_iso_manager_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); } void InjectIncomingIsoData(uint16_t cig_id, uint16_t cis_con_hdl, size_t payload_size) { BT_HDR* bt_hdr = (BT_HDR*)malloc(sizeof(BT_HDR) + payload_size); bt_hdr->offset = 0; bt_hdr->len = payload_size; bluetooth::hci::iso_manager::cis_data_evt cis_evt; cis_evt.cig_id = cig_id; cis_evt.cis_conn_hdl = cis_con_hdl; cis_evt.ts = 0; cis_evt.evt_lost = 0; cis_evt.p_msg = bt_hdr; ASSERT_NE(cig_callbacks_, nullptr); cig_callbacks_->OnCisEvent( bluetooth::hci::iso_manager::kIsoEventCisDataAvailable, &cis_evt); free(bt_hdr); } void InjectCisDisconnected(uint16_t cig_id, uint16_t cis_con_hdl, uint8_t reason = 0) { bluetooth::hci::iso_manager::cis_disconnected_evt cis_evt; cis_evt.cig_id = cig_id; cis_evt.cis_conn_hdl = cis_con_hdl; cis_evt.reason = reason; ASSERT_NE(cig_callbacks_, nullptr); cig_callbacks_->OnCisEvent( bluetooth::hci::iso_manager::kIsoEventCisDisconnected, &cis_evt); } void InjectCigRemoved(uint8_t cig_id) { bluetooth::hci::iso_manager::cig_remove_cmpl_evt evt; evt.status = 0; evt.cig_id = cig_id; ASSERT_NE(cig_callbacks_, nullptr); cig_callbacks_->OnCisEvent( bluetooth::hci::iso_manager::kIsoEventCigOnRemoveCmpl, &evt); } NiceMock mock_audio_hal_client_callbacks_; LeAudioSourceAudioHalClient::Callbacks* unicast_source_hal_cb_ = nullptr; LeAudioSinkAudioHalClient::Callbacks* unicast_sink_hal_cb_ = nullptr; uint8_t default_channel_cnt = 0x03; uint8_t default_ase_cnt = 1; NiceMock mock_csis_client_module_; NiceMock mock_groups_module_; bluetooth::groups::DeviceGroupsCallbacks* group_callbacks_; NiceMock mock_state_machine_; NiceMock> mock_storage_load; NiceMock> mock_hal_2_1_verifier; NiceMock mock_btm_interface_; NiceMock mock_gatt_interface_; NiceMock mock_gatt_queue_; tBTA_GATTC_CBACK* gatt_callback; const uint8_t gatt_if = 0xfe; uint16_t global_conn_id = 1; bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks_; std::map streaming_groups; bool block_streaming_state_callback = false; bool attach_to_stream_scheduled = false; bluetooth::hci::IsoManager* iso_manager_; MockIsoManager* mock_iso_manager_; bluetooth::hci::iso_manager::CigCallbacks* cig_callbacks_ = nullptr; uint16_t iso_con_counter_ = 1; bluetooth::le_audio::CodecManager* codec_manager_; MockCodecManager* mock_codec_manager_; uint16_t available_snk_context_types_ = 0xffff; uint16_t available_src_context_types_ = 0xffff; uint16_t supported_snk_context_types_ = 0xffff; uint16_t supported_src_context_types_ = 0xffff; bool empty_source_pack_; bool empty_sink_pack_; NiceMock mock_btif_storage_; std::map>> peer_devices; std::list group_locks; std::map groups; /* CCC descriptor data */ tGATT_STATUS gatt_read_ctp_ccc_status_ = GATT_SUCCESS; std::optional ccc_stored_byte_val_ = std::nullopt; /* Audio track metadata */ char* test_tags_ptr_ = nullptr; NiceMock controller_; }; class UnicastTest : public UnicastTestNoInit { protected: void SetUp() override { UnicastTestNoInit::SetUp(); EXPECT_CALL(mock_hal_2_1_verifier, Call()).Times(1); EXPECT_CALL(mock_storage_load, Call()).Times(1); ON_CALL(mock_btm_interface_, GetHCIConnHandle(_, _)) .WillByDefault([this](RawAddress const& bd_addr, tBT_TRANSPORT transport) -> uint16_t { for (auto const& [conn_id, dev_wrapper] : peer_devices) { if (dev_wrapper->addr == bd_addr) { return conn_id; } } log::error("GetHCIConnHandle Mock: not a valid test device!"); return 0x00FE; }); ON_CALL(mock_btm_interface_, AclDisconnectFromHandle(_, _)) .WillByDefault([this](uint16_t handle, tHCI_STATUS rs) { ASSERT_NE(handle, GATT_INVALID_CONN_ID); InjectDisconnectedEvent(handle, GATT_CONN_TERMINATE_LOCAL_HOST); }); std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; BtaAppRegisterCallback app_register_callback; EXPECT_CALL(mock_gatt_interface_, AppRegister(_, _, _)) .WillOnce(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction* foo) { foo->Call(); }, &mock_storage_load), base::Bind([](MockFunction* foo) { return foo->Call(); }, &mock_hal_2_1_verifier), framework_encode_preference); SyncOnMainLoop(); ASSERT_TRUE(gatt_callback); ASSERT_TRUE(group_callbacks_); ASSERT_TRUE(app_register_callback); app_register_callback.Run(gatt_if, GATT_SUCCESS); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } void TearDown() override { // Clear the default actions before the parent class teardown is called Mock::VerifyAndClear(&mock_btm_interface_); Mock::VerifyAndClear(&mock_gatt_interface_); Mock::VerifyAndClear(&mock_audio_hal_client_callbacks_); groups.clear(); UnicastTestNoInit::TearDown(); } }; class UnicastTestHealthStatus : public UnicastTest { protected: void SetUp() override { use_health_status = true; UnicastTest::SetUp(); group_ = new LeAudioDeviceGroup(group_id_); } void TearDown() override { delete group_; UnicastTest::TearDown(); } const int group_id_ = 0; LeAudioDeviceGroup* group_ = nullptr; }; class UnicastTestHandoverMode : public UnicastTest { protected: void SetUp() override { use_handover_mode = true; UnicastTest::SetUp(); group_ = new LeAudioDeviceGroup(group_id_); } void TearDown() override { delete group_; UnicastTest::TearDown(); } const int group_id_ = 0; LeAudioDeviceGroup* group_ = nullptr; }; RawAddress GetTestAddress(uint8_t index) { EXPECT_LT(index, UINT8_MAX); RawAddress result = {{0xC0, 0xDE, 0xC0, 0xDE, 0x00, index}}; return result; } TEST_F(UnicastTest, Initialize) { ASSERT_NE(LeAudioClient::Get(), nullptr); ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning()); } TEST_F(UnicastTestNoInit, InitializeNoHal_2_1) { ASSERT_FALSE(LeAudioClient::IsLeAudioClientRunning()); // Report False when asked for Audio HAL 2.1 support ON_CALL(mock_hal_2_1_verifier, Call()).WillByDefault([]() -> bool { return false; }); BtaAppRegisterCallback app_register_callback; ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; EXPECT_DEATH( LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction* foo) { foo->Call(); }, &mock_storage_load), base::Bind([](MockFunction* foo) { return foo->Call(); }, &mock_hal_2_1_verifier), framework_encode_preference), "LE Audio Client requires Bluetooth Audio HAL V2.1 at least. Either " "disable LE Audio Profile, or update your HAL"); } TEST_F(UnicastTest, ConnectAndSetupPhy) { const RawAddress test_address0 = GetTestAddress(0); uint16_t conn_id = 1; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt /*add_ascs*/); EXPECT_CALL(mock_btm_interface_, BleSetPhy(test_address0, PHY_LE_2M, PHY_LE_2M, 0)) .Times(1); ConnectLeAudio(test_address0, false); Mock::VerifyAndClearExpectations(&mock_btm_interface_); EXPECT_CALL(mock_btm_interface_, BleSetPhy(test_address0, PHY_LE_2M, PHY_LE_2M, 0)) .Times(1); InjectPhyChangedEvent(conn_id, 0, 0, GATT_REQ_NOT_SUPPORTED); SyncOnMainLoop(); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(true))); InjectEncryptionChangedEvent(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btm_interface_); /* Make sure flag `acl_phy_update_done_` is cleared after disconnect. * Just repeat previous steps after reconnection */ InjectDisconnectedEvent(conn_id); SyncOnMainLoop(); EXPECT_CALL(mock_btm_interface_, BleSetPhy(test_address0, PHY_LE_2M, PHY_LE_2M, 0)) .Times(1); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(false))); InjectConnectedEvent(test_address0, 1); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btm_interface_); EXPECT_CALL(mock_btm_interface_, BleSetPhy(test_address0, PHY_LE_2M, PHY_LE_2M, 0)) .Times(1); InjectPhyChangedEvent(conn_id, 0, 0, GATT_REQ_NOT_SUPPORTED); SyncOnMainLoop(); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(true))); InjectEncryptionChangedEvent(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btm_interface_); } TEST_F(UnicastTest, ConnectOneEarbudEmpty) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEmpty(1, test_address0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); ConnectLeAudio(test_address0); } TEST_F(UnicastTest, ConnectOneEarbudNoPacs) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ false, /*add_pacs*/ default_ase_cnt /*add_ascs*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); ConnectLeAudio(test_address0); } TEST_F(UnicastTest, ConnectOneEarbudNoAscs) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ 0 /*add_ascs*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); ConnectLeAudio(test_address0); } TEST_F(UnicastTest, ConnectOneEarbudNoCas) { const RawAddress test_address0 = GetTestAddress(0); uint16_t conn_id = 1; SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ false, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt /*add_ascs*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); } TEST_F(UnicastTest, ConnectOneEarbudNoCsis) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt /*add_ascs*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); } TEST_F(UnicastTest, ConnectOneEarbudWithInvalidCsis) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt /*add_ascs*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); /* Make sure Group has not knowledge about the device */ ON_CALL(mock_groups_module_, GetGroupId(_, _)) .WillByDefault([](const RawAddress& addr, bluetooth::Uuid uuid) { return bluetooth::groups::kGroupUnknown; }); ConnectLeAudio(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F_WITH_FLAGS(UnicastTestHealthStatus, ConnectOneEarbudEmpty_withHealthStatus, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_enable_health_based_actions))) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEmpty(1, test_address0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnHealthBasedRecommendationAction( test_address0, LeAudioHealthBasedAction::DISABLE)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); ConnectLeAudio(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); LeAudioHealthStatus::Get()->RemoveStatistics( test_address0, bluetooth::groups::kGroupUnknown); } TEST_F_WITH_FLAGS(UnicastTestHealthStatus, ConnectOneEarbudNoPacs_withHealthStatus, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_enable_health_based_actions))) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ false, /*add_pacs*/ default_ase_cnt /*add_ascs*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnHealthBasedRecommendationAction( test_address0, LeAudioHealthBasedAction::DISABLE)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); ConnectLeAudio(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); LeAudioHealthStatus::Get()->RemoveStatistics( test_address0, bluetooth::groups::kGroupUnknown); } TEST_F_WITH_FLAGS(UnicastTestHealthStatus, ConnectOneEarbudNoAscs_withHealthStatus, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_enable_health_based_actions))) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ 0 /*add_ascs*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnHealthBasedRecommendationAction( test_address0, LeAudioHealthBasedAction::DISABLE)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); ConnectLeAudio(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); LeAudioHealthStatus::Get()->RemoveStatistics( test_address0, bluetooth::groups::kGroupUnknown); } TEST_F(UnicastTestHealthStatus, ConnectOneEarbudNoCas_withHealthStatus) { const RawAddress test_address0 = GetTestAddress(0); uint16_t conn_id = 1; SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ false, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt /*add_ascs*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnHealthBasedRecommendationAction( test_address0, LeAudioHealthBasedAction::DISABLE)) .Times(0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); LeAudioHealthStatus::Get()->RemoveStatistics( test_address0, bluetooth::groups::kGroupUnknown); } TEST_F(UnicastTestHealthStatus, ConnectOneEarbudNoCsis_withHealthStatus) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt /*add_ascs*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnHealthBasedRecommendationAction( test_address0, LeAudioHealthBasedAction::DISABLE)) .Times(0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); LeAudioHealthStatus::Get()->RemoveStatistics( test_address0, bluetooth::groups::kGroupUnknown); } TEST_F_WITH_FLAGS(UnicastTestHealthStatus, ConnectOneEarbudWithInvalidCsis_withHealthStatus, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_enable_health_based_actions))) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt /*add_ascs*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnHealthBasedRecommendationAction( test_address0, LeAudioHealthBasedAction::DISABLE)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); /* Make sure Group has not knowledge about the device */ ON_CALL(mock_groups_module_, GetGroupId(_, _)) .WillByDefault([](const RawAddress& addr, bluetooth::Uuid uuid) { return bluetooth::groups::kGroupUnknown; }); ConnectLeAudio(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); LeAudioHealthStatus::Get()->RemoveStatistics( test_address0, bluetooth::groups::kGroupUnknown); } TEST_F_WITH_FLAGS(UnicastTestHealthStatus, ConnectOneEarbudDisable_withHealthStatus, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_enable_health_based_actions))) { const RawAddress test_address0 = GetTestAddress(0); int conn_id = 1; SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); LeAudioClient::Get()->GroupSetActive(group_id_); auto device = std::make_shared( test_address0, DeviceConnectState::DISCONNECTED); group_->AddNode(device); SyncOnMainLoop(); auto health_status = LeAudioHealthStatus::Get(); /* Inject stream error */ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnHealthBasedGroupRecommendationAction( group_id_, LeAudioHealthBasedAction::DISABLE)) .Times(1); health_status->AddStatisticForGroup( group_, LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED); health_status->AddStatisticForGroup( group_, LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED); /* Do not act on disconnect */ ON_CALL(mock_gatt_interface_, Close(_)).WillByDefault(DoAll(Return())); ON_CALL(mock_btm_interface_, AclDisconnectFromHandle(_, _)) .WillByDefault(DoAll(Return())); state_machine_callbacks_->OnStateTransitionTimeout(group_id_); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnHealthBasedGroupRecommendationAction( group_id_, LeAudioHealthBasedAction::DISABLE)) .Times(0); health_status->AddStatisticForGroup( group_, LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED); health_status->AddStatisticForGroup( group_, LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F_WITH_FLAGS(UnicastTestHealthStatus, ConnectOneEarbudConsiderDisabling_withHealthStatus, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_enable_health_based_actions))) { const RawAddress test_address0 = GetTestAddress(0); int conn_id = 1; SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); LeAudioClient::Get()->GroupSetActive(group_id_); auto device = std::make_shared( test_address0, DeviceConnectState::DISCONNECTED); group_->AddNode(device); SyncOnMainLoop(); auto health_status = LeAudioHealthStatus::Get(); /* Inject stream success and error */ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnHealthBasedGroupRecommendationAction( group_id_, LeAudioHealthBasedAction::CONSIDER_DISABLING)) .Times(1); health_status->AddStatisticForGroup( group_, LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS); health_status->AddStatisticForGroup( group_, LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED); health_status->AddStatisticForGroup( group_, LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED); /* Do not act on disconnect */ ON_CALL(mock_gatt_interface_, Close(_)).WillByDefault(DoAll(Return())); ON_CALL(mock_btm_interface_, AclDisconnectFromHandle(_, _)) .WillByDefault(DoAll(Return())); state_machine_callbacks_->OnStateTransitionTimeout(group_id_); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnHealthBasedGroupRecommendationAction( 1, LeAudioHealthBasedAction::CONSIDER_DISABLING)) .Times(0); health_status->AddStatisticForGroup( group_, LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED); health_status->AddStatisticForGroup( group_, LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, ConnectDisconnectOneEarbud) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid(1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); DisconnectLeAudioWithAclClose(test_address0, 1); } TEST_F(UnicastTest, ConnectRemoteServiceDiscoveryCompleteBeforeEncryption) { const RawAddress test_address0 = GetTestAddress(0); uint16_t conn_id = 1; SetSampleDatabaseEarbudsValid(conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(0); ConnectLeAudio(test_address0, false); InjectSearchCompleteEvent(conn_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(true))); InjectEncryptionChangedEvent(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, DisconnectWhenLinkKeyIsGone) { const RawAddress test_address0 = GetTestAddress(0); uint16_t conn_id = 1; SetSampleDatabaseEarbudsValid(conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(false))); ON_CALL(mock_btm_interface_, SetEncryption(test_address0, _, _, _, _)) .WillByDefault(Return(BTM_ERR_KEY_MISSING)); EXPECT_CALL(mock_gatt_interface_, Close(conn_id)).Times(1); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::Connect, base::Unretained(LeAudioClient::Get()), test_address0)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btm_interface_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } /* same as above case except the disconnect is initiated by remote */ TEST_F(UnicastTest, ConnectRemoteDisconnectOneEarbud) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid(1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); /* Make sure when remote device disconnects us, TA is used */ EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); /* When reconnected, we always remove background connect, as we do not track * which type (allow list or TA) was used and then make sure the TA is used. */ EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); /* For background connect, test needs to Inject Connected Event */ InjectConnectedEvent(test_address0, 1); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } /* same as above case except the disconnect is initiated by remote */ TEST_F(UnicastTest, ConnectRemoteDisconnectOnTimeoutOneEarbud) { const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid(1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); /* Remove default action on the direct connect */ ON_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(Return()); /* For remote disconnection, expect stack to try background re-connect */ EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); InjectDisconnectedEvent(1, GATT_CONN_TIMEOUT); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); /* For background connect, test needs to Inject Connected Event */ InjectConnectedEvent(test_address0, 1); SyncOnMainLoop(); } TEST_F(UnicastTest, ConnectTwoEarbudsCsisGrouped) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); Mock::VerifyAndClearExpectations(&mock_btif_storage_); /* for Target announcements AutoConnect is always there, until * device is removed */ EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, false)) .Times(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, false)) .Times(0); // Verify grouping information std::vector devs = LeAudioClient::Get()->GetGroupDevices(group_id); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); DisconnectLeAudioWithAclClose(test_address0, 1); DisconnectLeAudioWithAclClose(test_address1, 2); } TEST_F(UnicastTest, ConnectTwoEarbudsCsisGroupUnknownAtConnect) { uint8_t group_size = 2; uint8_t group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); // First earbud connects without known grouping const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); Mock::VerifyAndClearExpectations(&mock_btif_storage_); // Verify grouping information std::vector devs = LeAudioClient::Get()->GetGroupDevices(group_id); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); /* for Target announcements AutoConnect is always there, until * device is removed */ EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, false)) .Times(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, false)) .Times(0); DisconnectLeAudioWithAclClose(test_address0, 1); DisconnectLeAudioWithAclClose(test_address1, 2); } TEST_F(UnicastTestNoInit, ConnectFailedDueToInvalidParameters) { // Prepare two devices uint8_t group_size = 2; uint8_t group_id = 2; /* Prepare mock to not inject connect event so the device can stay in * CONNECTING state*/ ON_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_DIRECT_CONNECTION, false)) .WillByDefault(DoAll(Return())); const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt, /*add_ascs_cnt*/ group_size, 1); const RawAddress test_address1 = GetTestAddress(1); SetSampleDatabaseEarbudsValid( 2, test_address1, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt, /*add_ascs_cnt*/ group_size, 2); // Load devices from the storage when storage API is called bool autoconnect = true; /* Common storage values */ std::vector handles; LeAudioClient::GetHandlesForStorage(test_address0, handles); std::vector ases; LeAudioClient::GetAsesForStorage(test_address0, ases); std::vector src_pacs; LeAudioClient::GetSourcePacsForStorage(test_address0, src_pacs); std::vector snk_pacs; LeAudioClient::GetSinkPacsForStorage(test_address0, snk_pacs); EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { do_in_main_thread( FROM_HERE, base::Bind(&LeAudioClient::AddFromStorage, test_address0, autoconnect, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), std::move(ases))); do_in_main_thread( FROM_HERE, base::Bind(&LeAudioClient::AddFromStorage, test_address1, autoconnect, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), std::move(ases))); }); // Expect stored device0 to connect automatically (first directed connection ) EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); // Expect stored device1 to connect automatically (first direct connection) EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _)) .WillByDefault(DoAll(Return(true))); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(true))); ON_CALL(mock_groups_module_, GetGroupId(_, _)) .WillByDefault(DoAll(Return(group_id))); ON_CALL(mock_btm_interface_, GetSecurityFlagsByTransport(test_address0, NotNull(), _)) .WillByDefault( DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true))); std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; // Initialize BtaAppRegisterCallback app_register_callback; ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction* foo) { foo->Call(); }, &mock_storage_load), base::Bind([](MockFunction* foo) { return foo->Call(); }, &mock_hal_2_1_verifier), framework_encode_preference); if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS); // We need to wait for the storage callback before verifying stuff SyncOnMainLoop(); ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning()); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); // Simulate connect parameters are invalid and phone does not fallback // to background connect. EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(0); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(0); // Devices not found InjectConnectedEvent(test_address0, 0, GATT_ILLEGAL_PARAMETER); InjectConnectedEvent(test_address1, 0, GATT_ILLEGAL_PARAMETER); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTestNoInit, LoadStoredEarbudsBroakenStorage) { // Prepare two devices uint8_t group_size = 2; uint8_t group_id = 2; /* If the storage has been broken, make sure device will be rediscovered after * reconnection */ /* Prepare mock to not inject connect event so the device can stay in * CONNECTING state*/ ON_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_DIRECT_CONNECTION, false)) .WillByDefault(DoAll(Return())); const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt, /*add_ascs_cnt*/ group_size, 1); const RawAddress test_address1 = GetTestAddress(1); SetSampleDatabaseEarbudsValid( 2, test_address1, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt, /*add_ascs_cnt*/ group_size, 2); // Load devices from the storage when storage API is called bool autoconnect = true; std::vector empty_buf; EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::AddFromStorage, test_address0, autoconnect, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, 0xff, 0xff, std::move(empty_buf), std::move(empty_buf), std::move(empty_buf), std::move(empty_buf))); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::AddFromStorage, test_address1, autoconnect, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, 0xff, 0xff, std::move(empty_buf), std::move(empty_buf), std::move(empty_buf), std::move(empty_buf))); SyncOnMainLoop(); }); // Expect stored device0 to connect automatically (first directed connection ) EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); // Expect stored device1 to connect automatically (first direct connection) EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _)) .WillByDefault(DoAll(Return(true))); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(true))); ON_CALL(mock_groups_module_, GetGroupId(_, _)) .WillByDefault(DoAll(Return(group_id))); ON_CALL(mock_btm_interface_, GetSecurityFlagsByTransport(test_address0, NotNull(), _)) .WillByDefault( DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true))); std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; // Initialize BtaAppRegisterCallback app_register_callback; ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction* foo) { foo->Call(); }, &mock_storage_load), base::Bind([](MockFunction* foo) { return foo->Call(); }, &mock_hal_2_1_verifier), framework_encode_preference); if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS); // We need to wait for the storage callback before verifying stuff SyncOnMainLoop(); ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning()); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); // Simulate devices are not there and phone fallbacks to targeted // announcements EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); // Devices not found InjectConnectedEvent(test_address0, 0, GATT_ERROR); InjectConnectedEvent(test_address1, 0, GATT_ERROR); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); /* Stack should rediscover services as storage is broken */ EXPECT_CALL(mock_gatt_interface_, ServiceSearchRequest(2, _)).Times(1); EXPECT_CALL(mock_gatt_interface_, ServiceSearchRequest(1, _)).Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address1)) .Times(1); /* For background connect, test needs to Inject Connected Event */ InjectConnectedEvent(test_address0, 1); InjectConnectedEvent(test_address1, 2); SyncOnMainLoop(); // Verify if all went well and we got the proper group std::vector devs = LeAudioClient::Get()->GetGroupDevices(group_id); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); DisconnectLeAudioWithAclClose(test_address0, 1); DisconnectLeAudioWithAclClose(test_address1, 2); } TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGrouped) { // Prepare two devices uint8_t group_size = 2; uint8_t group_id = 2; /* Prepare mock to not inject connect event so the device can stay in * CONNECTING state*/ ON_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_DIRECT_CONNECTION, false)) .WillByDefault(DoAll(Return())); const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt, /*add_ascs_cnt*/ group_size, 1); const RawAddress test_address1 = GetTestAddress(1); SetSampleDatabaseEarbudsValid( 2, test_address1, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt, /*add_ascs_cnt*/ group_size, 2); // Load devices from the storage when storage API is called bool autoconnect = true; /* Common storage values */ std::vector handles; LeAudioClient::GetHandlesForStorage(test_address0, handles); std::vector ases; LeAudioClient::GetAsesForStorage(test_address0, ases); std::vector src_pacs; LeAudioClient::GetSourcePacsForStorage(test_address0, src_pacs); std::vector snk_pacs; LeAudioClient::GetSinkPacsForStorage(test_address0, snk_pacs); EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::AddFromStorage, test_address0, autoconnect, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), std::move(ases))); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::AddFromStorage, test_address1, autoconnect, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), std::move(ases))); SyncOnMainLoop(); }); // Expect stored device0 to connect automatically (first directed connection ) EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); // Expect stored device1 to connect automatically (first direct connection) EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _)) .WillByDefault(DoAll(Return(true))); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(true))); ON_CALL(mock_groups_module_, GetGroupId(_, _)) .WillByDefault(DoAll(Return(group_id))); ON_CALL(mock_btm_interface_, GetSecurityFlagsByTransport(test_address0, NotNull(), _)) .WillByDefault( DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true))); std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; // Initialize BtaAppRegisterCallback app_register_callback; ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction* foo) { foo->Call(); }, &mock_storage_load), base::Bind([](MockFunction* foo) { return foo->Call(); }, &mock_hal_2_1_verifier), framework_encode_preference); if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS); // We need to wait for the storage callback before verifying stuff SyncOnMainLoop(); ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning()); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); // Simulate devices are not there and phone fallbacks to targeted // announcements EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); // Devices not found InjectConnectedEvent(test_address0, 0, GATT_ERROR); InjectConnectedEvent(test_address1, 0, GATT_ERROR); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address1)) .Times(1); /* For background connect, test needs to Inject Connected Event */ InjectConnectedEvent(test_address0, 1); InjectConnectedEvent(test_address1, 2); SyncOnMainLoop(); // Verify if all went well and we got the proper group std::vector devs = LeAudioClient::Get()->GetGroupDevices(group_id); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); DisconnectLeAudioWithAclClose(test_address0, 1); DisconnectLeAudioWithAclClose(test_address1, 2); } TEST_F(UnicastTestNoInit, ServiceChangedBeforeServiceIsConnected) { // Prepare two devices uint8_t group_size = 2; uint8_t group_id = 2; /* Prepare mock to not inject connect event so the device can stay in * CONNECTING state*/ ON_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_DIRECT_CONNECTION, false)) .WillByDefault(DoAll(Return())); const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt, /*add_ascs_cnt*/ group_size, 1); const RawAddress test_address1 = GetTestAddress(1); SetSampleDatabaseEarbudsValid( 2, test_address1, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt, /*add_ascs_cnt*/ group_size, 2); // Load devices from the storage when storage API is called bool autoconnect = true; /* Common storage values */ std::vector handles; LeAudioClient::GetHandlesForStorage(test_address0, handles); std::vector ases; LeAudioClient::GetAsesForStorage(test_address0, ases); std::vector src_pacs; LeAudioClient::GetSourcePacsForStorage(test_address0, src_pacs); std::vector snk_pacs; LeAudioClient::GetSinkPacsForStorage(test_address0, snk_pacs); EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::AddFromStorage, test_address0, autoconnect, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), std::move(ases))); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::AddFromStorage, test_address1, autoconnect, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), std::move(ases))); SyncOnMainLoop(); }); // Expect stored device0 to connect automatically (first directed connection ) EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); // Expect stored device1 to connect automatically (first direct connection) EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _)) .WillByDefault(DoAll(Return(true))); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(true))); ON_CALL(mock_groups_module_, GetGroupId(_, _)) .WillByDefault(DoAll(Return(group_id))); ON_CALL(mock_btm_interface_, GetSecurityFlagsByTransport(test_address0, NotNull(), _)) .WillByDefault( DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true))); std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; // Initialize BtaAppRegisterCallback app_register_callback; ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction* foo) { foo->Call(); }, &mock_storage_load), base::Bind([](MockFunction* foo) { return foo->Call(); }, &mock_hal_2_1_verifier), framework_encode_preference); if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS); // We need to wait for the storage callback before verifying stuff SyncOnMainLoop(); ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning()); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); /* Inject Service Changed */ InjectServiceChangedEvent(test_address1, 0xffff); SyncOnMainLoop(); InjectServiceChangedEvent(test_address0, 0xffff); SyncOnMainLoop(); /* Stack should rediscover services as storage is broken */ EXPECT_CALL(mock_gatt_interface_, ServiceSearchRequest(2, _)).Times(1); EXPECT_CALL(mock_gatt_interface_, ServiceSearchRequest(1, _)).Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address1)) .Times(1); /* For background connect, test needs to Inject Connected Event */ InjectConnectedEvent(test_address0, 1); InjectConnectedEvent(test_address1, 2); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGroupedDifferently) { // Prepare two devices uint8_t group_size = 1; // Device 0 uint8_t group_id0 = 2; bool autoconnect0 = true; const RawAddress test_address0 = GetTestAddress(0); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ true, /*add_ascs*/ group_size, 1); ON_CALL(mock_groups_module_, GetGroupId(test_address0, _)) .WillByDefault(DoAll(Return(group_id0))); // Device 1 uint8_t group_id1 = 3; bool autoconnect1 = false; const RawAddress test_address1 = GetTestAddress(1); SetSampleDatabaseEarbudsValid( 2, test_address1, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt, /*add_ascs_cnt*/ group_size, 2); ON_CALL(mock_groups_module_, GetGroupId(test_address1, _)) .WillByDefault(DoAll(Return(group_id1))); /* Commont storage values */ std::vector handles; LeAudioClient::GetHandlesForStorage(test_address0, handles); std::vector ases; LeAudioClient::GetAsesForStorage(test_address0, ases); std::vector src_pacs; LeAudioClient::GetSourcePacsForStorage(test_address0, src_pacs); std::vector snk_pacs; LeAudioClient::GetSinkPacsForStorage(test_address0, snk_pacs); // Load devices from the storage when storage API is called EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::AddFromStorage, test_address0, autoconnect0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), std::move(ases))); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::AddFromStorage, test_address1, autoconnect1, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, 0xff, 0xff, std::move(handles), std::move(snk_pacs), std::move(src_pacs), std::move(ases))); }); // Expect stored device0 to connect automatically EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(true))); // First device will got connected EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); // Expect stored device1 to NOT connect automatically EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address1)) .Times(0); ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _)) .WillByDefault(DoAll(Return(true))); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, BTM_BLE_DIRECT_CONNECTION, _)) .Times(0); // Initialize BtaAppRegisterCallback app_register_callback; ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); std::vector<::bluetooth::le_audio::btle_audio_codec_config_t> framework_encode_preference; LeAudioClient::Initialize( &mock_audio_hal_client_callbacks_, base::Bind([](MockFunction* foo) { foo->Call(); }, &mock_storage_load), base::Bind([](MockFunction* foo) { return foo->Call(); }, &mock_hal_2_1_verifier), framework_encode_preference); if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS); // We need to wait for the storage callback before verifying stuff SyncOnMainLoop(); ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning()); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Simulate device is not there and phone fallbacks to targeted announcements EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); // Devices 0 is connected. Disconnect it InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); /* Keep device in Getting Ready state */ ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(false))); ON_CALL(mock_btm_interface_, SetEncryption(test_address0, _, _, _, _)) .WillByDefault(Return(BTM_SUCCESS)); /* For background connect, test needs to Inject Connected Event */ InjectConnectedEvent(test_address0, 1); // We need to wait for the storage callback before verifying stuff SyncOnMainLoop(); ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning()); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); std::vector devs = LeAudioClient::Get()->GetGroupDevices(group_id0); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); ASSERT_EQ(std::find(devs.begin(), devs.end(), test_address1), devs.end()); devs = LeAudioClient::Get()->GetGroupDevices(group_id1); ASSERT_EQ(std::find(devs.begin(), devs.end(), test_address0), devs.end()); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); /* Disconnects while being in getting ready state */ DisconnectLeAudioWithGattClose(test_address0, 1); } TEST_F(UnicastTest, GroupingAddRemove) { // Earbud connects without known grouping uint8_t group_id0 = bluetooth::groups::kGroupUnknown; const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectNonCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft); group_id0 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address0); // Earbud connects without known grouping uint8_t group_id1 = bluetooth::groups::kGroupUnknown; const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectNonCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight); group_id1 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1); Mock::VerifyAndClearExpectations(&mock_btif_storage_); // Verify individual groups ASSERT_NE(group_id0, bluetooth::groups::kGroupUnknown); ASSERT_NE(group_id1, bluetooth::groups::kGroupUnknown); ASSERT_NE(group_id0, group_id1); ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 1u); ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 1u); // Expectations on reassigning second earbud to the first group int dev1_storage_group = bluetooth::groups::kGroupUnknown; int dev1_new_group = bluetooth::groups::kGroupUnknown; EXPECT_CALL( mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address1, group_id1, GroupNodeStatus::REMOVED)) .Times(AtLeast(1)); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address1, _, GroupNodeStatus::ADDED)) .WillRepeatedly(SaveArg<1>(&dev1_new_group)); EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address1, group_id1)) .Times(AtLeast(1)); EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, _)) .Times(AnyNumber()); LeAudioClient::Get()->GroupRemoveNode(group_id1, test_address1); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_groups_module_); Mock::VerifyAndClearExpectations(&mock_btif_storage_); EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, group_id0)) .Times(1); LeAudioClient::Get()->GroupAddNode(group_id0, test_address1); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_groups_module_); dev1_storage_group = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1); // Verify regrouping results EXPECT_EQ(dev1_new_group, group_id0); EXPECT_EQ(dev1_new_group, dev1_storage_group); ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 0u); ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 2u); std::vector devs = LeAudioClient::Get()->GetGroupDevices(group_id0); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); } TEST_F(UnicastTest, DoubleResumeFromAF) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); constexpr int gmcs_ccid = 1; constexpr int gtbs_ccid = 2; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); types::BidirectionalPair> ccids = {.sink = {gmcs_ccid}, .source = {}}; EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); block_streaming_state_callback = true; UpdateLocalSourceMetadata(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC); LocalAudioSourceResume(false); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Additional resume shall be ignored. LocalAudioSourceResume(false, false); EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0); do_in_main_thread( FROM_HERE, base::BindOnce( [](int group_id, bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks) { state_machine_callbacks->StatusReportCb( group_id, GroupStreamStatus::STREAMING); }, group_id, base::Unretained(state_machine_callbacks_))); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); // Verify Data transfer on one audio source cis constexpr uint8_t cis_count_out = 1; constexpr uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); } TEST_F(UnicastTest, DoubleResumeFromAFOnLocalSink) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; default_channel_cnt = 1; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(1); block_streaming_state_callback = true; UpdateLocalSinkMetadata(AUDIO_SOURCE_MIC); LocalAudioSinkResume(); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); EXPECT_CALL(*mock_le_audio_sink_hal_client_, CancelStreamingRequest()) .Times(0); // Actuall test here: send additional resume which shall be ignored. LocalAudioSinkResume(); EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0); do_in_main_thread( FROM_HERE, base::BindOnce( [](int group_id, bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks) { state_machine_callbacks->StatusReportCb( group_id, GroupStreamStatus::STREAMING); }, group_id, base::Unretained(state_machine_callbacks_))); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); // Verify Data transfer on local audio sink which is started constexpr uint8_t cis_count_out = 0; constexpr uint8_t cis_count_in = 1; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 0, 40); } TEST_F(UnicastTest, HandleResumeWithoutMetadataUpdateOnLocalSink) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; /** * In this test we want to make sure that if MetadataUpdate is * not called before Resume, but the context type is supported, * stream should be created */ default_channel_cnt = 1; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(1); UpdateLocalSinkMetadata(AUDIO_SOURCE_MIC); LocalAudioSinkResume(); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); // Verify Data transfer on local audio sink which is started constexpr uint8_t cis_count_out = 0; constexpr uint8_t cis_count_in = 1; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 0, 40); SyncOnMainLoop(); /* Clear cache by changing context types, this is required for the test * as setting active device actually generate cache */ auto sink_available_context = types::kLeAudioContextAllRemoteSinkOnly; auto source_available_context = types::kLeAudioContextAllRemoteSource; InjectAvailableContextTypes(test_address0, 1, sink_available_context, source_available_context); StopStreaming(group_id, true); SyncOnMainLoop(); // simulate suspend timeout passed, alarm executing fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); SyncOnMainLoop(); EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(1); // Resume without metadata update while cached configuration is cleared LocalAudioSinkResume(); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); } TEST_F(UnicastTest, GroupSetActiveNonConnectedGroup) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; /** * In this test we want to make sure that Available context change reach Java * when group is in Configured state */ default_channel_cnt = 1; int conn_id = 1; SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(group_id, GroupStatus::INACTIVE)) .Times(1); InjectDisconnectedEvent(conn_id); SyncOnMainLoop(); // Audio sessions are started only when device gets active EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupSelectableCodecConf(group_id, _, _)) .Times(0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupCurrentCodecConf(group_id, _, _)) .Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(0); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(0); // try to set active group on non connected group LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } TEST_F_WITH_FLAGS(UnicastTest, GroupSetActive_CurrentCodecSentOfActive, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_codec_config_callback_order_fix))) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; /** * In this test we want to make sure that Available context change reach Java * when group is in Configured state */ default_channel_cnt = 1; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupSelectableCodecConf(group_id, _, _)) .Times(1); btle_audio_codec_config_t empty_conf{}; btle_audio_codec_config_t output_config = { .codec_type = LE_AUDIO_CODEC_INDEX_SOURCE_LC3, .sample_rate = LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ, .bits_per_sample = LE_AUDIO_BITS_PER_SAMPLE_INDEX_16, .channel_count = LE_AUDIO_CHANNEL_COUNT_INDEX_1, .frame_duration = LE_AUDIO_FRAME_DURATION_INDEX_10000US, .octets_per_frame = 120}; EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupCurrentCodecConf(group_id, empty_conf, output_config)) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } TEST_F(UnicastTest, GroupSetActive) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; /** * In this test we want to make sure that Available context change reach Java * when group is in Configured state */ default_channel_cnt = 1; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupSelectableCodecConf(group_id, _, _)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(group_id, GroupStatus::ACTIVE)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(_, GroupStatus::INACTIVE)) .Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_codec_manager_, UpdateActiveUnicastAudioHalClient( mock_le_audio_source_hal_client_, mock_le_audio_sink_hal_client_, true)) .Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_codec_manager_); } TEST_F(UnicastTest, GroupSetActive_SinkPacksEmpty) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; empty_sink_pack_ = true; /** * In this test we want to make sure that Available context change reach Java * when group is in Configured state */ default_channel_cnt = 1; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active std::vector empty_confs; EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupSelectableCodecConf(group_id, _, empty_confs)) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_codec_manager_, UpdateActiveUnicastAudioHalClient( mock_le_audio_source_hal_client_, mock_le_audio_sink_hal_client_, true)) .Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_codec_manager_); } TEST_F(UnicastTest, GroupSetActive_SourcePacksEmpty) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; empty_source_pack_ = true; /** * In this test we want to make sure that Available context change reach Java * when group is in Configured state */ default_channel_cnt = 1; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active std::vector empty_confs; EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupSelectableCodecConf(group_id, empty_confs, _)) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } TEST_F(UnicastTest, ChangeAvailableContextTypeWhenInCodecConfigured) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; /** * In this test we want to make sure that Available context change reach Java * when group is in Configured state */ default_channel_cnt = 1; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(1); UpdateLocalSinkMetadata(AUDIO_SOURCE_MIC); LocalAudioSinkResume(); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); // Verify Data transfer on local audio sink which is started constexpr uint8_t cis_count_out = 0; constexpr uint8_t cis_count_in = 1; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 0, 40); // Remember group to set the state after stopping the stream ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); SyncOnMainLoop(); StopStreaming(group_id, true); SyncOnMainLoop(); // simulate suspend timeout passed, alarm executing fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); SyncOnMainLoop(); // Simulate state Configured group->SetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioConf(_, _, _, _, _)); /* Check if available context will be sent to Java */ auto sink_available_context = types::kLeAudioContextAllRemoteSinkOnly; auto source_available_context = types::kLeAudioContextAllRemoteSource; InjectAvailableContextTypes(test_address0, 1, sink_available_context, source_available_context); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, TestUpdateConfigurationCallbackWhileStreaming) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupCurrentCodecConf(group_id, _, _)) .Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_state_machine_); SyncOnMainLoop(); // When metadata update happen, there should be no configuration change // callback sent EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupCurrentCodecConf(group_id, _, _)) .Times(0); EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(1); UpdateLocalSourceMetadata(AUDIO_USAGE_ALARM, AUDIO_CONTENT_TYPE_UNKNOWN); // Inject STREAMING Status from state machine. auto group = streaming_groups.at(group_id); do_in_main_thread( FROM_HERE, base::BindOnce( [](int group_id, bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks, LeAudioDeviceGroup* group) { state_machine_callbacks->StatusReportCb( group_id, GroupStreamStatus::STREAMING); }, group_id, base::Unretained(this->state_machine_callbacks_), std::move(group))); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, RemoveNodeWhileStreaming) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming constexpr uint8_t cis_count_out = 1; constexpr uint8_t cis_count_in = 0; constexpr int gmcs_ccid = 1; constexpr int gtbs_ccid = 2; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); types::BidirectionalPair> ccids = {.sink = {gmcs_ccid}, .source = {}}; EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address0, group_id)) .Times(1); EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1); EXPECT_CALL(mock_state_machine_, ProcessHciNotifAclDisconnected(_, _)) .Times(1); EXPECT_CALL( mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, group_id, GroupNodeStatus::REMOVED)); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); LeAudioClient::Get()->GroupRemoveNode(group_id, test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_groups_module_); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, InactiveDeviceOnInternalStateMachineError) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming constexpr uint8_t cis_count_out = 1; constexpr uint8_t cis_count_in = 0; constexpr int gmcs_ccid = 1; constexpr int gtbs_ccid = 2; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); types::BidirectionalPair> ccids = {.sink = {gmcs_ccid}, .source = {}}; EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); btle_audio_codec_config_t empty_conf{}; btle_audio_codec_config_t output_config = { .codec_type = LE_AUDIO_CODEC_INDEX_SOURCE_LC3, .sample_rate = LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ, .bits_per_sample = LE_AUDIO_BITS_PER_SAMPLE_INDEX_16, .channel_count = LE_AUDIO_CHANNEL_COUNT_INDEX_2, .frame_duration = LE_AUDIO_FRAME_DURATION_INDEX_10000US, .octets_per_frame = 120}; EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupCurrentCodecConf(group_id, empty_conf, output_config)) .Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(group_id, GroupStatus::INACTIVE)) .Times(1); /* This is internal error of the state machine */ state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::RELEASING); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); } TEST_F(UnicastTest, GroupingAddTwiceNoRemove) { // Earbud connects without known grouping uint8_t group_id0 = bluetooth::groups::kGroupUnknown; const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .WillOnce(Return()) .RetiresOnSaturation(); ConnectNonCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft); group_id0 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address0); // Earbud connects without known grouping uint8_t group_id1 = bluetooth::groups::kGroupUnknown; const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .WillOnce(Return()) .RetiresOnSaturation(); ConnectNonCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight); Mock::VerifyAndClearExpectations(&mock_btif_storage_); group_id1 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1); // Verify individual groups ASSERT_NE(group_id0, bluetooth::groups::kGroupUnknown); ASSERT_NE(group_id1, bluetooth::groups::kGroupUnknown); ASSERT_NE(group_id0, group_id1); ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 1u); ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 1u); // Expectations on reassigning second earbud to the first group int dev1_storage_group = bluetooth::groups::kGroupUnknown; int dev1_new_group = bluetooth::groups::kGroupUnknown; EXPECT_CALL( mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address1, group_id1, GroupNodeStatus::REMOVED)) .Times(AtLeast(1)); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address1, _, GroupNodeStatus::ADDED)) .WillRepeatedly(SaveArg<1>(&dev1_new_group)); // FIXME: We should expect removal with group_id context. No such API exists. EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address1, group_id1)) .Times(AtLeast(1)); EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, _)) .Times(AnyNumber()); EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, group_id0)) .Times(1); // Regroup device: assign new group without removing it from the first one LeAudioClient::Get()->GroupAddNode(group_id0, test_address1); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_groups_module_); dev1_storage_group = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1); // Verify regrouping results EXPECT_EQ(dev1_new_group, group_id0); EXPECT_EQ(dev1_new_group, dev1_storage_group); ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 0u); ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 2u); std::vector devs = LeAudioClient::Get()->GetGroupDevices(group_id0); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); } TEST_F(UnicastTest, RemoveTwoEarbudsCsisGrouped) { uint8_t group_size = 2; int group_id0 = 2; int group_id1 = 3; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); // First group - First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id0, 1 /* rank*/); // First group - Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id0, 2 /* rank*/, true /*connect_through_csis*/); // Second group - First earbud const RawAddress test_address2 = GetTestAddress(2); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address2, true)) .Times(1); ConnectCsisDevice(test_address2, 3 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id1, 1 /* rank*/); // Second group - Second earbud const RawAddress test_address3 = GetTestAddress(3); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address3, true)) .Times(1); ConnectCsisDevice(test_address3, 4 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id1, 2 /* rank*/, true /*connect_through_csis*/); // First group - verify grouping information std::vector group0_devs = LeAudioClient::Get()->GetGroupDevices(group_id0); ASSERT_NE(std::find(group0_devs.begin(), group0_devs.end(), test_address0), group0_devs.end()); ASSERT_NE(std::find(group0_devs.begin(), group0_devs.end(), test_address1), group0_devs.end()); // Second group - verify grouping information std::vector group1_devs = LeAudioClient::Get()->GetGroupDevices(group_id1); ASSERT_NE(std::find(group1_devs.begin(), group1_devs.end(), test_address2), group1_devs.end()); ASSERT_NE(std::find(group1_devs.begin(), group1_devs.end(), test_address3), group1_devs.end()); Mock::VerifyAndClearExpectations(&mock_btif_storage_); // Expect one of the groups to be dropped and devices to be disconnected EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address0, group_id0)) .Times(1); EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address1, group_id0)) .Times(1); EXPECT_CALL( mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, group_id0, GroupNodeStatus::REMOVED)); EXPECT_CALL( mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address1, group_id0, GroupNodeStatus::REMOVED)); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address1)) .Times(1); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(1, _)).Times(1); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(2, _)).Times(1); // Expect the other groups to be left as is EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(group_id1, _)) .Times(0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address2)) .Times(0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address3)) .Times(0); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(3, _)).Times(0); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(4, _)).Times(0); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::GroupDestroy, base::Unretained(LeAudioClient::Get()), group_id0)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btif_storage_); Mock::VerifyAndClearExpectations(&mock_btm_interface_); } TEST_F(UnicastTest, ConnectAfterRemove) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; uint16_t conn_id = 1; SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); /* RemoveDevice */ do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioClient* client, const RawAddress& test_address0) { client->RemoveDevice(test_address0); }, LeAudioClient::Get(), test_address0)); SyncOnMainLoop(); ON_CALL(mock_btm_interface_, IsLinkKeyKnown(_, _)) .WillByDefault(DoAll(Return(false))); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::Connect, base::Unretained(LeAudioClient::Get()), test_address0)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btif_storage_); Mock::VerifyAndClearExpectations(&mock_gatt_queue_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTest, RemoveDeviceWhenConnected) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; uint16_t conn_id = 1; SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); Mock::VerifyAndClearExpectations(&mock_btif_storage_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, false)) .Times(1); EXPECT_CALL(mock_gatt_queue_, Clean(conn_id)).Times(AtLeast(1)); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(1, _)).Times(1); /* * StopStream will put calls on main_loop so to keep the correct order * of operations and to avoid races we put the test command on main_loop as * well. */ do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioClient* client, const RawAddress& test_address0) { client->RemoveDevice(test_address0); }, LeAudioClient::Get(), test_address0)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btif_storage_); Mock::VerifyAndClearExpectations(&mock_gatt_queue_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTest, RemoveDeviceWhenConnecting) { const RawAddress test_address0 = GetTestAddress(0); uint16_t conn_id = 1; /* Prepare mock to not inject connect event so the device can stay in * CONNECTING state*/ ON_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(DoAll(Return())); SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(0); ConnectLeAudio(test_address0, true, false); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, true)) .Times(1); EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, false)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, _, _)) .Times(0); /* * StopStream will put calls on main_loop so to keep the correct order * of operations and to avoid races we put the test command on main_loop as * well. */ do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioClient* client, const RawAddress& test_address0) { client->RemoveDevice(test_address0); }, LeAudioClient::Get(), test_address0)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTest, RemoveDeviceWhenGettingConnectionReady) { const RawAddress test_address0 = GetTestAddress(0); uint16_t conn_id = 1; /* Prepare mock to not inject Service Search Complete*/ ON_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _)) .WillByDefault(DoAll(Return())); SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(0); EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, false)) .Times(0); ConnectLeAudio(test_address0); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); EXPECT_CALL(mock_gatt_queue_, Clean(conn_id)).Times(AtLeast(1)); EXPECT_CALL(mock_gatt_interface_, Close(conn_id)).Times(1); /* Cancel should be called in RemoveDevice */ EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, false)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, _, _)) .Times(0); /* * StopStream will put calls on main_loop so to keep the correct order * of operations and to avoid races we put the test command on main_loop as * well. */ do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioClient* client, const RawAddress& test_address0) { client->RemoveDevice(test_address0); }, LeAudioClient::Get(), test_address0)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btif_storage_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTest, DisconnectDeviceWhenConnected) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; uint16_t conn_id = 1; SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); Mock::VerifyAndClearExpectations(&mock_btif_storage_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); /* for Target announcements AutoConnect is always there, until * device is removed */ EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, false)) .Times(0); EXPECT_CALL(mock_gatt_queue_, Clean(conn_id)).Times(AtLeast(1)); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(1, _)).Times(1); LeAudioClient::Get()->Disconnect(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btif_storage_); Mock::VerifyAndClearExpectations(&mock_gatt_queue_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTest, DisconnectDeviceWhenConnecting) { const RawAddress test_address0 = GetTestAddress(0); uint16_t conn_id = 1; /* Prepare mock to not inject connect event so the device can stay in * CONNECTING state*/ ON_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(DoAll(Return())); SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(0); ConnectLeAudio(test_address0, true, false); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); /* Prepare on call mock on Close - to not trigger Inject Disconnection, as it * is done in default mock. */ ON_CALL(mock_gatt_interface_, Close(_)).WillByDefault(DoAll(Return())); EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, true)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, _, _)) .Times(0); LeAudioClient::Get()->Disconnect(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTest, DisconnectDeviceWhenGettingConnectionReady) { const RawAddress test_address0 = GetTestAddress(0); uint16_t conn_id = global_conn_id; /* Prepare mock to not inject Service Search Complete*/ ON_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _)) .WillByDefault(DoAll(Return())); SetSampleDatabaseEarbudsValid( conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(0); ConnectLeAudio(test_address0); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); /* TA reconnect is enabled in ConnectLeAudio. Make sure this is not removed */ EXPECT_CALL(mock_gatt_queue_, Clean(conn_id)).Times(AtLeast(1)); EXPECT_CALL(mock_gatt_interface_, Close(conn_id)).Times(1); EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, _)) .Times(0); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, _, _)) .Times(0); LeAudioClient::Get()->Disconnect(test_address0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_queue_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTest, RemoveWhileStreaming) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming constexpr uint8_t cis_count_out = 1; constexpr uint8_t cis_count_in = 0; constexpr int gmcs_ccid = 1; constexpr int gtbs_ccid = 2; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); types::BidirectionalPair> ccids = {.sink = {gmcs_ccid}, .source = {}}; EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address0, group_id)) .Times(1); LeAudioDeviceGroup* group = nullptr; EXPECT_CALL(mock_state_machine_, ProcessHciNotifAclDisconnected(_, _)) .WillOnce(DoAll(SaveArg<0>(&group))); EXPECT_CALL( mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, group_id, GroupNodeStatus::REMOVED)); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); /* * StopStream will put calls on main_loop so to keep the correct order * of operations and to avoid races we put the test command on main_loop as * well. */ do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioClient* client, const RawAddress& test_address0) { client->RemoveDevice(test_address0); }, LeAudioClient::Get(), test_address0)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_groups_module_); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); ASSERT_EQ(group, nullptr); } TEST_F(UnicastTest, DisconnecteWhileAlmostStreaming) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); constexpr int gmcs_ccid = 1; constexpr int gtbs_ccid = 2; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); types::BidirectionalPair> ccids = {.sink = {gmcs_ccid}, .source = {}}; EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); /* We want here to CIS be established but device not being yet in streaming * state */ StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); SyncOnMainLoop(); /* This is test code, which will change the group state to the one which * is required by test */ ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group_inject = streaming_groups.at(group_id); group_inject->SetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING); EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1); LeAudioDeviceGroup* group = nullptr; EXPECT_CALL(mock_state_machine_, ProcessHciNotifAclDisconnected(_, _)) .WillOnce(DoAll(SaveArg<0>(&group))); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); /* * StopStream will put calls on main_loop so to keep the correct order * of operations and to avoid races we put the test command on main_loop as * well. */ do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioClient* client, const RawAddress& test_address0) { client->Disconnect(test_address0); }, LeAudioClient::Get(), test_address0)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_groups_module_); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); } TEST_F(UnicastTest, EarbudsTwsStyleStreaming) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, 0x01, 0x01, codec_spec_caps::kLeAudioSamplingFreq16000Hz, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, 2 /*add_asc_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Suspend /*TODO Need a way to verify STOP */ LeAudioClient::Get()->GroupSuspend(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Resume StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Stop StopStreaming(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Release EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_codec_manager_, UpdateActiveUnicastAudioHalClient( mock_le_audio_source_hal_client_, mock_le_audio_sink_hal_client_, false)) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_codec_manager_); } TEST_F(UnicastTest, SpeakerFailedConversationalStreaming) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; available_src_context_types_ = 0; supported_src_context_types_ = available_src_context_types_ | types::AudioContexts(types::LeAudioContextType::UNSPECIFIED).value(); available_snk_context_types_ = 0x0004; supported_snk_context_types_ = available_snk_context_types_ | types::AudioContexts(types::LeAudioContextType::UNSPECIFIED).value(); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, 0, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); /* Nothing to do - expect no crash */ } TEST_F(UnicastTest, SpeakerStreaming) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming uint8_t cis_count_out = 1; uint8_t cis_count_in = 0; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Suspend /*TODO Need a way to verify STOP */ LeAudioClient::Get()->GroupSuspend(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Resume StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Stop StopStreaming(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Release EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_codec_manager_, UpdateActiveUnicastAudioHalClient( mock_le_audio_source_hal_client_, mock_le_audio_sink_hal_client_, false)) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_codec_manager_); } TEST_F(UnicastTest, SpeakerStreamingNonDefault) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; /** * Scenario test steps * 1. Set group active and stream VOICEASSISTANT * 2. Suspend group and resume with VOICEASSISTANT * 3. Stop Stream and make group inactive * 4. Start stream without setting metadata. * 5. Verify that UNSPECIFIED context type is used. */ available_snk_context_types_ = (types::LeAudioContextType::VOICEASSISTANTS | types::LeAudioContextType::MEDIA | types::LeAudioContextType::UNSPECIFIED) .value(); supported_snk_context_types_ = available_snk_context_types_; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming uint8_t cis_count_out = 1; uint8_t cis_count_in = 0; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_ASSISTANT, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Suspend /*TODO Need a way to verify STOP */ LeAudioClient::Get()->GroupSuspend(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Resume StartStreaming(AUDIO_USAGE_ASSISTANT, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Stop StopStreaming(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Release EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); /* When session is closed, the hal client mocks are freed - get new ones */ SetUpMockAudioHal(); /* Expect the previous release to clear the old audio session metadata */ LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); EXPECT_CALL(mock_state_machine_, StartStream(_, types::LeAudioContextType::VOICEASSISTANTS, _, _)) .Times(0); EXPECT_CALL(mock_state_machine_, StartStream(_, kLeAudioDefaultConfigurationContext, _, _)) .Times(1); LocalAudioSourceResume(); } TEST_F_WITH_FLAGS(UnicastTest, TestUnidirectionalVoiceAssistant_Sink, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, le_audio_support_unidirectional_voice_assistant))) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; /** * Scenario test steps * 1. Configure group to support VOICEASSISTANT only on SINK * 2. Start stream * 5. Verify that Unidirectional VOICEASSISTANT has been created */ available_snk_context_types_ = (types::LeAudioContextType::VOICEASSISTANTS | types::LeAudioContextType::MEDIA | types::LeAudioContextType::UNSPECIFIED) .value(); supported_snk_context_types_ = available_snk_context_types_; available_src_context_types_ = (types::LeAudioContextType::LIVE | types::LeAudioContextType::UNSPECIFIED) .value(); supported_src_context_types_ = available_src_context_types_; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); types::BidirectionalPair metadata_contexts = { .sink = types::AudioContexts(types::LeAudioContextType::VOICEASSISTANTS), .source = types::AudioContexts()}; EXPECT_CALL(mock_state_machine_, StartStream(_, types::LeAudioContextType::INSTRUCTIONAL, metadata_contexts, _)) .Times(1); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming uint8_t cis_count_out = 1; uint8_t cis_count_in = 0; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_codec_manager_, UpdateActiveUnicastAudioHalClient( mock_le_audio_source_hal_client_, mock_le_audio_sink_hal_client_, true)) .Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_ASSISTANT, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); Mock::VerifyAndClearExpectations(mock_codec_manager_); SyncOnMainLoop(); // Verify Data transfer on one local audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); SyncOnMainLoop(); } TEST_F_WITH_FLAGS(UnicastTest, TestUnidirectionalVoiceAssistant_Source, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, le_audio_support_unidirectional_voice_assistant))) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; /** * Scenario test steps * 1. Configure group to support VOICEASSISTANT only on SOURCE * 2. Start stream * 5. Verify that bi-direction VOICEASSISTANT has been created */ available_snk_context_types_ = (types::LeAudioContextType::MEDIA | types::LeAudioContextType::UNSPECIFIED) .value(); supported_snk_context_types_ = available_snk_context_types_; available_src_context_types_ = (types::LeAudioContextType::VOICEASSISTANTS | types::LeAudioContextType::LIVE | types::LeAudioContextType::UNSPECIFIED) .value(); supported_src_context_types_ = available_src_context_types_; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationFrontLeftOfCenter, codec_spec_conf::kLeAudioLocationFrontLeftOfCenter, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 2 /*set_size*/, 1 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); types::BidirectionalPair metadata_contexts = { .sink = types::AudioContexts(types::LeAudioContextType::UNSPECIFIED), .source = types::AudioContexts(types::LeAudioContextType::VOICEASSISTANTS)}; EXPECT_CALL(mock_state_machine_, StartStream(_, types::LeAudioContextType::VOICEASSISTANTS, metadata_contexts, _)) .Times(1); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming uint8_t cis_count_out = 1; uint8_t cis_count_in = 1; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_ASSISTANT, AUDIO_CONTENT_TYPE_UNKNOWN, group_id, AUDIO_SOURCE_VOICE_RECOGNITION); // Verify Data transfer on one local audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); } TEST_F(UnicastTest, SpeakerStreamingAutonomousRelease) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, 1 /* cis_count_out */, 0 /* cis_count_in */, 1920); // Inject the IDLE state as if an autonomous release happened ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); ASSERT_NE(group, nullptr); for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr; device = group->GetNextDevice(device)) { for (auto& ase : device->ases_) { ase.cis_state = types::CisState::IDLE; ase.data_path_state = types::DataPathState::IDLE; ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; InjectCisDisconnected(group_id, ase.cis_conn_hdl); } } // Verify no Data transfer after the autonomous release TestAudioDataTransfer(group_id, 0 /* cis_count_out */, 0 /* cis_count_in */, 1920); } TEST_F(UnicastTest, TwoEarbudsStreaming) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); /* Make sure configurations are non empty */ btle_audio_codec_config_t call_config = { .codec_type = LE_AUDIO_CODEC_INDEX_SOURCE_LC3, .sample_rate = LE_AUDIO_SAMPLE_RATE_INDEX_16000HZ, .bits_per_sample = LE_AUDIO_BITS_PER_SAMPLE_INDEX_16, .channel_count = LE_AUDIO_CHANNEL_COUNT_INDEX_1, .frame_duration = LE_AUDIO_FRAME_DURATION_INDEX_10000US, .octets_per_frame = 40}; EXPECT_CALL(mock_audio_hal_client_callbacks_, OnAudioGroupCurrentCodecConf(group_id, call_config, call_config)) .Times(1); StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Verify Data transfer on two peer sinks and one source uint8_t cis_count_out = 2; uint8_t cis_count_in = 2; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); // Suspend LeAudioClient::Get()->GroupSuspend(group_id); SyncOnMainLoop(); // Resume StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Verify Data transfer still works TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); // Stop StopStreaming(group_id, true); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Check if cache configuration is still present ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSource) .size()); // Release EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Setting group inactive, shall not change cached configuration ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSource) .size()); } TEST_F(UnicastTest, StreamingVxAospSampleSound) { uint8_t group_size = 2; int group_id = 2; /* Test to verify that tag VX_AOSP_SAMPLESOUND is always mapped to * LeAudioContextType::SOUNDEFFECTS */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Set a test TAG char test_tag[] = "TEST_TAG2;VX_AOSP_SAMPLESOUND;TEST_TAG1"; test_tags_ptr_ = test_tag; auto initial_context = types::LeAudioContextType::SOUNDEFFECTS; types::BidirectionalPair> ccids = {.sink = {}, .source = {}}; EXPECT_CALL(mock_state_machine_, StartStream(_, initial_context, _, ccids)) .Times(1); StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Verify Data transfer on two peer sinks and one source uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 0); } TEST_F(UnicastTest, UpdateActiveAudioConfigForLocalSinkSource) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Set group as active EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Start streaming EXPECT_CALL(*mock_le_audio_sink_hal_client_, UpdateAudioConfigToHal(_)) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, UpdateAudioConfigToHal(_)) .Times(1); EXPECT_CALL(*mock_codec_manager_, UpdateActiveAudioConfig(_, _, _)) .Times(1) .WillOnce( [](const types::BidirectionalPair& stream_params, types::BidirectionalPair delays_ms, std::function update_receiver) { bluetooth::le_audio::offload_config unicast_cfg; if (delays_ms.sink != 0) { update_receiver( unicast_cfg, bluetooth::le_audio::types::kLeAudioDirectionSink); } if (delays_ms.source != 0) { update_receiver( unicast_cfg, bluetooth::le_audio::types::kLeAudioDirectionSource); } }); StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_codec_manager_); // Verify Data transfer on two peer sinks and two sources uint8_t cis_count_out = 2; uint8_t cis_count_in = 2; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); // Suspend LeAudioClient::Get()->GroupSuspend(group_id); SyncOnMainLoop(); } TEST_F(UnicastTest, UpdateActiveAudioConfigForLocalSource) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Set group as active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, UpdateAudioConfigToHal(_)) .Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, UpdateAudioConfigToHal(_)) .Times(0); EXPECT_CALL(*mock_codec_manager_, UpdateActiveAudioConfig(_, _, _)) .Times(1) .WillOnce( [](const types::BidirectionalPair& stream_params, types::BidirectionalPair delays_ms, std::function update_receiver) { bluetooth::le_audio::offload_config unicast_cfg; if (delays_ms.sink != 0) { update_receiver( unicast_cfg, bluetooth::le_audio::types::kLeAudioDirectionSink); } if (delays_ms.source != 0) { update_receiver( unicast_cfg, bluetooth::le_audio::types::kLeAudioDirectionSource); } }); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_codec_manager_); // Verify Data transfer on two peer sinks and no source uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); // Suspend LeAudioClient::Get()->GroupSuspend(group_id); SyncOnMainLoop(); } TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchNoReconfigure) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Start streaming with new metadata, there was no previous stream so start // with this new configuration auto initial_context = types::LeAudioContextType::NOTIFICATIONS; types::BidirectionalPair contexts = { .sink = types::AudioContexts(initial_context), .source = types::AudioContexts()}; EXPECT_CALL(mock_state_machine_, StartStream(_, initial_context, contexts, _)) .Times(1); StartStreaming(AUDIO_USAGE_NOTIFICATION, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Do a metadata content switch to ALERTS but stay on previous configuration EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop).Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start).Times(0); contexts = {.sink = types::AudioContexts(types::LeAudioContextType::ALERTS), .source = types::AudioContexts()}; EXPECT_CALL(mock_state_machine_, StartStream(_, initial_context, contexts, _)) .Times(1); UpdateLocalSourceMetadata(AUDIO_USAGE_ALARM, AUDIO_CONTENT_TYPE_UNKNOWN); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Do a metadata content switch to EMERGENCY but stay on previous // configuration EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop).Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start).Times(0); contexts = { .sink = types::AudioContexts(types::LeAudioContextType::EMERGENCYALARM), .source = types::AudioContexts()}; EXPECT_CALL(mock_state_machine_, StartStream(_, initial_context, contexts, _)) .Times(1); UpdateLocalSourceMetadata(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Do a metadata content switch to INSTRUCTIONAL but stay on previous // configuration EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop).Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start).Times(0); contexts = { .sink = types::AudioContexts(types::LeAudioContextType::INSTRUCTIONAL), .source = types::AudioContexts()}; EXPECT_CALL(mock_state_machine_, StartStream(_, initial_context, contexts, _)) .Times(1); UpdateLocalSourceMetadata(AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, AUDIO_CONTENT_TYPE_UNKNOWN); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } TEST_F(UnicastTest, TwoEarbudsStopConversational_StartStreamSonification) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Start streaming with CONVERSATIONAL, there was no previous stream so start // with this new configuration auto initial_context = types::LeAudioContextType::CONVERSATIONAL; types::BidirectionalPair contexts = { .sink = types::AudioContexts(initial_context), .source = types::AudioContexts(initial_context)}; EXPECT_CALL(mock_state_machine_, StartStream(_, initial_context, contexts, _)) .Times(1); StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Stop the stream but KEEP cis UP StopStreaming(group_id, true); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); types::BidirectionalPair reconfigure_contexts = { .sink = types::AudioContexts(types::LeAudioContextType::ALERTS), .source = types::AudioContexts()}; EXPECT_CALL(mock_state_machine_, StartStream(_, initial_context, reconfigure_contexts, _)) .Times(1); // Change context type but expect configuration to by as previous StartStreaming(AUDIO_USAGE_ALARM, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); // Stop the stream and drop CISes StopStreaming(group_id, true); SyncOnMainLoop(); // simulate suspend timeout passed, alarm executing fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); auto reconfigure_context = types::LeAudioContextType::ALERTS; EXPECT_CALL(mock_state_machine_, StartStream(_, reconfigure_context, reconfigure_contexts, _)) .Times(1); // Update metadata StartStreaming(AUDIO_USAGE_ALARM, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); } TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); constexpr int gmcs_ccid = 1; constexpr int gtbs_ccid = 2; // Start streaming MEDIA EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); types::BidirectionalPair> ccids = {.sink = {gmcs_ccid}, .source = {}}; EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Verify Data transfer on two peer sinks uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Stop StopStreaming(group_id); // simulate suspend timeout passed, alarm executing fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // SetInCall is used by GTBS - and only then we can expect CCID to be set. LeAudioClient::Get()->SetInCall(true); // Conversational is a bidirectional scenario so expect GTBS CCID // in the metadata for both directions. Can be called twice when one // direction resume after the other and metadata is updated. ccids = {.sink = {gtbs_ccid}, .source = {gtbs_ccid}}; EXPECT_CALL( mock_state_machine_, StartStream(_, types::LeAudioContextType::CONVERSATIONAL, _, ccids)) .Times(AtLeast(1)); StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Verify Data transfer on two peer sinks and one source cis_count_out = 2; cis_count_in = 2; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); LeAudioClient::Get()->SetInCall(false); // Stop StopStreaming(group_id, true); // Switch back to MEDIA ccids = {.sink = {gmcs_ccid}, .source = {}}; types::BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::MEDIA), .source = types::AudioContexts()}; EXPECT_CALL( mock_state_machine_, ConfigureStream(_, bluetooth::le_audio::types::LeAudioContextType::MEDIA, contexts, ccids)) .Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id, AUDIO_SOURCE_INVALID, true); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } TEST_F(UnicastTest, TwoEarbudsVoipStreamingVerifyMetadataUpdate) { uint8_t group_size = 2; int group_id = 2; /* * Scenario * 1. Configure stream for the VOIP * 2. Verify CONVERSATIONAL metadata and context is used. * 3. Resume LocalSink * 4. Make sure there is no change of the metadata and context */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); constexpr int gtbs_ccid = 2; LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); // VOIP not using Telecom API has no ccids. types::BidirectionalPair> ccids = {.sink = {}, .source = {}}; EXPECT_CALL( mock_state_machine_, StartStream(_, types::LeAudioContextType::CONVERSATIONAL, _, ccids)) .Times(AtLeast(1)); UpdateLocalSourceMetadata(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH); UpdateLocalSinkMetadata(AUDIO_SOURCE_MIC); LocalAudioSourceResume(); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Verify Data transfer are sending. The LocalSink is not yet resumed. uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 0); types::BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL), .source = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL)}; EXPECT_CALL(mock_state_machine_, StartStream(_, types::LeAudioContextType::CONVERSATIONAL, contexts, ccids)) .Times(AtLeast(1)); LocalAudioSinkResume(); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); } TEST_F(UnicastTest, TwoReconfigureAndVerifyEnableContextType) { uint8_t group_size = 2; int group_id = 2; /* Scenario * 1. Earbuds streaming MEDIA * 2. Reconfigure to VOIP * 3. Check if Metadata in Enable command are set to CONVERSATIONAL */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); constexpr int gmcs_ccid = 1; constexpr int gtbs_ccid = 2; // Start streaming MEDIA EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); // Update metadata on local audio sink UpdateLocalSinkMetadata(AUDIO_SOURCE_MIC); types::BidirectionalPair> ccids = {.sink = {gmcs_ccid}, .source = {}}; EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Verify Data transfer on two peer sinks uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Conversational is a bidirectional scenario so expect GTBS CCID // in the metadata for both directions. Can be called twice when one // direction resume after the other and metadata is updated. ccids = {.sink = {gtbs_ccid}, .source = {gtbs_ccid}}; types::BidirectionalPair conversiational_contexts = { .sink = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL), .source = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL)}; EXPECT_CALL( mock_state_machine_, ConfigureStream(_, types::LeAudioContextType::CONVERSATIONAL, _, _)) .Times(AtLeast(1)); // Update metadata and resume UpdateLocalSourceMetadata(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, true); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); EXPECT_CALL(mock_state_machine_, StartStream(_, types::LeAudioContextType::CONVERSATIONAL, conversiational_contexts, ccids)) .Times(AtLeast(1)); LeAudioClient::Get()->SetInCall(true); LocalAudioSourceResume(true); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); } TEST_F(UnicastTest, TwoEarbuds2ndLateConnect) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Expect one iso channel to be fed with data uint8_t cis_count_out = 1; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Second earbud connects during stream ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); cis_count_out = 2; cis_count_in = 0; /* The above will trigger reconfiguration. After that Audio Hal action * is needed to restart the stream */ LocalAudioSourceResume(); TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); } TEST_F(UnicastTest, LateStreamConnectBasedOnContextType) { uint8_t group_size = 2; int group_id = 2; /* Scenario * 1. Two devices A and B are connect. Device A has all available context * types, Device B has no available context types. * 2. Stream creation to Device A has been started * 3. Device B notified us with new available Context Types - while A is not * yet streaming * 4. Make sure AttachToStream was called for Device B */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); /* Simulate available context type being cleared */ InjectAvailableContextTypes(test_address1, 2, types::AudioContexts(0), types::AudioContexts(0)); // Block streaming state block_streaming_state_callback = true; UpdateLocalSourceMetadata(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC); LocalAudioSourceResume(false); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); InjectAvailableContextTypes(test_address1, 2, types::kLeAudioContextAllRemoteSinkOnly, types::AudioContexts(0), false); // Now simulate group is finally streaming auto group = streaming_groups.at(group_id); do_in_main_thread( FROM_HERE, base::BindOnce( [](int group_id, bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks, LeAudioDeviceGroup* group) { group->SetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); state_machine_callbacks->StatusReportCb( group_id, GroupStreamStatus::STREAMING); }, group_id, base::Unretained(this->state_machine_callbacks_), std::move(group))); SyncOnMainLoop(); /* verify AttachToStream was called while stream was not yet created. */ ASSERT_TRUE(attach_to_stream_scheduled); // Expect two iso channel to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); SyncOnMainLoop(); } TEST_F(UnicastTest, CheckDeviceIsNotAttachedToStreamWhenNotNeeded) { uint8_t group_size = 2; int group_id = 2; /* Scenario * 1. Two devices A and B are connect. Both Devices have all available * contexts types * 2. Stream creation to Device A and B has been started * 3. Device B notified us with new available Context Types - while Group is * not yet streaming * 4. Make sure AttachToStream was not called for Device B since it is taking * part in Stream creation already */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); // Block streaming state block_streaming_state_callback = true; UpdateLocalSourceMetadata(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC); LocalAudioSourceResume(false); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); InjectAvailableContextTypes(test_address1, 2, types::kLeAudioContextAllRemoteSinkOnly, types::AudioContexts(0), false); // Now simulate group is finally streaming auto group = streaming_groups.at(group_id); do_in_main_thread( FROM_HERE, base::BindOnce( [](int group_id, bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks, LeAudioDeviceGroup* group) { group->SetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); state_machine_callbacks->StatusReportCb( group_id, GroupStreamStatus::STREAMING); }, group_id, base::Unretained(this->state_machine_callbacks_), std::move(group))); SyncOnMainLoop(); /* verify AttachToStream was NOT called while stream was not yet created. */ ASSERT_FALSE(attach_to_stream_scheduled); // Expect two iso channel to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); SyncOnMainLoop(); } TEST_F(UnicastTest, ReconnectedDeviceNotAttachedToStreamBecauseOfNotAvailableContext) { uint8_t group_size = 2; int group_id = 2; /* Scenario * 1. Two devices A and B are streaming * 2. Device A Release ASE and removes all available context types * 3. Device B keeps streaming * 4. Device A disconnectes * 5. Device A reconnect and should not be attached to the stream */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Expect two iso channel to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); /* Get group and Device A */ ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); ASSERT_NE(group, nullptr); auto device = group->GetFirstDevice(); /* Simulate available context type being cleared */ InjectAvailableContextTypes(device->address_, device->conn_id_, types::AudioContexts(0), types::AudioContexts(0)); /* Simulate ASE releasing and CIS Disconnection */ for (auto& ase : device->ases_) { /* Releasing state */ if (!ase.active) { continue; } std::vector releasing_state = { ase.id, static_cast( types::AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING)}; InjectNotificationEvent(device->address_, device->conn_id_, ase.hdls.val_hdl, releasing_state); SyncOnMainLoop(); InjectCisDisconnected(group_id, ase.cis_conn_hdl); SyncOnMainLoop(); } cis_count_out = 1; cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); /* Device A will disconnect, and do not reconnect automatically */ ON_CALL(mock_gatt_interface_, Open(_, device->address_, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(Return()); /* Disconnect first device */ auto conn_id = device->conn_id_; InjectDisconnectedEvent(conn_id, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); /* For background connect, test needs to Inject Connected Event */ InjectConnectedEvent(device->address_, conn_id); SyncOnMainLoop(); /* Check single device is streaming */ cis_count_out = 1; cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); } TEST_F(UnicastTest, TwoEarbuds2ndReleaseAseRemoveAvailableContextAndBack) { uint8_t group_size = 2; int group_id = 2; /* Scenario * 1. Two devices A and B are streaming * 2. Device A Release ASE and removes all available context types * 3. Device B keeps streaming * 4. Device A sets available context types * 5. Device A should be attached to the stream */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Expect two iso channel to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); /* Get group and Device A */ ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); ASSERT_NE(group, nullptr); auto device = group->GetFirstDevice(); /* Simulate available context type being cleared */ InjectAvailableContextTypes(device->address_, device->conn_id_, types::AudioContexts(0), types::AudioContexts(0)); /* Simulate ASE releasing and CIS Disconnection */ for (auto& ase : device->ases_) { /* Releasing state */ if (!ase.active) { continue; } std::vector releasing_state = { ase.id, static_cast( types::AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING)}; InjectNotificationEvent(device->address_, device->conn_id_, ase.hdls.val_hdl, releasing_state); SyncOnMainLoop(); InjectCisDisconnected(group_id, ase.cis_conn_hdl); SyncOnMainLoop(); } cis_count_out = 1; cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); /* Bring back available context types */ InjectAvailableContextTypes(device->address_, device->conn_id_, types::kLeAudioContextAllTypes, types::kLeAudioContextAllTypes); /* Check both devices are streaming */ cis_count_out = 2; cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); } TEST_F(UnicastTest, StartStream_AvailableContextTypeNotifiedLater) { uint8_t group_size = 2; int group_id = 2; available_snk_context_types_ = 0; /* Scenario (Devices A and B called "Remote") * 1. Remote does supports all the context types, but has NO available * contexts at the beginning * 2. After connection Remote add Available context types * 3. Android start stream with MEDIA * 4. Make sure stream will be started */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Inject Supported and available context types auto sink_available_contexts = types::kLeAudioContextAllRemoteSinkOnly; auto source_available_contexts = types::kLeAudioContextAllRemoteSource; InjectAvailableContextTypes(test_address0, 1, sink_available_contexts, source_available_contexts); InjectAvailableContextTypes(test_address1, 2, sink_available_contexts, source_available_contexts); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::MEDIA), .source = types::AudioContexts()}; EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::MEDIA, contexts, _)) .Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Expect two iso channel to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); } TEST_F(UnicastTest, ModifyContextTypeOnDeviceA_WhileDeviceB_IsDisconnected) { uint8_t group_size = 2; int group_id = 2; /* Scenario (Device A and B called Remote) * 1. Remote set does supports all the context types and make them available * 2. Android start stream with MEDIA, verify it works. * 3. Android stops the stream * 4. Device B disconnects * 5. Device A removes Media from Available Contexts * 6. Android start stream with MEDIA, verify it will not be started */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::MEDIA), .source = types::AudioContexts()}; EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::MEDIA, contexts, _)) .Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Expect two iso channel to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Stop StopStreaming(group_id); // simulate suspend timeout passed, alarm executing fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Device B got disconnected and will not reconnect. ON_CALL(mock_gatt_interface_, Open(_, test_address1, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(Return()); InjectDisconnectedEvent(2, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); // Device A changes available context type // Inject Supported and available context types auto sink_supported_context = types::kLeAudioContextAllRemoteSinkOnly; sink_supported_context.unset(LeAudioContextType::MEDIA); sink_supported_context.set(LeAudioContextType::UNSPECIFIED); auto source_supported_context = types::kLeAudioContextAllRemoteSource; source_supported_context.set(LeAudioContextType::UNSPECIFIED); InjectSupportedContextTypes(test_address0, 1, sink_supported_context, source_supported_context); InjectAvailableContextTypes(test_address0, 1, sink_supported_context, source_supported_context); /* Android starts stream. */ EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(0); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id, AUDIO_SOURCE_INVALID, false, false); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); } TEST_F(UnicastTest, StartStreamToUnsupportedContextTypeUsingUnspecified) { uint8_t group_size = 2; int group_id = 2; /* Scenario (Devices A and B called "Remote") * 1. Remote does supports all the context types and make them available * 2. Remote removes SoundEffect from the supported and available context * types * 3. Android start stream with SoundEffects * 4. Make sure stream will be started with Unspecified context type */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Inject Supported and available context types auto sink_supported_context = types::kLeAudioContextAllRemoteSinkOnly; sink_supported_context.unset(LeAudioContextType::SOUNDEFFECTS); sink_supported_context.set(LeAudioContextType::UNSPECIFIED); auto source_supported_context = types::kLeAudioContextAllRemoteSource; source_supported_context.set(LeAudioContextType::UNSPECIFIED); InjectSupportedContextTypes(test_address0, 1, sink_supported_context, source_supported_context); InjectAvailableContextTypes(test_address0, 1, sink_supported_context, source_supported_context); InjectSupportedContextTypes(test_address1, 2, sink_supported_context, source_supported_context); InjectAvailableContextTypes(test_address1, 2, sink_supported_context, source_supported_context); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::UNSPECIFIED), .source = types::AudioContexts(0)}; EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::SOUNDEFFECTS, contexts, _)) .Times(1); StartStreaming(AUDIO_USAGE_ASSISTANCE_SONIFICATION, AUDIO_CONTENT_TYPE_SONIFICATION, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Expect two iso channel to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); } TEST_F(UnicastTest, StartStreamToUnsupportedContextTypeUnspecifiedNotAvailable) { uint8_t group_size = 2; int group_id = 2; /* Scenario (Device A and B called Remote) * 1. Remote does supports all the context types and make them available * 2. Remote removes SoundEffect from the Available Context Types * 3. Remote also removes UNSPECIFIED from the Available Context Types. * 4. Android start stream with SoundEffects * 5. Make sure stream will be NOT be started */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Inject Supported and available context types auto sink_supported_context = types::kLeAudioContextAllRemoteSinkOnly; sink_supported_context.unset(LeAudioContextType::SOUNDEFFECTS); sink_supported_context.set(LeAudioContextType::UNSPECIFIED); auto source_supported_context = types::kLeAudioContextAllRemoteSource; source_supported_context.set(LeAudioContextType::UNSPECIFIED); InjectSupportedContextTypes(test_address0, 1, sink_supported_context, source_supported_context); InjectSupportedContextTypes(test_address1, 2, sink_supported_context, source_supported_context); auto sink_available_context = sink_supported_context; sink_available_context.unset(LeAudioContextType::UNSPECIFIED); auto source_available_context = source_supported_context; source_available_context.unset(LeAudioContextType::UNSPECIFIED); InjectAvailableContextTypes(test_address0, 1, sink_available_context, source_available_context); InjectAvailableContextTypes(test_address1, 2, sink_available_context, source_available_context); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::UNSPECIFIED), .source = types::AudioContexts()}; EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::SOUNDEFFECTS, contexts, _)) .Times(0); StartStreaming(AUDIO_USAGE_ASSISTANCE_SONIFICATION, AUDIO_CONTENT_TYPE_SONIFICATION, group_id, AUDIO_SOURCE_INVALID, false, false); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); } TEST_F(UnicastTest, StartStreamToSupportedContextTypeThenMixUnavailable) { uint8_t group_size = 2; int group_id = 2; /* Scenario (Device A and B called Remote) * 1. Remote set does supports all the context types and make them available * 2. Abdriud start stream with MEDIA, verify it works. * 3. Stream becomes to be mixed with Soundeffect and Media - verify metadata * update * 4. Android Stop stream. * 5. Remote removes SoundEffect from the supported and available context * types * 6. Android start stream with MEDIA, verify it works. * 7. Stream becomes to be mixed with Soundeffect and Media * 8. Make sure metadata updated does not contain unavailable context * note: eventually, Audio framework should not give us unwanted context * types */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::MEDIA), .source = types::AudioContexts()}; EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::MEDIA, contexts, _)) .Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); // Expect two iso channel to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); contexts.sink = types::AudioContexts(types::LeAudioContextType::MEDIA | types::LeAudioContextType::SOUNDEFFECTS); EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::MEDIA, contexts, _)) .Times(1); /* Simulate metadata update, expect upadate , metadata */ std::vector tracks = { {{AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, 0}, {AUDIO_USAGE_ASSISTANCE_SONIFICATION, AUDIO_CONTENT_TYPE_SONIFICATION, 0}}}; UpdateLocalSourceMetadata(tracks); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); /* Stop stream */ StopStreaming(group_id); // simulate suspend timeout passed, alarm executing fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Inject Supported and available context types auto sink_supported_context = types::kLeAudioContextAllRemoteSinkOnly; sink_supported_context.unset(LeAudioContextType::SOUNDEFFECTS); sink_supported_context.set(LeAudioContextType::UNSPECIFIED); auto source_supported_context = types::kLeAudioContextAllRemoteSource; source_supported_context.set(LeAudioContextType::UNSPECIFIED); InjectSupportedContextTypes(test_address0, 1, sink_supported_context, source_supported_context); InjectAvailableContextTypes(test_address0, 1, sink_supported_context, source_supported_context); InjectSupportedContextTypes(test_address1, 2, sink_supported_context, source_supported_context); InjectAvailableContextTypes(test_address1, 2, sink_supported_context, source_supported_context); // Verify cache has been removed due to available context change ASSERT_EQ(nullptr, group->GetCachedConfiguration(types::LeAudioContextType::MEDIA)); /* Start Media again */ contexts.sink = types::AudioContexts(types::LeAudioContextType::MEDIA); EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::MEDIA, contexts, _)) .Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); // Verify cache has been rebuilt ASSERT_NE(nullptr, group->GetCachedConfiguration(types::LeAudioContextType::MEDIA)); ASSERT_TRUE(group->GetCachedConfiguration(types::LeAudioContextType::MEDIA) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Expect two iso channel to be fed with data TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); /* Update metadata, and do not expect new context type*/ EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::MEDIA, contexts, _)) .Times(1); /* Simulate metadata update */ UpdateLocalSourceMetadata(tracks); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); } TEST_F(UnicastTest, TwoEarbuds2ndDisconnected) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); ASSERT_EQ(2, group->NumOfConnected()); // Expect two iso channels to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Disconnect one device and expect the group to keep on streaming EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0); auto device = group->GetFirstDevice(); for (auto& ase : device->ases_) { InjectCisDisconnected(group_id, ase.cis_conn_hdl); } /* Disconnect ACL and do not reconnect. */ ON_CALL(mock_gatt_interface_, Open(_, device->address_, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(Return()); EXPECT_CALL(mock_gatt_interface_, Open(_, device->address_, BTM_BLE_DIRECT_CONNECTION, false)) .Times(1); // Record NumOfConnected when groupStateMachine_ gets notified about the // disconnection int num_of_connected = 0; ON_CALL(mock_state_machine_, ProcessHciNotifAclDisconnected(_, _)) .WillByDefault([&num_of_connected](LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) { num_of_connected = group->NumOfConnected(); }); auto conn_id = device->conn_id_; InjectDisconnectedEvent(device->conn_id_, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); // Make sure the state machine knows about the disconnected device ASSERT_EQ(1, num_of_connected); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Expect one channel ISO Data to be sent cis_count_out = 1; cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); InjectConnectedEvent(device->address_, conn_id); SyncOnMainLoop(); // Expect two iso channels to be fed with data cis_count_out = 2; cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); } TEST_F(UnicastTest, TwoEarbudsStreamingProfileDisconnect) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Expect two iso channels to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1); /* Do not inject OPEN_EVENT by default */ ON_CALL(mock_gatt_interface_, Open(_, _, _, _)) .WillByDefault(DoAll(Return())); ON_CALL(mock_gatt_interface_, Close(_)).WillByDefault(DoAll(Return())); ON_CALL(mock_btm_interface_, AclDisconnectFromHandle(_, _)) .WillByDefault(DoAll(Return())); DisconnectLeAudioNoDisconnectedEvtExpected(test_address0, 1); DisconnectLeAudioNoDisconnectedEvtExpected(test_address1, 2); EXPECT_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(2); InjectDisconnectedEvent(1); InjectDisconnectedEvent(2); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTest, TwoEarbudsStreamingProfileDisconnectStreamStopTimeout) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Expect two iso channels to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Expect StopStream to be called before Close or ACL Disconnect is called. ON_CALL(mock_state_machine_, StopStream(_)) .WillByDefault([](LeAudioDeviceGroup* group) { /* Stub the process of stopping stream, just set the target state. * this simulates issue with stopping the stream */ group->SetTargetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); }); EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(2); EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(0); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(_, _)).Times(0); do_in_main_thread( FROM_HERE, base::Bind(&LeAudioClient::Disconnect, base::Unretained(LeAudioClient::Get()), test_address0)); do_in_main_thread( FROM_HERE, base::Bind(&LeAudioClient::Disconnect, base::Unretained(LeAudioClient::Get()), test_address1)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_btm_interface_); Mock::VerifyAndClearExpectations(&mock_state_machine_); /* Now stream is trying to be stopped and devices are about to be * disconnected. Simulate stop stream failure and timeout fired. Make sure * code will not try to do recovery connect */ ON_CALL(mock_btm_interface_, AclDisconnectFromHandle(_, _)) .WillByDefault(DoAll(Return())); EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(0); EXPECT_CALL(mock_btm_interface_, AclDisconnectFromHandle(_, _)).Times(2); ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); ASSERT_TRUE(group != nullptr); ASSERT_TRUE(group->NumOfConnected() > 0); state_machine_callbacks_->OnStateTransitionTimeout(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btm_interface_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); auto device = group->GetFirstDevice(); ASSERT_TRUE(device != nullptr); ASSERT_NE(device->GetConnectionState(), DeviceConnectState::DISCONNECTING_AND_RECOVER); device = group->GetNextDevice(device); ASSERT_TRUE(device != nullptr); ASSERT_NE(device->GetConnectionState(), DeviceConnectState::DISCONNECTING_AND_RECOVER); } TEST_F(UnicastTest, EarbudsWithStereoSinkMonoSourceSupporting32kHz) { const RawAddress test_address0 = GetTestAddress(0); int group_id = 0; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationFrontLeft, default_channel_cnt, default_channel_cnt, 0x0024, /* source sample freq 32/16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt /*add_ascs_cnt*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); // LeAudioCodecConfiguration received_af_sink_config; const LeAudioCodecConfiguration expected_af_sink_config = { .num_channels = 2, .sample_rate = bluetooth::audio::le_audio::kSampleRate32000, .bits_per_sample = bluetooth::audio::le_audio::kBitsPerSample16, .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us, }; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(expected_af_sink_config, _, _)) .Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); } TEST_F(UnicastTest, TwoEarbudsWithSourceSupporting32kHz) { const RawAddress test_address0 = GetTestAddress(0); int group_id = 0; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0024, /* source sample freq 32/16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ default_ase_cnt /*add_ascs_cnt*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); ConnectLeAudio(test_address0); // LeAudioCodecConfiguration received_af_sink_config; const LeAudioCodecConfiguration expected_af_sink_config = { .num_channels = 2, .sample_rate = bluetooth::audio::le_audio::kSampleRate32000, .bits_per_sample = bluetooth::audio::le_audio::kBitsPerSample16, .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us, }; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(expected_af_sink_config, _, _)) .Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); } TEST_F(UnicastTest, MicrophoneAttachToCurrentMediaScenario) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0024, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); // When the local audio source resumes we have no knowledge of recording EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::MEDIA, _, _)) .Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id, AUDIO_SOURCE_INVALID); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Verify Data transfer on one audio source cis uint8_t cis_count_out = 1; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // When the local audio sink resumes we should reconfigure EXPECT_CALL( mock_state_machine_, ConfigureStream(_, bluetooth::le_audio::types::LeAudioContextType::LIVE, _, _)) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, ReconfigurationComplete()) .Times(1); // Update metadata on local audio sink UpdateLocalSinkMetadata(AUDIO_SOURCE_MIC); // Resume on local audio sink ASSERT_NE(unicast_sink_hal_cb_, nullptr); do_in_main_thread( FROM_HERE, base::BindOnce( [](LeAudioSinkAudioHalClient::Callbacks* cb) { cb->OnAudioResume(); }, unicast_sink_hal_cb_)); /* The above will trigger reconfiguration. After that Audio Hal action * is needed to restart the stream */ SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); LocalAudioSourceResume(); do_in_main_thread( FROM_HERE, base::BindOnce( [](LeAudioSinkAudioHalClient::Callbacks* cb) { cb->OnAudioResume(); }, unicast_sink_hal_cb_)); SyncOnMainLoop(); // Verify Data transfer on one audio source and sink cis cis_count_out = 1; cis_count_in = 1; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 60); // Stop StopStreaming(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Release EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); do_in_main_thread( FROM_HERE, base::BindOnce( [](LeAudioClient* client) { client->GroupSetActive(bluetooth::groups::kGroupUnknown); }, LeAudioClient::Get())); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } /* When a certain context is unavailable and not supported we should stream * as UNSPECIFIED for the backwards compatibility. * Since UNSPECIFIED is available, put the UNSPECIFIED into the metadata instead * What we can do now is to keep streaming (and reconfigure if needed for the * use case). */ TEST_F(UnicastTest, UpdateNotSupportedContextTypeUnspecifiedAvailable) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; available_snk_context_types_ = (types::LeAudioContextType::RINGTONE | types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::UNSPECIFIED | types::LeAudioContextType::MEDIA) .value(); supported_snk_context_types_ = available_snk_context_types_; available_src_context_types_ = available_snk_context_types_; supported_src_context_types_ = available_src_context_types_; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming uint8_t cis_count_out = 1; uint8_t cis_count_in = 0; LeAudioClient::Get()->SetInCall(true); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); LocalAudioSourceResume(); LocalAudioSinkResume(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); LeAudioClient::Get()->SetInCall(false); LocalAudioSinkSuspend(); /* We should use GAME configuration, but do not send the GAME context type, as * it is not available on the remote device. */ EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0); types::BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::UNSPECIFIED), .source = types::AudioContexts(types::LeAudioContextType::UNSPECIFIED)}; EXPECT_CALL(mock_state_machine_, StartStream(_, types::LeAudioContextType::GAME, contexts, _)) .Times(1); UpdateLocalSourceMetadata(AUDIO_USAGE_GAME, AUDIO_CONTENT_TYPE_UNKNOWN, false); SyncOnMainLoop(); } /* Some bidirectional scenarios are triggered by the local sink, local source * metadata or the In Call preference callback call. Since each call invalidates * previous context source, make sure that getting all of these in a sequence, * always results with one bidirectional context, so that the remote device * is not confused about our intentions. */ TEST_F(UnicastTest, UpdateMultipleBidirContextTypes) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; available_snk_context_types_ = (types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::GAME | types::LeAudioContextType::LIVE) .value(); supported_snk_context_types_ = available_snk_context_types_ | types::AudioContexts(types::LeAudioContextType::UNSPECIFIED).value(); available_src_context_types_ = available_snk_context_types_; supported_src_context_types_ = available_src_context_types_ | types::AudioContexts(types::LeAudioContextType::UNSPECIFIED).value(); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationAnyLeft, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0024, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); // When the local audio sink resumes expect only LIVE context types::BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::LIVE), .source = types::AudioContexts(types::LeAudioContextType::LIVE)}; EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::LIVE, contexts, _)) .Times(1); // 1) Start the recording. Sink resume will trigger the reconfiguration // --------------------------------------------------------------------- ASSERT_NE(nullptr, unicast_sink_hal_cb_); UpdateLocalSinkMetadata(AUDIO_SOURCE_MIC); LocalAudioSinkResume(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); /* After the reconfiguration the local Audio Sink HAL has to resume again */ LocalAudioSourceResume(); LocalAudioSinkResume(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); // Verify Data transfer on one audio source and sink cis uint8_t cis_count_out = 1; uint8_t cis_count_in = 1; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); // Stop StopStreaming(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // 2) Now set in call preference to get CONVERSATIONAL into the mix // ----------------------------------------------------------------- LeAudioClient::Get()->SetInCall(true); // Verify that we only got CONVERSATIONAL context and no LIVE contexts = { .sink = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL), .source = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL)}; EXPECT_CALL( mock_state_machine_, StartStream( _, bluetooth::le_audio::types::LeAudioContextType::CONVERSATIONAL, contexts, _)) .Times(1); // Start with ringtone on local source ASSERT_NE(nullptr, unicast_sink_hal_cb_); StartStreaming(AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); // Resume both directions LocalAudioSourceResume(); LocalAudioSinkResume(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); // Verify Data transfer on one audio source cis cis_count_out = 1; cis_count_in = 1; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); // 3) Disable call so we could go to GAME // --------------------------------------- LeAudioClient::Get()->SetInCall(false); /* Start the game on local source - expect no previous sink (LIVE) metadata */ EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0); contexts = {.sink = types::AudioContexts(types::LeAudioContextType::GAME), .source = types::AudioContexts(types::LeAudioContextType::GAME)}; EXPECT_CALL(mock_state_machine_, StartStream(_, types::LeAudioContextType::GAME, contexts, _)) .Times(1); UpdateLocalSourceMetadata(AUDIO_USAGE_GAME, AUDIO_CONTENT_TYPE_UNKNOWN, false); /* If the above triggers reconfiguration, Audio Hal action is needed to * restart the stream. */ LocalAudioSourceResume(); LocalAudioSinkResume(); Mock::VerifyAndClearExpectations(&mock_state_machine_); // 4) Stop streaming // ------------------ StopStreaming(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Release EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); do_in_main_thread( FROM_HERE, base::BindOnce( [](LeAudioClient* client) { client->GroupSetActive(bluetooth::groups::kGroupUnknown); }, LeAudioClient::Get())); SyncOnMainLoop(); } TEST_F(UnicastTest, UpdateDisableLocalAudioSinkOnGame) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; available_snk_context_types_ = (types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::GAME | types::LeAudioContextType::LIVE) .value(); supported_snk_context_types_ = available_snk_context_types_ | types::AudioContexts(types::LeAudioContextType::UNSPECIFIED).value(); available_src_context_types_ = available_snk_context_types_; supported_src_context_types_ = available_src_context_types_ | types::AudioContexts(types::LeAudioContextType::UNSPECIFIED).value(); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationAnyLeft, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0024, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); // Start GAME stream types::BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::GAME), .source = types::AudioContexts(types::LeAudioContextType::GAME)}; EXPECT_CALL( mock_state_machine_, StartStream(_, bluetooth::le_audio::types::LeAudioContextType::GAME, contexts, _)) .Times(1); // 1) Start the recording. Sink resume will trigger the reconfiguration // --------------------------------------------------------------------- StartStreaming(AUDIO_USAGE_GAME, AUDIO_CONTENT_TYPE_MUSIC, group_id, AUDIO_SOURCE_MIC); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); // Verify Data transfer on one audio source and sink cis uint8_t cis_count_out = 1; uint8_t cis_count_in = 1; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); SyncOnMainLoop(); // 2) Now Lets suspend MIC and do not expect reconfiguration // ----------------------------------------------------------------- EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0); LocalAudioSinkSuspend(); // simulate suspend timeout passed, alarm executing fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); } /* Start music when in a call, end the call, continue with music only */ TEST_F(UnicastTest, MusicDuringCallContextTypes) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; available_snk_context_types_ = (types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::RINGTONE | types::LeAudioContextType::GAME | types::LeAudioContextType::MEDIA | types::LeAudioContextType::LIVE) .value(); supported_snk_context_types_ = available_snk_context_types_ | types::AudioContexts(types::LeAudioContextType::UNSPECIFIED).value(); available_src_context_types_ = available_snk_context_types_; supported_src_context_types_ = available_src_context_types_ | types::AudioContexts(types::LeAudioContextType::UNSPECIFIED).value(); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationAnyLeft, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0024, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); // 1) Start with the call first // ----------------------------- // CONVERSATIONAL is from In Call preference, and RINGTONE is from metadata LeAudioClient::Get()->SetInCall(true); types::BidirectionalPair contexts = { .sink = types::AudioContexts(types::LeAudioContextType::RINGTONE | types::LeAudioContextType::CONVERSATIONAL), .source = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL)}; EXPECT_CALL( mock_state_machine_, StartStream( _, bluetooth::le_audio::types::LeAudioContextType::CONVERSATIONAL, contexts, _)) .Times(1); StartStreaming(AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); LocalAudioSourceResume(); LocalAudioSinkResume(); // Verify Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); // Verify Data transfer uint8_t cis_count_out = 1; uint8_t cis_count_in = 1; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); // 2) Start MEDIA during the call, expect MEDIA only on the remote sink contexts = { .sink = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::MEDIA), .source = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL)}; EXPECT_CALL( mock_state_machine_, StartStream( _, bluetooth::le_audio::types::LeAudioContextType::CONVERSATIONAL, contexts, _)) .Times(1); UpdateLocalSourceMetadata(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, false); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); // 2) Disable In Call preference but do not suspend the local sink // We should stay in CONVERSATIONAL until the local sink suspends // --------------------------------------- LeAudioClient::Get()->SetInCall(false); EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0); contexts = { .sink = types::AudioContexts(types::LeAudioContextType::MEDIA | types::LeAudioContextType::CONVERSATIONAL), .source = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL)}; EXPECT_CALL( mock_state_machine_, StartStream(_, types::LeAudioContextType::CONVERSATIONAL, contexts, _)) .Times(1); UpdateLocalSourceMetadata(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, /*reconfigure=*/false); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); // 3) Disable call so we could go back to MEDIA // --------------------------------------- // Suspend should stop the stream EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1); LocalAudioSourceSuspend(); LocalAudioSinkSuspend(); // simulate suspend timeout passed, alarm executing fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); Mock::VerifyAndClearExpectations(&mock_state_machine_); // Restart the stream with MEDIA contexts = {.sink = types::AudioContexts(types::LeAudioContextType::MEDIA), .source = types::AudioContexts()}; EXPECT_CALL(mock_state_machine_, StartStream(_, types::LeAudioContextType::MEDIA, contexts, _)) .Times(1); UpdateLocalSourceMetadata(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, /*reconfigure=*/false); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); /* The source needs to resume to reconfigure to MEDIA */ LocalAudioSourceResume(/*expect_confirm=*/false); LocalAudioSourceResume(/*expect_confirm=*/true); Mock::VerifyAndClearExpectations(&mock_state_machine_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); // 4) Stop streaming // ------------------ StopStreaming(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Release EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); do_in_main_thread( FROM_HERE, base::BindOnce( [](LeAudioClient* client) { client->GroupSetActive(bluetooth::groups::kGroupUnknown); }, LeAudioClient::Get())); SyncOnMainLoop(); } /* When a certain context is unavailable but supported we should not stream that * context - either stop the stream or eliminate this strim from the mix * This could be na IOP issue so continue streaming (and reconfigure if needed * for that use case). * Since the unavailable context is supported, do not put this context into * the metadata, and do not replace it with UNSPECIFIED. */ TEST_F(UnicastTest, StartNotAvailableSupportedContextType) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; // EMERGENCYALARM is not available, but supported available_snk_context_types_ = (types::LeAudioContextType::RINGTONE | types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::UNSPECIFIED | types::LeAudioContextType::MEDIA) .value(); available_src_context_types_ = available_snk_context_types_; supported_snk_context_types_ = types::kLeAudioContextAllTypes.value(); supported_src_context_types_ = (types::kLeAudioContextAllRemoteSource | types::LeAudioContextType::UNSPECIFIED) .value(); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Expect configuring to (or staying with) the right configuration but the // metadata should not get the EMERGENCYALARM context, nor the UNSPECIFIED // Since the initial config is UNSPECIFIED, then even for sonification events // we should reconfigure to less generic EMERGENCYALARM scenario EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); types::BidirectionalPair metadata = { .sink = types::AudioContexts(), .source = types::AudioContexts()}; EXPECT_CALL( mock_state_machine_, StartStream(_, types::LeAudioContextType::EMERGENCYALARM, metadata, _)) .Times(0); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN, group_id, AUDIO_SOURCE_INVALID, false, false); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } /* When a certain context is unavailable and not supported and the UNSPECIFIED * is not available we should stop the stream. * For now, stream will not be started in such a case. * In future we should be able to eliminate this context from the track mix. */ TEST_F(UnicastTest, StartNotAvailableUnsupportedContextTypeUnspecifiedUnavail) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; // EMERGENCYALARM is not available, nor supported available_snk_context_types_ = (types::LeAudioContextType::RINGTONE | types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::MEDIA) .value(); available_src_context_types_ = available_snk_context_types_; supported_snk_context_types_ = (available_snk_context_types_ | types::LeAudioContextType::UNSPECIFIED) .value(); supported_src_context_types_ = (available_src_context_types_ | types::LeAudioContextType::UNSPECIFIED) .value(); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Expect configuring to the default config since the EMERGENCYALARM is // not on the list of supported contexts and UNSPECIFIED should not be // in the metadata as it is unavailable. EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); types::BidirectionalPair metadata = { .sink = types::AudioContexts(), .source = types::AudioContexts()}; EXPECT_CALL( mock_state_machine_, StartStream(_, types::LeAudioContextType::EMERGENCYALARM, metadata, _)) .Times(0); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN, group_id, AUDIO_SOURCE_INVALID, false, false); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } /* This test verifies if we use UNSPCIFIED context when another context is * unavailable and not supported but UNSPCIFIED is in available audio contexts. */ TEST_F(UnicastTest, StartNotAvailableUnsupportedContextTypeUnspecifiedAvail) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; // EMERGENCYALARM is not available, nor supported available_snk_context_types_ = (types::LeAudioContextType::RINGTONE | types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::UNSPECIFIED | types::LeAudioContextType::MEDIA) .value(); available_src_context_types_ = available_snk_context_types_; supported_snk_context_types_ = available_snk_context_types_; supported_src_context_types_ = available_src_context_types_; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Expect configuring to the default config since the EMERGENCYALARM is // not on the list of supported contexts and UNSPECIFIED will be used in // the metadata. EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); types::BidirectionalPair metadata = { .sink = types::AudioContexts(types::LeAudioContextType::UNSPECIFIED), .source = types::AudioContexts()}; EXPECT_CALL( mock_state_machine_, StartStream(_, types::LeAudioContextType::EMERGENCYALARM, metadata, _)) .Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Verify Data transfer on one audio source cis uint8_t cis_count_out = 1; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); } TEST_F(UnicastTest, NotifyAboutGroupTunrnedIdleEnabled) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; osi_property_set_bool(kNotifyUpperLayerAboutGroupBeingInIdleDuringCall, true); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming uint8_t cis_count_out = 1; uint8_t cis_count_in = 0; LeAudioClient::Get()->SetInCall(true); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Release /* To be called twice * 1. GroupStatus::INACTIVE * 2. GroupStatus::TURNED_IDLE_DURING_CALL */ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(group_id, _)) .Times(2); EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); do_in_main_thread( FROM_HERE, base::BindOnce( [](LeAudioClient* client) { client->GroupSetActive(bluetooth::groups::kGroupUnknown); }, LeAudioClient::Get())); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); LeAudioClient::Get()->SetInCall(false); osi_property_set_bool(kNotifyUpperLayerAboutGroupBeingInIdleDuringCall, false); } TEST_F(UnicastTest, NotifyAboutGroupTunrnedIdleDisabled) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming uint8_t cis_count_out = 1; uint8_t cis_count_in = 0; LeAudioClient::Get()->SetInCall(true); // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE, AUDIO_CONTENT_TYPE_UNKNOWN, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Release /* To be called once only * 1. GroupStatus::INACTIVE */ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(group_id, _)) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); do_in_main_thread( FROM_HERE, base::BindOnce( [](LeAudioClient* client) { client->GroupSetActive(bluetooth::groups::kGroupUnknown); }, LeAudioClient::Get())); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); LeAudioClient::Get()->SetInCall(false); } TEST_F(UnicastTest, HandleDatabaseOutOfSync) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); /* Simulate DATABASE OUT OF SYNC */ ccc_stored_byte_val_ = 0x01; gatt_read_ctp_ccc_status_ = GATT_DATABASE_OUT_OF_SYNC; EXPECT_CALL(mock_gatt_queue_, WriteDescriptor(_, _, _, _, _, _)).Times(0); ON_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _)) .WillByDefault(Return()); EXPECT_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _)); InjectConnectedEvent(test_address0, 1); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_gatt_queue_); } TEST_F(UnicastTest, TestRemoteDeviceKeepCccValues) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_gatt_queue_); /* Simulate remote cache is good */ ccc_stored_byte_val_ = 0x01; EXPECT_CALL(mock_gatt_queue_, WriteDescriptor(_, _, _, _, _, _)).Times(0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); InjectConnectedEvent(test_address0, 1); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_queue_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, TestRemoteDeviceForgetsCccValues) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) .Times(1); InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_gatt_queue_); /* Simulate remote cache is broken */ ccc_stored_byte_val_ = 0; EXPECT_CALL(mock_gatt_queue_, WriteDescriptor(_, _, _, _, _, _)) .Times(AtLeast(1)); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); InjectConnectedEvent(test_address0, 1); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_queue_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, SpeakerStreamingTimeout) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming uint8_t cis_count_out = 1; uint8_t cis_count_in = 0; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); // Do not accept direct connect, but expect it to arrive. ON_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(Return()); state_machine_callbacks_->OnStateTransitionTimeout(group_id); SyncOnMainLoop(); /* No assigned cises should remain when transition remains in IDLE state */ ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); ASSERT_EQ(0, static_cast(group->cig.cises.size())); } TEST_F(UnicastTest, AddMemberToAllowListWhenOneDeviceConnected) { uint8_t group_size = 2; int group_id = 2; int conn_id_dev_0 = 1; int conn_id_dev_1 = 2; /*Scenario to test * 1. Connect Device A and disconnect * 2. Connect Device B * 3. verify Device B is in the allow list with direct connect. */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, conn_id_dev_0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); SyncOnMainLoop(); EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); InjectDisconnectedEvent(conn_id_dev_0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); /* Do not connect first device but expect Open will arrive.*/ EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, false)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); ON_CALL(mock_gatt_interface_, Open(_, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(Return()); ConnectCsisDevice(test_address1, conn_id_dev_1, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTest, ResetToDefaultReconnectionMode) { uint8_t group_size = 2; int group_id = 2; int conn_id_dev_0 = 1; int conn_id_dev_1 = 2; /*Scenario to test * 1. Connect Device A and disconnect * 2. Connect Device B * 3. verify Device B is in the allow list. * 4. Disconnect B device * 5, Verify A and B device are back in targeted announcement reconnection * mode */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, conn_id_dev_0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); SyncOnMainLoop(); EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); InjectDisconnectedEvent(conn_id_dev_0); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); /* Verify first earbud will start doing direct connect first */ EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, false)) .Times(1); ON_CALL(mock_gatt_interface_, Open(_, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(Return()); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); ConnectCsisDevice(test_address1, conn_id_dev_1, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); // Disconnect Device B, expect default reconnection mode for Device A. EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, false)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address1, false)) .Times(1); InjectDisconnectedEvent(conn_id_dev_1, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); } TEST_F(UnicastTest, DisconnectAclBeforeGettingReadResponses) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); /* Due to imitated problems with GATT read operations (status != GATT_SUCCESS) * a CONNECTED state should not be propagated together with audio location */ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(0); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnSinkAudioLocationAvailable( test_address0, codec_spec_conf::kLeAudioLocationFrontLeft)) .Times(0); // First earbud initial connection SetSampleDatabaseEarbudsValid(1 /* conn_id */, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ true, /*add_ascs*/ group_size, 1 /* rank */, GATT_INTERNAL_ERROR); groups[test_address0] = group_id; // by default indicate link as encrypted ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) .WillByDefault(DoAll(Return(true))); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_DIRECT_CONNECTION, _)) .Times(1); /* When connected it will got to TA */ EXPECT_CALL(mock_gatt_interface_, CancelOpen(gatt_if, test_address0, _)) .Times(1); EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS, _)) .Times(1); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::Connect, base::Unretained(LeAudioClient::Get()), test_address0)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_btm_interface_); Mock::VerifyAndClearExpectations(&mock_gatt_interface_); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); InjectGroupDeviceAdded(test_address0, group_id); // Second earbud initial connection EXPECT_CALL(mock_audio_hal_client_callbacks_, OnSinkAudioLocationAvailable( test_address1, codec_spec_conf::kLeAudioLocationFrontRight)) .Times(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); Mock::VerifyAndClearExpectations(&mock_btif_storage_); /* for Target announcements AutoConnect is always there, until * device is removed */ EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, false)) .Times(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, false)) .Times(0); // Verify grouping information std::vector devs = LeAudioClient::Get()->GetGroupDevices(group_id); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); /* Remove default action on the direct connect */ ON_CALL(mock_gatt_interface_, Open(_, _, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(Return()); /* Initiate disconnection with timeout reason, the possible reason why GATT * read attribute operation may be not handled */ InjectDisconnectedEvent(1, GATT_CONN_TIMEOUT); SyncOnMainLoop(); /* After reconnection a sink audio location callback with connection state * should be propagated. */ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnSinkAudioLocationAvailable( test_address0, codec_spec_conf::kLeAudioLocationFrontLeft)) .Times(1); /* Prepare valid GATT status responsing attributes */ SetSampleDatabaseEarbudsValid(1 /* conn_id */, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ true, /*add_csis*/ true, /*add_cas*/ true, /*add_pacs*/ true, /*add_ascs*/ group_size, 1 /* rank */); /* For background connect, test needs to Inject Connected Event */ InjectConnectedEvent(test_address0, 1); SyncOnMainLoop(); } TEST_F_WITH_FLAGS(UnicastTest, GroupStreamStatus, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_callback_on_group_stream_status))) { int group_id = bluetooth::groups::kGroupUnknown; InSequence s; /* Check if all states are properly notified */ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::IDLE); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::STREAMING)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::RELEASING); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::STREAMING)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::SUSPENDING); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::STREAMING)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::SUSPENDED); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::STREAMING)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(1); state_machine_callbacks_->StatusReportCb( group_id, GroupStreamStatus::CONFIGURED_AUTONOMOUS); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::STREAMING)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(1); state_machine_callbacks_->StatusReportCb( group_id, GroupStreamStatus::CONFIGURED_BY_USER); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::STREAMING)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::DESTROYED); /* Check if there are no resending of the same state */ state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::RELEASING); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::SUSPENDING); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::SUSPENDED); state_machine_callbacks_->StatusReportCb( group_id, GroupStreamStatus::CONFIGURED_AUTONOMOUS); state_machine_callbacks_->StatusReportCb( group_id, GroupStreamStatus::CONFIGURED_BY_USER); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::IDLE); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::STREAMING)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING); } TEST_F_WITH_FLAGS(UnicastTest, GroupStreamStatusManyGroups, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_callback_on_group_stream_status))) { uint8_t group_size = 2; int group_id_1 = 1; int group_id_2 = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(_)) .WillByDefault(Return(group_size)); // First group - First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id_1, 1 /* rank*/); // First group - Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id_1, 2 /* rank*/, true /*connect_through_csis*/); // Second group - First earbud const RawAddress test_address2 = GetTestAddress(2); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address2, true)) .Times(1); ConnectCsisDevice(test_address2, 3 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id_2, 1 /* rank*/); // Second group - Second earbud const RawAddress test_address3 = GetTestAddress(3); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address3, true)) .Times(1); ConnectCsisDevice(test_address3, 4 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id_2, 2 /* rank*/, true /*connect_through_csis*/); InSequence s; // Group 1 IDLE EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id_1, GroupStreamStatus::IDLE)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id_1, GroupStreamStatus::IDLE); // Group 2 IDLE EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id_2, GroupStreamStatus::IDLE)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id_2, GroupStreamStatus::IDLE); // Group 1 active and start streaming EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id_1, GroupStreamStatus::STREAMING)) .Times(1); LeAudioClient::Get()->GroupSetActive(group_id_1); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id_1); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Group 2 active EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id_1, GroupStreamStatus::IDLE)) .Times(1); LeAudioClient::Get()->GroupSetActive(group_id_2); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Group 2 start streaming EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id_2, GroupStreamStatus::STREAMING)) .Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id_2); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F_WITH_FLAGS(UnicastTest, GroupStreamStatusResendAfterRemove, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_callback_on_group_stream_status))) { uint8_t group_size = 2; int group_id = 1; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(_)) .WillByDefault(Return(group_size)); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); Mock::VerifyAndClearExpectations(&mock_btif_storage_); InSequence s; // Activate group, start streaming and immediately stop EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::STREAMING)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // No resend EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(0); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::IDLE); // No resend after removing only one device /* * StopStream will put calls on main_loop so to keep the correct order * of operations and to avoid races we put the test command on main_loop as * well. */ do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioClient* client, const RawAddress& test_address0) { client->RemoveDevice(test_address0); }, LeAudioClient::Get(), test_address0)); SyncOnMainLoop(); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(0); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::IDLE); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Resend after removing last device /* * StopStream will put calls on main_loop so to keep the correct order * of operations and to avoid races we put the test command on main_loop as * well. */ do_in_main_thread(FROM_HERE, base::BindOnce( [](LeAudioClient* client, const RawAddress& test_address1) { client->RemoveDevice(test_address1); }, LeAudioClient::Get(), test_address1)); SyncOnMainLoop(); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE)) .Times(1); state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::IDLE); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F_WITH_FLAGS(UnicastTestHandoverMode, SetSinkMonitorModeWhileUnicastIsActive, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_broadcast_audio_handover_policies))) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Verify Data transfer on two peer sinks and one source uint8_t cis_count_out = 2; uint8_t cis_count_in = 2; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); // Imitate activation of monitor mode do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSink, true /* enable */)); ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); // Stop streaming and expect Service to be informed about straming suspension EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSink, UnicastMonitorModeStatus::STREAMING_SUSPENDED)) .Times(1); // Stop StopStreaming(group_id, true); // Check if cache configuration is still present ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSource) .size()); // Release, Sink HAL client should remain in monitor mode EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(0); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(0); LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); // Re-initialize mock for destroyed hal client RegisterSourceHalClientMock(); // Setting group inactive, shall not change cached configuration ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSource) .size()); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSink, UnicastMonitorModeStatus::STREAMING_REQUESTED)) .Times(1); // Start streaming to trigger next group going to IDLE state LocalAudioSinkResume(); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Stop streaming and expect Service to be informed about straming suspension EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSink, UnicastMonitorModeStatus::STREAMING_SUSPENDED)) .Times(1); // Stop StopStreaming(group_id, true); // Release, Sink HAL client should remain in monitor mode EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(0); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(0); LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); // De-activate monitoring mode EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSink, false /* enable */)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); } TEST_F_WITH_FLAGS(UnicastTestHandoverMode, SetSinkMonitorModeWhileUnicastIsInactive, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_broadcast_audio_handover_policies))) { uint8_t group_size = 2; int group_id = 2; // Imitate activation of monitor mode do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSink, true /* enable */)); // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Expect no streaming request on stream resume when group is already active EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSink, UnicastMonitorModeStatus::STREAMING_REQUESTED)) .Times(0); StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Verify Data transfer on two peer sinks and one source uint8_t cis_count_out = 2; uint8_t cis_count_in = 2; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); // Stop streaming and expect Service to be informed about straming suspension EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSink, UnicastMonitorModeStatus::STREAMING_SUSPENDED)) .Times(1); // Stop StopStreaming(group_id, true); // Check if cache configuration is still present ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSource) .size()); // Release, Sink HAL client should remain in monitor mode EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(0); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(0); LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); // Setting group inactive, shall not change cached configuration ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSource) .size()); } TEST_F_WITH_FLAGS(UnicastTestHandoverMode, ClearSinkMonitorModeWhileUnicastIsActive, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_broadcast_audio_handover_policies))) { uint8_t group_size = 2; int group_id = 2; // Imitate activation of monitor mode do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSink, true /* enable */)); // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); // Expect no streaming request on stream resume when group is already active EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSink, UnicastMonitorModeStatus::STREAMING_REQUESTED)) .Times(0); StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Verify Data transfer on two peer sinks and one source uint8_t cis_count_out = 2; uint8_t cis_count_in = 2; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); // De-activate monitoring mode do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSink, false /* enable */)); // Stop StopStreaming(group_id, true); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); // Check if cache configuration is still present ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSource) .size()); // Release of sink and source hals due to de-activating monitor mode EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); // Setting group inactive, shall not change cached configuration ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSource) .size()); } TEST_F_WITH_FLAGS(UnicastTestHandoverMode, SetAndClearSinkMonitorModeWhileUnicastIsInactive, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_broadcast_audio_handover_policies))) { EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(0); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(0); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(0); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(0); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(0); // Imitate activation of monitor mode do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSink, true /* enable */)); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSink, false /* enable */)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); } TEST_F_WITH_FLAGS(UnicastTestHandoverMode, SetSourceMonitorModeWhileUnicastIsInactive, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_broadcast_audio_handover_policies))) { /* Enabling monitor mode for source while group is not active should result in * sending STREAMING_SUSPENDED notification. */ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSource, UnicastMonitorModeStatus::STREAMING_SUSPENDED)) .Times(1); // Imitate activation of monitor mode do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSource, true /* enable */)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F_WITH_FLAGS(UnicastTestHandoverMode, SetSourceMonitorModeWhileUnicastIsNotStreaming, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_broadcast_audio_handover_policies))) { int group_id = 2; LeAudioClient::Get()->GroupSetActive(group_id); /* Enabling monitor mode for source while group is not active should result in * sending STREAMING_SUSPENDED notification. */ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSource, UnicastMonitorModeStatus::STREAMING_SUSPENDED)) .Times(1); // Imitate activation of monitor mode do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSource, true /* enable */)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F_WITH_FLAGS(UnicastTestHandoverMode, SetSourceMonitorModeWhileUnicastIsActive, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_broadcast_audio_handover_policies))) { uint8_t group_size = 2; int group_id = 2; // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) .WillByDefault(Return(true)); // First earbud const RawAddress test_address0 = GetTestAddress(0); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) .Times(1); ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud const RawAddress test_address1 = GetTestAddress(1); EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) .Times(1); ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return 2; })); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); SyncOnMainLoop(); // Verify Data transfer on two peer sinks and one source uint8_t cis_count_out = 2; uint8_t cis_count_in = 2; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40); /* Enabling monitor mode for source while stream is active should result in * sending STREAMING notification. */ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSource, UnicastMonitorModeStatus::STREAMING)) .Times(1); // Imitate activation of monitor mode do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSource, true /* enable */)); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); // Stop streaming and expect Service to be informed about straming suspension EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSource, UnicastMonitorModeStatus::STREAMING_SUSPENDED)) .Times(1); // Stop StopStreaming(group_id, true); // Check if cache configuration is still present ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSource) .size()); // Both Sink and Source HAL clients should be stopped EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); // Re-initialize mock for destroyed hal client RegisterSourceHalClientMock(); RegisterSinkHalClientMock(); // Setting group inactive, shall not change cached configuration ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSink) .size()); ASSERT_TRUE( group->GetCachedConfiguration(types::LeAudioContextType::CONVERSATIONAL) ->confs.get(le_audio::types::kLeAudioDirectionSource) .size()); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSource, UnicastMonitorModeStatus::STREAMING_REQUESTED)) .Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); // Start streaming to trigger next group going to IDLE state StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); // Stop streaming and expect Service to be informed about straming suspension EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSource, UnicastMonitorModeStatus::STREAMING_SUSPENDED)) .Times(1); // Stop StopStreaming(group_id, true); // Both Sink and Source HAL clients should be stopped EXPECT_CALL(*mock_le_audio_source_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_source_hal_client_, OnDestroyed()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Stop()).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, OnDestroyed()).Times(1); LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_); // De-activate monitoring mode EXPECT_CALL(mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSource, UnicastMonitorModeStatus::STREAMING_SUSPENDED)) .Times(0); do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSink, false /* enable */)); } TEST_F(UnicastTestHandoverMode, SetAllowedContextMask) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; available_snk_context_types_ = (types::LeAudioContextType::RINGTONE | types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::UNSPECIFIED | types::LeAudioContextType::MEDIA | types::LeAudioContextType::SOUNDEFFECTS) .value(); available_src_context_types_ = available_snk_context_types_; supported_snk_context_types_ = types::kLeAudioContextAllTypes.value(); supported_src_context_types_ = (types::kLeAudioContextAllRemoteSource | types::LeAudioContextType::UNSPECIFIED) .value(); /* Don't allow SOUNDEFFECTS context type to be streamed */ int allowed_context_types = (types::LeAudioContextType::RINGTONE | types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::UNSPECIFIED | types::LeAudioContextType::MEDIA) .value(); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); types::BidirectionalPair metadata = { .sink = types::AudioContexts(), .source = types::AudioContexts()}; EXPECT_CALL( mock_state_machine_, StartStream(_, types::LeAudioContextType::SOUNDEFFECTS, metadata, _)) .Times(0); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); /* Set the same allowed context mask for sink and source */ LeAudioClient::Get()->SetGroupAllowedContextMask( group_id, allowed_context_types, allowed_context_types); StartStreaming(AUDIO_USAGE_ASSISTANCE_SONIFICATION, AUDIO_CONTENT_TYPE_UNKNOWN, group_id, AUDIO_SOURCE_INVALID, false, false); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } TEST_F_WITH_FLAGS(UnicastTest, NoContextvalidateStreamingRequest, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG( TEST_BT, leaudio_no_context_validate_streaming_request))) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; available_snk_context_types_ = (types::LeAudioContextType::RINGTONE | types::LeAudioContextType::CONVERSATIONAL | types::LeAudioContextType::UNSPECIFIED | types::LeAudioContextType::MEDIA) .value(); available_src_context_types_ = available_snk_context_types_; supported_snk_context_types_ = types::kLeAudioContextAllTypes.value(); supported_src_context_types_ = (types::kLeAudioContextAllRemoteSource | types::LeAudioContextType::UNSPECIFIED) .value(); SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); types::BidirectionalPair metadata = { .sink = types::AudioContexts(), .source = types::AudioContexts()}; EXPECT_CALL( mock_state_machine_, StartStream(_, types::LeAudioContextType::SOUNDEFFECTS, metadata, _)) .Times(0); LeAudioClient::Get()->GroupSetActive(group_id); // Imitate activation of monitor mode do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioClient::SetUnicastMonitorMode, base::Unretained(LeAudioClient::Get()), bluetooth::le_audio::types::kLeAudioDirectionSource, true /* enable */)); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); SyncOnMainLoop(); // Stop streaming and expect Service to be informed about straming suspension EXPECT_CALL( mock_audio_hal_client_callbacks_, OnUnicastMonitorModeStatus( bluetooth::le_audio::types::kLeAudioDirectionSource, UnicastMonitorModeStatus::STREAMING_REQUESTED_NO_CONTEXT_VALIDATE)) .Times(1); StartStreaming(AUDIO_USAGE_ASSISTANCE_SONIFICATION, AUDIO_CONTENT_TYPE_UNKNOWN, group_id, AUDIO_SOURCE_INVALID, false, false); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } } // namespace bluetooth::le_audio