| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_bluetooth_sapphire/internal/host/l2cap/a2dp_offload_manager.h" |
| |
| #include <memory> |
| |
| #include "pw_bluetooth_sapphire/internal/host/common/host_error.h" |
| #include "pw_bluetooth_sapphire/internal/host/testing/controller_test.h" |
| #include "pw_bluetooth_sapphire/internal/host/testing/mock_controller.h" |
| #include "pw_bluetooth_sapphire/internal/host/testing/test_packets.h" |
| |
| namespace bt::l2cap { |
| namespace { |
| |
| namespace hci_android = bt::hci_spec::vendor::android; |
| using namespace bt::testing; |
| |
| constexpr hci_spec::ConnectionHandle kTestHandle1 = 0x0001; |
| constexpr ChannelId kLocalId = 0x0040; |
| constexpr ChannelId kRemoteId = 0x9042; |
| |
| A2dpOffloadManager::Configuration BuildConfiguration( |
| hci_android::A2dpCodecType codec = hci_android::A2dpCodecType::kSbc) { |
| hci_android::A2dpScmsTEnable scms_t_enable; |
| scms_t_enable.enabled = pw::bluetooth::emboss::GenericEnableParam::DISABLE; |
| scms_t_enable.header = 0x00; |
| |
| hci_android::A2dpOffloadCodecInformation codec_information; |
| switch (codec) { |
| case hci_android::A2dpCodecType::kSbc: |
| codec_information.sbc.blocklen_subbands_alloc_method = 0x00; |
| codec_information.sbc.min_bitpool_value = 0x00; |
| codec_information.sbc.max_bitpool_value = 0xFF; |
| memset(codec_information.sbc.reserved, |
| 0, |
| sizeof(codec_information.sbc.reserved)); |
| break; |
| case hci_android::A2dpCodecType::kAac: |
| codec_information.aac.object_type = 0x00; |
| codec_information.aac.variable_bit_rate = |
| hci_android::A2dpAacEnableVariableBitRate::kDisable; |
| memset(codec_information.aac.reserved, |
| 0, |
| sizeof(codec_information.aac.reserved)); |
| break; |
| case hci_android::A2dpCodecType::kLdac: |
| codec_information.ldac.vendor_id = 0x0000012D; |
| codec_information.ldac.codec_id = 0x00AA; |
| codec_information.ldac.bitrate_index = |
| hci_android::A2dpBitrateIndex::kLow; |
| codec_information.ldac.ldac_channel_mode = |
| hci_android::A2dpLdacChannelMode::kStereo; |
| memset(codec_information.ldac.reserved, |
| 0, |
| sizeof(codec_information.ldac.reserved)); |
| break; |
| default: |
| memset(codec_information.aptx.reserved, |
| 0, |
| sizeof(codec_information.aptx.reserved)); |
| break; |
| } |
| |
| A2dpOffloadManager::Configuration config; |
| config.codec = codec; |
| config.max_latency = 0xFFFF; |
| config.scms_t_enable = scms_t_enable; |
| config.sampling_frequency = hci_android::A2dpSamplingFrequency::k44100Hz; |
| config.bits_per_sample = hci_android::A2dpBitsPerSample::k16BitsPerSample; |
| config.channel_mode = hci_android::A2dpChannelMode::kMono; |
| config.encoded_audio_bit_rate = 0x0; |
| config.codec_information = codec_information; |
| |
| 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<hci_android::A2dpCodecType> {}; |
| |
| TEST_P(StartA2dpOffloadTest, StartA2dpOffloadSuccess) { |
| const hci_android::A2dpCodecType codec = GetParam(); |
| A2dpOffloadManager::Configuration config = BuildConfiguration(codec); |
| |
| const auto command_complete = |
| CommandCompletePacket(hci_android::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<hci_android::A2dpCodecType> kA2dpCodecTypeParams = { |
| hci_android::A2dpCodecType::kSbc, |
| hci_android::A2dpCodecType::kAac, |
| hci_android::A2dpCodecType::kLdac, |
| hci_android::A2dpCodecType::kAptx}; |
| INSTANTIATE_TEST_SUITE_P(ChannelManagerTest, |
| StartA2dpOffloadTest, |
| ::testing::ValuesIn(kA2dpCodecTypeParams)); |
| |
| TEST_F(A2dpOffloadTest, StartA2dpOffloadInvalidConfiguration) { |
| A2dpOffloadManager::Configuration config = BuildConfiguration(); |
| |
| const auto command_complete = CommandCompletePacket( |
| hci_android::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(hci_android::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(hci_android::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(hci_android::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(hci_android::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(hci_android::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(hci_android::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(hci_android::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(hci_android::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 |