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

Commit 824c22bd authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "LeAudio: Prepare data path before "receiver start ready"" into main

parents c5cc97ce 937d71ba
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
@@ -603,24 +603,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,
@@ -862,6 +850,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 =
@@ -896,16 +894,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,
@@ -1513,18 +1501,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)
@@ -2567,35 +2543,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: {
@@ -2608,45 +2561,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;
      }
@@ -2861,6 +2809,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 "
@@ -2868,18 +2817,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(),
@@ -2888,6 +2830,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);