Loading system/bta/le_audio/state_machine.cc +57 −20 Original line number Diff line number Diff line Loading @@ -677,8 +677,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event) override { std::vector<uint8_t> value; auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl); if (event->status) { Loading Loading @@ -730,11 +728,17 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } if (!leAudioDevice->HaveAllActiveAsesCisEst()) { /* More cis established event has to come */ /* More cis established events has to come */ return; } std::vector<uint8_t> ids; if (!leAudioDevice->IsReadyToCreateStream()) { /* Device still remains in ready to create stream state. It means that * more enabling status notifications has to come. This may only happen * for reconnection scenario for bi-directional CIS. */ return; } /* All CISes created. Send start ready for source ASE before we can go * to streaming state. Loading @@ -745,21 +749,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { "id: %d, cis handle 0x%04x", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), event->cig_id, event->cis_conn_hdl); do { if (ase->direction == le_audio::types::kLeAudioDirectionSource) ids.push_back(ase->id); } while ((ase = leAudioDevice->GetNextActiveAse(ase))); if (ids.size() > 0) { le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady( ids, value); BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, value, GATT_WRITE_NO_RSP, NULL, NULL); return; } PrepareAndSendReceiverStartReady(leAudioDevice, ase); /* Cis establishment may came after setting group state to streaming, e.g. * for autonomous scenario when ase is sink */ Loading Loading @@ -2183,6 +2174,28 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } } void PrepareAndSendReceiverStartReady(LeAudioDevice* leAudioDevice, struct ase* ase) { std::vector<uint8_t> ids; std::vector<uint8_t> value; do { if (ase->direction == le_audio::types::kLeAudioDirectionSource) ids.push_back(ase->id); } while ((ase = leAudioDevice->GetNextActiveAse(ase))); if (ids.size() > 0) { le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady( ids, value); BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, value, GATT_WRITE_NO_RSP, NULL, NULL); return; } } void AseStateMachineProcessEnabling( struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) { Loading @@ -2196,8 +2209,32 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING; if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { if (ase->data_path_state < AudioStreamDataPathState::CIS_PENDING) { /* We are here because of the reconnection of the single device. */ CisCreateForDevice(leAudioDevice); } if (!leAudioDevice->HaveAllActiveAsesCisEst()) { /* More cis established events has to come */ return; } if (!leAudioDevice->IsReadyToCreateStream()) { /* Device still remains in ready to create stream state. It means * that more enabling status notifications has to come. */ return; } /* All CISes created. Send start ready for source ASE before we can go * to streaming state. */ struct ase* ase = leAudioDevice->GetFirstActiveAse(); ASSERT_LOG(ase != nullptr, "shouldn't be called without an active ASE, device %s", leAudioDevice->address_.ToString().c_str()); PrepareAndSendReceiverStartReady(leAudioDevice, ase); return; } Loading system/bta/le_audio/state_machine_test.cc +112 −0 Original line number Diff line number Diff line Loading @@ -3097,6 +3097,118 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStream) { ASSERT_NE(ase->retrans_nb, 0); } TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { const auto context_type = kContextTypeConversational; const auto leaudio_group_id = 6; const auto num_devices = 2; ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_ccid); // Prepare multiple fake connected devices in a group auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); ASSERT_EQ(group->Size(), num_devices); PrepareConfigureCodecHandler(group); PrepareConfigureQosHandler(group); PrepareEnableHandler(group); PrepareReceiverStartReady(group); PrepareDisableHandler(group); PrepareReleaseHandler(group); auto* leAudioDevice = group->GetFirstDevice(); LeAudioDevice* lastDevice; LeAudioDevice* fistDevice = leAudioDevice; auto expected_devices_written = 0; while (leAudioDevice) { /* Three Writes: * 1: Codec Config * 2: Codec QoS * 3: Enabling */ lastDevice = leAudioDevice; EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(3)); expected_devices_written++; leAudioDevice = group->GetNextDevice(leAudioDevice); } ASSERT_EQ(expected_devices_written, num_devices); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4); InjectInitialIdleNotification(group); // Start the configuration and stream Conversational content LeAudioGroupStateMachine::Get()->StartStream( group, static_cast<LeAudioContextType>(context_type), types::AudioContexts(context_type)); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT); InjectAclDisconnected(group, lastDevice); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); lastDevice->conn_id_ = 3; group->UpdateAudioContextTypeAvailability(); // Make sure ASE with disconnected CIS are not left in STREAMING ASSERT_EQ(lastDevice->GetFirstAseWithState( ::le_audio::types::kLeAudioDirectionSink, types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING), nullptr); ASSERT_EQ(lastDevice->GetFirstAseWithState( ::le_audio::types::kLeAudioDirectionSource, types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING), nullptr); EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_, lastDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(3)); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); // Verify that the joining device receives the right CCID list auto lastMeta = lastDevice->GetFirstActiveAse()->metadata; bool parsedOk = false; auto ltv = le_audio::types::LeAudioLtvMap::Parse(lastMeta.data(), lastMeta.size(), parsedOk); ASSERT_TRUE(parsedOk); auto ccids = ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList); ASSERT_TRUE(ccids.has_value()); ASSERT_NE(std::find(ccids->begin(), ccids->end(), call_ccid), ccids->end()); /* Verify that ASE of first device are still good*/ auto ase = fistDevice->GetFirstActiveAse(); ASSERT_NE(ase->max_transport_latency, 0); ASSERT_NE(ase->retrans_nb, 0); // Make sure ASEs with reconnected CIS are in STREAMING state ASSERT_TRUE(lastDevice->HaveAllActiveAsesSameState( types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)); } TEST_F(StateMachineTest, StartStreamAfterConfigure) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; Loading Loading
system/bta/le_audio/state_machine.cc +57 −20 Original line number Diff line number Diff line Loading @@ -677,8 +677,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event) override { std::vector<uint8_t> value; auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl); if (event->status) { Loading Loading @@ -730,11 +728,17 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } if (!leAudioDevice->HaveAllActiveAsesCisEst()) { /* More cis established event has to come */ /* More cis established events has to come */ return; } std::vector<uint8_t> ids; if (!leAudioDevice->IsReadyToCreateStream()) { /* Device still remains in ready to create stream state. It means that * more enabling status notifications has to come. This may only happen * for reconnection scenario for bi-directional CIS. */ return; } /* All CISes created. Send start ready for source ASE before we can go * to streaming state. Loading @@ -745,21 +749,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { "id: %d, cis handle 0x%04x", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), event->cig_id, event->cis_conn_hdl); do { if (ase->direction == le_audio::types::kLeAudioDirectionSource) ids.push_back(ase->id); } while ((ase = leAudioDevice->GetNextActiveAse(ase))); if (ids.size() > 0) { le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady( ids, value); BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, value, GATT_WRITE_NO_RSP, NULL, NULL); return; } PrepareAndSendReceiverStartReady(leAudioDevice, ase); /* Cis establishment may came after setting group state to streaming, e.g. * for autonomous scenario when ase is sink */ Loading Loading @@ -2183,6 +2174,28 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } } void PrepareAndSendReceiverStartReady(LeAudioDevice* leAudioDevice, struct ase* ase) { std::vector<uint8_t> ids; std::vector<uint8_t> value; do { if (ase->direction == le_audio::types::kLeAudioDirectionSource) ids.push_back(ase->id); } while ((ase = leAudioDevice->GetNextActiveAse(ase))); if (ids.size() > 0) { le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady( ids, value); BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, value, GATT_WRITE_NO_RSP, NULL, NULL); return; } } void AseStateMachineProcessEnabling( struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) { Loading @@ -2196,8 +2209,32 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING; if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { if (ase->data_path_state < AudioStreamDataPathState::CIS_PENDING) { /* We are here because of the reconnection of the single device. */ CisCreateForDevice(leAudioDevice); } if (!leAudioDevice->HaveAllActiveAsesCisEst()) { /* More cis established events has to come */ return; } if (!leAudioDevice->IsReadyToCreateStream()) { /* Device still remains in ready to create stream state. It means * that more enabling status notifications has to come. */ return; } /* All CISes created. Send start ready for source ASE before we can go * to streaming state. */ struct ase* ase = leAudioDevice->GetFirstActiveAse(); ASSERT_LOG(ase != nullptr, "shouldn't be called without an active ASE, device %s", leAudioDevice->address_.ToString().c_str()); PrepareAndSendReceiverStartReady(leAudioDevice, ase); return; } Loading
system/bta/le_audio/state_machine_test.cc +112 −0 Original line number Diff line number Diff line Loading @@ -3097,6 +3097,118 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStream) { ASSERT_NE(ase->retrans_nb, 0); } TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { const auto context_type = kContextTypeConversational; const auto leaudio_group_id = 6; const auto num_devices = 2; ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_ccid); // Prepare multiple fake connected devices in a group auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); ASSERT_EQ(group->Size(), num_devices); PrepareConfigureCodecHandler(group); PrepareConfigureQosHandler(group); PrepareEnableHandler(group); PrepareReceiverStartReady(group); PrepareDisableHandler(group); PrepareReleaseHandler(group); auto* leAudioDevice = group->GetFirstDevice(); LeAudioDevice* lastDevice; LeAudioDevice* fistDevice = leAudioDevice; auto expected_devices_written = 0; while (leAudioDevice) { /* Three Writes: * 1: Codec Config * 2: Codec QoS * 3: Enabling */ lastDevice = leAudioDevice; EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(3)); expected_devices_written++; leAudioDevice = group->GetNextDevice(leAudioDevice); } ASSERT_EQ(expected_devices_written, num_devices); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4); InjectInitialIdleNotification(group); // Start the configuration and stream Conversational content LeAudioGroupStateMachine::Get()->StartStream( group, static_cast<LeAudioContextType>(context_type), types::AudioContexts(context_type)); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT); InjectAclDisconnected(group, lastDevice); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); lastDevice->conn_id_ = 3; group->UpdateAudioContextTypeAvailability(); // Make sure ASE with disconnected CIS are not left in STREAMING ASSERT_EQ(lastDevice->GetFirstAseWithState( ::le_audio::types::kLeAudioDirectionSink, types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING), nullptr); ASSERT_EQ(lastDevice->GetFirstAseWithState( ::le_audio::types::kLeAudioDirectionSource, types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING), nullptr); EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_, lastDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(3)); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); // Verify that the joining device receives the right CCID list auto lastMeta = lastDevice->GetFirstActiveAse()->metadata; bool parsedOk = false; auto ltv = le_audio::types::LeAudioLtvMap::Parse(lastMeta.data(), lastMeta.size(), parsedOk); ASSERT_TRUE(parsedOk); auto ccids = ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList); ASSERT_TRUE(ccids.has_value()); ASSERT_NE(std::find(ccids->begin(), ccids->end(), call_ccid), ccids->end()); /* Verify that ASE of first device are still good*/ auto ase = fistDevice->GetFirstActiveAse(); ASSERT_NE(ase->max_transport_latency, 0); ASSERT_NE(ase->retrans_nb, 0); // Make sure ASEs with reconnected CIS are in STREAMING state ASSERT_TRUE(lastDevice->HaveAllActiveAsesSameState( types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)); } TEST_F(StateMachineTest, StartStreamAfterConfigure) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; Loading