| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/a2dp_offload_manager.h" |
| |
| #include <memory> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/host_error.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/controller_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/mock_controller.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_packets.h" |
| |
| namespace bt::l2cap { |
| namespace { |
| |
| namespace android_hci = bt::hci_spec::vendor::android; |
| namespace android_emb = pw::bluetooth::vendor::android_hci; |
| using namespace bt::testing; |
| |
| constexpr hci_spec::ConnectionHandle kTestHandle1 = 0x0001; |
| constexpr ChannelId kLocalId = 0x0040; |
| constexpr ChannelId kRemoteId = 0x9042; |
| |
| A2dpOffloadManager::Configuration BuildConfiguration( |
| android_emb::A2dpCodecType codec = android_emb::A2dpCodecType::SBC) { |
| A2dpOffloadManager::Configuration config; |
| config.codec = codec; |
| config.max_latency = 0xFFFF; |
| config.scms_t_enable.view().enabled().Write( |
| pw::bluetooth::emboss::GenericEnableParam::DISABLE); |
| config.scms_t_enable.view().header().Write(0x00); |
| config.sampling_frequency = android_emb::A2dpSamplingFrequency::HZ_44100; |
| config.bits_per_sample = android_emb::A2dpBitsPerSample::BITS_PER_SAMPLE_16; |
| config.channel_mode = android_emb::A2dpChannelMode::MONO; |
| config.encoded_audio_bit_rate = 0x0; |
| |
| switch (codec) { |
| case android_emb::A2dpCodecType::SBC: |
| config.sbc_configuration.view().block_length().Write( |
| android_emb::SbcBlockLen::BLOCK_LEN_4); |
| config.sbc_configuration.view().subbands().Write( |
| android_emb::SbcSubBands::SUBBANDS_4); |
| config.sbc_configuration.view().allocation_method().Write( |
| android_emb::SbcAllocationMethod::SNR); |
| config.sbc_configuration.view().min_bitpool_value().Write(0x00); |
| config.sbc_configuration.view().max_bitpool_value().Write(0xFF); |
| break; |
| case android_emb::A2dpCodecType::AAC: |
| config.aac_configuration.view().object_type().Write(0x00); |
| config.aac_configuration.view().variable_bit_rate().Write( |
| android_emb::AacEnableVariableBitRate::DISABLE); |
| break; |
| case android_emb::A2dpCodecType::LDAC: |
| config.ldac_configuration.view().vendor_id().Write( |
| android_hci::kLdacVendorId); |
| config.ldac_configuration.view().codec_id().Write( |
| android_hci::kLdacCodecId); |
| config.ldac_configuration.view().bitrate_index().Write( |
| android_emb::LdacBitrateIndex::LOW); |
| config.ldac_configuration.view().ldac_channel_mode().stereo().Write(true); |
| break; |
| default: |
| break; |
| } |
| |
| return config; |
| } |
| |
| using TestingBase = FakeDispatcherControllerTest<MockController>; |
| |
| class A2dpOffloadTest : public TestingBase { |
| public: |
| A2dpOffloadTest() = default; |
| ~A2dpOffloadTest() override = default; |
| |
| void SetUp() override { |
| TestingBase::SetUp(); |
| |
| offload_mgr_ = |
| std::make_unique<A2dpOffloadManager>(cmd_channel()->AsWeakPtr()); |
| } |
| |
| void TearDown() override { TestingBase::TearDown(); } |
| |
| A2dpOffloadManager* offload_mgr() const { return offload_mgr_.get(); } |
| |
| private: |
| std::unique_ptr<A2dpOffloadManager> offload_mgr_; |
| |
| BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(A2dpOffloadTest); |
| }; |
| |
| class StartA2dpOffloadTest |
| : public A2dpOffloadTest, |
| public ::testing::WithParamInterface<android_emb::A2dpCodecType> {}; |
| |
| TEST_P(StartA2dpOffloadTest, StartA2dpOffloadSuccess) { |
| const android_emb::A2dpCodecType codec = GetParam(); |
| A2dpOffloadManager::Configuration config = BuildConfiguration(codec); |
| |
| const auto command_complete = |
| CommandCompletePacket(android_hci::kA2dpOffloadCommand, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU), |
| &command_complete); |
| |
| std::optional<hci::Result<>> start_result; |
| offload_mgr()->StartA2dpOffload( |
| config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| start_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_ok()); |
| } |
| |
| const std::vector<android_emb::A2dpCodecType> kA2dpCodecTypeParams = { |
| android_emb::A2dpCodecType::SBC, |
| android_emb::A2dpCodecType::AAC, |
| android_emb::A2dpCodecType::LDAC}; |
| INSTANTIATE_TEST_SUITE_P(ChannelManagerTest, |
| StartA2dpOffloadTest, |
| ::testing::ValuesIn(kA2dpCodecTypeParams)); |
| |
| TEST_F(A2dpOffloadTest, StartA2dpOffloadInvalidConfiguration) { |
| A2dpOffloadManager::Configuration config = BuildConfiguration(); |
| |
| const auto command_complete = CommandCompletePacket( |
| android_hci::kA2dpOffloadCommand, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU), |
| &command_complete); |
| |
| std::optional<hci::Result<>> start_result; |
| offload_mgr()->StartA2dpOffload( |
| config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode:: |
| INVALID_HCI_COMMAND_PARAMETERS), |
| res); |
| start_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_error()); |
| } |
| |
| TEST_F(A2dpOffloadTest, StartAndStopA2dpOffloadSuccess) { |
| A2dpOffloadManager::Configuration config = BuildConfiguration(); |
| |
| const auto command_complete = |
| CommandCompletePacket(android_hci::kA2dpOffloadCommand, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU), |
| &command_complete); |
| |
| std::optional<hci::Result<>> start_result; |
| offload_mgr()->StartA2dpOffload( |
| config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| start_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_ok()); |
| |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), StopA2dpOffloadRequest(), &command_complete); |
| |
| std::optional<hci::Result<>> stop_result; |
| offload_mgr()->RequestStopA2dpOffload( |
| kLocalId, kTestHandle1, [&stop_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| stop_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(stop_result.has_value()); |
| EXPECT_TRUE(stop_result->is_ok()); |
| } |
| |
| TEST_F(A2dpOffloadTest, StartA2dpOffloadAlreadyStarted) { |
| A2dpOffloadManager::Configuration config = BuildConfiguration(); |
| |
| const auto command_complete = |
| CommandCompletePacket(android_hci::kA2dpOffloadCommand, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU), |
| &command_complete); |
| |
| std::optional<hci::Result<>> start_result; |
| offload_mgr()->StartA2dpOffload( |
| config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| start_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_ok()); |
| |
| start_result.reset(); |
| offload_mgr()->StartA2dpOffload(config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(HostError::kInProgress), |
| res); |
| start_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_error()); |
| } |
| |
| TEST_F(A2dpOffloadTest, StartA2dpOffloadStillStarting) { |
| A2dpOffloadManager::Configuration config = BuildConfiguration(); |
| |
| const auto command_complete = |
| CommandCompletePacket(android_hci::kA2dpOffloadCommand, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU), |
| &command_complete); |
| |
| std::optional<hci::Result<>> start_result; |
| offload_mgr()->StartA2dpOffload( |
| config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| start_result = res; |
| }); |
| EXPECT_FALSE(start_result.has_value()); |
| |
| offload_mgr()->StartA2dpOffload(config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(HostError::kInProgress), |
| res); |
| start_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_ok()); |
| } |
| |
| TEST_F(A2dpOffloadTest, StartA2dpOffloadStillStopping) { |
| A2dpOffloadManager::Configuration config = BuildConfiguration(); |
| |
| const auto command_complete = |
| CommandCompletePacket(android_hci::kA2dpOffloadCommand, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU), |
| &command_complete); |
| |
| std::optional<hci::Result<>> start_result; |
| offload_mgr()->StartA2dpOffload( |
| config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| start_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_ok()); |
| |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), StopA2dpOffloadRequest(), &command_complete); |
| |
| std::optional<hci::Result<>> stop_result; |
| offload_mgr()->RequestStopA2dpOffload( |
| kLocalId, kTestHandle1, [&stop_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| stop_result = res; |
| }); |
| EXPECT_FALSE(stop_result.has_value()); |
| |
| start_result.reset(); |
| offload_mgr()->StartA2dpOffload(config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(HostError::kInProgress), |
| res); |
| start_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_error()); |
| ASSERT_TRUE(stop_result.has_value()); |
| EXPECT_TRUE(stop_result->is_ok()); |
| } |
| |
| TEST_F(A2dpOffloadTest, StopA2dpOffloadStillStarting) { |
| A2dpOffloadManager::Configuration config = BuildConfiguration(); |
| |
| const auto command_complete = |
| CommandCompletePacket(android_hci::kA2dpOffloadCommand, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU), |
| &command_complete); |
| |
| std::optional<hci::Result<>> start_result; |
| offload_mgr()->StartA2dpOffload( |
| config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| start_result = res; |
| }); |
| EXPECT_FALSE(start_result.has_value()); |
| |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), StopA2dpOffloadRequest(), &command_complete); |
| |
| std::optional<hci::Result<>> stop_result; |
| offload_mgr()->RequestStopA2dpOffload( |
| kLocalId, kTestHandle1, [&stop_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| stop_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_ok()); |
| ASSERT_TRUE(stop_result.has_value()); |
| EXPECT_TRUE(stop_result->is_ok()); |
| } |
| |
| TEST_F(A2dpOffloadTest, StopA2dpOffloadStillStopping) { |
| A2dpOffloadManager::Configuration config = BuildConfiguration(); |
| |
| const auto command_complete = |
| CommandCompletePacket(android_hci::kA2dpOffloadCommand, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU), |
| &command_complete); |
| |
| std::optional<hci::Result<>> start_result; |
| offload_mgr()->StartA2dpOffload( |
| config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| start_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_ok()); |
| |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), StopA2dpOffloadRequest(), &command_complete); |
| |
| std::optional<hci::Result<>> stop_result; |
| offload_mgr()->RequestStopA2dpOffload( |
| kLocalId, kTestHandle1, [&stop_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| stop_result = res; |
| }); |
| EXPECT_FALSE(stop_result.has_value()); |
| |
| offload_mgr()->RequestStopA2dpOffload( |
| kLocalId, kTestHandle1, [&stop_result](auto res) { |
| EXPECT_EQ(ToResult(HostError::kInProgress), res); |
| stop_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(stop_result.has_value()); |
| EXPECT_TRUE(stop_result->is_ok()); |
| } |
| |
| TEST_F(A2dpOffloadTest, StopA2dpOffloadAlreadyStopped) { |
| std::optional<hci::Result<>> stop_result; |
| offload_mgr()->RequestStopA2dpOffload( |
| kLocalId, kTestHandle1, [&stop_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| stop_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| ASSERT_TRUE(stop_result.has_value()); |
| EXPECT_TRUE(stop_result->is_ok()); |
| } |
| |
| TEST_F(A2dpOffloadTest, A2dpOffloadOnlyOneChannel) { |
| A2dpOffloadManager::Configuration config = BuildConfiguration(); |
| |
| const auto command_complete = |
| CommandCompletePacket(android_hci::kA2dpOffloadCommand, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU), |
| &command_complete); |
| |
| std::optional<hci::Result<>> start_result_0; |
| offload_mgr()->StartA2dpOffload( |
| config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result_0](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| start_result_0 = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result_0.has_value()); |
| EXPECT_TRUE(start_result_0->is_ok()); |
| |
| std::optional<hci::Result<>> start_result_1; |
| offload_mgr()->StartA2dpOffload(config, |
| kLocalId + 1, |
| kRemoteId + 1, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result_1](auto res) { |
| EXPECT_EQ(ToResult(HostError::kInProgress), |
| res); |
| start_result_1 = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId + 1, kTestHandle1)); |
| ASSERT_TRUE(start_result_1.has_value()); |
| EXPECT_TRUE(start_result_1->is_error()); |
| } |
| |
| TEST_F(A2dpOffloadTest, DifferentChannelCannotStopA2dpOffloading) { |
| A2dpOffloadManager::Configuration config = BuildConfiguration(); |
| |
| const auto command_complete = |
| CommandCompletePacket(android_hci::kA2dpOffloadCommand, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU), |
| &command_complete); |
| |
| std::optional<hci::Result<>> start_result; |
| offload_mgr()->StartA2dpOffload( |
| config, |
| kLocalId, |
| kRemoteId, |
| kTestHandle1, |
| kMaxMTU, |
| [&start_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| start_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent()); |
| ASSERT_TRUE(start_result.has_value()); |
| EXPECT_TRUE(start_result->is_ok()); |
| |
| std::optional<hci::Result<>> stop_result; |
| offload_mgr()->RequestStopA2dpOffload( |
| kLocalId + 1, kTestHandle1 + 1, [&stop_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| stop_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| ASSERT_TRUE(stop_result.has_value()); |
| EXPECT_TRUE(stop_result->is_ok()); |
| |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), StopA2dpOffloadRequest(), &command_complete); |
| |
| // Can still stop it from the correct one. |
| stop_result = std::nullopt; |
| offload_mgr()->RequestStopA2dpOffload( |
| kLocalId, kTestHandle1, [&stop_result](auto res) { |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res); |
| stop_result = res; |
| }); |
| RunUntilIdle(); |
| EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1)); |
| ASSERT_TRUE(stop_result.has_value()); |
| EXPECT_TRUE(stop_result->is_ok()); |
| } |
| |
| } // namespace |
| } // namespace bt::l2cap |