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

Commit 9444222d authored by Grzegorz Kołodziejczyk's avatar Grzegorz Kołodziejczyk
Browse files

le_audio: Handle late CIS connection event for joining device

This change adds handling of CIS connection event that will come after
receiving streaming ASE state notification. This may also happen when
establish data path event will come after getting streaming ASE state
notification.

Tag: #feature
Bug: 275663612
Test: atest StateMachineTest#testAttachDeviceToTheStreamCisFailure
Change-Id: Ib3183507779a5867311d63cc33f8a3c34b120bbe
parent 45dd4839
Loading
Loading
Loading
Loading
+12 −2
Original line number Original line Diff line number Diff line
@@ -806,7 +806,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    /* Cis establishment may came after setting group state to streaming, e.g.
    /* Cis establishment may came after setting group state to streaming, e.g.
     * for autonomous scenario when ase is sink */
     * for autonomous scenario when ase is sink */
    if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
    if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
        group->IsGroupStreamReady()) {
        group->GetFirstActiveDeviceByDataPathState(
            AudioStreamDataPathState::CIS_ESTABLISHED)) {
      /* No more transition for group */
      /* No more transition for group */
      cancel_watchdog_if_needed(group->group_id_);
      cancel_watchdog_if_needed(group->group_id_);
      PrepareDataPath(group);
      PrepareDataPath(group);
@@ -2609,8 +2610,17 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
          return;
          return;
        }
        }


        /* This case may happen because of the reconnection device. */
        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. */
          /* Not all CISes establish evens came */
          if (group->GetFirstActiveDeviceByDataPathState(
                  AudioStreamDataPathState::CIS_PENDING))
            return;

          /* Streaming status notification came after setting data path */
          if (!group->GetFirstActiveDeviceByDataPathState(
                  AudioStreamDataPathState::CIS_ESTABLISHED))
            return;
          PrepareDataPath(group);
          PrepareDataPath(group);
          return;
          return;
        }
        }
+120 −0
Original line number Original line Diff line number Diff line
@@ -177,6 +177,7 @@ class StateMachineTest : public Test {


  /* Use to simulated error status on Cis creation */
  /* Use to simulated error status on Cis creation */
  bool overwrite_cis_status_;
  bool overwrite_cis_status_;
  bool do_not_send_cis_establish_event_;
  uint8_t overwrite_cis_status_idx_;
  uint8_t overwrite_cis_status_idx_;
  std::vector<uint8_t> cis_status_;
  std::vector<uint8_t> cis_status_;


@@ -190,6 +191,7 @@ class StateMachineTest : public Test {


    overwrite_cis_status_idx_ = 0;
    overwrite_cis_status_idx_ = 0;
    overwrite_cis_status_ = false;
    overwrite_cis_status_ = false;
    do_not_send_cis_establish_event_ = false;
    cis_status_.clear();
    cis_status_.clear();


    ::le_audio::AudioSetConfigurationProvider::Initialize();
    ::le_audio::AudioSetConfigurationProvider::Initialize();
@@ -362,6 +364,11 @@ class StateMachineTest : public Test {
                                  conn_params) {
                                  conn_params) {
          DLOG(INFO) << "EstablishCis";
          DLOG(INFO) << "EstablishCis";


          if (do_not_send_cis_establish_event_) {
            DLOG(INFO) << "Don't send cis establish event";
            return;
          }

          for (auto& pair : conn_params.conn_pairs) {
          for (auto& pair : conn_params.conn_pairs) {
            auto dev_it = std::find_if(
            auto dev_it = std::find_if(
                le_audio_devices_.begin(), le_audio_devices_.end(),
                le_audio_devices_.begin(), le_audio_devices_.end(),
@@ -4353,5 +4360,118 @@ TEST_F(StateMachineTest, StreamStartWithDifferentContextFromConfiguredState) {


  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
}
}

TEST_F(StateMachineTest, testAttachDeviceToTheStreamCisFailure) {
  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* 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);

  lastDevice->conn_id_ = 3;
  lastDevice->SetConnectionState(DeviceConnectState::CONNECTED);

  group->UpdateAudioContextTypeAvailability();

  // 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);

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

  do_not_send_cis_establish_event_ = true;

  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
  LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice);

  // 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);
}

}  // namespace internal
}  // namespace internal
}  // namespace le_audio
}  // namespace le_audio