Loading system/bta/le_audio/device_groups.cc +14 −9 Original line number Diff line number Diff line Loading @@ -868,16 +868,21 @@ bool LeAudioDeviceGroup::IsReleasingOrIdle(void) const { } bool LeAudioDeviceGroup::IsGroupStreamReady(void) const { auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) { if (d.expired() || (d.lock().get()->GetConnectionState() != DeviceConnectState::CONNECTED)) return false; else return !(((d.lock()).get())->HaveAllActiveAsesCisEst()); }); bool is_device_ready = false; return iter == leAudioDevices_.end(); /* All connected devices must be ready */ for (auto& weak : leAudioDevices_) { auto dev = weak.lock(); if (!dev) return false; if (dev->GetConnectionState() == DeviceConnectState::CONNECTED) { if (!dev->IsReadyToStream()) { return false; } is_device_ready = true; } } return is_device_ready; } bool LeAudioDeviceGroup::HaveAllCisesDisconnected(void) const { Loading system/bta/le_audio/devices.cc +27 −5 Original line number Diff line number Diff line Loading @@ -570,18 +570,32 @@ bool LeAudioDevice::HaveAnyUnconfiguredAses(void) { bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) { auto iter = std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) { LOG_INFO("id: %d, active: %d, state: %d", ase.id, ase.active, (int)ase.state); LOG_VERBOSE("ASE id: %d, active: %d, state: %s", ase.id, ase.active, bluetooth::common::ToString(ase.state).c_str()); return ase.active && (ase.state != state); }); return iter == ases_.end(); } bool LeAudioDevice::HaveAllActiveAsesSameDataPathState( types::DataPathState state) const { auto iter = std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) { LOG_VERBOSE("ASE id: %d, active: %d, state: %s", ase.id, ase.active, bluetooth::common::ToString(ase.data_path_state).c_str()); return ase.active && (ase.data_path_state != state); }); return iter == ases_.end(); } bool LeAudioDevice::IsReadyToCreateStream(void) { auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { if (!ase.active) return false; LOG_VERBOSE("ASE id: %d, state: %s, direction: %d", ase.id, bluetooth::common::ToString(ase.state).c_str(), ase.direction); if (ase.direction == types::kLeAudioDirectionSink && (ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING && ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING)) Loading Loading @@ -615,7 +629,7 @@ bool LeAudioDevice::IsReadyToSuspendStream(void) { return iter == ases_.end(); } bool LeAudioDevice::HaveAllActiveAsesCisEst(void) { bool LeAudioDevice::HaveAllActiveAsesCisEst(void) const { if (ases_.empty()) { LOG_WARN("No ases for device %s", ADDRESS_TO_LOGGABLE_CSTR(address_)); /* If there is no ASEs at all, it means we are good here - meaning, it is Loading @@ -624,11 +638,19 @@ bool LeAudioDevice::HaveAllActiveAsesCisEst(void) { return true; } auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { bool has_active_ase = false; auto iter = std::find_if(ases_.begin(), ases_.end(), [&](const auto& ase) { if (!has_active_ase && ase.active) { has_active_ase = true; } LOG_VERBOSE("ASE id: %d, cis_state: %s, direction: %d", ase.id, bluetooth::common::ToString(ase.cis_state).c_str(), ase.direction); return ase.active && (ase.cis_state != CisState::CONNECTED); }); return iter == ases_.end(); return iter == ases_.end() && has_active_ase; } bool LeAudioDevice::HaveAnyCisConnected(void) { Loading system/bta/le_audio/devices.h +6 −1 Original line number Diff line number Diff line Loading @@ -168,10 +168,15 @@ class LeAudioDevice { types::BidirectionalPair<struct types::ase*> GetAsesByCisId(uint8_t cis_id); bool HaveActiveAse(void); bool HaveAllActiveAsesSameState(types::AseState state); bool HaveAllActiveAsesSameDataPathState(types::DataPathState state) const; bool HaveAnyUnconfiguredAses(void); bool IsReadyToCreateStream(void); bool IsReadyToStream(void) const { return HaveAllActiveAsesCisEst() && HaveAllActiveAsesSameDataPathState(types::DataPathState::CONFIGURED); } bool IsReadyToSuspendStream(void); bool HaveAllActiveAsesCisEst(void); bool HaveAllActiveAsesCisEst(void) const; bool HaveAnyCisConnected(void); bool HasCisId(uint8_t id); uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase); Loading system/bta/le_audio/state_machine.cc +43 −100 Original line number Diff line number Diff line Loading @@ -603,24 +603,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { AddCisToStreamConfiguration(group, ase); ase = leAudioDevice->GetFirstActiveAseByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE); if (!ase) { leAudioDevice = group->GetNextActiveDeviceByCisAndDataPathState( leAudioDevice, CisState::CONNECTED, DataPathState::IDLE); if (!leAudioDevice) { state_machine_callbacks_->StatusReportCb(group->group_id_, GroupStreamStatus::STREAMING); return; } ase = leAudioDevice->GetFirstActiveAseByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE); if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING && !group->GetFirstActiveDeviceByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE)) { /* No more transition for group */ cancel_watchdog_if_needed(group->group_id_); } ASSERT_LOG(ase, "shouldn't be called without an active ASE"); PrepareDataPath(group->group_id_, ase); } void ProcessHciNotifRemoveIsoDataPath(LeAudioDeviceGroup* group, Loading Loading @@ -862,6 +850,16 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { if (ases_pair.sink) ases_pair.sink->cis_state = CisState::CONNECTED; if (ases_pair.source) ases_pair.source->cis_state = CisState::CONNECTED; if (ases_pair.sink && (ases_pair.sink->data_path_state == DataPathState::IDLE)) { PrepareDataPath(group->group_id_, ases_pair.sink); } if (ases_pair.source && (ases_pair.source->data_path_state == DataPathState::IDLE)) { PrepareDataPath(group->group_id_, ases_pair.source); } if (osi_property_get_bool("persist.bluetooth.iso_link_quality_report", false)) { leAudioDevice->link_quality_timer = Loading Loading @@ -896,16 +894,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { event->cis_conn_hdl); PrepareAndSendReceiverStartReady(leAudioDevice, ase); /* Cis establishment may came after setting group state to streaming, e.g. * for autonomous scenario when ase is sink */ if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING && group->GetFirstActiveDeviceByCisAndDataPathState(CisState::CONNECTED, DataPathState::IDLE)) { /* No more transition for group */ cancel_watchdog_if_needed(group->group_id_); PrepareDataPath(group); } } static void WriteToControlPoint(LeAudioDevice* leAudioDevice, Loading Loading @@ -1513,18 +1501,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { std::move(param)); } static inline void PrepareDataPath(LeAudioDeviceGroup* group) { auto* leAudioDevice = group->GetFirstActiveDeviceByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE); LOG_ASSERT(leAudioDevice) << __func__ << " Shouldn't be called without an active device."; auto* ase = leAudioDevice->GetFirstActiveAseByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE); LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE"; PrepareDataPath(group->group_id_, ase); } static void ReleaseDataPath(LeAudioDeviceGroup* group) { LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); LOG_ASSERT(leAudioDevice) Loading Loading @@ -2567,35 +2543,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { switch (ase->state) { case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: /* As per ASCS 1.0 : * If a CIS has been established and the server is acting as Audio Sink * for the ASE, and if the server is ready to receive audio data * transmitted by the client, the server may autonomously initiate the * Receiver Start Ready, as defined in Section 5.4, without first * sending a notification of the ASE characteristic value in the * Enabling state. */ if (ase->direction != le_audio::types::kLeAudioDirectionSink) { LOG(ERROR) << __func__ << ", invalid state transition, from: " << static_cast<int>(ase->state) << ", to: " << static_cast<int>( AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); LOG_ERROR( "%s, ase_id: %d, moving from QoS Configured to Streaming is " "impossible.", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id); group->PrintDebugState(); StopStream(group); return; } SetAseState(leAudioDevice, ase, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { /* We are here because of the reconnection of the single device. */ PrepareDataPath(group); return; } if (leAudioDevice->IsReadyToCreateStream()) ProcessGroupEnable(group); break; case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING: { Loading @@ -2608,45 +2561,40 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) { /* More ASEs notification form this device has to come for this group */ return; } /* This case may happen because of the reconnection device. */ if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { /* Not all CISes establish evens came */ if (group->GetFirstActiveDeviceByCisAndDataPathState( CisState::CONNECTING, DataPathState::IDLE)) return; /* Streaming status notification came after setting data path */ if (!group->GetFirstActiveDeviceByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE)) return; PrepareDataPath(group); /* We are here because of the reconnection of the single device */ LOG_INFO("%s, Ase id: %d, ase state: %s", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id, bluetooth::common::ToString(ase->state).c_str()); cancel_watchdog_if_needed(group->group_id_); state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::STREAMING); return; } /* Last node is in streaming state */ group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); /* Not all CISes establish evens came */ /* Not all CISes establish events will came */ if (!group->IsGroupStreamReady()) return; if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { /* No more transition for group */ cancel_watchdog_if_needed(group->group_id_); PrepareDataPath(group); /* Last node is in streaming state */ group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::STREAMING); return; } else { } LOG_ERROR(", invalid state transition, from: %s, to: %s", ToString(group->GetState()).c_str(), ToString(group->GetTargetState()).c_str()); StopStream(group); return; } break; } Loading Loading @@ -2861,6 +2809,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { void ProcessGroupEnable(LeAudioDeviceGroup* group) { if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING) { /* Check if the group is ready to create stream. If not, keep waiting. */ if (!group->IsGroupReadyToCreateStream()) { LOG_DEBUG( "Waiting for more ASEs to be in enabling or directly in streaming " Loading @@ -2868,18 +2817,11 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return; } /* Group can move to Enabling state now. */ group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING); } /* At this point all of the active ASEs within group are enabled. The server * might perform autonomous state transition for Sink ASE and skip Enabling * state notification and transit to Streaming directly. So check the group * state, because we might be ready to create CIS. */ if (group->HaveAllActiveDevicesAsesTheSameState( AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) { group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); } /* If Target State is not streaming, then something is wrong. */ if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { LOG_ERROR(", invalid state transition, from: %s , to: %s ", ToString(group->GetState()).c_str(), Loading @@ -2888,6 +2830,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return; } /* Try to create CISes for the group */ if (!CisCreate(group)) { StopStream(group); } Loading system/bta/le_audio/state_machine_test.cc +131 −26 Original line number Diff line number Diff line Loading @@ -1800,6 +1800,13 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) { /* Device is banded headphones with 2x snk + none src ase * (2x unidirectional CIS) */ /* Not, that when remote device skip Enabling it is considered as an error and * group will not be able to go to Streaming state. * It is because, Android is not creating CISes before all ASEs gets into * Enabling state, therefore it is impossible to remote device to skip * Enabling state. */ const auto context_type = kContextTypeMedia; const int leaudio_group_id = 4; Loading @@ -1813,15 +1820,21 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) { PrepareConfigureQosHandler(group, 2); PrepareEnableHandler(group, 2, false); /* * 1. Configure * 2. QoS Config * 3. Enable * 4. Release */ auto* leAudioDevice = group->GetFirstDevice(); EXPECT_CALL(gatt_queue, WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(3); .Times(4); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0); Loading @@ -1829,10 +1842,15 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) { InjectInitialIdleNotification(group); // Validate GroupStreamStatus EXPECT_CALL( mock_callbacks_, EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::STREAMING)); bluetooth::le_audio::GroupStreamStatus::STREAMING)) .Times(0); EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)) .Times(1); // Start the configuration and stream Media content ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( Loading @@ -1840,10 +1858,6 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) { {.sink = types::AudioContexts(context_type), .source = types::AudioContexts(context_type)})); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); ASSERT_EQ(1, get_func_call_count("alarm_cancel")); } Loading @@ -1867,6 +1881,12 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) { PrepareEnableHandler(group, 3, false); PrepareReceiverStartReadyHandler(group, 1); /* * 1. Codec Config * 2. Qos Config * 3. Enable * 4. Release */ auto* leAudioDevice = group->GetFirstDevice(); EXPECT_CALL(gatt_queue, WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, Loading @@ -1874,8 +1894,8 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) { .Times(4); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0); Loading @@ -1883,10 +1903,14 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) { InjectInitialIdleNotification(group); // Validate GroupStreamStatus EXPECT_CALL( mock_callbacks_, EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::STREAMING)); bluetooth::le_audio::GroupStreamStatus::STREAMING)) .Times(0); EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)) .Times(1); // Start the configuration and stream Media content ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( Loading @@ -1894,9 +1918,6 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) { {.sink = types::AudioContexts(context_type), .source = types::AudioContexts(context_type)})); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); ASSERT_EQ(1, get_func_call_count("alarm_cancel")); } Loading Loading @@ -2109,6 +2130,10 @@ TEST_F(StateMachineTest, testStreamMultipleConversational) { ASSERT_EQ(1, get_func_call_count("alarm_cancel")); } MATCHER_P(dataPathDirIsEq, expected, "") { return (arg.data_path_dir == expected); } TEST_F(StateMachineTest, testFailedStreamMultipleConversational) { /* Testing here CIS Failed to be established */ const auto context_type = kContextTypeConversational; Loading @@ -2133,8 +2158,27 @@ TEST_F(StateMachineTest, testFailedStreamMultipleConversational) { EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(AtLeast(1)); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); /* Bidirectional CIS data path is configured in tw ocalls and removed for both * directions with a single call. */ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( _, dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionIn))) .Times(1); EXPECT_CALL( *mock_iso_manager_, SetupIsoDataPath( _, dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionOut))) .Times(1); EXPECT_CALL( *mock_iso_manager_, RemoveIsoDataPath( _, bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput | bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput)) .Times(1); /* This check is the major one in this test, as we want to make sure, * it will not be called twice but only once (when both bidirectional ASEs are Loading Loading @@ -4562,7 +4606,7 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { auto* leAudioDevice = group->GetFirstDevice(); LeAudioDevice* lastDevice; LeAudioDevice* fistDevice = leAudioDevice; LeAudioDevice* firstDevice = leAudioDevice; auto expected_devices_written = 0; while (leAudioDevice) { Loading @@ -4581,10 +4625,43 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { leAudioDevice = group->GetNextDevice(leAudioDevice); } ASSERT_EQ(expected_devices_written, num_devices); ASSERT_NE(nullptr, firstDevice); ASSERT_NE(nullptr, lastDevice); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( _, dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionIn))) .Times(2); // Make sure the Out data path is set before we declare that we are ready { ::testing::InSequence seq; EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 0), dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionOut))) .Times(1); EXPECT_CALL(ase_ctp_handler, AseCtpReceiverStartReadyHandler(firstDevice, _, _, _)) .Times(1); } { ::testing::InSequence seq; EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 1), dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionOut))) .Times(1); EXPECT_CALL(ase_ctp_handler, AseCtpReceiverStartReadyHandler(lastDevice, _, _, _)) .Times(1); } InjectInitialIdleNotification(group); Loading @@ -4599,9 +4676,19 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); // Verify data path removal on the second bidirectional CIS EXPECT_CALL( *mock_iso_manager_, RemoveIsoDataPath( UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 1), bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput | bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput)) .Times(1); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT); InjectAclDisconnected(group, lastDevice); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); // Check if group keeps streaming ASSERT_EQ(group->GetState(), Loading @@ -4626,7 +4713,25 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { .Times(AtLeast(3)); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( _, dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionIn))) .Times(1); // Make sure the Out data path is set before we declare that we are ready { ::testing::InSequence seq; EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 1), dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionOut))) .Times(1); EXPECT_CALL(ase_ctp_handler, AseCtpReceiverStartReadyHandler(lastDevice, _, _, _)) .Times(1); } LeAudioGroupStateMachine::Get()->AttachToStream( group, lastDevice, {.sink = {call_ccid}, .source = {call_ccid}}); Loading @@ -4646,7 +4751,7 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { ASSERT_NE(std::find(ccids->begin(), ccids->end(), call_ccid), ccids->end()); /* Verify that ASE of first device are still good*/ auto ase = fistDevice->GetFirstActiveAse(); auto ase = firstDevice->GetFirstActiveAse(); ASSERT_NE(ase->max_transport_latency, 0); ASSERT_NE(ase->retrans_nb, 0); Loading Loading
system/bta/le_audio/device_groups.cc +14 −9 Original line number Diff line number Diff line Loading @@ -868,16 +868,21 @@ bool LeAudioDeviceGroup::IsReleasingOrIdle(void) const { } bool LeAudioDeviceGroup::IsGroupStreamReady(void) const { auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) { if (d.expired() || (d.lock().get()->GetConnectionState() != DeviceConnectState::CONNECTED)) return false; else return !(((d.lock()).get())->HaveAllActiveAsesCisEst()); }); bool is_device_ready = false; return iter == leAudioDevices_.end(); /* All connected devices must be ready */ for (auto& weak : leAudioDevices_) { auto dev = weak.lock(); if (!dev) return false; if (dev->GetConnectionState() == DeviceConnectState::CONNECTED) { if (!dev->IsReadyToStream()) { return false; } is_device_ready = true; } } return is_device_ready; } bool LeAudioDeviceGroup::HaveAllCisesDisconnected(void) const { Loading
system/bta/le_audio/devices.cc +27 −5 Original line number Diff line number Diff line Loading @@ -570,18 +570,32 @@ bool LeAudioDevice::HaveAnyUnconfiguredAses(void) { bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) { auto iter = std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) { LOG_INFO("id: %d, active: %d, state: %d", ase.id, ase.active, (int)ase.state); LOG_VERBOSE("ASE id: %d, active: %d, state: %s", ase.id, ase.active, bluetooth::common::ToString(ase.state).c_str()); return ase.active && (ase.state != state); }); return iter == ases_.end(); } bool LeAudioDevice::HaveAllActiveAsesSameDataPathState( types::DataPathState state) const { auto iter = std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) { LOG_VERBOSE("ASE id: %d, active: %d, state: %s", ase.id, ase.active, bluetooth::common::ToString(ase.data_path_state).c_str()); return ase.active && (ase.data_path_state != state); }); return iter == ases_.end(); } bool LeAudioDevice::IsReadyToCreateStream(void) { auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { if (!ase.active) return false; LOG_VERBOSE("ASE id: %d, state: %s, direction: %d", ase.id, bluetooth::common::ToString(ase.state).c_str(), ase.direction); if (ase.direction == types::kLeAudioDirectionSink && (ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING && ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING)) Loading Loading @@ -615,7 +629,7 @@ bool LeAudioDevice::IsReadyToSuspendStream(void) { return iter == ases_.end(); } bool LeAudioDevice::HaveAllActiveAsesCisEst(void) { bool LeAudioDevice::HaveAllActiveAsesCisEst(void) const { if (ases_.empty()) { LOG_WARN("No ases for device %s", ADDRESS_TO_LOGGABLE_CSTR(address_)); /* If there is no ASEs at all, it means we are good here - meaning, it is Loading @@ -624,11 +638,19 @@ bool LeAudioDevice::HaveAllActiveAsesCisEst(void) { return true; } auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { bool has_active_ase = false; auto iter = std::find_if(ases_.begin(), ases_.end(), [&](const auto& ase) { if (!has_active_ase && ase.active) { has_active_ase = true; } LOG_VERBOSE("ASE id: %d, cis_state: %s, direction: %d", ase.id, bluetooth::common::ToString(ase.cis_state).c_str(), ase.direction); return ase.active && (ase.cis_state != CisState::CONNECTED); }); return iter == ases_.end(); return iter == ases_.end() && has_active_ase; } bool LeAudioDevice::HaveAnyCisConnected(void) { Loading
system/bta/le_audio/devices.h +6 −1 Original line number Diff line number Diff line Loading @@ -168,10 +168,15 @@ class LeAudioDevice { types::BidirectionalPair<struct types::ase*> GetAsesByCisId(uint8_t cis_id); bool HaveActiveAse(void); bool HaveAllActiveAsesSameState(types::AseState state); bool HaveAllActiveAsesSameDataPathState(types::DataPathState state) const; bool HaveAnyUnconfiguredAses(void); bool IsReadyToCreateStream(void); bool IsReadyToStream(void) const { return HaveAllActiveAsesCisEst() && HaveAllActiveAsesSameDataPathState(types::DataPathState::CONFIGURED); } bool IsReadyToSuspendStream(void); bool HaveAllActiveAsesCisEst(void); bool HaveAllActiveAsesCisEst(void) const; bool HaveAnyCisConnected(void); bool HasCisId(uint8_t id); uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase); Loading
system/bta/le_audio/state_machine.cc +43 −100 Original line number Diff line number Diff line Loading @@ -603,24 +603,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { AddCisToStreamConfiguration(group, ase); ase = leAudioDevice->GetFirstActiveAseByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE); if (!ase) { leAudioDevice = group->GetNextActiveDeviceByCisAndDataPathState( leAudioDevice, CisState::CONNECTED, DataPathState::IDLE); if (!leAudioDevice) { state_machine_callbacks_->StatusReportCb(group->group_id_, GroupStreamStatus::STREAMING); return; } ase = leAudioDevice->GetFirstActiveAseByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE); if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING && !group->GetFirstActiveDeviceByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE)) { /* No more transition for group */ cancel_watchdog_if_needed(group->group_id_); } ASSERT_LOG(ase, "shouldn't be called without an active ASE"); PrepareDataPath(group->group_id_, ase); } void ProcessHciNotifRemoveIsoDataPath(LeAudioDeviceGroup* group, Loading Loading @@ -862,6 +850,16 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { if (ases_pair.sink) ases_pair.sink->cis_state = CisState::CONNECTED; if (ases_pair.source) ases_pair.source->cis_state = CisState::CONNECTED; if (ases_pair.sink && (ases_pair.sink->data_path_state == DataPathState::IDLE)) { PrepareDataPath(group->group_id_, ases_pair.sink); } if (ases_pair.source && (ases_pair.source->data_path_state == DataPathState::IDLE)) { PrepareDataPath(group->group_id_, ases_pair.source); } if (osi_property_get_bool("persist.bluetooth.iso_link_quality_report", false)) { leAudioDevice->link_quality_timer = Loading Loading @@ -896,16 +894,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { event->cis_conn_hdl); PrepareAndSendReceiverStartReady(leAudioDevice, ase); /* Cis establishment may came after setting group state to streaming, e.g. * for autonomous scenario when ase is sink */ if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING && group->GetFirstActiveDeviceByCisAndDataPathState(CisState::CONNECTED, DataPathState::IDLE)) { /* No more transition for group */ cancel_watchdog_if_needed(group->group_id_); PrepareDataPath(group); } } static void WriteToControlPoint(LeAudioDevice* leAudioDevice, Loading Loading @@ -1513,18 +1501,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { std::move(param)); } static inline void PrepareDataPath(LeAudioDeviceGroup* group) { auto* leAudioDevice = group->GetFirstActiveDeviceByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE); LOG_ASSERT(leAudioDevice) << __func__ << " Shouldn't be called without an active device."; auto* ase = leAudioDevice->GetFirstActiveAseByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE); LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE"; PrepareDataPath(group->group_id_, ase); } static void ReleaseDataPath(LeAudioDeviceGroup* group) { LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); LOG_ASSERT(leAudioDevice) Loading Loading @@ -2567,35 +2543,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { switch (ase->state) { case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: /* As per ASCS 1.0 : * If a CIS has been established and the server is acting as Audio Sink * for the ASE, and if the server is ready to receive audio data * transmitted by the client, the server may autonomously initiate the * Receiver Start Ready, as defined in Section 5.4, without first * sending a notification of the ASE characteristic value in the * Enabling state. */ if (ase->direction != le_audio::types::kLeAudioDirectionSink) { LOG(ERROR) << __func__ << ", invalid state transition, from: " << static_cast<int>(ase->state) << ", to: " << static_cast<int>( AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); LOG_ERROR( "%s, ase_id: %d, moving from QoS Configured to Streaming is " "impossible.", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id); group->PrintDebugState(); StopStream(group); return; } SetAseState(leAudioDevice, ase, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { /* We are here because of the reconnection of the single device. */ PrepareDataPath(group); return; } if (leAudioDevice->IsReadyToCreateStream()) ProcessGroupEnable(group); break; case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING: { Loading @@ -2608,45 +2561,40 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) { /* More ASEs notification form this device has to come for this group */ return; } /* This case may happen because of the reconnection device. */ if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { /* Not all CISes establish evens came */ if (group->GetFirstActiveDeviceByCisAndDataPathState( CisState::CONNECTING, DataPathState::IDLE)) return; /* Streaming status notification came after setting data path */ if (!group->GetFirstActiveDeviceByCisAndDataPathState( CisState::CONNECTED, DataPathState::IDLE)) return; PrepareDataPath(group); /* We are here because of the reconnection of the single device */ LOG_INFO("%s, Ase id: %d, ase state: %s", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id, bluetooth::common::ToString(ase->state).c_str()); cancel_watchdog_if_needed(group->group_id_); state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::STREAMING); return; } /* Last node is in streaming state */ group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); /* Not all CISes establish evens came */ /* Not all CISes establish events will came */ if (!group->IsGroupStreamReady()) return; if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { /* No more transition for group */ cancel_watchdog_if_needed(group->group_id_); PrepareDataPath(group); /* Last node is in streaming state */ group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::STREAMING); return; } else { } LOG_ERROR(", invalid state transition, from: %s, to: %s", ToString(group->GetState()).c_str(), ToString(group->GetTargetState()).c_str()); StopStream(group); return; } break; } Loading Loading @@ -2861,6 +2809,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { void ProcessGroupEnable(LeAudioDeviceGroup* group) { if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING) { /* Check if the group is ready to create stream. If not, keep waiting. */ if (!group->IsGroupReadyToCreateStream()) { LOG_DEBUG( "Waiting for more ASEs to be in enabling or directly in streaming " Loading @@ -2868,18 +2817,11 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return; } /* Group can move to Enabling state now. */ group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING); } /* At this point all of the active ASEs within group are enabled. The server * might perform autonomous state transition for Sink ASE and skip Enabling * state notification and transit to Streaming directly. So check the group * state, because we might be ready to create CIS. */ if (group->HaveAllActiveDevicesAsesTheSameState( AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) { group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); } /* If Target State is not streaming, then something is wrong. */ if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { LOG_ERROR(", invalid state transition, from: %s , to: %s ", ToString(group->GetState()).c_str(), Loading @@ -2888,6 +2830,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return; } /* Try to create CISes for the group */ if (!CisCreate(group)) { StopStream(group); } Loading
system/bta/le_audio/state_machine_test.cc +131 −26 Original line number Diff line number Diff line Loading @@ -1800,6 +1800,13 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) { /* Device is banded headphones with 2x snk + none src ase * (2x unidirectional CIS) */ /* Not, that when remote device skip Enabling it is considered as an error and * group will not be able to go to Streaming state. * It is because, Android is not creating CISes before all ASEs gets into * Enabling state, therefore it is impossible to remote device to skip * Enabling state. */ const auto context_type = kContextTypeMedia; const int leaudio_group_id = 4; Loading @@ -1813,15 +1820,21 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) { PrepareConfigureQosHandler(group, 2); PrepareEnableHandler(group, 2, false); /* * 1. Configure * 2. QoS Config * 3. Enable * 4. Release */ auto* leAudioDevice = group->GetFirstDevice(); EXPECT_CALL(gatt_queue, WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(3); .Times(4); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0); Loading @@ -1829,10 +1842,15 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) { InjectInitialIdleNotification(group); // Validate GroupStreamStatus EXPECT_CALL( mock_callbacks_, EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::STREAMING)); bluetooth::le_audio::GroupStreamStatus::STREAMING)) .Times(0); EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)) .Times(1); // Start the configuration and stream Media content ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( Loading @@ -1840,10 +1858,6 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) { {.sink = types::AudioContexts(context_type), .source = types::AudioContexts(context_type)})); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); ASSERT_EQ(1, get_func_call_count("alarm_cancel")); } Loading @@ -1867,6 +1881,12 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) { PrepareEnableHandler(group, 3, false); PrepareReceiverStartReadyHandler(group, 1); /* * 1. Codec Config * 2. Qos Config * 3. Enable * 4. Release */ auto* leAudioDevice = group->GetFirstDevice(); EXPECT_CALL(gatt_queue, WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, Loading @@ -1874,8 +1894,8 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) { .Times(4); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0); Loading @@ -1883,10 +1903,14 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) { InjectInitialIdleNotification(group); // Validate GroupStreamStatus EXPECT_CALL( mock_callbacks_, EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::STREAMING)); bluetooth::le_audio::GroupStreamStatus::STREAMING)) .Times(0); EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)) .Times(1); // Start the configuration and stream Media content ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( Loading @@ -1894,9 +1918,6 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) { {.sink = types::AudioContexts(context_type), .source = types::AudioContexts(context_type)})); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); ASSERT_EQ(1, get_func_call_count("alarm_cancel")); } Loading Loading @@ -2109,6 +2130,10 @@ TEST_F(StateMachineTest, testStreamMultipleConversational) { ASSERT_EQ(1, get_func_call_count("alarm_cancel")); } MATCHER_P(dataPathDirIsEq, expected, "") { return (arg.data_path_dir == expected); } TEST_F(StateMachineTest, testFailedStreamMultipleConversational) { /* Testing here CIS Failed to be established */ const auto context_type = kContextTypeConversational; Loading @@ -2133,8 +2158,27 @@ TEST_F(StateMachineTest, testFailedStreamMultipleConversational) { EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(AtLeast(1)); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); /* Bidirectional CIS data path is configured in tw ocalls and removed for both * directions with a single call. */ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( _, dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionIn))) .Times(1); EXPECT_CALL( *mock_iso_manager_, SetupIsoDataPath( _, dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionOut))) .Times(1); EXPECT_CALL( *mock_iso_manager_, RemoveIsoDataPath( _, bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput | bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput)) .Times(1); /* This check is the major one in this test, as we want to make sure, * it will not be called twice but only once (when both bidirectional ASEs are Loading Loading @@ -4562,7 +4606,7 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { auto* leAudioDevice = group->GetFirstDevice(); LeAudioDevice* lastDevice; LeAudioDevice* fistDevice = leAudioDevice; LeAudioDevice* firstDevice = leAudioDevice; auto expected_devices_written = 0; while (leAudioDevice) { Loading @@ -4581,10 +4625,43 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { leAudioDevice = group->GetNextDevice(leAudioDevice); } ASSERT_EQ(expected_devices_written, num_devices); ASSERT_NE(nullptr, firstDevice); ASSERT_NE(nullptr, lastDevice); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( _, dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionIn))) .Times(2); // Make sure the Out data path is set before we declare that we are ready { ::testing::InSequence seq; EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 0), dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionOut))) .Times(1); EXPECT_CALL(ase_ctp_handler, AseCtpReceiverStartReadyHandler(firstDevice, _, _, _)) .Times(1); } { ::testing::InSequence seq; EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 1), dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionOut))) .Times(1); EXPECT_CALL(ase_ctp_handler, AseCtpReceiverStartReadyHandler(lastDevice, _, _, _)) .Times(1); } InjectInitialIdleNotification(group); Loading @@ -4599,9 +4676,19 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); // Verify data path removal on the second bidirectional CIS EXPECT_CALL( *mock_iso_manager_, RemoveIsoDataPath( UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 1), bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput | bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput)) .Times(1); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT); InjectAclDisconnected(group, lastDevice); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); // Check if group keeps streaming ASSERT_EQ(group->GetState(), Loading @@ -4626,7 +4713,25 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { .Times(AtLeast(3)); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( _, dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionIn))) .Times(1); // Make sure the Out data path is set before we declare that we are ready { ::testing::InSequence seq; EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath( UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 1), dataPathDirIsEq( bluetooth::hci::iso_manager::kIsoDataPathDirectionOut))) .Times(1); EXPECT_CALL(ase_ctp_handler, AseCtpReceiverStartReadyHandler(lastDevice, _, _, _)) .Times(1); } LeAudioGroupStateMachine::Get()->AttachToStream( group, lastDevice, {.sink = {call_ccid}, .source = {call_ccid}}); Loading @@ -4646,7 +4751,7 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { ASSERT_NE(std::find(ccids->begin(), ccids->end(), call_ccid), ccids->end()); /* Verify that ASE of first device are still good*/ auto ase = fistDevice->GetFirstActiveAse(); auto ase = firstDevice->GetFirstActiveAse(); ASSERT_NE(ase->max_transport_latency, 0); ASSERT_NE(ase->retrans_nb, 0); Loading