Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a5133f49 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Do not trigger QoS config if device is not ready

Remote device might sent CONFIGURED state just after reconnection, but
from the stack point of view device is no yet ready to be attached to
the stream e.g. MTU exchange is not completed.

Stack will attach device to the stream once it is ready.

Bug: 302279664
Test: atest bluetooth_le_audio_test
Tag: #feature
Change-Id: Ic778093cfc531e061c15bff2c65df30ea7901933
parent 89cd993a
Loading
Loading
Loading
Loading
+26 −2
Original line number Original line Diff line number Diff line
@@ -1797,7 +1797,19 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {


        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
          /* We are here because of the reconnection of the single device. */
          /* We are here because of the reconnection of the single device. */
          /* Make sure that device is ready to be configured as we could also
           * get here triggered by the remote device. If device is not connected
           * yet, we should wait for the stack to trigger adding device to the
           * stream */
          if (leAudioDevice->GetConnectionState() ==
              le_audio::DeviceConnectState::CONNECTED) {
            PrepareAndSendConfigQos(group, leAudioDevice);
            PrepareAndSendConfigQos(group, leAudioDevice);
          } else {
            LOG_DEBUG(
                "Device %s initiated configured state but it is not yet ready "
                "to be configured",
                ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
          }
          return;
          return;
        }
        }


@@ -1896,7 +1908,19 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {


        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
          /* We are here because of the reconnection of the single device. */
          /* We are here because of the reconnection of the single device. */
          /* Make sure that device is ready to be configured as we could also
           * get here triggered by the remote device. If device is not connected
           * yet, we should wait for the stack to trigger adding device to the
           * stream */
          if (leAudioDevice->GetConnectionState() ==
              le_audio::DeviceConnectState::CONNECTED) {
            PrepareAndSendConfigQos(group, leAudioDevice);
            PrepareAndSendConfigQos(group, leAudioDevice);
          } else {
            LOG_DEBUG(
                "Device %s initiated configured state but it is not yet ready "
                "to be configured",
                ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
          }
          return;
          return;
        }
        }


+147 −0
Original line number Original line Diff line number Diff line
@@ -3942,6 +3942,153 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStream) {
  ASSERT_NE(ase->retrans_nb, 0);
  ASSERT_NE(ase->retrans_nb, 0);
}
}


TEST_F(StateMachineTest, testAutonomousConfiguredAndAttachToStream) {
  const auto context_type = kContextTypeMedia;
  const auto leaudio_group_id = 6;
  const auto num_devices = 2;

  /* Scenario
   * 1. Start streaming
   * 2. Stop stream on one device
   * 3. Reconnect
   * 4. Autonomous Configured state
   * 5. Make sure QoS Configure is not send out
   * 6. Trigger attach the stream
   * 7. Make sure stream is up
   */

  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, 0, true);
  PrepareConfigureQosHandler(group);
  PrepareEnableHandler(group);
  PrepareDisableHandler(group);
  PrepareReleaseHandler(group);

  auto* leAudioDevice = group->GetFirstDevice();
  LeAudioDevice* lastDevice;
  LeAudioDevice* fistDevice = leAudioDevice;

  auto expected_devices_written = 0;
  while (leAudioDevice) {
    /* Three Writes:
     * 1: Codec Config
     * 2: Codec QoS
     * 3: Enabling
     */
    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);

  // Check if group keeps streaming
  ASSERT_EQ(group->GetState(),
            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);

  /* Set device is getting ready for the connection */
  lastDevice->conn_id_ = 3;
  lastDevice->SetConnectionState(
      DeviceConnectState::CONNECTED_AUTOCONNECT_GETTING_READY);

  // Make sure ASE with disconnected CIS are not left in STREAMING
  ASSERT_EQ(lastDevice->GetFirstAseWithState(
                ::le_audio::types::kLeAudioDirectionSink,
                types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
            nullptr);
  ASSERT_EQ(lastDevice->GetFirstAseWithState(
                ::le_audio::types::kLeAudioDirectionSource,
                types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
            nullptr);

  // Symulate remote autonomous CONFIGURE state
  EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_,
                                              lastDevice->ctp_hdls_.val_hdl, _,
                                              GATT_WRITE_NO_RSP, _, _))
      .Times(0);

  int num_of_notifications = 0;
  for (auto& ase : lastDevice->ases_) {
    if (ase.id == le_audio::types::ase::kAseIdInvalid) {
      continue;
    }
    LOG_ERROR("ID : %d,  status %s", ase.id,
              bluetooth::common::ToString(ase.state).c_str());
    num_of_notifications++;
    InjectAseStateNotification(&ase, lastDevice, group,
                               ascs::kAseStateCodecConfigured,
                               &cached_codec_configuration_map_[ase.id]);
    break;
  }
  ASSERT_EQ(num_of_notifications, 1);

  testing::Mock::VerifyAndClearExpectations(&gatt_queue);
  // Now device is connected. Attach it to the stream

  lastDevice->SetConnectionState(DeviceConnectState::CONNECTED);

  EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_,
                                              lastDevice->ctp_hdls_.val_hdl, _,
                                              GATT_WRITE_NO_RSP, _, _))
      .Times(AtLeast(3));

  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
  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 the joining device receives the right CCID list
  auto lastMeta = lastDevice->GetFirstActiveAse()->metadata;
  bool parsedOk = false;
  auto ltv = le_audio::types::LeAudioLtvMap::Parse(lastMeta.data(),
                                                   lastMeta.size(), parsedOk);
  ASSERT_TRUE(parsedOk);

  auto ccids = ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList);
  ASSERT_TRUE(ccids.has_value());
  ASSERT_NE(std::find(ccids->begin(), ccids->end(), media_ccid), ccids->end());

  /* Verify that ASE of first device are still good*/
  auto ase = fistDevice->GetFirstActiveAse();
  ASSERT_NE(ase->max_transport_latency, 0);
  ASSERT_NE(ase->retrans_nb, 0);
}

TEST_F(StateMachineTest,
TEST_F(StateMachineTest,
       testAttachDeviceToTheStream_autonomusQoSConfiguredState) {
       testAttachDeviceToTheStream_autonomusQoSConfiguredState) {
  const auto context_type = kContextTypeMedia;
  const auto context_type = kContextTypeMedia;
+4 −1
Original line number Original line Diff line number Diff line
@@ -303,7 +303,10 @@ struct iso_impl {
      LOG_ASSERT(cis) << "No such cis: " << +el.cis_conn_handle;
      LOG_ASSERT(cis) << "No such cis: " << +el.cis_conn_handle;
      LOG_ASSERT(!(cis->state_flags &
      LOG_ASSERT(!(cis->state_flags &
                   (kStateFlagIsConnected | kStateFlagIsConnecting)))
                   (kStateFlagIsConnected | kStateFlagIsConnecting)))
          << "Already connected or connecting";
          << "cis: " << +el.cis_conn_handle
          << " is already connected or connecting flags: " << +cis->state_flags
          << ", num of cis params: " << +conn_params.conn_pairs.size();

      cis->state_flags |= kStateFlagIsConnecting;
      cis->state_flags |= kStateFlagIsConnecting;


      tBTM_SEC_DEV_REC* p_rec = btm_find_dev_by_handle(el.acl_conn_handle);
      tBTM_SEC_DEV_REC* p_rec = btm_find_dev_by_handle(el.acl_conn_handle);
+1 −1
Original line number Original line Diff line number Diff line
@@ -1028,7 +1028,7 @@ TEST_F(IsoManagerDeathTest, ConnectSameCisTwice) {
  ASSERT_EXIT(
  ASSERT_EXIT(
      IsoManager::GetInstance()->IsoManager::GetInstance()->EstablishCis(
      IsoManager::GetInstance()->IsoManager::GetInstance()->EstablishCis(
          params),
          params),
      ::testing::KilledBySignal(SIGABRT), "Already connected or connecting");
      ::testing::KilledBySignal(SIGABRT), "already connected or connecting");
}
}


TEST_F(IsoManagerDeathTest, EstablishCisInvalidResponsePacket) {
TEST_F(IsoManagerDeathTest, EstablishCisInvalidResponsePacket) {