xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/l2cap/a2dp_offload_manager_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_bluetooth_sapphire/internal/host/l2cap/a2dp_offload_manager.h"
16 
17 #include <memory>
18 
19 #include "pw_bluetooth_sapphire/internal/host/common/host_error.h"
20 #include "pw_bluetooth_sapphire/internal/host/hci-spec/vendor_protocol.h"
21 #include "pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
22 #include "pw_bluetooth_sapphire/internal/host/testing/mock_controller.h"
23 #include "pw_bluetooth_sapphire/internal/host/testing/test_packets.h"
24 
25 namespace bt::l2cap {
26 namespace {
27 
28 namespace android_hci = bt::hci_spec::vendor::android;
29 namespace android_emb = pw::bluetooth::vendor::android_hci;
30 using namespace bt::testing;
31 
32 constexpr hci_spec::ConnectionHandle kTestHandle1 = 0x0001;
33 constexpr ChannelId kLocalId = 0x0040;
34 constexpr ChannelId kRemoteId = 0x9042;
35 
BuildConfiguration(android_emb::A2dpCodecType codec=android_emb::A2dpCodecType::SBC)36 A2dpOffloadManager::Configuration BuildConfiguration(
37     android_emb::A2dpCodecType codec = android_emb::A2dpCodecType::SBC) {
38   A2dpOffloadManager::Configuration config;
39   config.codec = codec;
40   config.max_latency = 0xFFFF;
41   config.scms_t_enable.view().enabled().Write(
42       pw::bluetooth::emboss::GenericEnableParam::DISABLE);
43   config.scms_t_enable.view().header().Write(0x00);
44   config.sampling_frequency = android_emb::A2dpSamplingFrequency::HZ_44100;
45   config.bits_per_sample = android_emb::A2dpBitsPerSample::BITS_PER_SAMPLE_16;
46   config.channel_mode = android_emb::A2dpChannelMode::MONO;
47   config.encoded_audio_bit_rate = 0x0;
48 
49   switch (codec) {
50     case android_emb::A2dpCodecType::SBC:
51       config.sbc_configuration.view().block_length().Write(
52           android_emb::SbcBlockLen::BLOCK_LEN_4);
53       config.sbc_configuration.view().subbands().Write(
54           android_emb::SbcSubBands::SUBBANDS_4);
55       config.sbc_configuration.view().allocation_method().Write(
56           android_emb::SbcAllocationMethod::SNR);
57       config.sbc_configuration.view().min_bitpool_value().Write(0x00);
58       config.sbc_configuration.view().max_bitpool_value().Write(0xFF);
59       break;
60     case android_emb::A2dpCodecType::AAC:
61       config.aac_configuration.view().object_type().Write(0x00);
62       config.aac_configuration.view().variable_bit_rate().Write(
63           android_emb::AacEnableVariableBitRate::DISABLE);
64       break;
65     case android_emb::A2dpCodecType::LDAC:
66       config.ldac_configuration.view().vendor_id().Write(
67           android_hci::kLdacVendorId);
68       config.ldac_configuration.view().codec_id().Write(
69           android_hci::kLdacCodecId);
70       config.ldac_configuration.view().bitrate_index().Write(
71           android_emb::LdacBitrateIndex::LOW);
72       config.ldac_configuration.view().ldac_channel_mode().stereo().Write(true);
73       break;
74     case android_emb::A2dpCodecType::APTX:
75     case android_emb::A2dpCodecType::APTX_HD:
76       break;
77   }
78 
79   return config;
80 }
81 
82 using TestingBase = FakeDispatcherControllerTest<MockController>;
83 
84 class A2dpOffloadTest : public TestingBase {
85  public:
86   A2dpOffloadTest() = default;
87   ~A2dpOffloadTest() override = default;
88 
SetUp()89   void SetUp() override {
90     TestingBase::SetUp();
91 
92     offload_mgr_ =
93         std::make_unique<A2dpOffloadManager>(cmd_channel()->AsWeakPtr());
94   }
95 
TearDown()96   void TearDown() override { TestingBase::TearDown(); }
97 
offload_mgr() const98   A2dpOffloadManager* offload_mgr() const { return offload_mgr_.get(); }
99 
100  private:
101   std::unique_ptr<A2dpOffloadManager> offload_mgr_;
102 
103   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(A2dpOffloadTest);
104 };
105 
106 class StartA2dpOffloadTest
107     : public A2dpOffloadTest,
108       public ::testing::WithParamInterface<android_emb::A2dpCodecType> {};
109 
TEST_P(StartA2dpOffloadTest,StartA2dpOffloadSuccess)110 TEST_P(StartA2dpOffloadTest, StartA2dpOffloadSuccess) {
111   const android_emb::A2dpCodecType codec = GetParam();
112   A2dpOffloadManager::Configuration config = BuildConfiguration(codec);
113 
114   const auto command_complete =
115       CommandCompletePacket(android_hci::kA2dpOffloadCommand,
116                             pw::bluetooth::emboss::StatusCode::SUCCESS);
117   EXPECT_CMD_PACKET_OUT(
118       test_device(),
119       StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
120       &command_complete);
121 
122   std::optional<hci::Result<>> start_result;
123   offload_mgr()->StartA2dpOffload(
124       config,
125       kLocalId,
126       kRemoteId,
127       kTestHandle1,
128       kMaxMTU,
129       [&start_result](auto res) {
130         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
131         start_result = res;
132       });
133   RunUntilIdle();
134   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
135   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
136   ASSERT_TRUE(start_result.has_value());
137   EXPECT_TRUE(start_result->is_ok());
138 }
139 
140 const std::vector<android_emb::A2dpCodecType> kA2dpCodecTypeParams = {
141     android_emb::A2dpCodecType::SBC,
142     android_emb::A2dpCodecType::AAC,
143     android_emb::A2dpCodecType::LDAC};
144 INSTANTIATE_TEST_SUITE_P(ChannelManagerTest,
145                          StartA2dpOffloadTest,
146                          ::testing::ValuesIn(kA2dpCodecTypeParams));
147 
TEST_F(A2dpOffloadTest,StartA2dpOffloadInvalidConfiguration)148 TEST_F(A2dpOffloadTest, StartA2dpOffloadInvalidConfiguration) {
149   A2dpOffloadManager::Configuration config = BuildConfiguration();
150 
151   const auto command_complete = CommandCompletePacket(
152       android_hci::kA2dpOffloadCommand,
153       pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS);
154   EXPECT_CMD_PACKET_OUT(
155       test_device(),
156       StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
157       &command_complete);
158 
159   std::optional<hci::Result<>> start_result;
160   offload_mgr()->StartA2dpOffload(
161       config,
162       kLocalId,
163       kRemoteId,
164       kTestHandle1,
165       kMaxMTU,
166       [&start_result](auto res) {
167         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::
168                                INVALID_HCI_COMMAND_PARAMETERS),
169                   res);
170         start_result = res;
171       });
172   RunUntilIdle();
173   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
174   ASSERT_TRUE(start_result.has_value());
175   EXPECT_TRUE(start_result->is_error());
176 }
177 
TEST_F(A2dpOffloadTest,StartAndStopA2dpOffloadSuccess)178 TEST_F(A2dpOffloadTest, StartAndStopA2dpOffloadSuccess) {
179   A2dpOffloadManager::Configuration config = BuildConfiguration();
180 
181   const auto command_complete =
182       CommandCompletePacket(android_hci::kA2dpOffloadCommand,
183                             pw::bluetooth::emboss::StatusCode::SUCCESS);
184   EXPECT_CMD_PACKET_OUT(
185       test_device(),
186       StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
187       &command_complete);
188 
189   std::optional<hci::Result<>> start_result;
190   offload_mgr()->StartA2dpOffload(
191       config,
192       kLocalId,
193       kRemoteId,
194       kTestHandle1,
195       kMaxMTU,
196       [&start_result](auto res) {
197         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
198         start_result = res;
199       });
200   RunUntilIdle();
201   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
202   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
203   ASSERT_TRUE(start_result.has_value());
204   EXPECT_TRUE(start_result->is_ok());
205 
206   EXPECT_CMD_PACKET_OUT(
207       test_device(), StopA2dpOffloadRequest(), &command_complete);
208 
209   std::optional<hci::Result<>> stop_result;
210   offload_mgr()->RequestStopA2dpOffload(
211       kLocalId, kTestHandle1, [&stop_result](auto res) {
212         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
213         stop_result = res;
214       });
215   RunUntilIdle();
216   EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
217   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
218   ASSERT_TRUE(stop_result.has_value());
219   EXPECT_TRUE(stop_result->is_ok());
220 }
221 
TEST_F(A2dpOffloadTest,StartA2dpOffloadAlreadyStarted)222 TEST_F(A2dpOffloadTest, StartA2dpOffloadAlreadyStarted) {
223   A2dpOffloadManager::Configuration config = BuildConfiguration();
224 
225   const auto command_complete =
226       CommandCompletePacket(android_hci::kA2dpOffloadCommand,
227                             pw::bluetooth::emboss::StatusCode::SUCCESS);
228   EXPECT_CMD_PACKET_OUT(
229       test_device(),
230       StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
231       &command_complete);
232 
233   std::optional<hci::Result<>> start_result;
234   offload_mgr()->StartA2dpOffload(
235       config,
236       kLocalId,
237       kRemoteId,
238       kTestHandle1,
239       kMaxMTU,
240       [&start_result](auto res) {
241         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
242         start_result = res;
243       });
244   RunUntilIdle();
245   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
246   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
247   ASSERT_TRUE(start_result.has_value());
248   EXPECT_TRUE(start_result->is_ok());
249 
250   start_result.reset();
251   offload_mgr()->StartA2dpOffload(config,
252                                   kLocalId,
253                                   kRemoteId,
254                                   kTestHandle1,
255                                   kMaxMTU,
256                                   [&start_result](auto res) {
257                                     EXPECT_EQ(ToResult(HostError::kInProgress),
258                                               res);
259                                     start_result = res;
260                                   });
261   RunUntilIdle();
262   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
263   ASSERT_TRUE(start_result.has_value());
264   EXPECT_TRUE(start_result->is_error());
265 }
266 
TEST_F(A2dpOffloadTest,StartA2dpOffloadStillStarting)267 TEST_F(A2dpOffloadTest, StartA2dpOffloadStillStarting) {
268   A2dpOffloadManager::Configuration config = BuildConfiguration();
269 
270   const auto command_complete =
271       CommandCompletePacket(android_hci::kA2dpOffloadCommand,
272                             pw::bluetooth::emboss::StatusCode::SUCCESS);
273   EXPECT_CMD_PACKET_OUT(
274       test_device(),
275       StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
276       &command_complete);
277 
278   std::optional<hci::Result<>> start_result;
279   offload_mgr()->StartA2dpOffload(
280       config,
281       kLocalId,
282       kRemoteId,
283       kTestHandle1,
284       kMaxMTU,
285       [&start_result](auto res) {
286         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
287         start_result = res;
288       });
289   EXPECT_FALSE(start_result.has_value());
290 
291   offload_mgr()->StartA2dpOffload(config,
292                                   kLocalId,
293                                   kRemoteId,
294                                   kTestHandle1,
295                                   kMaxMTU,
296                                   [&start_result](auto res) {
297                                     EXPECT_EQ(ToResult(HostError::kInProgress),
298                                               res);
299                                     start_result = res;
300                                   });
301   RunUntilIdle();
302   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
303   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
304   ASSERT_TRUE(start_result.has_value());
305   EXPECT_TRUE(start_result->is_ok());
306 }
307 
TEST_F(A2dpOffloadTest,StartA2dpOffloadStillStopping)308 TEST_F(A2dpOffloadTest, StartA2dpOffloadStillStopping) {
309   A2dpOffloadManager::Configuration config = BuildConfiguration();
310 
311   const auto command_complete =
312       CommandCompletePacket(android_hci::kA2dpOffloadCommand,
313                             pw::bluetooth::emboss::StatusCode::SUCCESS);
314   EXPECT_CMD_PACKET_OUT(
315       test_device(),
316       StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
317       &command_complete);
318 
319   std::optional<hci::Result<>> start_result;
320   offload_mgr()->StartA2dpOffload(
321       config,
322       kLocalId,
323       kRemoteId,
324       kTestHandle1,
325       kMaxMTU,
326       [&start_result](auto res) {
327         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
328         start_result = res;
329       });
330   RunUntilIdle();
331   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
332   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
333   ASSERT_TRUE(start_result.has_value());
334   EXPECT_TRUE(start_result->is_ok());
335 
336   EXPECT_CMD_PACKET_OUT(
337       test_device(), StopA2dpOffloadRequest(), &command_complete);
338 
339   std::optional<hci::Result<>> stop_result;
340   offload_mgr()->RequestStopA2dpOffload(
341       kLocalId, kTestHandle1, [&stop_result](auto res) {
342         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
343         stop_result = res;
344       });
345   EXPECT_FALSE(stop_result.has_value());
346 
347   start_result.reset();
348   offload_mgr()->StartA2dpOffload(config,
349                                   kLocalId,
350                                   kRemoteId,
351                                   kTestHandle1,
352                                   kMaxMTU,
353                                   [&start_result](auto res) {
354                                     EXPECT_EQ(ToResult(HostError::kInProgress),
355                                               res);
356                                     start_result = res;
357                                   });
358   RunUntilIdle();
359   EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
360   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
361   ASSERT_TRUE(start_result.has_value());
362   EXPECT_TRUE(start_result->is_error());
363   ASSERT_TRUE(stop_result.has_value());
364   EXPECT_TRUE(stop_result->is_ok());
365 }
366 
TEST_F(A2dpOffloadTest,StopA2dpOffloadStillStarting)367 TEST_F(A2dpOffloadTest, StopA2dpOffloadStillStarting) {
368   A2dpOffloadManager::Configuration config = BuildConfiguration();
369 
370   const auto command_complete =
371       CommandCompletePacket(android_hci::kA2dpOffloadCommand,
372                             pw::bluetooth::emboss::StatusCode::SUCCESS);
373   EXPECT_CMD_PACKET_OUT(
374       test_device(),
375       StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
376       &command_complete);
377 
378   std::optional<hci::Result<>> start_result;
379   offload_mgr()->StartA2dpOffload(
380       config,
381       kLocalId,
382       kRemoteId,
383       kTestHandle1,
384       kMaxMTU,
385       [&start_result](auto res) {
386         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
387         start_result = res;
388       });
389   EXPECT_FALSE(start_result.has_value());
390 
391   EXPECT_CMD_PACKET_OUT(
392       test_device(), StopA2dpOffloadRequest(), &command_complete);
393 
394   std::optional<hci::Result<>> stop_result;
395   offload_mgr()->RequestStopA2dpOffload(
396       kLocalId, kTestHandle1, [&stop_result](auto res) {
397         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
398         stop_result = res;
399       });
400   RunUntilIdle();
401   EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
402   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
403   ASSERT_TRUE(start_result.has_value());
404   EXPECT_TRUE(start_result->is_ok());
405   ASSERT_TRUE(stop_result.has_value());
406   EXPECT_TRUE(stop_result->is_ok());
407 }
408 
TEST_F(A2dpOffloadTest,StopA2dpOffloadStillStopping)409 TEST_F(A2dpOffloadTest, StopA2dpOffloadStillStopping) {
410   A2dpOffloadManager::Configuration config = BuildConfiguration();
411 
412   const auto command_complete =
413       CommandCompletePacket(android_hci::kA2dpOffloadCommand,
414                             pw::bluetooth::emboss::StatusCode::SUCCESS);
415   EXPECT_CMD_PACKET_OUT(
416       test_device(),
417       StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
418       &command_complete);
419 
420   std::optional<hci::Result<>> start_result;
421   offload_mgr()->StartA2dpOffload(
422       config,
423       kLocalId,
424       kRemoteId,
425       kTestHandle1,
426       kMaxMTU,
427       [&start_result](auto res) {
428         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
429         start_result = res;
430       });
431   RunUntilIdle();
432   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
433   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
434   ASSERT_TRUE(start_result.has_value());
435   EXPECT_TRUE(start_result->is_ok());
436 
437   EXPECT_CMD_PACKET_OUT(
438       test_device(), StopA2dpOffloadRequest(), &command_complete);
439 
440   std::optional<hci::Result<>> stop_result;
441   offload_mgr()->RequestStopA2dpOffload(
442       kLocalId, kTestHandle1, [&stop_result](auto res) {
443         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
444         stop_result = res;
445       });
446   EXPECT_FALSE(stop_result.has_value());
447 
448   offload_mgr()->RequestStopA2dpOffload(
449       kLocalId, kTestHandle1, [&stop_result](auto res) {
450         EXPECT_EQ(ToResult(HostError::kInProgress), res);
451         stop_result = res;
452       });
453   RunUntilIdle();
454   EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
455   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
456   ASSERT_TRUE(stop_result.has_value());
457   EXPECT_TRUE(stop_result->is_ok());
458 }
459 
TEST_F(A2dpOffloadTest,StopA2dpOffloadAlreadyStopped)460 TEST_F(A2dpOffloadTest, StopA2dpOffloadAlreadyStopped) {
461   std::optional<hci::Result<>> stop_result;
462   offload_mgr()->RequestStopA2dpOffload(
463       kLocalId, kTestHandle1, [&stop_result](auto res) {
464         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
465         stop_result = res;
466       });
467   RunUntilIdle();
468   EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
469   ASSERT_TRUE(stop_result.has_value());
470   EXPECT_TRUE(stop_result->is_ok());
471 }
472 
TEST_F(A2dpOffloadTest,A2dpOffloadOnlyOneChannel)473 TEST_F(A2dpOffloadTest, A2dpOffloadOnlyOneChannel) {
474   A2dpOffloadManager::Configuration config = BuildConfiguration();
475 
476   const auto command_complete =
477       CommandCompletePacket(android_hci::kA2dpOffloadCommand,
478                             pw::bluetooth::emboss::StatusCode::SUCCESS);
479   EXPECT_CMD_PACKET_OUT(
480       test_device(),
481       StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
482       &command_complete);
483 
484   std::optional<hci::Result<>> start_result_0;
485   offload_mgr()->StartA2dpOffload(
486       config,
487       kLocalId,
488       kRemoteId,
489       kTestHandle1,
490       kMaxMTU,
491       [&start_result_0](auto res) {
492         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
493         start_result_0 = res;
494       });
495   RunUntilIdle();
496   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
497   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
498   ASSERT_TRUE(start_result_0.has_value());
499   EXPECT_TRUE(start_result_0->is_ok());
500 
501   std::optional<hci::Result<>> start_result_1;
502   offload_mgr()->StartA2dpOffload(config,
503                                   kLocalId + 1,
504                                   kRemoteId + 1,
505                                   kTestHandle1,
506                                   kMaxMTU,
507                                   [&start_result_1](auto res) {
508                                     EXPECT_EQ(ToResult(HostError::kInProgress),
509                                               res);
510                                     start_result_1 = res;
511                                   });
512   RunUntilIdle();
513   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
514   EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId + 1, kTestHandle1));
515   ASSERT_TRUE(start_result_1.has_value());
516   EXPECT_TRUE(start_result_1->is_error());
517 }
518 
TEST_F(A2dpOffloadTest,DifferentChannelCannotStopA2dpOffloading)519 TEST_F(A2dpOffloadTest, DifferentChannelCannotStopA2dpOffloading) {
520   A2dpOffloadManager::Configuration config = BuildConfiguration();
521 
522   const auto command_complete =
523       CommandCompletePacket(android_hci::kA2dpOffloadCommand,
524                             pw::bluetooth::emboss::StatusCode::SUCCESS);
525   EXPECT_CMD_PACKET_OUT(
526       test_device(),
527       StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
528       &command_complete);
529 
530   std::optional<hci::Result<>> start_result;
531   offload_mgr()->StartA2dpOffload(
532       config,
533       kLocalId,
534       kRemoteId,
535       kTestHandle1,
536       kMaxMTU,
537       [&start_result](auto res) {
538         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
539         start_result = res;
540       });
541   RunUntilIdle();
542   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
543   EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
544   ASSERT_TRUE(start_result.has_value());
545   EXPECT_TRUE(start_result->is_ok());
546 
547   std::optional<hci::Result<>> stop_result;
548   offload_mgr()->RequestStopA2dpOffload(
549       kLocalId + 1, kTestHandle1 + 1, [&stop_result](auto res) {
550         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
551         stop_result = res;
552       });
553   RunUntilIdle();
554   EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
555   ASSERT_TRUE(stop_result.has_value());
556   EXPECT_TRUE(stop_result->is_ok());
557 
558   EXPECT_CMD_PACKET_OUT(
559       test_device(), StopA2dpOffloadRequest(), &command_complete);
560 
561   // Can still stop it from the correct one.
562   stop_result = std::nullopt;
563   offload_mgr()->RequestStopA2dpOffload(
564       kLocalId, kTestHandle1, [&stop_result](auto res) {
565         EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
566         stop_result = res;
567       });
568   RunUntilIdle();
569   EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
570   ASSERT_TRUE(stop_result.has_value());
571   EXPECT_TRUE(stop_result->is_ok());
572 }
573 
574 }  // namespace
575 }  // namespace bt::l2cap
576