Loading system/bta/le_audio/client.cc +2 −0 Original line number Diff line number Diff line Loading @@ -460,6 +460,8 @@ class LeAudioClientImpl : public LeAudioClient { ToString(group->GetTargetState()).c_str()); group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); group->PrintDebugState(); /* There is an issue with a setting up stream or any other operation which * are gatt operations. It means peer is not responsable. Lets close ACL */ Loading system/bta/le_audio/devices.cc +15 −17 Original line number Diff line number Diff line Loading @@ -882,16 +882,12 @@ bool LeAudioDeviceGroup::IsGroupStreamReady(void) { return iter == leAudioDevices_.end(); } bool LeAudioDeviceGroup::HaveAllActiveDevicesCisDisc(void) { auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) { if (d.expired()) return false; else return !(((d.lock()).get())->HaveAllAsesCisDisc()); }); return iter == leAudioDevices_.end(); bool LeAudioDeviceGroup::HaveAllCisesDisconnected(void) { for (auto const dev : leAudioDevices_) { if (dev.expired()) continue; if (dev.lock().get()->HaveAnyCisConnected()) return false; } return true; } uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(void) { Loading Loading @@ -2400,13 +2396,15 @@ bool LeAudioDevice::HaveAllActiveAsesCisEst(void) { return iter == ases_.end(); } bool LeAudioDevice::HaveAllAsesCisDisc(void) { auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { return ase.active && (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED); }); return iter == ases_.end(); bool LeAudioDevice::HaveAnyCisConnected(void) { /* Pending and Disconnecting is considered as connected in this function */ for (auto const ase : ases_) { if (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED && ase.data_path_state != AudioStreamDataPathState::IDLE) { return true; } } return false; } bool LeAudioDevice::HasCisId(uint8_t id) { Loading system/bta/le_audio/devices.h +2 −2 Original line number Diff line number Diff line Loading @@ -155,7 +155,7 @@ class LeAudioDevice { bool IsReadyToCreateStream(void); bool IsReadyToSuspendStream(void); bool HaveAllActiveAsesCisEst(void); bool HaveAllAsesCisDisc(void); bool HaveAnyCisConnected(void); bool HasCisId(uint8_t id); uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase); const struct types::acs_ac_record* GetCodecConfigurationSupportedPac( Loading Loading @@ -286,7 +286,7 @@ class LeAudioDeviceGroup { bool IsDeviceInTheGroup(LeAudioDevice* leAudioDevice); bool HaveAllActiveDevicesAsesTheSameState(types::AseState state); bool IsGroupStreamReady(void); bool HaveAllActiveDevicesCisDisc(void); bool HaveAllCisesDisconnected(void); uint8_t GetFirstFreeCisId(void); uint8_t GetFirstFreeCisId(types::CisType cis_type); void CigGenerateCisIds(types::LeAudioContextType context_type); Loading system/bta/le_audio/state_machine.cc +90 −14 Original line number Diff line number Diff line Loading @@ -638,7 +638,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { LOG_DEBUG( " device: %s, group connected: %d, all active ase disconnected:: %d", leAudioDevice->address_.ToString().c_str(), group->IsAnyDeviceConnected(), group->HaveAllActiveDevicesCisDisc()); group->IsAnyDeviceConnected(), group->HaveAllCisesDisconnected()); /* Update the current group audio context availability which could change * due to disconnected group member. Loading @@ -649,8 +649,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { * If there is active CIS, do nothing here. Just update the available * contexts table. */ if (group->IsAnyDeviceConnected() && !group->HaveAllActiveDevicesCisDisc()) { if (group->IsAnyDeviceConnected() && !group->HaveAllCisesDisconnected()) { if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { /* We keep streaming but want others to let know user that it might be * need to update offloader with new CIS configuration Loading Loading @@ -693,7 +692,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { * or pending. If CIS is established, this will be handled in disconnected * complete event */ if (group->HaveAllActiveDevicesCisDisc()) { if (group->HaveAllCisesDisconnected()) { RemoveCigForGroup(group); } Loading Loading @@ -837,7 +836,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { * If there is other device connected and streaming, just leave it as it * is, otherwise stop the stream. */ if (!group->HaveAllActiveDevicesCisDisc()) { if (!group->HaveAllCisesDisconnected()) { /* There is ASE streaming for some device. Continue streaming. */ LOG_WARN( "Group member disconnected during streaming. Cis handle 0x%04x", Loading @@ -863,7 +862,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { */ if ((group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) && group->HaveAllActiveDevicesCisDisc()) { group->HaveAllCisesDisconnected()) { /* No more transition for group */ alarm_cancel(watchdog_); Loading @@ -873,15 +872,58 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } break; case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: { /* Those two are used when closing the stream and CIS disconnection is * expected */ if (group->HaveAllActiveDevicesCisDisc()) { RemoveCigForGroup(group); if (!group->HaveAllCisesDisconnected()) { LOG_DEBUG( "Still waiting for all CISes being disconnected for group:%d", group->group_id_); return; } break; auto current_group_state = group->GetState(); LOG_INFO("group %d current state: %s, target state: %s", group->group_id_, bluetooth::common::ToString(current_group_state).c_str(), bluetooth::common::ToString(target_state).c_str()); /* It might happen that controller notified about CIS disconnection * later, after ASE state already changed. * In such an event, there is need to notify upper layer about state * from here. */ if (current_group_state == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { LOG_INFO( "Cises disconnected for group %d, we are good in Idle state.", group->group_id_); ReleaseCisIds(group); state_machine_callbacks_->StatusReportCb(group->group_id_, GroupStreamStatus::IDLE); } else if (current_group_state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) { auto reconfig = group->IsPendingConfiguration(); LOG_INFO( "Cises disconnected for group: %d, we are good in Configured " "state, reconfig=%d.", group->group_id_, reconfig); if (reconfig) { group->ClearPendingConfiguration(); state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER); /* No more transition for group */ alarm_cancel(watchdog_); } else { /* This is Autonomous change if both, target and current state * is CODEC_CONFIGURED */ if (target_state == current_group_state) { state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS); } } } RemoveCigForGroup(group); } break; default: break; } Loading Loading @@ -1468,9 +1510,21 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { PrepareAndSendRelease(leAudioDeviceNext); } else { /* Last node is in releasing state*/ if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_); group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); group->PrintDebugState(); /* If all CISes are disconnected, notify upper layer about IDLE state, * otherwise wait for */ if (!group->HaveAllCisesDisconnected()) { LOG_WARN( "Not all CISes removed before going to IDLE for group %d, " "waiting...", group->group_id_); group->PrintDebugState(); return; } if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_); ReleaseCisIds(group); state_machine_callbacks_->StatusReportCb(group->group_id_, GroupStreamStatus::IDLE); Loading Loading @@ -1651,6 +1705,19 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED && group->IsPendingConfiguration()) { LOG_INFO(" Configured state completed "); /* If all CISes are disconnected, notify upper layer about IDLE * state, otherwise wait for */ if (!group->HaveAllCisesDisconnected()) { LOG_WARN( "Not all CISes removed before going to CONFIGURED for group " "%d, " "waiting...", group->group_id_); group->PrintDebugState(); return; } group->ClearPendingConfiguration(); state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER); Loading Loading @@ -1789,7 +1856,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { PrepareAndSendRelease(leAudioDeviceNext); } else { /* Last node is in releasing state*/ if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_); group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); /* Remote device has cache and keep staying in configured state after Loading @@ -1797,6 +1863,16 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { * remote device. */ group->SetTargetState(group->GetState()); if (!group->HaveAllCisesDisconnected()) { LOG_WARN( "Not all CISes removed before going to IDLE for group %d, " "waiting...", group->group_id_); group->PrintDebugState(); return; } state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS); } Loading Loading @@ -1885,7 +1961,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); if (!group->HaveAllActiveDevicesCisDisc()) return; if (!group->HaveAllCisesDisconnected()) return; if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) { Loading system/bta/le_audio/state_machine_test.cc +257 −9 Original line number Diff line number Diff line Loading @@ -2885,15 +2885,15 @@ static void InjectCisDisconnected(LeAudioDeviceGroup* group, uint8_t reason) { bluetooth::hci::iso_manager::cis_disconnected_evt event; auto* ase = leAudioDevice->GetFirstActiveAse(); while (ase) { for (auto const ase : leAudioDevice->ases_) { if (ase.data_path_state != types::AudioStreamDataPathState::CIS_ASSIGNED && ase.data_path_state != types::AudioStreamDataPathState::IDLE) { event.reason = reason; event.cig_id = group->group_id_; event.cis_conn_hdl = ase->cis_conn_hdl; event.cis_conn_hdl = ase.cis_conn_hdl; LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected( group, leAudioDevice, &event); ase = leAudioDevice->GetNextActiveAse(ase); } } } Loading Loading @@ -3332,5 +3332,253 @@ TEST_F(StateMachineTest, BoundedHeadphonesConversationalToMediaChannelCount_1) { testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); } TEST_F(StateMachineTest, lateCisDisconnectedEvent_ConfiguredByUser) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; const auto num_devices = 1; ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid); // Prepare multiple fake connected devices in a group auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); ASSERT_EQ(group->Size(), num_devices); PrepareConfigureCodecHandler(group, 0, true); PrepareConfigureQosHandler(group); PrepareEnableHandler(group); PrepareDisableHandler(group); PrepareReleaseHandler(group); auto* leAudioDevice = group->GetFirstDevice(); auto expected_devices_written = 0; /* Three Writes: * 1: Codec Config * 2: Codec QoS * 3: Enabling */ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(3)); expected_devices_written++; ASSERT_EQ(expected_devices_written, num_devices); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); InjectInitialIdleNotification(group); // Start the configuration and stream Media content LeAudioGroupStateMachine::Get()->StartStream( group, static_cast<LeAudioContextType>(context_type), types::AudioContexts(context_type)); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); /* Prepare DisconnectCis mock to not symulate CisDisconnection */ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return()); /* Do reconfiguration */ group->SetPendingConfiguration(); // Validate GroupStreamStatus EXPECT_CALL( mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)); EXPECT_CALL(mock_callbacks_, StatusReportCb( leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER)) .Times(0); LeAudioGroupStateMachine::Get()->StopStream(group); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); EXPECT_CALL(mock_callbacks_, StatusReportCb( leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER)); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); } TEST_F(StateMachineTest, lateCisDisconnectedEvent_AutonomousConfigured) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; const auto num_devices = 1; ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid); // Prepare multiple fake connected devices in a group auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); ASSERT_EQ(group->Size(), num_devices); PrepareConfigureCodecHandler(group, 0, true); PrepareConfigureQosHandler(group); PrepareEnableHandler(group); PrepareDisableHandler(group); PrepareReleaseHandler(group); auto* leAudioDevice = group->GetFirstDevice(); auto expected_devices_written = 0; /* Three Writes: * 1: Codec Config * 2: Codec QoS * 3: Enabling */ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(3)); expected_devices_written++; ASSERT_EQ(expected_devices_written, num_devices); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); InjectInitialIdleNotification(group); // Start the configuration and stream Media content LeAudioGroupStateMachine::Get()->StartStream( group, static_cast<LeAudioContextType>(context_type), types::AudioContexts(context_type)); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); /* Prepare DisconnectCis mock to not symulate CisDisconnection */ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return()); // Validate GroupStreamStatus EXPECT_CALL( mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)); EXPECT_CALL( mock_callbacks_, StatusReportCb( leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS)) .Times(0); // Stop the stream LeAudioGroupStateMachine::Get()->StopStream(group); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); EXPECT_CALL( mock_callbacks_, StatusReportCb( leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS)); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); } TEST_F(StateMachineTest, lateCisDisconnectedEvent_Idle) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; const auto num_devices = 1; ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid); // Prepare multiple fake connected devices in a group auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); ASSERT_EQ(group->Size(), num_devices); PrepareConfigureCodecHandler(group); PrepareConfigureQosHandler(group); PrepareEnableHandler(group); PrepareDisableHandler(group); PrepareReleaseHandler(group); auto* leAudioDevice = group->GetFirstDevice(); auto expected_devices_written = 0; /* Three Writes: * 1: Codec Config * 2: Codec QoS * 3: Enabling */ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(3)); expected_devices_written++; ASSERT_EQ(expected_devices_written, num_devices); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); InjectInitialIdleNotification(group); // Start the configuration and stream Media content LeAudioGroupStateMachine::Get()->StartStream( group, static_cast<LeAudioContextType>(context_type), types::AudioContexts(context_type)); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); /* Prepare DisconnectCis mock to not symulate CisDisconnection */ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return()); // Validate GroupStreamStatus EXPECT_CALL( mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)); EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::IDLE)) .Times(0); // Stop the stream LeAudioGroupStateMachine::Get()->StopStream(group); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::IDLE)); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); } } // namespace internal } // namespace le_audio Loading
system/bta/le_audio/client.cc +2 −0 Original line number Diff line number Diff line Loading @@ -460,6 +460,8 @@ class LeAudioClientImpl : public LeAudioClient { ToString(group->GetTargetState()).c_str()); group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); group->PrintDebugState(); /* There is an issue with a setting up stream or any other operation which * are gatt operations. It means peer is not responsable. Lets close ACL */ Loading
system/bta/le_audio/devices.cc +15 −17 Original line number Diff line number Diff line Loading @@ -882,16 +882,12 @@ bool LeAudioDeviceGroup::IsGroupStreamReady(void) { return iter == leAudioDevices_.end(); } bool LeAudioDeviceGroup::HaveAllActiveDevicesCisDisc(void) { auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) { if (d.expired()) return false; else return !(((d.lock()).get())->HaveAllAsesCisDisc()); }); return iter == leAudioDevices_.end(); bool LeAudioDeviceGroup::HaveAllCisesDisconnected(void) { for (auto const dev : leAudioDevices_) { if (dev.expired()) continue; if (dev.lock().get()->HaveAnyCisConnected()) return false; } return true; } uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(void) { Loading Loading @@ -2400,13 +2396,15 @@ bool LeAudioDevice::HaveAllActiveAsesCisEst(void) { return iter == ases_.end(); } bool LeAudioDevice::HaveAllAsesCisDisc(void) { auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { return ase.active && (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED); }); return iter == ases_.end(); bool LeAudioDevice::HaveAnyCisConnected(void) { /* Pending and Disconnecting is considered as connected in this function */ for (auto const ase : ases_) { if (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED && ase.data_path_state != AudioStreamDataPathState::IDLE) { return true; } } return false; } bool LeAudioDevice::HasCisId(uint8_t id) { Loading
system/bta/le_audio/devices.h +2 −2 Original line number Diff line number Diff line Loading @@ -155,7 +155,7 @@ class LeAudioDevice { bool IsReadyToCreateStream(void); bool IsReadyToSuspendStream(void); bool HaveAllActiveAsesCisEst(void); bool HaveAllAsesCisDisc(void); bool HaveAnyCisConnected(void); bool HasCisId(uint8_t id); uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase); const struct types::acs_ac_record* GetCodecConfigurationSupportedPac( Loading Loading @@ -286,7 +286,7 @@ class LeAudioDeviceGroup { bool IsDeviceInTheGroup(LeAudioDevice* leAudioDevice); bool HaveAllActiveDevicesAsesTheSameState(types::AseState state); bool IsGroupStreamReady(void); bool HaveAllActiveDevicesCisDisc(void); bool HaveAllCisesDisconnected(void); uint8_t GetFirstFreeCisId(void); uint8_t GetFirstFreeCisId(types::CisType cis_type); void CigGenerateCisIds(types::LeAudioContextType context_type); Loading
system/bta/le_audio/state_machine.cc +90 −14 Original line number Diff line number Diff line Loading @@ -638,7 +638,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { LOG_DEBUG( " device: %s, group connected: %d, all active ase disconnected:: %d", leAudioDevice->address_.ToString().c_str(), group->IsAnyDeviceConnected(), group->HaveAllActiveDevicesCisDisc()); group->IsAnyDeviceConnected(), group->HaveAllCisesDisconnected()); /* Update the current group audio context availability which could change * due to disconnected group member. Loading @@ -649,8 +649,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { * If there is active CIS, do nothing here. Just update the available * contexts table. */ if (group->IsAnyDeviceConnected() && !group->HaveAllActiveDevicesCisDisc()) { if (group->IsAnyDeviceConnected() && !group->HaveAllCisesDisconnected()) { if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { /* We keep streaming but want others to let know user that it might be * need to update offloader with new CIS configuration Loading Loading @@ -693,7 +692,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { * or pending. If CIS is established, this will be handled in disconnected * complete event */ if (group->HaveAllActiveDevicesCisDisc()) { if (group->HaveAllCisesDisconnected()) { RemoveCigForGroup(group); } Loading Loading @@ -837,7 +836,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { * If there is other device connected and streaming, just leave it as it * is, otherwise stop the stream. */ if (!group->HaveAllActiveDevicesCisDisc()) { if (!group->HaveAllCisesDisconnected()) { /* There is ASE streaming for some device. Continue streaming. */ LOG_WARN( "Group member disconnected during streaming. Cis handle 0x%04x", Loading @@ -863,7 +862,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { */ if ((group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) && group->HaveAllActiveDevicesCisDisc()) { group->HaveAllCisesDisconnected()) { /* No more transition for group */ alarm_cancel(watchdog_); Loading @@ -873,15 +872,58 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } break; case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: { /* Those two are used when closing the stream and CIS disconnection is * expected */ if (group->HaveAllActiveDevicesCisDisc()) { RemoveCigForGroup(group); if (!group->HaveAllCisesDisconnected()) { LOG_DEBUG( "Still waiting for all CISes being disconnected for group:%d", group->group_id_); return; } break; auto current_group_state = group->GetState(); LOG_INFO("group %d current state: %s, target state: %s", group->group_id_, bluetooth::common::ToString(current_group_state).c_str(), bluetooth::common::ToString(target_state).c_str()); /* It might happen that controller notified about CIS disconnection * later, after ASE state already changed. * In such an event, there is need to notify upper layer about state * from here. */ if (current_group_state == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { LOG_INFO( "Cises disconnected for group %d, we are good in Idle state.", group->group_id_); ReleaseCisIds(group); state_machine_callbacks_->StatusReportCb(group->group_id_, GroupStreamStatus::IDLE); } else if (current_group_state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) { auto reconfig = group->IsPendingConfiguration(); LOG_INFO( "Cises disconnected for group: %d, we are good in Configured " "state, reconfig=%d.", group->group_id_, reconfig); if (reconfig) { group->ClearPendingConfiguration(); state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER); /* No more transition for group */ alarm_cancel(watchdog_); } else { /* This is Autonomous change if both, target and current state * is CODEC_CONFIGURED */ if (target_state == current_group_state) { state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS); } } } RemoveCigForGroup(group); } break; default: break; } Loading Loading @@ -1468,9 +1510,21 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { PrepareAndSendRelease(leAudioDeviceNext); } else { /* Last node is in releasing state*/ if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_); group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); group->PrintDebugState(); /* If all CISes are disconnected, notify upper layer about IDLE state, * otherwise wait for */ if (!group->HaveAllCisesDisconnected()) { LOG_WARN( "Not all CISes removed before going to IDLE for group %d, " "waiting...", group->group_id_); group->PrintDebugState(); return; } if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_); ReleaseCisIds(group); state_machine_callbacks_->StatusReportCb(group->group_id_, GroupStreamStatus::IDLE); Loading Loading @@ -1651,6 +1705,19 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED && group->IsPendingConfiguration()) { LOG_INFO(" Configured state completed "); /* If all CISes are disconnected, notify upper layer about IDLE * state, otherwise wait for */ if (!group->HaveAllCisesDisconnected()) { LOG_WARN( "Not all CISes removed before going to CONFIGURED for group " "%d, " "waiting...", group->group_id_); group->PrintDebugState(); return; } group->ClearPendingConfiguration(); state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER); Loading Loading @@ -1789,7 +1856,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { PrepareAndSendRelease(leAudioDeviceNext); } else { /* Last node is in releasing state*/ if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_); group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); /* Remote device has cache and keep staying in configured state after Loading @@ -1797,6 +1863,16 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { * remote device. */ group->SetTargetState(group->GetState()); if (!group->HaveAllCisesDisconnected()) { LOG_WARN( "Not all CISes removed before going to IDLE for group %d, " "waiting...", group->group_id_); group->PrintDebugState(); return; } state_machine_callbacks_->StatusReportCb( group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS); } Loading Loading @@ -1885,7 +1961,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); if (!group->HaveAllActiveDevicesCisDisc()) return; if (!group->HaveAllCisesDisconnected()) return; if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) { Loading
system/bta/le_audio/state_machine_test.cc +257 −9 Original line number Diff line number Diff line Loading @@ -2885,15 +2885,15 @@ static void InjectCisDisconnected(LeAudioDeviceGroup* group, uint8_t reason) { bluetooth::hci::iso_manager::cis_disconnected_evt event; auto* ase = leAudioDevice->GetFirstActiveAse(); while (ase) { for (auto const ase : leAudioDevice->ases_) { if (ase.data_path_state != types::AudioStreamDataPathState::CIS_ASSIGNED && ase.data_path_state != types::AudioStreamDataPathState::IDLE) { event.reason = reason; event.cig_id = group->group_id_; event.cis_conn_hdl = ase->cis_conn_hdl; event.cis_conn_hdl = ase.cis_conn_hdl; LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected( group, leAudioDevice, &event); ase = leAudioDevice->GetNextActiveAse(ase); } } } Loading Loading @@ -3332,5 +3332,253 @@ TEST_F(StateMachineTest, BoundedHeadphonesConversationalToMediaChannelCount_1) { testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); } TEST_F(StateMachineTest, lateCisDisconnectedEvent_ConfiguredByUser) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; const auto num_devices = 1; ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid); // Prepare multiple fake connected devices in a group auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); ASSERT_EQ(group->Size(), num_devices); PrepareConfigureCodecHandler(group, 0, true); PrepareConfigureQosHandler(group); PrepareEnableHandler(group); PrepareDisableHandler(group); PrepareReleaseHandler(group); auto* leAudioDevice = group->GetFirstDevice(); auto expected_devices_written = 0; /* Three Writes: * 1: Codec Config * 2: Codec QoS * 3: Enabling */ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(3)); expected_devices_written++; ASSERT_EQ(expected_devices_written, num_devices); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); InjectInitialIdleNotification(group); // Start the configuration and stream Media content LeAudioGroupStateMachine::Get()->StartStream( group, static_cast<LeAudioContextType>(context_type), types::AudioContexts(context_type)); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); /* Prepare DisconnectCis mock to not symulate CisDisconnection */ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return()); /* Do reconfiguration */ group->SetPendingConfiguration(); // Validate GroupStreamStatus EXPECT_CALL( mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)); EXPECT_CALL(mock_callbacks_, StatusReportCb( leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER)) .Times(0); LeAudioGroupStateMachine::Get()->StopStream(group); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); EXPECT_CALL(mock_callbacks_, StatusReportCb( leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER)); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); } TEST_F(StateMachineTest, lateCisDisconnectedEvent_AutonomousConfigured) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; const auto num_devices = 1; ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid); // Prepare multiple fake connected devices in a group auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); ASSERT_EQ(group->Size(), num_devices); PrepareConfigureCodecHandler(group, 0, true); PrepareConfigureQosHandler(group); PrepareEnableHandler(group); PrepareDisableHandler(group); PrepareReleaseHandler(group); auto* leAudioDevice = group->GetFirstDevice(); auto expected_devices_written = 0; /* Three Writes: * 1: Codec Config * 2: Codec QoS * 3: Enabling */ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(3)); expected_devices_written++; ASSERT_EQ(expected_devices_written, num_devices); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); InjectInitialIdleNotification(group); // Start the configuration and stream Media content LeAudioGroupStateMachine::Get()->StartStream( group, static_cast<LeAudioContextType>(context_type), types::AudioContexts(context_type)); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); /* Prepare DisconnectCis mock to not symulate CisDisconnection */ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return()); // Validate GroupStreamStatus EXPECT_CALL( mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)); EXPECT_CALL( mock_callbacks_, StatusReportCb( leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS)) .Times(0); // Stop the stream LeAudioGroupStateMachine::Get()->StopStream(group); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); EXPECT_CALL( mock_callbacks_, StatusReportCb( leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS)); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); } TEST_F(StateMachineTest, lateCisDisconnectedEvent_Idle) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; const auto num_devices = 1; ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid); // Prepare multiple fake connected devices in a group auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); ASSERT_EQ(group->Size(), num_devices); PrepareConfigureCodecHandler(group); PrepareConfigureQosHandler(group); PrepareEnableHandler(group); PrepareDisableHandler(group); PrepareReleaseHandler(group); auto* leAudioDevice = group->GetFirstDevice(); auto expected_devices_written = 0; /* Three Writes: * 1: Codec Config * 2: Codec QoS * 3: Enabling */ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(3)); expected_devices_written++; ASSERT_EQ(expected_devices_written, num_devices); EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); InjectInitialIdleNotification(group); // Start the configuration and stream Media content LeAudioGroupStateMachine::Get()->StartStream( group, static_cast<LeAudioContextType>(context_type), types::AudioContexts(context_type)); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_); /* Prepare DisconnectCis mock to not symulate CisDisconnection */ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return()); // Validate GroupStreamStatus EXPECT_CALL( mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING)); EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::IDLE)) .Times(0); // Stop the stream LeAudioGroupStateMachine::Get()->StopStream(group); // Check if group has transitioned to a proper state ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); EXPECT_CALL(mock_callbacks_, StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::IDLE)); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); } } // namespace internal } // namespace le_audio