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

Commit a0f88a05 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Improve handling audio driver requestes

With this patch we make sure that audio driver will get
ConfirmStreamRequest when ISO stream is in the STREAMING state or Stream
creation has been started.
In all other cases, CancelStreamRequest means: please come back later

Also new AudioState::RELEASING has been introduce which is used
for the time when ISO stream is taking down.
When this is happening, we also as audio driver to wait.

Tag: #feature
Test: atest --host  bluetooth_le_audio_client_test
bluetooth_le_audio_test
Sponsor: jpawlowski@
Bug: 150670922

Change-Id: I05cca4c2d6c0ec1f0c2a7f3835d830dd0cc46696
parent 27eb01ed
Loading
Loading
Loading
Loading
+227 −123
Original line number Diff line number Diff line
@@ -80,6 +80,8 @@ enum class AudioState {
  IDLE = 0x00,
  READY_TO_START,
  STARTED,
  READY_TO_RELEASE,
  RELEASING,
};

std::ostream& operator<<(std::ostream& os, const AudioState& audio_state) {
@@ -93,6 +95,12 @@ std::ostream& operator<<(std::ostream& os, const AudioState& audio_state) {
    case AudioState::STARTED:
      os << "STARTED";
      break;
    case AudioState::READY_TO_RELEASE:
      os << "READY_TO_RELEASE";
      break;
    case AudioState::RELEASING:
      os << "RELEASING";
      break;
    default:
      os << "UNKNOWN";
      break;
@@ -154,7 +162,6 @@ class LeAudioClientImpl : public LeAudioClient {
      : gatt_if_(0),
        callbacks_(callbacks_),
        active_group_id_(bluetooth::groups::kGroupUnknown),
        stream_request_started_(false),
        current_context_type_(LeAudioContextType::MEDIA),
        upcoming_context_type_(LeAudioContextType::MEDIA),
        audio_receiver_state_(AudioState::IDLE),
@@ -284,6 +291,8 @@ class LeAudioClientImpl : public LeAudioClient {

    /* Releasement didn't finished in time */
    if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
      audio_sender_state_ = AudioState::IDLE;
      audio_receiver_state_ = AudioState::IDLE;
      LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
      LOG_ASSERT(leAudioDevice)
          << __func__ << " Shouldn't be called without an active device.";
@@ -511,21 +520,19 @@ class LeAudioClientImpl : public LeAudioClient {
    group_remove_node(group, address, true);
  }

  void GroupStream(const int group_id, const uint16_t context_type) override {
  bool InternalGroupStream(const int group_id, const uint16_t context_type) {
    LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
    auto final_context_type = context_type;

    if (context_type >= static_cast<uint16_t>(LeAudioContextType::RFU)) {
      LOG(ERROR) << __func__ << ", stream context type is not supported: "
                 << loghex(context_type);
      CancelStreamingRequest();
      return;
      return false;
    }

    if (!group) {
      LOG(ERROR) << __func__ << ", unknown group id: " << group_id;
      CancelStreamingRequest();
      return;
      return false;
    }

    auto supported_context_type = group->GetActiveContexts();
@@ -538,23 +545,22 @@ class LeAudioClientImpl : public LeAudioClient {

    if (!group->IsAnyDeviceConnected()) {
      LOG(ERROR) << __func__ << ", group " << group_id << " is not connected ";
      CancelStreamingRequest();
      return;
      return false;
    }

    /* Check if any group is in the transition state. If so, we don't allow to
     * start new group to stream */
    if (aseGroups_.IsAnyInTransition()) {
      LOG(INFO) << __func__ << " some group is already in the transition state";
      CancelStreamingRequest();
      return;
      return false;
    }

    if (groupStateMachine_->StartStream(
            group, static_cast<LeAudioContextType>(final_context_type)))
      stream_request_started_ = true;
    else
      ClientAudioIntefraceRelease();
    return groupStateMachine_->StartStream(
        group, static_cast<LeAudioContextType>(final_context_type));
  }

  void GroupStream(const int group_id, const uint16_t context_type) override {
    InternalGroupStream(group_id, context_type);
  }

  void GroupSuspend(const int group_id) override {
@@ -583,9 +589,6 @@ class LeAudioClientImpl : public LeAudioClient {
      return;
    }

    audio_sender_state_ = AudioState::IDLE;
    audio_receiver_state_ = AudioState::IDLE;

    groupStateMachine_->SuspendStream(group);
  }

@@ -2385,27 +2388,20 @@ class LeAudioClientImpl : public LeAudioClient {
    current_context_type_ = upcoming_context_type_;
  }

  void OnAudioResume() {
    if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
      LOG(WARNING) << ", cannot start straming if no active group set";
      return;
    }

    auto group = aseGroups_.FindById(active_group_id_);
    if (!group) {
      LOG(ERROR) << __func__
                 << ", Invalid group: " << static_cast<int>(active_group_id_);
      return;
  bool OnAudioResume(LeAudioDeviceGroup* group) {
    if (upcoming_context_type_ != current_context_type_) {
      return false;
    }

    if (upcoming_context_type_ != current_context_type_) {
      /* Wait until session is updated */
      CancelStreamingRequest();
      return;
    /* Even context type is same, we might need more audio channels e.g. because
     * new device got connected */
    if (ReconfigureHalSessionIfNeeded(group, current_context_type_)) {
      return false;
    }

    /* TODO check if group already started streaming */
    GroupStream(active_group_id_, static_cast<uint16_t>(current_context_type_));
    return InternalGroupStream(active_group_id_,
                               static_cast<uint16_t>(current_context_type_));
  }

  void OnAudioSuspend() {
@@ -2418,18 +2414,38 @@ class LeAudioClientImpl : public LeAudioClient {
  }

  void OnAudioSinkSuspend() {
    LOG(INFO) << __func__;
    DLOG(INFO) << __func__
               << " IN: audio_receiver_state_: " << audio_receiver_state_
               << " audio_sender_state_: " << audio_sender_state_;

    /* Note: This callback is from audio hal driver.
     * Bluetooth peer is a Sink for Audio Framework.
     * e.g. Peer is a speaker
     */
    if (audio_sender_state_ == AudioState::IDLE) return;

    audio_sender_state_ = AudioState::IDLE;
    switch (audio_sender_state_) {
      case AudioState::READY_TO_START:
      case AudioState::STARTED:
        audio_sender_state_ = AudioState::READY_TO_RELEASE;
        break;
      case AudioState::RELEASING:
        return;
      case AudioState::IDLE:
        if (audio_receiver_state_ == AudioState::READY_TO_RELEASE) {
          OnAudioSuspend();
        }
        return;
      case AudioState::READY_TO_RELEASE:
        break;
    }

    /* Last suspends group - triggers group stop */
    if (audio_receiver_state_ == AudioState::IDLE) OnAudioSuspend();
    if ((audio_receiver_state_ == AudioState::IDLE) ||
        (audio_receiver_state_ == AudioState::READY_TO_RELEASE))
      OnAudioSuspend();

    DLOG(INFO) << __func__
               << " OUT: audio_receiver_state_: " << audio_receiver_state_
               << " audio_sender_state_: " << audio_sender_state_;
  }

  void OnAudioSinkResume() {
@@ -2455,49 +2471,93 @@ class LeAudioClientImpl : public LeAudioClient {
      return;
    }

    /* First resume request from sink/source triggers group start */
    if (audio_receiver_state_ == AudioState::IDLE &&
        audio_sender_state_ == AudioState::IDLE) {
      DLOG(INFO) << __func__ << " audio_sender_state_ READY_TO_START";
      audio_sender_state_ = AudioState::READY_TO_START;
      OnAudioResume();

      return;
    }

    if (audio_receiver_state_ >= AudioState::READY_TO_START) {
      LOG(INFO) << __func__ << " audio_receiver_state_ is READY_TO_START";
      audio_sender_state_ = AudioState::READY_TO_START;
      /* If signalling part is completed trigger start reveivin audio here,
       * otherwise it'll be called on group streaming state callback
       */
      if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)
        StartSendingAudio(active_group_id_);
    } else {
      /* Ask framework to come back later */
    DLOG(INFO) << __func__ << " active_group_id: " << active_group_id_ << "\n"
               << " audio_receiver_state: " << audio_receiver_state_ << "\n"
               << " audio_sender_state: " << audio_sender_state_ << "\n"
               << " current_context_type_: "
               << static_cast<int>(current_context_type_) << "\n"
                 << " group exist? " << (group ? " yes " : " no ") << "\n";
      CancelStreamingRequest();
               << " upcoming_context_type_: "
               << static_cast<int>(upcoming_context_type_) << "\n"
               << " group " << (group ? " exist " : " does not exist ") << "\n";

    switch (audio_sender_state_) {
      case AudioState::STARTED:
        /* Looks like previous Confirm did not get to the Audio Framework*/
        LeAudioClientAudioSource::ConfirmStreamingRequest();
        break;
      case AudioState::IDLE:
        if (audio_receiver_state_ == AudioState::IDLE) {
          /* Stream is not started. Try to do it.*/
          if (OnAudioResume(group)) {
            audio_sender_state_ = AudioState::READY_TO_START;
          } else {
            LeAudioClientAudioSource::CancelStreamingRequest();
          }
        } else {
          /* Stream has been started by the Source. */
          audio_sender_state_ = AudioState::READY_TO_START;
          if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
            StartSendingAudio(active_group_id_);
          } else {
            LeAudioClientAudioSource::CancelStreamingRequest();
          }
        }
        break;
      case AudioState::READY_TO_START:
        LOG(WARNING) << __func__
                     << " called in wrong state. \n audio_receiver_state: "
                     << audio_receiver_state_ << "\n"
                     << " audio_sender_state: " << audio_sender_state_ << "\n";
        break;
      case AudioState::READY_TO_RELEASE:
        if (audio_receiver_state_ == AudioState::STARTED) {
          /* Stream is up just restore it */
          audio_sender_state_ = AudioState::STARTED;
          LeAudioClientAudioSource::ConfirmStreamingRequest();
          return;
        }
        LeAudioClientAudioSource::CancelStreamingRequest();
        break;
      case AudioState::RELEASING:
        /* Keep wainting */
        LeAudioClientAudioSource::CancelStreamingRequest();
        break;
    }
  }

  void OnAudioSourceSuspend() {
    LOG(INFO) << __func__;
    DLOG(INFO) << __func__
               << " IN: audio_receiver_state_: " << audio_receiver_state_
               << " audio_sender_state_: " << audio_sender_state_;

    /* Note: This callback is from audio hal driver.
     * Bluetooth peer is a Source for Audio Framework.
     * e.g. Peer is microphone.
     */
    if (audio_receiver_state_ == AudioState::IDLE) return;

    audio_receiver_state_ = AudioState::IDLE;
    switch (audio_receiver_state_) {
      case AudioState::READY_TO_START:
      case AudioState::STARTED:
        audio_receiver_state_ = AudioState::READY_TO_RELEASE;
        break;
      case AudioState::RELEASING:
        return;
      case AudioState::IDLE:
        if (audio_sender_state_ == AudioState::READY_TO_RELEASE) {
          OnAudioSuspend();
        }
        return;
      case AudioState::READY_TO_RELEASE:
        break;
    }

    /* Last suspends group - triggers group stop */
    if (audio_sender_state_ == AudioState::IDLE) OnAudioSuspend();
    if ((audio_sender_state_ == AudioState::IDLE) ||
        (audio_sender_state_ == AudioState::READY_TO_RELEASE))
      OnAudioSuspend();

    DLOG(INFO) << __func__
               << " OUT: audio_receiver_state_: " << audio_receiver_state_
               << " audio_sender_state_: " << audio_sender_state_;
  }

  void OnAudioSourceResume() {
@@ -2523,22 +2583,57 @@ class LeAudioClientImpl : public LeAudioClient {
      return;
    }

    /* First resume request from sink/source triggers group start */
    if ((audio_receiver_state_ == AudioState::IDLE) &&
        (audio_sender_state_ == AudioState::IDLE)) {
      OnAudioResume();
      audio_receiver_state_ = AudioState::READY_TO_START;
    DLOG(INFO) << __func__ << " active_group_id: " << active_group_id_ << "\n"
               << " audio_receiver_state: " << audio_receiver_state_ << "\n"
               << " audio_sender_state: " << audio_sender_state_ << "\n"
               << " current_context_type_: "
               << static_cast<int>(current_context_type_) << "\n"
               << " upcoming_context_type_: "
               << static_cast<int>(upcoming_context_type_) << "\n"
               << " group " << (group ? " exist " : " does not exist ") << "\n";

      return;
    switch (audio_receiver_state_) {
      case AudioState::STARTED:
        LeAudioClientAudioSink::ConfirmStreamingRequest();
        break;
      case AudioState::IDLE:
        if (audio_sender_state_ == AudioState::IDLE) {
          if (OnAudioResume(group)) {
            audio_receiver_state_ = AudioState::READY_TO_START;
          } else {
            LeAudioClientAudioSink::CancelStreamingRequest();
          }

    if (audio_sender_state_ >= AudioState::READY_TO_START) {
        } else {
          audio_receiver_state_ = AudioState::READY_TO_START;
          /* If signalling part is completed trigger start reveivin audio here,
           * otherwise it'll be called on group streaming state callback
           */
      if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)
          if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
            StartReceivingAudio(active_group_id_);
          } else {
            LeAudioClientAudioSink::CancelStreamingRequest();
          }
        }
        break;
      case AudioState::READY_TO_START:
        LOG(WARNING) << __func__
                     << " called in wrong state. \n audio_receiver_state: "
                     << audio_receiver_state_ << "\n"
                     << " audio_sender_state: " << audio_sender_state_ << "\n";
        break;
      case AudioState::READY_TO_RELEASE:
        if (audio_sender_state_ == AudioState::STARTED) {
          /* Just return to started */
          audio_receiver_state_ = AudioState::STARTED;
          LeAudioClientAudioSink::ConfirmStreamingRequest();
          return;
        }

        LeAudioClientAudioSink::CancelStreamingRequest();
        break;
      case AudioState::RELEASING:
        LeAudioClientAudioSink::CancelStreamingRequest();
        break;
    }
  }

@@ -2617,6 +2712,40 @@ class LeAudioClientImpl : public LeAudioClient {
    return available_contents[0];
  }

  bool ReconfigureHalSessionIfNeeded(LeAudioDeviceGroup* group,
                                     LeAudioContextType new_context_type) {
    std::optional<LeAudioCodecConfiguration> source_configuration =
        group->GetCodecConfigurationByDirection(
            new_context_type, le_audio::types::kLeAudioDirectionSink);

    std::optional<LeAudioCodecConfiguration> sink_configuration =
        group->GetCodecConfigurationByDirection(
            new_context_type, le_audio::types::kLeAudioDirectionSource);

    if ((source_configuration &&
         (*source_configuration != current_source_codec_config)) ||
        (sink_configuration &&
         (*sink_configuration != current_sink_codec_config))) {
      LOG(INFO) << __func__
                << " Session reconfiguration needed group: " << group->group_id_
                << "for context type: " << static_cast<int>(new_context_type);

      if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
        GroupStop(group->group_id_);
      }

      upcoming_context_type_ = new_context_type;
      /* Schedule HAL Session update */
      do_in_main_thread(FROM_HERE,
                        base::Bind(&LeAudioClientImpl::UpdateCurrentHalSessions,
                                   base::Unretained(instance), group->group_id_,
                                   upcoming_context_type_));
      return true;
    }

    return false;
  }

  void OnAudioMetadataUpdate(const source_metadata_t& source_metadata) {
    auto tracks = source_metadata.tracks;
    auto track_count = source_metadata.track_count;
@@ -2668,40 +2797,15 @@ class LeAudioClientImpl : public LeAudioClient {
      return;
    }

    std::optional<LeAudioCodecConfiguration> source_configuration =
        group->GetCodecConfigurationByDirection(
            new_context, le_audio::types::kLeAudioDirectionSink);

    std::optional<LeAudioCodecConfiguration> sink_configuration =
        group->GetCodecConfigurationByDirection(
            new_context, le_audio::types::kLeAudioDirectionSource);

    if ((source_configuration &&
         (*source_configuration != current_source_codec_config)) ||
        (sink_configuration &&
         (*sink_configuration != current_sink_codec_config))) {
      DLOG(INFO) << __func__ << " Will UpdateCurrentHalSessions group"
                 << group->group_id_ << "for context type: "
                 << static_cast<int>(upcoming_context_type_);

      if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
        GroupStop(group->group_id_);
    if (ReconfigureHalSessionIfNeeded(group, new_context)) {
      return;
    }

      upcoming_context_type_ = new_context;
      /* Schedule HAL Session update */
      do_in_main_thread(FROM_HERE,
                        base::Bind(&LeAudioClientImpl::UpdateCurrentHalSessions,
                                   base::Unretained(instance), group->group_id_,
                                   upcoming_context_type_));

    } else {
    /* Configuration is the same for new context, just will do update
     * metadata of stream
     */
    GroupStream(active_group_id_, static_cast<uint16_t>(new_context));
  }
  }

  static void OnGattReadRspStatic(uint16_t conn_id, tGATT_STATUS status,
                                  uint16_t hdl, uint16_t len, uint8_t* value,
@@ -2848,7 +2952,6 @@ class LeAudioClientImpl : public LeAudioClient {
  void StatusReportCb(int group_id, GroupStreamStatus status) {
    switch (status) {
      case GroupStreamStatus::STREAMING:
        stream_request_started_ = false;
        if (audio_sender_state_ == AudioState::READY_TO_START)
          StartSendingAudio(active_group_id_);
        if (audio_receiver_state_ == AudioState::READY_TO_START)
@@ -2859,10 +2962,12 @@ class LeAudioClientImpl : public LeAudioClient {
        SuspendAudio();
        break;
      case GroupStreamStatus::IDLE:
        if (stream_request_started_) {
          stream_request_started_ = false;
        CancelStreamingRequest();
        }
        break;
      case GroupStreamStatus::RELEASING:
      case GroupStreamStatus::SUSPENDING:
        audio_sender_state_ = AudioState::RELEASING;
        audio_receiver_state_ = AudioState::RELEASING;
        break;
      default:
        break;
@@ -2876,7 +2981,6 @@ class LeAudioClientImpl : public LeAudioClient {
  LeAudioDeviceGroups aseGroups_;
  LeAudioGroupStateMachine* groupStateMachine_;
  int active_group_id_;
  bool stream_request_started_;
  LeAudioContextType current_context_type_;
  LeAudioContextType upcoming_context_type_;

+5 −7
Original line number Diff line number Diff line
@@ -963,14 +963,15 @@ class UnicastTestNoInit : public Test {
    UpdateMetadata(usage, content_type);

    if (reconfigured_sink) {
      EXPECT_CALL(mock_audio_source_, CancelStreamingRequest()).Times(1);
      audio_sink_receiver_->OnAudioResume();
      SyncOnMainLoop();
    }
    SyncOnMainLoop();
    audio_sink_receiver_->OnAudioResume();

    EXPECT_CALL(mock_audio_source_, ConfirmStreamingRequest()).Times(1);
    audio_sink_receiver_->OnAudioResume();
    state_machine_callbacks_->StatusReportCb(
              group_id, GroupStreamStatus::STREAMING);
    state_machine_callbacks_->StatusReportCb(group_id,
                                             GroupStreamStatus::STREAMING);

    if (usage == AUDIO_USAGE_VOICE_COMMUNICATION) {
      ASSERT_NE(audio_source_receiver_, nullptr);
@@ -2432,7 +2433,6 @@ TEST_F(UnicastTest, TwoEarbudsStreaming) {

  // Start streaming with reconfiguration from default media stream setup
  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
  EXPECT_CALL(mock_audio_source_, CancelStreamingRequest()).Times(1);
  EXPECT_CALL(mock_audio_source_, Stop()).Times(1);
  EXPECT_CALL(mock_audio_sink_, Start(_, _)).Times(1);

@@ -2519,7 +2519,6 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchSimple) {
      StartStream(_, le_audio::types::LeAudioContextType::NOTIFICATIONS))
      .Times(1);
  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
  EXPECT_CALL(mock_audio_source_, CancelStreamingRequest()).Times(1);
  EXPECT_CALL(mock_audio_source_, Stop()).Times(1);

  StartStreaming(AUDIO_USAGE_NOTIFICATION, AUDIO_CONTENT_TYPE_UNKNOWN, group_id,
@@ -2600,7 +2599,6 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure) {

  // Start streaming
  EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
  EXPECT_CALL(mock_audio_source_, CancelStreamingRequest()).Times(1);
  EXPECT_CALL(mock_audio_source_, Stop()).Times(1);
  EXPECT_CALL(mock_audio_sink_, Start(_, _)).Times(1);
  LeAudioClient::Get()->GroupSetActive(group_id);
+6 −0
Original line number Diff line number Diff line
@@ -202,6 +202,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    /* All ASEs should aim to achieve target state */
    SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
    PrepareAndSendDisable(leAudioDevice);
    state_machine_callbacks_->StatusReportCb(group->group_id_,
                                             GroupStreamStatus::SUSPENDING);
  }

  void StopStream(LeAudioDeviceGroup* group) override {
@@ -214,12 +216,16 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    if (leAudioDevice == nullptr) {
      LOG(ERROR) << __func__
                 << " Shouldn't be called without an active device.";
      state_machine_callbacks_->StatusReportCb(group->group_id_,
                                               GroupStreamStatus::IDLE);
      return;
    }

    /* All Ases should aim to achieve target state */
    SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
    PrepareAndSendRelease(leAudioDevice);
    state_machine_callbacks_->StatusReportCb(group->group_id_,
                                             GroupStreamStatus::RELEASING);
  }

  void ProcessGattNotifEvent(uint8_t* value, uint16_t len, struct ase* ase,
+26 −0
Original line number Diff line number Diff line
@@ -1510,6 +1510,10 @@ TEST_F(StateMachineTest, testDisableSingle) {
            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::SUSPENDING));
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
@@ -1569,6 +1573,10 @@ TEST_F(StateMachineTest, testDisableMultiple) {
            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::SUSPENDING));
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
@@ -1668,6 +1676,10 @@ TEST_F(StateMachineTest, testReleaseSingle) {
            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::RELEASING));
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::IDLE));
@@ -1711,6 +1723,11 @@ TEST_F(StateMachineTest, testReleaseCachingSingle) {
  InjectInitialIdleNotification(group);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::RELEASING));

  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::IDLE));
@@ -1767,6 +1784,11 @@ TEST_F(StateMachineTest, testStreamCachingSingle) {
  InjectInitialIdleNotification(group);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::RELEASING));

  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::IDLE));
@@ -1847,6 +1869,10 @@ TEST_F(StateMachineTest, testReleaseMultiple) {
            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::RELEASING));
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::IDLE));
+2 −0
Original line number Diff line number Diff line
@@ -40,6 +40,8 @@ enum class GroupStatus {
enum class GroupStreamStatus {
  IDLE = 0,
  STREAMING,
  RELEASING,
  SUSPENDING,
  SUSPENDED,
  RECONFIGURED,
  DESTROYED,