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

Commit 937d71ba authored by Jakub Tyszkowski's avatar Jakub Tyszkowski Committed by Łukasz Rymanowski
Browse files

LeAudio: Prepare data path before "receiver start ready"

We should have the data path prepared before we send the
ReceiverStartRedy ASCS operation.

Also this patch removes not necessary code for handling
autonomous QoS Configured -> Streaming state transition.
It is removed, as this is impossible to happen with Android
stack, because Android create CISes only when all ASEs gets
into Enabling state.

Bug: 227116068
Test: atest --host bluetooth_le_audio_test bluetooth_le_audio_client_test
Change-Id: Ic33e2b9207ed3ac6302e3d04a360b2fac1dcd9c7
parent 676d5520
Loading
Loading
Loading
Loading
+14 −9
Original line number Diff line number Diff line
@@ -868,16 +868,21 @@ bool LeAudioDeviceGroup::IsReleasingOrIdle(void) const {
}

bool LeAudioDeviceGroup::IsGroupStreamReady(void) const {
  auto iter =
      std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
        if (d.expired() || (d.lock().get()->GetConnectionState() !=
                            DeviceConnectState::CONNECTED))
          return false;
        else
          return !(((d.lock()).get())->HaveAllActiveAsesCisEst());
      });
  bool is_device_ready = false;

  return iter == leAudioDevices_.end();
  /* All connected devices must be ready */
  for (auto& weak : leAudioDevices_) {
    auto dev = weak.lock();
    if (!dev) return false;

    if (dev->GetConnectionState() == DeviceConnectState::CONNECTED) {
      if (!dev->IsReadyToStream()) {
        return false;
      }
      is_device_ready = true;
    }
  }
  return is_device_ready;
}

bool LeAudioDeviceGroup::HaveAllCisesDisconnected(void) const {
+27 −5
Original line number Diff line number Diff line
@@ -570,18 +570,32 @@ bool LeAudioDevice::HaveAnyUnconfiguredAses(void) {
bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) {
  auto iter =
      std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) {
        LOG_INFO("id: %d, active: %d, state: %d", ase.id, ase.active,
                 (int)ase.state);
        LOG_VERBOSE("ASE id: %d, active: %d, state: %s", ase.id, ase.active,
                    bluetooth::common::ToString(ase.state).c_str());
        return ase.active && (ase.state != state);
      });

  return iter == ases_.end();
}

bool LeAudioDevice::HaveAllActiveAsesSameDataPathState(
    types::DataPathState state) const {
  auto iter =
      std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) {
        LOG_VERBOSE("ASE id: %d, active: %d, state: %s", ase.id, ase.active,
                    bluetooth::common::ToString(ase.data_path_state).c_str());
        return ase.active && (ase.data_path_state != state);
      });

  return iter == ases_.end();
}

bool LeAudioDevice::IsReadyToCreateStream(void) {
  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
    if (!ase.active) return false;

    LOG_VERBOSE("ASE id: %d, state: %s, direction: %d", ase.id,
                bluetooth::common::ToString(ase.state).c_str(), ase.direction);
    if (ase.direction == types::kLeAudioDirectionSink &&
        (ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
         ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING))
@@ -615,7 +629,7 @@ bool LeAudioDevice::IsReadyToSuspendStream(void) {
  return iter == ases_.end();
}

bool LeAudioDevice::HaveAllActiveAsesCisEst(void) {
bool LeAudioDevice::HaveAllActiveAsesCisEst(void) const {
  if (ases_.empty()) {
    LOG_WARN("No ases for device %s", ADDRESS_TO_LOGGABLE_CSTR(address_));
    /* If there is no ASEs at all, it means we are good here - meaning, it is
@@ -624,11 +638,19 @@ bool LeAudioDevice::HaveAllActiveAsesCisEst(void) {
    return true;
  }

  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
  bool has_active_ase = false;
  auto iter = std::find_if(ases_.begin(), ases_.end(), [&](const auto& ase) {
    if (!has_active_ase && ase.active) {
      has_active_ase = true;
    }
    LOG_VERBOSE("ASE id: %d, cis_state: %s, direction: %d", ase.id,
                bluetooth::common::ToString(ase.cis_state).c_str(),
                ase.direction);

    return ase.active && (ase.cis_state != CisState::CONNECTED);
  });

  return iter == ases_.end();
  return iter == ases_.end() && has_active_ase;
}

bool LeAudioDevice::HaveAnyCisConnected(void) {
+6 −1
Original line number Diff line number Diff line
@@ -168,10 +168,15 @@ class LeAudioDevice {
  types::BidirectionalPair<struct types::ase*> GetAsesByCisId(uint8_t cis_id);
  bool HaveActiveAse(void);
  bool HaveAllActiveAsesSameState(types::AseState state);
  bool HaveAllActiveAsesSameDataPathState(types::DataPathState state) const;
  bool HaveAnyUnconfiguredAses(void);
  bool IsReadyToCreateStream(void);
  bool IsReadyToStream(void) const {
    return HaveAllActiveAsesCisEst() &&
           HaveAllActiveAsesSameDataPathState(types::DataPathState::CONFIGURED);
  }
  bool IsReadyToSuspendStream(void);
  bool HaveAllActiveAsesCisEst(void);
  bool HaveAllActiveAsesCisEst(void) const;
  bool HaveAnyCisConnected(void);
  bool HasCisId(uint8_t id);
  uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase);
+43 −100
Original line number Diff line number Diff line
@@ -608,24 +608,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

    AddCisToStreamConfiguration(group, ase);

    ase = leAudioDevice->GetFirstActiveAseByCisAndDataPathState(
        CisState::CONNECTED, DataPathState::IDLE);
    if (!ase) {
      leAudioDevice = group->GetNextActiveDeviceByCisAndDataPathState(
          leAudioDevice, CisState::CONNECTED, DataPathState::IDLE);

      if (!leAudioDevice) {
        state_machine_callbacks_->StatusReportCb(group->group_id_,
                                                 GroupStreamStatus::STREAMING);
        return;
      }

      ase = leAudioDevice->GetFirstActiveAseByCisAndDataPathState(
          CisState::CONNECTED, DataPathState::IDLE);
    if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
        !group->GetFirstActiveDeviceByCisAndDataPathState(
            CisState::CONNECTED, DataPathState::IDLE)) {
      /* No more transition for group */
      cancel_watchdog_if_needed(group->group_id_);
    }

    ASSERT_LOG(ase, "shouldn't be called without an active ASE");
    PrepareDataPath(group->group_id_, ase);
  }

  void ProcessHciNotifRemoveIsoDataPath(LeAudioDeviceGroup* group,
@@ -867,6 +855,16 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    if (ases_pair.sink) ases_pair.sink->cis_state = CisState::CONNECTED;
    if (ases_pair.source) ases_pair.source->cis_state = CisState::CONNECTED;

    if (ases_pair.sink &&
        (ases_pair.sink->data_path_state == DataPathState::IDLE)) {
      PrepareDataPath(group->group_id_, ases_pair.sink);
    }

    if (ases_pair.source &&
        (ases_pair.source->data_path_state == DataPathState::IDLE)) {
      PrepareDataPath(group->group_id_, ases_pair.source);
    }

    if (osi_property_get_bool("persist.bluetooth.iso_link_quality_report",
                              false)) {
      leAudioDevice->link_quality_timer =
@@ -901,16 +899,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
               event->cis_conn_hdl);

    PrepareAndSendReceiverStartReady(leAudioDevice, ase);

    /* Cis establishment may came after setting group state to streaming, e.g.
     * for autonomous scenario when ase is sink */
    if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
        group->GetFirstActiveDeviceByCisAndDataPathState(CisState::CONNECTED,
                                                         DataPathState::IDLE)) {
      /* No more transition for group */
      cancel_watchdog_if_needed(group->group_id_);
      PrepareDataPath(group);
    }
  }

  static void WriteToControlPoint(LeAudioDevice* leAudioDevice,
@@ -1518,18 +1506,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
                                                std::move(param));
  }

  static inline void PrepareDataPath(LeAudioDeviceGroup* group) {
    auto* leAudioDevice = group->GetFirstActiveDeviceByCisAndDataPathState(
        CisState::CONNECTED, DataPathState::IDLE);
    LOG_ASSERT(leAudioDevice)
        << __func__ << " Shouldn't be called without an active device.";

    auto* ase = leAudioDevice->GetFirstActiveAseByCisAndDataPathState(
        CisState::CONNECTED, DataPathState::IDLE);
    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
    PrepareDataPath(group->group_id_, ase);
  }

  static void ReleaseDataPath(LeAudioDeviceGroup* group) {
    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
    LOG_ASSERT(leAudioDevice)
@@ -2572,35 +2548,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

    switch (ase->state) {
      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
        /* As per ASCS 1.0 :
         * If a CIS has been established and the server is acting as Audio Sink
         * for the ASE, and if the server is ready to receive audio data
         * transmitted by the client, the server may autonomously initiate the
         * Receiver Start Ready, as defined in Section 5.4, without first
         * sending a notification of the ASE characteristic value in the
         * Enabling state.
         */
        if (ase->direction != le_audio::types::kLeAudioDirectionSink) {
          LOG(ERROR) << __func__ << ", invalid state transition, from: "
                     << static_cast<int>(ase->state) << ", to: "
                     << static_cast<int>(
                            AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
        LOG_ERROR(
            "%s, ase_id: %d, moving from QoS Configured to Streaming is "
            "impossible.",
            ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id);
        group->PrintDebugState();
        StopStream(group);
          return;
        }

        SetAseState(leAudioDevice, ase,
                    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. */
          PrepareDataPath(group);
          return;
        }

        if (leAudioDevice->IsReadyToCreateStream())
          ProcessGroupEnable(group);

        break;

      case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING: {
@@ -2613,45 +2566,40 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
                AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
          /* More ASEs notification form this device has to come for this group
           */

          return;
        }

        /* This case may happen because of the reconnection device. */
        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
          /* Not all CISes establish evens came */
          if (group->GetFirstActiveDeviceByCisAndDataPathState(
                  CisState::CONNECTING, DataPathState::IDLE))
            return;

          /* Streaming status notification came after setting data path */
          if (!group->GetFirstActiveDeviceByCisAndDataPathState(
                  CisState::CONNECTED, DataPathState::IDLE))
            return;
          PrepareDataPath(group);
          /* We are here because of the reconnection of the single device */
          LOG_INFO("%s, Ase id: %d, ase state: %s",
                   ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id,
                   bluetooth::common::ToString(ase->state).c_str());
          cancel_watchdog_if_needed(group->group_id_);
          state_machine_callbacks_->StatusReportCb(
              group->group_id_, GroupStreamStatus::STREAMING);
          return;
        }

        /* Last node is in streaming state */
        group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);

        /* Not all CISes establish evens came */
        /* Not all CISes establish events will came */
        if (!group->IsGroupStreamReady()) return;

        if (group->GetTargetState() ==
            AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
          /* No more transition for group */
          cancel_watchdog_if_needed(group->group_id_);
          PrepareDataPath(group);

          /* Last node is in streaming state */
          group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);

          state_machine_callbacks_->StatusReportCb(
              group->group_id_, GroupStreamStatus::STREAMING);
          return;
        } else {
        }

        LOG_ERROR(", invalid state transition, from: %s, to: %s",
                  ToString(group->GetState()).c_str(),
                  ToString(group->GetTargetState()).c_str());
        StopStream(group);
          return;
        }

        break;
      }
@@ -2866,6 +2814,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

  void ProcessGroupEnable(LeAudioDeviceGroup* group) {
    if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING) {
      /* Check if the group is ready to create stream. If not, keep waiting. */
      if (!group->IsGroupReadyToCreateStream()) {
        LOG_DEBUG(
            "Waiting for more ASEs to be in enabling or directly in streaming "
@@ -2873,18 +2822,11 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        return;
      }

      /* Group can move to Enabling state now. */
      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING);
    }

    /* At this point all of the active ASEs within group are enabled. The server
     * might perform autonomous state transition for Sink ASE and skip Enabling
     * state notification and transit to Streaming directly. So check the group
     * state, because we might be ready to create CIS. */
    if (group->HaveAllActiveDevicesAsesTheSameState(
            AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
    }

    /* If Target State is not streaming, then something is wrong. */
    if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
      LOG_ERROR(", invalid state transition, from: %s , to: %s ",
                ToString(group->GetState()).c_str(),
@@ -2893,6 +2835,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
      return;
    }

    /* Try to create CISes for the group */
    if (!CisCreate(group)) {
      StopStream(group);
    }
+131 −26
Original line number Diff line number Diff line
@@ -1800,6 +1800,13 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
  /* Device is banded headphones with 2x snk + none src ase
   * (2x unidirectional CIS)
   */

  /* Not, that when remote device skip Enabling it is considered as an error and
   * group will not be able to go to Streaming state.
   * It is because, Android is not creating CISes before all ASEs gets into
   * Enabling state, therefore it is impossible to remote device to skip
   * Enabling state.
   */
  const auto context_type = kContextTypeMedia;
  const int leaudio_group_id = 4;

@@ -1813,15 +1820,21 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
  PrepareConfigureQosHandler(group, 2);
  PrepareEnableHandler(group, 2, false);

  /*
   * 1. Configure
   * 2. QoS Config
   * 3. Enable
   * 4. Release
   */
  auto* leAudioDevice = group->GetFirstDevice();
  EXPECT_CALL(gatt_queue,
              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
                                  GATT_WRITE_NO_RSP, _, _))
      .Times(3);
      .Times(4);

  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
@@ -1829,10 +1842,15 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
  InjectInitialIdleNotification(group);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
                             bluetooth::le_audio::GroupStreamStatus::STREAMING))
      .Times(0);

  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::RELEASING))
      .Times(1);

  // Start the configuration and stream Media content
  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
@@ -1840,10 +1858,6 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
      {.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);

  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
}

@@ -1867,6 +1881,12 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
  PrepareEnableHandler(group, 3, false);
  PrepareReceiverStartReadyHandler(group, 1);

  /*
   * 1. Codec Config
   * 2. Qos Config
   * 3. Enable
   * 4. Release
   */
  auto* leAudioDevice = group->GetFirstDevice();
  EXPECT_CALL(gatt_queue,
              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
@@ -1874,8 +1894,8 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
      .Times(4);

  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3);
  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
@@ -1883,10 +1903,14 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
  InjectInitialIdleNotification(group);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
                             bluetooth::le_audio::GroupStreamStatus::STREAMING))
      .Times(0);
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::RELEASING))
      .Times(1);

  // Start the configuration and stream Media content
  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
@@ -1894,9 +1918,6 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
      {.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);
  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
}

@@ -2109,6 +2130,10 @@ TEST_F(StateMachineTest, testStreamMultipleConversational) {
  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
}

MATCHER_P(dataPathDirIsEq, expected, "") {
  return (arg.data_path_dir == expected);
}

TEST_F(StateMachineTest, testFailedStreamMultipleConversational) {
  /* Testing here CIS Failed to be established */
  const auto context_type = kContextTypeConversational;
@@ -2133,8 +2158,27 @@ TEST_F(StateMachineTest, testFailedStreamMultipleConversational) {

  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(AtLeast(1));
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);

  /* Bidirectional CIS data path is configured in tw ocalls and removed for both
   * directions with a single call.
   */
  EXPECT_CALL(*mock_iso_manager_,
              SetupIsoDataPath(
                  _, dataPathDirIsEq(
                         bluetooth::hci::iso_manager::kIsoDataPathDirectionIn)))
      .Times(1);
  EXPECT_CALL(
      *mock_iso_manager_,
      SetupIsoDataPath(
          _, dataPathDirIsEq(
                 bluetooth::hci::iso_manager::kIsoDataPathDirectionOut)))
      .Times(1);
  EXPECT_CALL(
      *mock_iso_manager_,
      RemoveIsoDataPath(
          _, bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput |
                 bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput))
      .Times(1);

  /* This check is the major one in this test, as we want to make sure,
   * it will not be called twice but only once (when both bidirectional ASEs are
@@ -4562,7 +4606,7 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) {

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

  auto expected_devices_written = 0;
  while (leAudioDevice) {
@@ -4581,10 +4625,43 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) {
    leAudioDevice = group->GetNextDevice(leAudioDevice);
  }
  ASSERT_EQ(expected_devices_written, num_devices);
  ASSERT_NE(nullptr, firstDevice);
  ASSERT_NE(nullptr, lastDevice);

  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4);

  EXPECT_CALL(*mock_iso_manager_,
              SetupIsoDataPath(
                  _, dataPathDirIsEq(
                         bluetooth::hci::iso_manager::kIsoDataPathDirectionIn)))
      .Times(2);

  // Make sure the Out data path is set before we declare that we are ready
  {
    ::testing::InSequence seq;
    EXPECT_CALL(*mock_iso_manager_,
                SetupIsoDataPath(
                    UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 0),
                    dataPathDirIsEq(
                        bluetooth::hci::iso_manager::kIsoDataPathDirectionOut)))
        .Times(1);
    EXPECT_CALL(ase_ctp_handler,
                AseCtpReceiverStartReadyHandler(firstDevice, _, _, _))
        .Times(1);
  }
  {
    ::testing::InSequence seq;
    EXPECT_CALL(*mock_iso_manager_,
                SetupIsoDataPath(
                    UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 1),
                    dataPathDirIsEq(
                        bluetooth::hci::iso_manager::kIsoDataPathDirectionOut)))
        .Times(1);
    EXPECT_CALL(ase_ctp_handler,
                AseCtpReceiverStartReadyHandler(lastDevice, _, _, _))
        .Times(1);
  }

  InjectInitialIdleNotification(group);

@@ -4599,9 +4676,19 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) {
            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);

  // Verify data path removal on the second bidirectional CIS
  EXPECT_CALL(
      *mock_iso_manager_,
      RemoveIsoDataPath(
          UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 1),
          bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput |
              bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput))
      .Times(1);

  // Inject CIS and ACL disconnection of first device
  InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT);
  InjectAclDisconnected(group, lastDevice);
  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);

  // Check if group keeps streaming
  ASSERT_EQ(group->GetState(),
@@ -4626,7 +4713,25 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) {
      .Times(AtLeast(3));

  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
  EXPECT_CALL(*mock_iso_manager_,
              SetupIsoDataPath(
                  _, dataPathDirIsEq(
                         bluetooth::hci::iso_manager::kIsoDataPathDirectionIn)))
      .Times(1);
  // Make sure the Out data path is set before we declare that we are ready
  {
    ::testing::InSequence seq;
    EXPECT_CALL(*mock_iso_manager_,
                SetupIsoDataPath(
                    UNIQUE_CIS_CONN_HANDLE(leaudio_group_id, 1),
                    dataPathDirIsEq(
                        bluetooth::hci::iso_manager::kIsoDataPathDirectionOut)))
        .Times(1);
    EXPECT_CALL(ase_ctp_handler,
                AseCtpReceiverStartReadyHandler(lastDevice, _, _, _))
        .Times(1);
  }

  LeAudioGroupStateMachine::Get()->AttachToStream(
      group, lastDevice, {.sink = {call_ccid}, .source = {call_ccid}});

@@ -4646,7 +4751,7 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) {
  ASSERT_NE(std::find(ccids->begin(), ccids->end(), call_ccid), ccids->end());

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