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

Commit 1643edea authored by Grzegorz Kolodziejczyk's avatar Grzegorz Kolodziejczyk Committed by Gerrit Code Review
Browse files

Merge "le_audio: Handle reconnection in conversational scenario"

parents 854dda1b d7e9e7c4
Loading
Loading
Loading
Loading
+57 −20
Original line number Diff line number Diff line
@@ -677,8 +677,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
      const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event)
      override {
    std::vector<uint8_t> value;

    auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl);

    if (event->status) {
@@ -730,11 +728,17 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    }

    if (!leAudioDevice->HaveAllActiveAsesCisEst()) {
      /* More cis established event has to come */
      /* More cis established events has to come */
      return;
    }

    std::vector<uint8_t> ids;
    if (!leAudioDevice->IsReadyToCreateStream()) {
      /* Device still remains in ready to create stream state. It means that
       * more enabling status notifications has to come. This may only happen
       * for reconnection scenario for bi-directional CIS.
       */
      return;
    }

    /* All CISes created. Send start ready for source ASE before we can go
     * to streaming state.
@@ -745,21 +749,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
               "id: %d, cis handle 0x%04x",
               ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), event->cig_id,
               event->cis_conn_hdl);
    do {
      if (ase->direction == le_audio::types::kLeAudioDirectionSource)
        ids.push_back(ase->id);
    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));

    if (ids.size() > 0) {
      le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady(
          ids, value);

      BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
                                        leAudioDevice->ctp_hdls_.val_hdl, value,
                                        GATT_WRITE_NO_RSP, NULL, NULL);

      return;
    }
    PrepareAndSendReceiverStartReady(leAudioDevice, ase);

    /* Cis establishment may came after setting group state to streaming, e.g.
     * for autonomous scenario when ase is sink */
@@ -2183,6 +2174,28 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    }
  }

  void PrepareAndSendReceiverStartReady(LeAudioDevice* leAudioDevice,
                                        struct ase* ase) {
    std::vector<uint8_t> ids;
    std::vector<uint8_t> value;

    do {
      if (ase->direction == le_audio::types::kLeAudioDirectionSource)
        ids.push_back(ase->id);
    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));

    if (ids.size() > 0) {
      le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady(
          ids, value);

      BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
                                        leAudioDevice->ctp_hdls_.val_hdl, value,
                                        GATT_WRITE_NO_RSP, NULL, NULL);

      return;
    }
  }

  void AseStateMachineProcessEnabling(
      struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
@@ -2196,8 +2209,32 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING;

        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
          if (ase->data_path_state < AudioStreamDataPathState::CIS_PENDING) {
            /* We are here because of the reconnection of the single device. */
            CisCreateForDevice(leAudioDevice);
          }

          if (!leAudioDevice->HaveAllActiveAsesCisEst()) {
            /* More cis established events has to come */
            return;
          }

          if (!leAudioDevice->IsReadyToCreateStream()) {
            /* Device still remains in ready to create stream state. It means
             * that more enabling status notifications has to come.
             */
            return;
          }

          /* All CISes created. Send start ready for source ASE before we can go
           * to streaming state.
           */
          struct ase* ase = leAudioDevice->GetFirstActiveAse();
          ASSERT_LOG(ase != nullptr,
                     "shouldn't be called without an active ASE, device %s",
                     leAudioDevice->address_.ToString().c_str());
          PrepareAndSendReceiverStartReady(leAudioDevice, ase);

          return;
        }

+112 −0
Original line number Diff line number Diff line
@@ -3097,6 +3097,118 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStream) {
  ASSERT_NE(ase->retrans_nb, 0);
}

TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) {
  const auto context_type = kContextTypeConversational;
  const auto leaudio_group_id = 6;
  const auto num_devices = 2;

  ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_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);
  PrepareReceiverStartReady(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(4);

  InjectInitialIdleNotification(group);

  // Start the configuration and stream Conversational content
  LeAudioGroupStateMachine::Get()->StartStream(
      group, static_cast<LeAudioContextType>(context_type),
      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;
  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));

  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
  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(), call_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);

  // Make sure ASEs with reconnected CIS are in STREAMING state
  ASSERT_TRUE(lastDevice->HaveAllActiveAsesSameState(
      types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING));
}

TEST_F(StateMachineTest, StartStreamAfterConfigure) {
  const auto context_type = kContextTypeMedia;
  const auto leaudio_group_id = 6;