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

Commit a5da10a2 authored by Jack He's avatar Jack He Committed by Gerrit Code Review
Browse files

Merge changes I19aef9d5,If0cf3746 into main

* changes:
  state_machine: Add address to multiple logs
  state_machine: Fix race on switching buds
parents f910abb3 fbe7bbec
Loading
Loading
Loading
Loading
+30 −19
Original line number Diff line number Diff line
@@ -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;
  }
@@ -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;
    }
  }
@@ -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;
    }
  }
@@ -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;
    }

@@ -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;
    }

@@ -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);
    }
@@ -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();
  }

@@ -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;
  }

@@ -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,
@@ -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,
@@ -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;

@@ -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) {
@@ -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;
  }

@@ -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;
      }
@@ -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;
}
@@ -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;
+77 −33
Original line number Diff line number Diff line
@@ -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);
      }
    }

@@ -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.
@@ -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);
@@ -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
@@ -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,
@@ -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");
@@ -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...",
@@ -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);
@@ -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);
@@ -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) {
@@ -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);
@@ -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
@@ -3067,7 +3110,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        }

        if (group->cig.GetState() == CigState::CREATED &&
            group->HaveAllCisesDisconnected()) {
            group->HaveAllCisesDisconnected() &&
            getDeviceTryingToAttachTheStream(group) == nullptr) {
          RemoveCigForGroup(group);
        }

+188 −9
Original line number Diff line number Diff line
@@ -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) {
@@ -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];
@@ -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;
@@ -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);
  }
@@ -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;