Loading system/bta/le_audio/client.cc +50 −14 Original line number Diff line number Diff line Loading @@ -517,6 +517,12 @@ class LeAudioClientImpl : public LeAudioClient { DisconnectDevice(leAudioDevice, true, recovery); leAudioDevice = group->GetNextActiveDevice(leAudioDevice); } while (leAudioDevice); if (recovery) { /* Both devices will be disconnected soon. Notify upper layer that group * is inactive */ groupSetAndNotifyInactive(); } } void OnDeviceAutonomousStateTransitionTimeout(LeAudioDevice* leAudioDevice) { Loading Loading @@ -1118,26 +1124,35 @@ class LeAudioClientImpl : public LeAudioClient { return group->is_duplex_preference_le_audio; } void GroupSetActive(const int group_id) override { LOG_INFO(" group_id: %d", group_id); if (group_id == bluetooth::groups::kGroupUnknown) { void groupSetAndNotifyInactive(void) { if (active_group_id_ == bluetooth::groups::kGroupUnknown) { /* Nothing to do */ return; } LOG_INFO("Active group_id changed %d -> %d", active_group_id_, group_id); auto group_id_to_close = active_group_id_; active_group_id_ = bluetooth::groups::kGroupUnknown; LOG_INFO("Group id: %d", group_id_to_close); if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_); StopAudio(); ClientAudioInterfaceRelease(); callbacks_->OnGroupStatus(group_id_to_close, GroupStatus::INACTIVE); } void GroupSetActive(const int group_id) override { LOG_INFO(" group_id: %d", group_id); if (group_id == bluetooth::groups::kGroupUnknown) { if (active_group_id_ == bluetooth::groups::kGroupUnknown) { /* Nothing to do */ return; } LOG_INFO("Active group_id changed %d -> %d", active_group_id_, group_id); auto group_id_to_close = active_group_id_; groupSetAndNotifyInactive(); GroupStop(group_id_to_close); callbacks_->OnGroupStatus(group_id_to_close, GroupStatus::INACTIVE); return; } Loading Loading @@ -1202,18 +1217,24 @@ class LeAudioClientImpl : public LeAudioClient { return; } if (active_group_id_ == bluetooth::groups::kGroupUnknown) { auto previous_active_group = active_group_id_; LOG_INFO("Active group_id changed %d -> %d", previous_active_group, group_id); if (previous_active_group == bluetooth::groups::kGroupUnknown) { /* Expose audio sessions if there was no previous active group */ StartAudioSession(group, ¤t_source_codec_config, ¤t_sink_codec_config); active_group_id_ = group_id; } else { /* In case there was an active group. Stop the stream */ GroupStop(active_group_id_); callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE); /* In case there was an active group. Stop the stream, but before that, set * the new group so the group change is correctly handled in OnStateMachineStatusReportCb */ active_group_id_ = group_id; GroupStop(previous_active_group); callbacks_->OnGroupStatus(previous_active_group, GroupStatus::INACTIVE); } LOG_INFO("Active group_id changed %d -> %d", active_group_id_, group_id); active_group_id_ = group_id; callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE); SendAudioGroupSelectableCodecConfigChanged(group); } Loading Loading @@ -5422,6 +5443,21 @@ class LeAudioClientImpl : public LeAudioClient { } case GroupStreamStatus::RELEASING: case GroupStreamStatus::SUSPENDING: if (active_group_id_ != bluetooth::groups::kGroupUnknown && (active_group_id_ == group->group_id_) && !group->IsPendingConfiguration() && (audio_sender_state_ == AudioState::STARTED || audio_receiver_state_ == AudioState::STARTED)) { /* If releasing state is happening but it was not initiated either by * reconfiguration or Audio Framework actions either by the Active group change, * it means that it is some internal state machine error. This is very unlikely and * for now just Inactivate the group. */ LOG_ERROR("Internal state machine error"); group->PrintDebugState(); groupSetAndNotifyInactive(); } if (audio_sender_state_ != AudioState::IDLE) audio_sender_state_ = AudioState::RELEASING; Loading system/bta/le_audio/le_audio_client_test.cc +63 −0 Original line number Diff line number Diff line Loading @@ -4326,6 +4326,69 @@ TEST_F(UnicastTest, RemoveNodeWhileStreaming) { Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, InactiveDeviceOnInternalStateMachineError) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming constexpr uint8_t cis_count_out = 1; constexpr uint8_t cis_count_in = 0; constexpr int gmcs_ccid = 1; constexpr int gtbs_ccid = 2; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1); LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); types::BidirectionalPair<std::vector<uint8_t>> ccids = {.sink = {gmcs_ccid}, .source = {}}; EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(group_id, GroupStatus::INACTIVE)) .Times(1); /* This is internal error of the state machine */ state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::RELEASING); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, GroupingAddTwiceNoRemove) { // Earbud connects without known grouping uint8_t group_id0 = bluetooth::groups::kGroupUnknown; Loading Loading
system/bta/le_audio/client.cc +50 −14 Original line number Diff line number Diff line Loading @@ -517,6 +517,12 @@ class LeAudioClientImpl : public LeAudioClient { DisconnectDevice(leAudioDevice, true, recovery); leAudioDevice = group->GetNextActiveDevice(leAudioDevice); } while (leAudioDevice); if (recovery) { /* Both devices will be disconnected soon. Notify upper layer that group * is inactive */ groupSetAndNotifyInactive(); } } void OnDeviceAutonomousStateTransitionTimeout(LeAudioDevice* leAudioDevice) { Loading Loading @@ -1118,26 +1124,35 @@ class LeAudioClientImpl : public LeAudioClient { return group->is_duplex_preference_le_audio; } void GroupSetActive(const int group_id) override { LOG_INFO(" group_id: %d", group_id); if (group_id == bluetooth::groups::kGroupUnknown) { void groupSetAndNotifyInactive(void) { if (active_group_id_ == bluetooth::groups::kGroupUnknown) { /* Nothing to do */ return; } LOG_INFO("Active group_id changed %d -> %d", active_group_id_, group_id); auto group_id_to_close = active_group_id_; active_group_id_ = bluetooth::groups::kGroupUnknown; LOG_INFO("Group id: %d", group_id_to_close); if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_); StopAudio(); ClientAudioInterfaceRelease(); callbacks_->OnGroupStatus(group_id_to_close, GroupStatus::INACTIVE); } void GroupSetActive(const int group_id) override { LOG_INFO(" group_id: %d", group_id); if (group_id == bluetooth::groups::kGroupUnknown) { if (active_group_id_ == bluetooth::groups::kGroupUnknown) { /* Nothing to do */ return; } LOG_INFO("Active group_id changed %d -> %d", active_group_id_, group_id); auto group_id_to_close = active_group_id_; groupSetAndNotifyInactive(); GroupStop(group_id_to_close); callbacks_->OnGroupStatus(group_id_to_close, GroupStatus::INACTIVE); return; } Loading Loading @@ -1202,18 +1217,24 @@ class LeAudioClientImpl : public LeAudioClient { return; } if (active_group_id_ == bluetooth::groups::kGroupUnknown) { auto previous_active_group = active_group_id_; LOG_INFO("Active group_id changed %d -> %d", previous_active_group, group_id); if (previous_active_group == bluetooth::groups::kGroupUnknown) { /* Expose audio sessions if there was no previous active group */ StartAudioSession(group, ¤t_source_codec_config, ¤t_sink_codec_config); active_group_id_ = group_id; } else { /* In case there was an active group. Stop the stream */ GroupStop(active_group_id_); callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE); /* In case there was an active group. Stop the stream, but before that, set * the new group so the group change is correctly handled in OnStateMachineStatusReportCb */ active_group_id_ = group_id; GroupStop(previous_active_group); callbacks_->OnGroupStatus(previous_active_group, GroupStatus::INACTIVE); } LOG_INFO("Active group_id changed %d -> %d", active_group_id_, group_id); active_group_id_ = group_id; callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE); SendAudioGroupSelectableCodecConfigChanged(group); } Loading Loading @@ -5422,6 +5443,21 @@ class LeAudioClientImpl : public LeAudioClient { } case GroupStreamStatus::RELEASING: case GroupStreamStatus::SUSPENDING: if (active_group_id_ != bluetooth::groups::kGroupUnknown && (active_group_id_ == group->group_id_) && !group->IsPendingConfiguration() && (audio_sender_state_ == AudioState::STARTED || audio_receiver_state_ == AudioState::STARTED)) { /* If releasing state is happening but it was not initiated either by * reconfiguration or Audio Framework actions either by the Active group change, * it means that it is some internal state machine error. This is very unlikely and * for now just Inactivate the group. */ LOG_ERROR("Internal state machine error"); group->PrintDebugState(); groupSetAndNotifyInactive(); } if (audio_sender_state_ != AudioState::IDLE) audio_sender_state_ = AudioState::RELEASING; Loading
system/bta/le_audio/le_audio_client_test.cc +63 −0 Original line number Diff line number Diff line Loading @@ -4326,6 +4326,69 @@ TEST_F(UnicastTest, RemoveNodeWhileStreaming) { Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, InactiveDeviceOnInternalStateMachineError) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; SetSampleDatabaseEarbudsValid( 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004, /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnConnectionState(ConnectionState::CONNECTED, test_address0)) .Times(1); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) .WillOnce(DoAll(SaveArg<1>(&group_id))); ConnectLeAudio(test_address0); ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); // Start streaming constexpr uint8_t cis_count_out = 1; constexpr uint8_t cis_count_in = 0; constexpr int gmcs_ccid = 1; constexpr int gtbs_ccid = 2; // Audio sessions are started only when device gets active EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1); LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); types::BidirectionalPair<std::vector<uint8_t>> ccids = {.sink = {gmcs_ccid}, .source = {}}; EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(&mock_le_audio_source_hal_client_); Mock::VerifyAndClearExpectations(&mock_state_machine_); SyncOnMainLoop(); // Verify Data transfer on one audio source cis TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(group_id, GroupStatus::INACTIVE)) .Times(1); /* This is internal error of the state machine */ state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::RELEASING); SyncOnMainLoop(); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); } TEST_F(UnicastTest, GroupingAddTwiceNoRemove) { // Earbud connects without known grouping uint8_t group_id0 = bluetooth::groups::kGroupUnknown; Loading