Loading system/bta/le_audio/devices.cc +30 −19 Original line number Diff line number Diff line Loading @@ -250,13 +250,13 @@ bool LeAudioDevice::ConfigureAses( /* First try to use the already configured ASE */ auto ase = GetFirstActiveAseByDirection(direction); if (ase) { log::info("Using an already active ASE id={}", ase->id); log::info("{}, using an already active ASE id={}", address_, ase->id); } else { ase = GetFirstInactiveAse(direction, reuse_cis_id); } if (!ase) { log::error("Unable to find an ASE to configure"); log::error("{}, unable to find an ASE to configure", address_); PrintDebugState(); return false; } Loading Loading @@ -294,7 +294,7 @@ bool LeAudioDevice::ConfigureAses( auto const& ase_cfg = ase_configs.at(i); if (utils::IsCodecUsingLtvFormat(ase_cfg.codec.id) && !utils::GetConfigurationSupportedPac(pacs, ase_cfg.codec)) { log::error("No matching PAC found. Stop the activation."); log::error("{}, no matching PAC found. Stop the activation.", address_); return false; } } Loading @@ -309,8 +309,9 @@ bool LeAudioDevice::ConfigureAses( if (CodecManager::GetInstance()->CheckCodecConfigIsDualBiDirSwb( *audio_set_conf)) { log::error( "Trying to configure the dual bidir SWB, but the feature is " "disabled. This should not happen! Skipping ASE activation."); "{}, trying to configure the dual bidir SWB, but the feature is " "disabled. This should not happen! Skipping ASE activation.", address_); return true; } } Loading Loading @@ -443,7 +444,7 @@ void LeAudioDevice::ParseHeadtrackingCodec( */ std::vector<uint8_t> ltv = pac.metadata; if (ltv.size() < 7) { log::info("Headtracker codec does not have metadata"); log::info("{}, headtracker codec does not have metadata", address_); return; } Loading @@ -452,7 +453,7 @@ void LeAudioDevice::ParseHeadtrackingCodec( ltv[3] != (types::kLeAudioVendorCompanyIdGoogle >> 8) || ltv[4] != types::kLeAudioMetadataHeadtrackerTransportLen || ltv[5] != types::kLeAudioMetadataHeadtrackerTransportVal) { log::warn("Headtracker codec metadata invalid"); log::warn("{}, headtracker codec metadata invalid", address_); return; } Loading @@ -461,13 +462,13 @@ void LeAudioDevice::ParseHeadtrackingCodec( if ((supported_transports & types::kLeAudioMetadataHeadtrackerTransportLeAcl) != 0) { log::debug("Headtracking supported over LE-ACL"); log::debug("{}, headtracking supported over LE-ACL", address_); dsa_modes.push_back(DsaMode::ACL); } if ((supported_transports & types::kLeAudioMetadataHeadtrackerTransportLeIso) != 0) { log::debug("Headtracking supported over LE-ISO"); log::debug("{}, headtracking supported over LE-ISO", address_); dsa_modes.push_back(DsaMode::ISO_SW); dsa_modes.push_back(DsaMode::ISO_HW); } Loading @@ -481,7 +482,7 @@ void LeAudioDevice::RegisterPACs( std::vector<struct types::acs_ac_record>* pac_recs) { /* Clear PAC database for characteristic in case if re-read, indicated */ if (!pac_db->empty()) { log::debug("upgrade PACs for characteristic"); log::debug("{}, upgrade PACs for characteristic", address_); pac_db->clear(); } Loading Loading @@ -578,7 +579,8 @@ struct ase* LeAudioDevice::GetNextActiveAseWithDifferentDirection( /* Invalid ase given */ if (std::distance(iter, ases_.end()) < 1) { log::debug("ASE {} does not use bidirectional CIS", base_ase->id); log::debug("{}, ASE {} does not use bidirectional CIS", address_, base_ase->id); return nullptr; } Loading Loading @@ -715,6 +717,7 @@ bool LeAudioDevice::HaveAnyUnconfiguredAses(void) { } bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) { log::verbose("{}", address_); auto iter = std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) { log::verbose("ASE id: {}, active: {}, state: {}", ase.id, ase.active, Loading @@ -727,6 +730,7 @@ bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) { bool LeAudioDevice::HaveAllActiveAsesSameDataPathState( types::DataPathState state) const { log::verbose("{}", address_); auto iter = std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) { log::verbose("ASE id: {}, active: {}, state: {}", ase.id, ase.active, Loading @@ -738,6 +742,7 @@ bool LeAudioDevice::HaveAllActiveAsesSameDataPathState( } bool LeAudioDevice::IsReadyToCreateStream(void) { log::verbose("{}", address_); auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { if (!ase.active) return false; Loading Loading @@ -785,6 +790,8 @@ bool LeAudioDevice::HaveAllActiveAsesCisEst(void) const { return true; } log::verbose("{}", address_); bool has_active_ase = false; auto iter = std::find_if(ases_.begin(), ases_.end(), [&](const auto& ase) { if (!has_active_ase && ase.active) { Loading Loading @@ -816,7 +823,7 @@ uint8_t LeAudioDevice::GetSupportedAudioChannelCounts(uint8_t direction) const { direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_; if (pacs.size() == 0) { log::error("missing PAC for direction {}", direction); log::error("{}, missing PAC for direction {}", address_, direction); return 0; } Loading @@ -826,7 +833,7 @@ uint8_t LeAudioDevice::GetSupportedAudioChannelCounts(uint8_t direction) const { for (const auto pac : pac_recs) { if (!utils::IsCodecUsingLtvFormat(pac.codec_id)) { log::warn("Unknown codec PAC record for codec: {}", log::warn(" {} Unknown codec PAC record for codec: {}", address_, bluetooth::common::ToString(pac.codec_id)); continue; } Loading Loading @@ -910,12 +917,14 @@ uint8_t LeAudioDevice::GetPreferredPhyBitmask(uint8_t preferred_phy) const { // Take the preferences if possible if (preferred_phy && (phy_bitmask & preferred_phy)) { phy_bitmask &= preferred_phy; log::debug("Using ASE preferred phy 0x{:02x}", log::debug("{}, using ASE preferred phy 0x{:02x}", address_, static_cast<int>(phy_bitmask)); } else { log::warn( "ASE preferred 0x{:02x} has nothing common with phy_bitfield 0x{:02x}", static_cast<int>(preferred_phy), static_cast<int>(phy_bitmask)); " {}, ASE preferred 0x{:02x} has nothing common with phy_bitfield " "0x{:02x}", address_, static_cast<int>(preferred_phy), static_cast<int>(phy_bitmask)); } return phy_bitmask; } Loading Loading @@ -1033,10 +1042,12 @@ void LeAudioDevice::DisconnectAcl(void) { void LeAudioDevice::SetAvailableContexts( BidirectionalPair<AudioContexts> contexts) { log::debug( "\n\t previous_contexts_.sink: {} \n\t previous_contexts_.source: {} " "{}: \n\t previous_contexts_.sink: {} \n\t previous_contexts_.source: {} " " " "\n\t new_contexts.sink: {} \n\t new_contexts.source: {} \n\t", avail_contexts_.sink.to_string(), avail_contexts_.source.to_string(), contexts.sink.to_string(), contexts.source.to_string()); address_, avail_contexts_.sink.to_string(), avail_contexts_.source.to_string(), contexts.sink.to_string(), contexts.source.to_string()); avail_contexts_.sink = contexts.sink; avail_contexts_.source = contexts.source; Loading system/bta/le_audio/state_machine.cc +77 −33 Original line number Diff line number Diff line Loading @@ -1149,12 +1149,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { if (ases_pair.sink && ases_pair.sink->state == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { SetAseState(leAudioDevice, ases_pair.sink, AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); } if (ases_pair.source && ases_pair.source->state == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { SetAseState(leAudioDevice, ases_pair.source, AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); } } Loading @@ -1162,7 +1162,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { auto target_state = group->GetTargetState(); switch (target_state) { case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: { /* Something wrong happen when streaming or when creating stream. * If there is other device connected and streaming, just leave it as it * is, otherwise stop the stream. Loading @@ -1175,6 +1175,29 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return; } /* CISes are disconnected, but it could be a case here, that there is * another set member trying to get STREAMING state. Can happen when * while streaming user switch buds. In such a case, lets try to allow * that device to continue */ LeAudioDevice* attaching_device = getDeviceTryingToAttachTheStream(group); if (attaching_device != nullptr) { /* There is a device willitng to stream. Let's wait for it to start * streaming */ auto active_ase = attaching_device->GetFirstActiveAse(); group->SetState(active_ase->state); /* this is just to start timer */ group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); log::info( "{} is still attaching to stream while other members got " "disconnected from the group_id: {}", attaching_device->address_, group->group_id_); return; } log::info("Lost all members from the group {}", group->group_id_); group->cig.cises.clear(); RemoveCigForGroup(group); Loading @@ -1185,6 +1208,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { state_machine_callbacks_->StatusReportCb(group->group_id_, GroupStreamStatus::IDLE); return; } case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: /* Intentional group disconnect has finished, but the last CIS in the Loading Loading @@ -1769,6 +1793,29 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { ase->state = state; } LeAudioDevice* getDeviceTryingToAttachTheStream(LeAudioDeviceGroup* group) { /* Device which is attaching the stream is just an active device not in * STREAMING state. the precondition is, that TargetState is Streaming */ log::debug("group_id: {}, targetState: {}", group->group_id_, ToString(group->GetTargetState())); if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { return nullptr; } for (auto dev = group->GetFirstActiveDevice(); dev != nullptr; dev = group->GetNextActiveDevice(dev)) { if (!dev->HaveAllActiveAsesSameState( AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) { log::debug("Attaching device {} to group_id: {}", dev->address_, group->group_id_); return dev; } } return nullptr; } void AseStateMachineProcessIdle( struct bluetooth::le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, LeAudioDeviceGroup* group, Loading @@ -1792,15 +1839,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return; } /* Before continue with release, make sure this is what is requested. * If not (e.g. only single device got disconnected), stop here */ if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { log::debug("Autonomus change of stated for device {}, ase id: {}", leAudioDevice->address_, ase->id); return; } if (!group->HaveAllActiveDevicesAsesTheSameState( AseState::BTA_LE_AUDIO_ASE_STATE_IDLE)) { log::debug("Waiting for more devices to get into idle state"); Loading @@ -1813,7 +1851,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { /* If all CISes are disconnected, notify upper layer about IDLE state, * otherwise wait for */ if (!group->HaveAllCisesDisconnected()) { if (!group->HaveAllCisesDisconnected() || getDeviceTryingToAttachTheStream(group) != nullptr) { log::warn( "Not all CISes removed before going to IDLE for group {}, " "waiting...", Loading Loading @@ -2079,7 +2118,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { if (!CigCreate(group)) { if (group->cig.GetState() == CigState::CREATED) { /* It can happen on the earbuds switch scenario. When one device * is getting remove while other is adding to the stream and CIG is * already created */ PrepareAndSendConfigQos(group, leAudioDevice); } else if (!CigCreate(group)) { log::error("Could not create CIG. Stop the stream for group {}", group->group_id_); StopStream(group); Loading Loading @@ -2174,7 +2218,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { if (!CigCreate(group)) { if (group->cig.GetState() == CigState::CREATED) { /* It can happen on the earbuds switch scenario. When one device * is getting remove while other is adding to the stream and CIG is * already created */ PrepareAndSendConfigQos(group, leAudioDevice); } else if (!CigCreate(group)) { log::error("Could not create CIG. Stop the stream for group {}", group->group_id_); StopStream(group); Loading Loading @@ -2229,15 +2278,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return; } /* Before continue with release, make sure this is what is requested. * If not (e.g. only single device got disconnected), stop here */ if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { log::debug("Autonomus change of stated for device {}, ase id: {}", leAudioDevice->address_, ase->id); return; } { auto activeDevice = group->GetFirstActiveDevice(); if (activeDevice) { Loading Loading @@ -2296,6 +2336,17 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } switch (ase->state) { case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: log::info( "Unexpected state transition from {} to {}, {}, ase_id: {}, " "fallback to transition from {} to {}", ToString(ase->state), ToString(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED), leAudioDevice->address_, ase->id, ToString(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED), ToString(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)); group->PrintDebugState(); FMT_FALLTHROUGH; case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: { SetAseState(leAudioDevice, ase, AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); Loading Loading @@ -2379,14 +2430,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } break; } case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: log::info("Unexpected state transition from {} to {}, {}, ase_id: {}", ToString(ase->state), ToString(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED), leAudioDevice->address_, ase->id); group->PrintDebugState(); break; case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING: // Do nothing here, just print an error message Loading Loading @@ -3067,7 +3110,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } if (group->cig.GetState() == CigState::CREATED && group->HaveAllCisesDisconnected()) { group->HaveAllCisesDisconnected() && getDeviceTryingToAttachTheStream(group) == nullptr) { RemoveCigForGroup(group); } Loading system/bta/le_audio/state_machine_test.cc +188 −9 Original line number Diff line number Diff line Loading @@ -747,6 +747,35 @@ class StateMachineTestBase : public Test { group, leAudioDevice); } void InjectReleasingAndIdleState(LeAudioDeviceGroup* group, LeAudioDevice* device) { for (auto& ase : device->ases_) { if (ase.id == bluetooth::le_audio::types::ase::kAseIdInvalid) { continue; } // Simulate autonomus RELEASE and moving to IDLE state InjectAseStateNotification(&ase, device, group, ascs::kAseStateReleasing, nullptr); InjectAseStateNotification(&ase, device, group, ascs::kAseStateIdle, nullptr); } } void InjectCachedConfiguratuibForActiveAses(LeAudioDeviceGroup* group, LeAudioDevice* device) { for (auto& ase : device->ases_) { if (!ase.active) { continue; } log::info("ID : {}, status {}", ase.id, bluetooth::common::ToString(ase.state)); InjectAseStateNotification(&ase, device, group, ascs::kAseStateCodecConfigured, &cached_codec_configuration_map_[ase.id]); } } void InjectAseStateNotification(types::ase* ase, LeAudioDevice* device, LeAudioDeviceGroup* group, uint8_t new_state, void* new_state_params) { Loading Loading @@ -1109,10 +1138,12 @@ class StateMachineTestBase : public Test { void PrepareConfigureCodecHandler(LeAudioDeviceGroup* group, int verify_ase_count = 0, bool caching = false) { bool caching = false, bool inject_configured = true) { ON_CALL(ase_ctp_handler, AseCtpConfigureCodecHandler) .WillByDefault(Invoke([group, verify_ase_count, caching, this]( LeAudioDevice* device, .WillByDefault(Invoke([group, verify_ase_count, caching, inject_configured, this](LeAudioDevice* device, std::vector<uint8_t> value, GATT_WRITE_OP_CB cb, void* cb_data) { auto num_ase = value[1]; Loading Loading @@ -1169,9 +1200,12 @@ class StateMachineTestBase : public Test { cached_codec_configuration_map_[ase_id] = codec_configured_state_params; } if (inject_configured) { InjectAseStateNotification(ase, device, group, ascs::kAseStateCodecConfigured, &codec_configured_state_params); } if (stop_inject_configured_ase_after_first_ase_configured_) { return; Loading Loading @@ -6245,7 +6279,7 @@ TEST_F(StateMachineTest, StartStreamCachedConfigReconfigInvalidBehavior) { for (auto& ase : device->ases_) { if (i++ == 0) continue; // Simulate autonomus release for one ASE - this is invalid behaviour // Simulate autonomus release for one ASE InjectAseStateNotification(&ase, device, group, ascs::kAseStateReleasing, nullptr); } Loading Loading @@ -7444,6 +7478,151 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStreamCisFailure) { ASSERT_NE(ase->qos_config.retrans_nb, 0); } TEST_F(StateMachineTest, testAttachDeviceWhileSecondDeviceDisconnects) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; const auto num_devices = 2; 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(); LeAudioDevice* lastDevice; LeAudioDevice* firstDevice = leAudioDevice; auto expected_devices_written = 0; while (leAudioDevice) { /* Three Writes: * 1: Codec Config * 2: Codec QoS * 3: Enable */ 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(2); InjectInitialIdleNotification(group); // Start the configuration and stream Media content LeAudioGroupStateMachine::Get()->StartStream( group, context_type, {.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); testing::Mock::VerifyAndClearExpectations(mock_iso_manager_); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT); InjectAclDisconnected(group, lastDevice); log::info(" Device B - Disconnected "); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); // Set second device is connected now. lastDevice->conn_id_ = 3; lastDevice->SetConnectionState(DeviceConnectState::CONNECTED); // Make sure ASE with disconnected CIS are not left in STREAMING ASSERT_EQ(lastDevice->GetFirstAseWithState( ::bluetooth::le_audio::types::kLeAudioDirectionSink, types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING), nullptr); ASSERT_EQ(lastDevice->GetFirstAseWithState( ::bluetooth::le_audio::types::kLeAudioDirectionSource, types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING), nullptr); // Expect just Codec Configure on ASCS Control Point EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_, lastDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(1)); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); // Remove Configuration incjection but cache configuration for future // injection PrepareConfigureCodecHandler(group, 0, true, false); log::info("Device B - Attaching to the stream"); LeAudioGroupStateMachine::Get()->AttachToStream( group, lastDevice, {.sink = {media_ccid}, .source = {}}); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); /* Verify that ASE of first device are still good*/ auto ase = firstDevice->GetFirstActiveAse(); ASSERT_NE(ase->qos_config.max_transport_latency, 0); ASSERT_NE(ase->qos_config.retrans_nb, 0); testing::Mock::VerifyAndClearExpectations(mock_iso_manager_); testing::Mock::VerifyAndClearExpectations(&gatt_queue); log::info( "Device A is disconnecting while Device B is attaching to the stream"); InjectCisDisconnected(group, firstDevice, HCI_ERR_CONNECTION_TOUT); InjectReleasingAndIdleState(group, firstDevice); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); ASSERT_EQ(group->GetTargetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); ASSERT_EQ(group->cig.GetState(), types::CigState::CREATED); log::info("Device B continues configuration and streaming"); // Expect QoS config and Enable on ASCS Control Point EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_, lastDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(2)); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1); InjectCachedConfiguratuibForActiveAses(group, lastDevice); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(mock_iso_manager_); testing::Mock::VerifyAndClearExpectations(&gatt_queue); } TEST_F(StateMachineTest, testAclDropWithoutApriorCisDisconnection) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; Loading Loading
system/bta/le_audio/devices.cc +30 −19 Original line number Diff line number Diff line Loading @@ -250,13 +250,13 @@ bool LeAudioDevice::ConfigureAses( /* First try to use the already configured ASE */ auto ase = GetFirstActiveAseByDirection(direction); if (ase) { log::info("Using an already active ASE id={}", ase->id); log::info("{}, using an already active ASE id={}", address_, ase->id); } else { ase = GetFirstInactiveAse(direction, reuse_cis_id); } if (!ase) { log::error("Unable to find an ASE to configure"); log::error("{}, unable to find an ASE to configure", address_); PrintDebugState(); return false; } Loading Loading @@ -294,7 +294,7 @@ bool LeAudioDevice::ConfigureAses( auto const& ase_cfg = ase_configs.at(i); if (utils::IsCodecUsingLtvFormat(ase_cfg.codec.id) && !utils::GetConfigurationSupportedPac(pacs, ase_cfg.codec)) { log::error("No matching PAC found. Stop the activation."); log::error("{}, no matching PAC found. Stop the activation.", address_); return false; } } Loading @@ -309,8 +309,9 @@ bool LeAudioDevice::ConfigureAses( if (CodecManager::GetInstance()->CheckCodecConfigIsDualBiDirSwb( *audio_set_conf)) { log::error( "Trying to configure the dual bidir SWB, but the feature is " "disabled. This should not happen! Skipping ASE activation."); "{}, trying to configure the dual bidir SWB, but the feature is " "disabled. This should not happen! Skipping ASE activation.", address_); return true; } } Loading Loading @@ -443,7 +444,7 @@ void LeAudioDevice::ParseHeadtrackingCodec( */ std::vector<uint8_t> ltv = pac.metadata; if (ltv.size() < 7) { log::info("Headtracker codec does not have metadata"); log::info("{}, headtracker codec does not have metadata", address_); return; } Loading @@ -452,7 +453,7 @@ void LeAudioDevice::ParseHeadtrackingCodec( ltv[3] != (types::kLeAudioVendorCompanyIdGoogle >> 8) || ltv[4] != types::kLeAudioMetadataHeadtrackerTransportLen || ltv[5] != types::kLeAudioMetadataHeadtrackerTransportVal) { log::warn("Headtracker codec metadata invalid"); log::warn("{}, headtracker codec metadata invalid", address_); return; } Loading @@ -461,13 +462,13 @@ void LeAudioDevice::ParseHeadtrackingCodec( if ((supported_transports & types::kLeAudioMetadataHeadtrackerTransportLeAcl) != 0) { log::debug("Headtracking supported over LE-ACL"); log::debug("{}, headtracking supported over LE-ACL", address_); dsa_modes.push_back(DsaMode::ACL); } if ((supported_transports & types::kLeAudioMetadataHeadtrackerTransportLeIso) != 0) { log::debug("Headtracking supported over LE-ISO"); log::debug("{}, headtracking supported over LE-ISO", address_); dsa_modes.push_back(DsaMode::ISO_SW); dsa_modes.push_back(DsaMode::ISO_HW); } Loading @@ -481,7 +482,7 @@ void LeAudioDevice::RegisterPACs( std::vector<struct types::acs_ac_record>* pac_recs) { /* Clear PAC database for characteristic in case if re-read, indicated */ if (!pac_db->empty()) { log::debug("upgrade PACs for characteristic"); log::debug("{}, upgrade PACs for characteristic", address_); pac_db->clear(); } Loading Loading @@ -578,7 +579,8 @@ struct ase* LeAudioDevice::GetNextActiveAseWithDifferentDirection( /* Invalid ase given */ if (std::distance(iter, ases_.end()) < 1) { log::debug("ASE {} does not use bidirectional CIS", base_ase->id); log::debug("{}, ASE {} does not use bidirectional CIS", address_, base_ase->id); return nullptr; } Loading Loading @@ -715,6 +717,7 @@ bool LeAudioDevice::HaveAnyUnconfiguredAses(void) { } bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) { log::verbose("{}", address_); auto iter = std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) { log::verbose("ASE id: {}, active: {}, state: {}", ase.id, ase.active, Loading @@ -727,6 +730,7 @@ bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) { bool LeAudioDevice::HaveAllActiveAsesSameDataPathState( types::DataPathState state) const { log::verbose("{}", address_); auto iter = std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) { log::verbose("ASE id: {}, active: {}, state: {}", ase.id, ase.active, Loading @@ -738,6 +742,7 @@ bool LeAudioDevice::HaveAllActiveAsesSameDataPathState( } bool LeAudioDevice::IsReadyToCreateStream(void) { log::verbose("{}", address_); auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { if (!ase.active) return false; Loading Loading @@ -785,6 +790,8 @@ bool LeAudioDevice::HaveAllActiveAsesCisEst(void) const { return true; } log::verbose("{}", address_); bool has_active_ase = false; auto iter = std::find_if(ases_.begin(), ases_.end(), [&](const auto& ase) { if (!has_active_ase && ase.active) { Loading Loading @@ -816,7 +823,7 @@ uint8_t LeAudioDevice::GetSupportedAudioChannelCounts(uint8_t direction) const { direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_; if (pacs.size() == 0) { log::error("missing PAC for direction {}", direction); log::error("{}, missing PAC for direction {}", address_, direction); return 0; } Loading @@ -826,7 +833,7 @@ uint8_t LeAudioDevice::GetSupportedAudioChannelCounts(uint8_t direction) const { for (const auto pac : pac_recs) { if (!utils::IsCodecUsingLtvFormat(pac.codec_id)) { log::warn("Unknown codec PAC record for codec: {}", log::warn(" {} Unknown codec PAC record for codec: {}", address_, bluetooth::common::ToString(pac.codec_id)); continue; } Loading Loading @@ -910,12 +917,14 @@ uint8_t LeAudioDevice::GetPreferredPhyBitmask(uint8_t preferred_phy) const { // Take the preferences if possible if (preferred_phy && (phy_bitmask & preferred_phy)) { phy_bitmask &= preferred_phy; log::debug("Using ASE preferred phy 0x{:02x}", log::debug("{}, using ASE preferred phy 0x{:02x}", address_, static_cast<int>(phy_bitmask)); } else { log::warn( "ASE preferred 0x{:02x} has nothing common with phy_bitfield 0x{:02x}", static_cast<int>(preferred_phy), static_cast<int>(phy_bitmask)); " {}, ASE preferred 0x{:02x} has nothing common with phy_bitfield " "0x{:02x}", address_, static_cast<int>(preferred_phy), static_cast<int>(phy_bitmask)); } return phy_bitmask; } Loading Loading @@ -1033,10 +1042,12 @@ void LeAudioDevice::DisconnectAcl(void) { void LeAudioDevice::SetAvailableContexts( BidirectionalPair<AudioContexts> contexts) { log::debug( "\n\t previous_contexts_.sink: {} \n\t previous_contexts_.source: {} " "{}: \n\t previous_contexts_.sink: {} \n\t previous_contexts_.source: {} " " " "\n\t new_contexts.sink: {} \n\t new_contexts.source: {} \n\t", avail_contexts_.sink.to_string(), avail_contexts_.source.to_string(), contexts.sink.to_string(), contexts.source.to_string()); address_, avail_contexts_.sink.to_string(), avail_contexts_.source.to_string(), contexts.sink.to_string(), contexts.source.to_string()); avail_contexts_.sink = contexts.sink; avail_contexts_.source = contexts.source; Loading
system/bta/le_audio/state_machine.cc +77 −33 Original line number Diff line number Diff line Loading @@ -1149,12 +1149,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { if (ases_pair.sink && ases_pair.sink->state == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { SetAseState(leAudioDevice, ases_pair.sink, AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); } if (ases_pair.source && ases_pair.source->state == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { SetAseState(leAudioDevice, ases_pair.source, AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); } } Loading @@ -1162,7 +1162,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { auto target_state = group->GetTargetState(); switch (target_state) { case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: { /* Something wrong happen when streaming or when creating stream. * If there is other device connected and streaming, just leave it as it * is, otherwise stop the stream. Loading @@ -1175,6 +1175,29 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return; } /* CISes are disconnected, but it could be a case here, that there is * another set member trying to get STREAMING state. Can happen when * while streaming user switch buds. In such a case, lets try to allow * that device to continue */ LeAudioDevice* attaching_device = getDeviceTryingToAttachTheStream(group); if (attaching_device != nullptr) { /* There is a device willitng to stream. Let's wait for it to start * streaming */ auto active_ase = attaching_device->GetFirstActiveAse(); group->SetState(active_ase->state); /* this is just to start timer */ group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); log::info( "{} is still attaching to stream while other members got " "disconnected from the group_id: {}", attaching_device->address_, group->group_id_); return; } log::info("Lost all members from the group {}", group->group_id_); group->cig.cises.clear(); RemoveCigForGroup(group); Loading @@ -1185,6 +1208,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { state_machine_callbacks_->StatusReportCb(group->group_id_, GroupStreamStatus::IDLE); return; } case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: /* Intentional group disconnect has finished, but the last CIS in the Loading Loading @@ -1769,6 +1793,29 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { ase->state = state; } LeAudioDevice* getDeviceTryingToAttachTheStream(LeAudioDeviceGroup* group) { /* Device which is attaching the stream is just an active device not in * STREAMING state. the precondition is, that TargetState is Streaming */ log::debug("group_id: {}, targetState: {}", group->group_id_, ToString(group->GetTargetState())); if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { return nullptr; } for (auto dev = group->GetFirstActiveDevice(); dev != nullptr; dev = group->GetNextActiveDevice(dev)) { if (!dev->HaveAllActiveAsesSameState( AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) { log::debug("Attaching device {} to group_id: {}", dev->address_, group->group_id_); return dev; } } return nullptr; } void AseStateMachineProcessIdle( struct bluetooth::le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, LeAudioDeviceGroup* group, Loading @@ -1792,15 +1839,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return; } /* Before continue with release, make sure this is what is requested. * If not (e.g. only single device got disconnected), stop here */ if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { log::debug("Autonomus change of stated for device {}, ase id: {}", leAudioDevice->address_, ase->id); return; } if (!group->HaveAllActiveDevicesAsesTheSameState( AseState::BTA_LE_AUDIO_ASE_STATE_IDLE)) { log::debug("Waiting for more devices to get into idle state"); Loading @@ -1813,7 +1851,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { /* If all CISes are disconnected, notify upper layer about IDLE state, * otherwise wait for */ if (!group->HaveAllCisesDisconnected()) { if (!group->HaveAllCisesDisconnected() || getDeviceTryingToAttachTheStream(group) != nullptr) { log::warn( "Not all CISes removed before going to IDLE for group {}, " "waiting...", Loading Loading @@ -2079,7 +2118,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { if (!CigCreate(group)) { if (group->cig.GetState() == CigState::CREATED) { /* It can happen on the earbuds switch scenario. When one device * is getting remove while other is adding to the stream and CIG is * already created */ PrepareAndSendConfigQos(group, leAudioDevice); } else if (!CigCreate(group)) { log::error("Could not create CIG. Stop the stream for group {}", group->group_id_); StopStream(group); Loading Loading @@ -2174,7 +2218,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { if (!CigCreate(group)) { if (group->cig.GetState() == CigState::CREATED) { /* It can happen on the earbuds switch scenario. When one device * is getting remove while other is adding to the stream and CIG is * already created */ PrepareAndSendConfigQos(group, leAudioDevice); } else if (!CigCreate(group)) { log::error("Could not create CIG. Stop the stream for group {}", group->group_id_); StopStream(group); Loading Loading @@ -2229,15 +2278,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return; } /* Before continue with release, make sure this is what is requested. * If not (e.g. only single device got disconnected), stop here */ if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { log::debug("Autonomus change of stated for device {}, ase id: {}", leAudioDevice->address_, ase->id); return; } { auto activeDevice = group->GetFirstActiveDevice(); if (activeDevice) { Loading Loading @@ -2296,6 +2336,17 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } switch (ase->state) { case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: log::info( "Unexpected state transition from {} to {}, {}, ase_id: {}, " "fallback to transition from {} to {}", ToString(ase->state), ToString(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED), leAudioDevice->address_, ase->id, ToString(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED), ToString(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)); group->PrintDebugState(); FMT_FALLTHROUGH; case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: { SetAseState(leAudioDevice, ase, AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); Loading Loading @@ -2379,14 +2430,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } break; } case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: log::info("Unexpected state transition from {} to {}, {}, ase_id: {}", ToString(ase->state), ToString(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED), leAudioDevice->address_, ase->id); group->PrintDebugState(); break; case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING: // Do nothing here, just print an error message Loading Loading @@ -3067,7 +3110,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { } if (group->cig.GetState() == CigState::CREATED && group->HaveAllCisesDisconnected()) { group->HaveAllCisesDisconnected() && getDeviceTryingToAttachTheStream(group) == nullptr) { RemoveCigForGroup(group); } Loading
system/bta/le_audio/state_machine_test.cc +188 −9 Original line number Diff line number Diff line Loading @@ -747,6 +747,35 @@ class StateMachineTestBase : public Test { group, leAudioDevice); } void InjectReleasingAndIdleState(LeAudioDeviceGroup* group, LeAudioDevice* device) { for (auto& ase : device->ases_) { if (ase.id == bluetooth::le_audio::types::ase::kAseIdInvalid) { continue; } // Simulate autonomus RELEASE and moving to IDLE state InjectAseStateNotification(&ase, device, group, ascs::kAseStateReleasing, nullptr); InjectAseStateNotification(&ase, device, group, ascs::kAseStateIdle, nullptr); } } void InjectCachedConfiguratuibForActiveAses(LeAudioDeviceGroup* group, LeAudioDevice* device) { for (auto& ase : device->ases_) { if (!ase.active) { continue; } log::info("ID : {}, status {}", ase.id, bluetooth::common::ToString(ase.state)); InjectAseStateNotification(&ase, device, group, ascs::kAseStateCodecConfigured, &cached_codec_configuration_map_[ase.id]); } } void InjectAseStateNotification(types::ase* ase, LeAudioDevice* device, LeAudioDeviceGroup* group, uint8_t new_state, void* new_state_params) { Loading Loading @@ -1109,10 +1138,12 @@ class StateMachineTestBase : public Test { void PrepareConfigureCodecHandler(LeAudioDeviceGroup* group, int verify_ase_count = 0, bool caching = false) { bool caching = false, bool inject_configured = true) { ON_CALL(ase_ctp_handler, AseCtpConfigureCodecHandler) .WillByDefault(Invoke([group, verify_ase_count, caching, this]( LeAudioDevice* device, .WillByDefault(Invoke([group, verify_ase_count, caching, inject_configured, this](LeAudioDevice* device, std::vector<uint8_t> value, GATT_WRITE_OP_CB cb, void* cb_data) { auto num_ase = value[1]; Loading Loading @@ -1169,9 +1200,12 @@ class StateMachineTestBase : public Test { cached_codec_configuration_map_[ase_id] = codec_configured_state_params; } if (inject_configured) { InjectAseStateNotification(ase, device, group, ascs::kAseStateCodecConfigured, &codec_configured_state_params); } if (stop_inject_configured_ase_after_first_ase_configured_) { return; Loading Loading @@ -6245,7 +6279,7 @@ TEST_F(StateMachineTest, StartStreamCachedConfigReconfigInvalidBehavior) { for (auto& ase : device->ases_) { if (i++ == 0) continue; // Simulate autonomus release for one ASE - this is invalid behaviour // Simulate autonomus release for one ASE InjectAseStateNotification(&ase, device, group, ascs::kAseStateReleasing, nullptr); } Loading Loading @@ -7444,6 +7478,151 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStreamCisFailure) { ASSERT_NE(ase->qos_config.retrans_nb, 0); } TEST_F(StateMachineTest, testAttachDeviceWhileSecondDeviceDisconnects) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; const auto num_devices = 2; 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(); LeAudioDevice* lastDevice; LeAudioDevice* firstDevice = leAudioDevice; auto expected_devices_written = 0; while (leAudioDevice) { /* Three Writes: * 1: Codec Config * 2: Codec QoS * 3: Enable */ 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(2); InjectInitialIdleNotification(group); // Start the configuration and stream Media content LeAudioGroupStateMachine::Get()->StartStream( group, context_type, {.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); testing::Mock::VerifyAndClearExpectations(mock_iso_manager_); // Inject CIS and ACL disconnection of first device InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT); InjectAclDisconnected(group, lastDevice); log::info(" Device B - Disconnected "); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); // Set second device is connected now. lastDevice->conn_id_ = 3; lastDevice->SetConnectionState(DeviceConnectState::CONNECTED); // Make sure ASE with disconnected CIS are not left in STREAMING ASSERT_EQ(lastDevice->GetFirstAseWithState( ::bluetooth::le_audio::types::kLeAudioDirectionSink, types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING), nullptr); ASSERT_EQ(lastDevice->GetFirstAseWithState( ::bluetooth::le_audio::types::kLeAudioDirectionSource, types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING), nullptr); // Expect just Codec Configure on ASCS Control Point EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_, lastDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(1)); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); // Remove Configuration incjection but cache configuration for future // injection PrepareConfigureCodecHandler(group, 0, true, false); log::info("Device B - Attaching to the stream"); LeAudioGroupStateMachine::Get()->AttachToStream( group, lastDevice, {.sink = {media_ccid}, .source = {}}); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); /* Verify that ASE of first device are still good*/ auto ase = firstDevice->GetFirstActiveAse(); ASSERT_NE(ase->qos_config.max_transport_latency, 0); ASSERT_NE(ase->qos_config.retrans_nb, 0); testing::Mock::VerifyAndClearExpectations(mock_iso_manager_); testing::Mock::VerifyAndClearExpectations(&gatt_queue); log::info( "Device A is disconnecting while Device B is attaching to the stream"); InjectCisDisconnected(group, firstDevice, HCI_ERR_CONNECTION_TOUT); InjectReleasingAndIdleState(group, firstDevice); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); ASSERT_EQ(group->GetTargetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); ASSERT_EQ(group->cig.GetState(), types::CigState::CREATED); log::info("Device B continues configuration and streaming"); // Expect QoS config and Enable on ASCS Control Point EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_, lastDevice->ctp_hdls_.val_hdl, _, GATT_WRITE_NO_RSP, _, _)) .Times(AtLeast(2)); EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1); InjectCachedConfiguratuibForActiveAses(group, lastDevice); // Check if group keeps streaming ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); testing::Mock::VerifyAndClearExpectations(mock_iso_manager_); testing::Mock::VerifyAndClearExpectations(&gatt_queue); } TEST_F(StateMachineTest, testAclDropWithoutApriorCisDisconnection) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 6; Loading