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

Commit 1fca289f authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "leaudio: Improve handling Autonomous Release" into main am: b3fc3ddd

parents a5a4bdbd b3fc3ddd
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -5818,10 +5818,10 @@ class LeAudioClientImpl : public LeAudioClient {
      case GroupStreamStatus::RELEASING:
      case GroupStreamStatus::SUSPENDING:
        if (active_group_id_ != bluetooth::groups::kGroupUnknown &&
            (active_group_id_ == group->group_id_) &&
            !group->IsPendingConfiguration() &&
            (active_group_id_ == group->group_id_) && !group->IsPendingConfiguration() &&
            (audio_sender_state_ == AudioState::STARTED ||
             audio_receiver_state_ == AudioState::STARTED)) {
             audio_receiver_state_ == AudioState::STARTED) &&
            group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
          /* If releasing state is happening but it was not initiated either by
           * reconfiguration or Audio Framework actions either by the Active group change,
           * it means that it is some internal state machine error. This is very unlikely and
+12 −0
Original line number Diff line number Diff line
@@ -719,6 +719,18 @@ bool LeAudioDevice::HaveActiveAse(void) {
  return iter != ases_.end();
}

bool LeAudioDevice::HaveAnyReleasingAse(void) {
  /* In configuring state when active in Idle or Configured and reconfigure */
  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
    if (!ase.active) {
      return false;
    }
    return ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING;
  });

  return iter != ases_.end();
}

bool LeAudioDevice::HaveAnyStreamingAses(void) {
  /* In configuring state when active in Idle or Configured and reconfigure */
  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
+1 −0
Original line number Diff line number Diff line
@@ -166,6 +166,7 @@ class LeAudioDevice {
  bool HaveAllActiveAsesSameDataPathState(types::DataPathState state) const;
  bool HaveAnyUnconfiguredAses(void);
  bool HaveAnyStreamingAses(void);
  bool HaveAnyReleasingAse(void);
  bool IsReadyToCreateStream(void);
  bool IsReadyToStream(void) const {
    return HaveAllActiveAsesCisEst() &&
+17 −3
Original line number Diff line number Diff line
@@ -1803,7 +1803,9 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

  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  */
     * STREAMING state and NOT in  the RELEASING state.
     * The precondition is, that TargetState is Streaming
     */

    log::debug("group_id: {}, targetState: {}", group->group_id_,
               ToString(group->GetTargetState()));
@@ -1814,8 +1816,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

    for (auto dev = group->GetFirstActiveDevice(); dev != nullptr;
         dev = group->GetNextActiveDevice(dev)) {
      if (!dev->HaveAllActiveAsesSameState(
              AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
      if (!dev->HaveAllActiveAsesSameState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) &&
          !dev->HaveAnyReleasingAse()) {
        log::debug("Attaching device {} to group_id: {}", dev->address_,
                   group->group_id_);
        return dev;
@@ -3189,6 +3191,18 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
          log::debug("Nothing to do ase data path state: {}",
                     static_cast<int>(ase->data_path_state));
        }

        if (group->HaveAllActiveDevicesAsesTheSameState(
                    AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING)) {
          group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING);
          if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
            log::info("Group {} is doing autonomous release", group->group_id_);
            SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
            state_machine_callbacks_->StatusReportCb(group->group_id_,
                                                     GroupStreamStatus::RELEASING);
          }
        }

        break;
      }
      default:
+122 −26
Original line number Diff line number Diff line
@@ -747,26 +747,29 @@ class StateMachineTestBase : public Test {
        group, leAudioDevice);
  }

  void InjectReleasingAndIdleState(LeAudioDeviceGroup* group,
                                   LeAudioDevice* device) {
  void InjectReleasingAndIdleState(LeAudioDeviceGroup* group, LeAudioDevice* device,
                                   bool release = true, bool idle = true) {
    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);
      if (release) {
        InjectAseStateNotification(&ase, device, group, ascs::kAseStateReleasing, nullptr);
      }
      if (idle) {
        InjectAseStateNotification(&ase, device, group, ascs::kAseStateIdle, nullptr);
      }
    }
  }

  void InjectReleaseAndIdleStateForAGroup(LeAudioDeviceGroup* group) {
  void InjectReleaseAndIdleStateForAGroup(LeAudioDeviceGroup* group, bool release = true,
                                          bool idle = true) {
    auto leAudioDevice = group->GetFirstActiveDevice();
    while (leAudioDevice) {
      log::info("Group : {},  dev: {}", group->group_id_,
                leAudioDevice->address_);
      InjectReleasingAndIdleState(group, leAudioDevice);
      InjectReleasingAndIdleState(group, leAudioDevice, release, idle);
      leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
    }
  }
@@ -3996,6 +3999,117 @@ TEST_F(StateMachineTest, testReleaseMultiple) {
  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
}

static void InjectCisDisconnected(LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
                                  uint8_t reason, bool first_cis_disconnect_only = false) {
  bluetooth::hci::iso_manager::cis_disconnected_evt event;

  for (auto const ase : leAudioDevice->ases_) {
    if (ase.cis_state != types::CisState::ASSIGNED && ase.cis_state != types::CisState::IDLE) {
      event.reason = reason;
      event.cig_id = group->group_id_;
      event.cis_conn_hdl = ase.cis_conn_hdl;
      LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected(group, leAudioDevice, &event);
      if (first_cis_disconnect_only) {
        break;
      }
    }
  }
}

TEST_F(StateMachineTest, testAutonomousReleaseMultiple) {
  const auto context_type = kContextTypeMedia;
  const auto leaudio_group_id = 6;
  const auto num_devices = 2;

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

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

  /*
   * 1. Codec Config
   * 2. QoS Config
   * 3. Enable
   */
  auto expected_devices_written = 0;
  while (leAudioDevice) {
    EXPECT_CALL(gatt_queue,
                WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, _,
                                    GATT_WRITE_NO_RSP, _, _))
            .Times(3);
    expected_devices_written++;
    secondDevice = leAudioDevice;
    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);
  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2);
  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);

  InjectInitialIdleNotification(group);

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

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

  ASSERT_EQ(1, get_func_call_count("alarm_set_on_mloop"));
  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
  reset_mock_function_count_map();

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

  // Do not take any actions on DisconnectCis. Later it will be injected.
  ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());

  log::info("Inject Release of all ASEs");

  // Inject Release state from remove
  InjectReleaseAndIdleStateForAGroup(group, true, false);

  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING);

  log::info("Inject CIS Disconnected Event");

  // Inject CIS Disconnection from remote
  InjectCisDisconnected(group, firstDevice, HCI_ERR_PEER_USER);
  InjectCisDisconnected(group, secondDevice, HCI_ERR_PEER_USER);

  // Inject Idle ASE
  InjectReleaseAndIdleStateForAGroup(group, false, true);

  // Check if group has transitioned to a proper state
  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
  ASSERT_EQ(1, get_func_call_count("alarm_set_on_mloop"));
  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
}

TEST_F(StateMachineTest, testReleaseMultiple_DeviceDisconnectedDuringRelease) {
  const auto context_type = kContextTypeMedia;
  const auto leaudio_group_id = 6;
@@ -4995,24 +5109,6 @@ TEST_F(StateMachineTestAdsp, testStreamConfigurationAdspDownMix) {
  // Note: The actual channel mixing is verified by the CodecManager unit tests.
}

static void InjectCisDisconnected(LeAudioDeviceGroup* group,
                                  LeAudioDevice* leAudioDevice, uint8_t reason,
                                  bool first_cis_disconnect_only = false) {
  bluetooth::hci::iso_manager::cis_disconnected_evt event;

  for (auto const ase : leAudioDevice->ases_) {
    if (ase.cis_state != types::CisState::ASSIGNED &&
        ase.cis_state != types::CisState::IDLE) {
      event.reason = reason;
      event.cig_id = group->group_id_;
      event.cis_conn_hdl = ase.cis_conn_hdl;
      LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected(
          group, leAudioDevice, &event);
      if (first_cis_disconnect_only) break;
    }
  }
}

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