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

Commit 606e9ed8 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Gerrit Code Review
Browse files

Merge changes If750f680,I900e4dc7,Iaed6f7e6,Icfdd55e6,If4a917f7

* changes:
  leaudio: Improve Codec Configure operation
  leaudio: Improve group QoS Configure
  leaudio: Improve disabling stream
  leaudio: Improve enabling stream
  leaudio: Fix invalid state report from group state machine
parents b242301a 9c224f62
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
@@ -347,6 +347,42 @@ bool LeAudioDeviceGroup::IsDeviceInTheGroup(LeAudioDevice* leAudioDevice) {
  return true;
}

bool LeAudioDeviceGroup::IsGroupReadyToCreateStream(void) {
  auto iter =
      std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
        if (d.expired())
          return false;
        else
          return !(((d.lock()).get())->IsReadyToCreateStream());
      });

  return iter == leAudioDevices_.end();
}

bool LeAudioDeviceGroup::IsGroupReadyToSuspendStream(void) {
  auto iter =
      std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
        if (d.expired())
          return false;
        else
          return !(((d.lock()).get())->IsReadyToSuspendStream());
      });

  return iter == leAudioDevices_.end();
}

bool LeAudioDeviceGroup::HaveAnyActiveDeviceInUnconfiguredState() {
  auto iter =
      std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
        if (d.expired())
          return false;
        else
          return (((d.lock()).get())->HaveAnyUnconfiguredAses());
      });

  return iter != leAudioDevices_.end();
}

bool LeAudioDeviceGroup::HaveAllActiveDevicesAsesTheSameState(AseState state) {
  auto iter = std::find_if(
      leAudioDevices_.begin(), leAudioDevices_.end(), [&state](auto& d) {
+3 −0
Original line number Diff line number Diff line
@@ -294,7 +294,10 @@ class LeAudioDeviceGroup {
      types::AudioStreamDataPathState data_path_state);
  bool IsDeviceInTheGroup(LeAudioDevice* leAudioDevice);
  bool HaveAllActiveDevicesAsesTheSameState(types::AseState state);
  bool HaveAnyActiveDeviceInUnconfiguredState();
  bool IsGroupStreamReady(void);
  bool IsGroupReadyToCreateStream(void);
  bool IsGroupReadyToSuspendStream(void);
  bool HaveAllCisesDisconnected(void);
  uint8_t GetFirstFreeCisId(void);
  uint8_t GetFirstFreeCisId(types::CisType cis_type);
+199 −155
Original line number Diff line number Diff line
@@ -194,7 +194,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        group->CigGenerateCisIds(context_type);
        /* All ASEs should aim to achieve target state */
        SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
        PrepareAndSendCodecConfigure(group, group->GetFirstActiveDevice());
        PrepareAndSendCodecConfigToTheGroup(group);
        break;

      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: {
@@ -206,7 +206,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

        /* All ASEs should aim to achieve target state */
        SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
        PrepareAndSendEnable(leAudioDevice);
        PrepareAndSendEnableToTheGroup(group);
        break;
      }

@@ -263,21 +263,14 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

    group->CigGenerateCisIds(context_type);
    SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
    PrepareAndSendCodecConfigure(group, group->GetFirstActiveDevice());

    return true;
    return PrepareAndSendCodecConfigToTheGroup(group);
  }

  void SuspendStream(LeAudioDeviceGroup* group) override {
    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
    LOG_ASSERT(leAudioDevice)
        << __func__ << " Shouldn't be called without an active device.";

    /* 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);
    auto status = PrepareAndSendDisableToTheGroup(group);
    state_machine_callbacks_->StatusReportCb(group->group_id_, status);
  }

  void StopStream(LeAudioDeviceGroup* group) override {
@@ -287,20 +280,11 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
      return;
    }

    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
    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);

    auto status = PrepareAndSendReleaseToTheGroup(group);
    state_machine_callbacks_->StatusReportCb(group->group_id_, status);
  }

  void ProcessGattNotifEvent(uint8_t* value, uint16_t len, struct ase* ase,
@@ -402,7 +386,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);

    if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
      StartConfigQoSForTheGroup(group);
      PrepareAndSendQoSToTheGroup(group);
    } else {
      LOG_ERROR(", invalid state transition, from: %s , to: %s",
                ToString(group->GetState()).c_str(),
@@ -661,7 +645,9 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
     * contexts table.
     */
    if (group->IsAnyDeviceConnected() && !group->HaveAllCisesDisconnected()) {
      if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
      if ((group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) &&
          (group->GetTargetState() ==
           AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
        /* We keep streaming but want others to let know user that it might be
         * need to update offloader with new CIS configuration
         */
@@ -1490,7 +1476,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        }
        break;
      case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING: {
        LeAudioDevice* leAudioDeviceNext;
        ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
        ase->active = false;
        ase->configured_for_context_type =
@@ -1514,16 +1499,16 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
          return;
        }

        leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice);
        if (!group->HaveAllActiveDevicesAsesTheSameState(
                AseState::BTA_LE_AUDIO_ASE_STATE_IDLE)) {
          LOG_DEBUG("Waiting for more devices to get into idle state");
          return;
        }

        /* Configure ASEs for next device in group */
        if (leAudioDeviceNext) {
          PrepareAndSendRelease(leAudioDeviceNext);
        } else {
        /* Last node is in releasing state*/
        group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);

        group->PrintDebugState();

        /* If all CISes are disconnected, notify upper layer about IDLE state,
         * otherwise wait for */
        if (!group->HaveAllCisesDisconnected()) {
@@ -1539,7 +1524,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        ReleaseCisIds(group);
        state_machine_callbacks_->StatusReportCb(group->group_id_,
                                                 GroupStreamStatus::IDLE);
        }

        break;
      }
      default:
@@ -1551,7 +1536,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    }
  }

  void StartConfigQoSForTheGroup(LeAudioDeviceGroup* group) {
  void PrepareAndSendQoSToTheGroup(LeAudioDeviceGroup* group) {
    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
    if (!leAudioDevice) {
      LOG(ERROR) << __func__ << ", no active devices in group";
@@ -1559,8 +1544,26 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
      return;
    }

    for (; leAudioDevice;
         leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
      PrepareAndSendConfigQos(group, leAudioDevice);
    }
  }

  bool PrepareAndSendCodecConfigToTheGroup(LeAudioDeviceGroup* group) {
    LOG_INFO("group_id: %d", group->group_id_);
    auto leAudioDevice = group->GetFirstActiveDevice();
    if (!leAudioDevice) {
      LOG(ERROR) << __func__ << ", no active devices in group";
      return false;
    }

    for (; leAudioDevice;
         leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
      PrepareAndSendCodecConfigure(group, leAudioDevice);
    }
    return true;
  }

  void PrepareAndSendCodecConfigure(LeAudioDeviceGroup* group,
                                    LeAudioDevice* leAudioDevice) {
@@ -1618,8 +1621,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
          ase->id = arh.id;
        }

        LeAudioDevice* leAudioDeviceNext;

        struct le_audio::client_parser::ascs::ase_codec_configured_state_params
            rsp;

@@ -1693,12 +1694,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
          return;
        }

        leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice);

        /* Configure ASEs for next device in group */
        if (leAudioDeviceNext) {
          PrepareAndSendCodecConfigure(group, leAudioDeviceNext);
        } else {
        if (group->HaveAnyActiveDeviceInUnconfiguredState()) {
          LOG_DEBUG("Waiting for all the ASES in the Configured state");
          return;
        }

        /* Last node configured, process group to codec configured state */
        group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);

@@ -1742,8 +1743,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
                  ToString(group->GetState()).c_str(),
                  ToString(group->GetTargetState()).c_str());
        StopStream(group);
          return;
        }

        break;
      }
@@ -1794,13 +1793,13 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
          return;
        }

        LeAudioDevice* leAudioDeviceNext =
            group->GetNextActiveDevice(leAudioDevice);
        if (group->HaveAnyActiveDeviceInUnconfiguredState()) {
          LOG_DEBUG(
              "Waiting for all the devices to be configured for group id %d",
              group->group_id_);
          return;
        }

        /* Configure ASEs for next device in group */
        if (leAudioDeviceNext) {
          PrepareAndSendCodecConfigure(group, leAudioDeviceNext);
        } else {
        /* Last node configured, process group to codec configured state */
        group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);

@@ -1830,7 +1829,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        LOG_ERROR(", Autonomouse change, from: %s to %s",
                  ToString(group->GetState()).c_str(),
                  ToString(group->GetTargetState()).c_str());
        }

        break;
      }
@@ -1926,19 +1924,14 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
          return;
        }

        LeAudioDevice* leAudioDeviceNext =
            group->GetNextActiveDevice(leAudioDevice);

        /* Configure ASEs qos for next device in group */
        if (leAudioDeviceNext) {
          PrepareAndSendConfigQos(group, leAudioDeviceNext);
        } else {
          leAudioDevice = group->GetFirstActiveDevice();
          LOG_ASSERT(leAudioDevice)
              << __func__ << " Shouldn't be called without an active device.";
          PrepareAndSendEnable(leAudioDevice);
        if (!group->HaveAllActiveDevicesAsesTheSameState(
                AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)) {
          LOG_DEBUG("Waiting for all the devices to be in QoS state");
          return;
        }

        PrepareAndSendEnableToTheGroup(group);

        break;
      }
      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
@@ -1960,7 +1953,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        /* Process the Disable Transition of the rest of group members if no
         * more ASE notifications has to come from this device. */
        if (leAudioDevice->IsReadyToSuspendStream())
          ProcessGroupDisable(group, leAudioDevice);
          ProcessGroupDisable(group);

        break;

@@ -2002,6 +1995,22 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    }
  }

  void PrepareAndSendEnableToTheGroup(LeAudioDeviceGroup* group) {
    LOG_INFO("group_id: %d", group->group_id_);

    auto leAudioDevice = group->GetFirstActiveDevice();
    if (!leAudioDevice) {
      LOG_ERROR("No active for the group,");
      StopStream(group);
      return;
    }

    for (; leAudioDevice;
         leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
      PrepareAndSendEnable(leAudioDevice);
    }
  }

  void PrepareAndSendEnable(LeAudioDevice* leAudioDevice) {
    struct le_audio::client_parser::ascs::ctp_enable conf;
    std::vector<struct le_audio::client_parser::ascs::ctp_enable> confs;
@@ -2028,6 +2037,23 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
                                      GATT_WRITE_NO_RSP, NULL, NULL);
  }

  GroupStreamStatus PrepareAndSendDisableToTheGroup(LeAudioDeviceGroup* group) {
    LOG_INFO("grop_id: %d", group->group_id_);

    auto leAudioDevice = group->GetFirstActiveDevice();
    if (!leAudioDevice) {
      LOG_ERROR("Shall not be called if there is no active device.");
      StopStream(group);
      return GroupStreamStatus::IDLE;
    }

    for (; leAudioDevice;
         leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
      PrepareAndSendDisable(leAudioDevice);
    }
    return GroupStreamStatus::SUSPENDING;
  }

  void PrepareAndSendDisable(LeAudioDevice* leAudioDevice) {
    ase* ase = leAudioDevice->GetFirstActiveAse();
    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
@@ -2050,6 +2076,22 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
                                      GATT_WRITE_NO_RSP, NULL, NULL);
  }

  GroupStreamStatus PrepareAndSendReleaseToTheGroup(LeAudioDeviceGroup* group) {
    LOG_INFO("group_id: %d", group->group_id_);
    LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
    if (!leAudioDevice) {
      LOG_ERROR(" Shouldn't be called without an active device.");
      return GroupStreamStatus::IDLE;
    }

    for (; leAudioDevice;
         leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
      PrepareAndSendRelease(leAudioDevice);
    }

    return GroupStreamStatus::RELEASING;
  }

  void PrepareAndSendRelease(LeAudioDevice* leAudioDevice) {
    ase* ase = leAudioDevice->GetFirstActiveAse();
    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
@@ -2269,7 +2311,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        }

        if (leAudioDevice->IsReadyToCreateStream())
          ProcessGroupEnable(group, leAudioDevice);
          ProcessGroupEnable(group);

        break;

@@ -2324,7 +2366,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        }

        if (leAudioDevice->IsReadyToCreateStream())
          ProcessGroupEnable(group, leAudioDevice);
          ProcessGroupEnable(group);

        break;

@@ -2425,7 +2467,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        /* Process the Disable Transition of the rest of group members if no
         * more ASE notifications has to come from this device. */
        if (leAudioDevice->IsReadyToSuspendStream())
          ProcessGroupDisable(group, leAudioDevice);
          ProcessGroupDisable(group);

        break;

@@ -2524,14 +2566,18 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    }
  }

  void ProcessGroupEnable(LeAudioDeviceGroup* group, LeAudioDevice* device) {
    /* Enable ASEs for next device in group. */
    LeAudioDevice* deviceNext = group->GetNextActiveDevice(device);
    if (deviceNext) {
      PrepareAndSendEnable(deviceNext);
  void ProcessGroupEnable(LeAudioDeviceGroup* group) {
    if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING) {
      if (!group->IsGroupReadyToCreateStream()) {
        LOG_DEBUG(
            "Waiting for more ASEs to be in enabling or directly in streaming "
            "state");
        return;
      }

      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
@@ -2539,8 +2585,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    if (group->HaveAllActiveDevicesAsesTheSameState(
            AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
    } else {
      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING);
    }

    if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
@@ -2553,13 +2597,15 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    }
  }

  void ProcessGroupDisable(LeAudioDeviceGroup* group, LeAudioDevice* device) {
  void ProcessGroupDisable(LeAudioDeviceGroup* group) {
    /* Disable ASEs for next device in group. */
    LeAudioDevice* deviceNext = group->GetNextActiveDevice(device);
    if (deviceNext) {
      PrepareAndSendDisable(deviceNext);
    if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING) {
      if (!group->IsGroupReadyToSuspendStream()) {
        LOG_INFO("Waiting for all devices to be in disable state");
        return;
      }
      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING);
    }

    /* At this point all of the active ASEs within group are disabled. As there
     * is no Disabling state for Sink ASE, it might happen that all of the
@@ -2568,8 +2614,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    if (group->HaveAllActiveDevicesAsesTheSameState(
            AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)) {
      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
    } else {
      group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING);
    }

    /* Transition to QoS configured is done by CIS disconnection */
+108 −10
Original line number Diff line number Diff line
@@ -546,6 +546,12 @@ class StateMachineTest : public Test {
    return &(*group);
  }

  void InjectAclDisconnected(LeAudioDeviceGroup* group,
                             LeAudioDevice* leAudioDevice) {
    LeAudioGroupStateMachine::Get()->ProcessHciNotifAclDisconnected(
        group, leAudioDevice);
  }

  void InjectAseStateNotification(types::ase* ase, LeAudioDevice* device,
                                  LeAudioDeviceGroup* group, uint8_t new_state,
                                  void* new_state_params) {
@@ -1139,10 +1145,11 @@ class StateMachineTest : public Test {
  }

  void PrepareReleaseHandler(LeAudioDeviceGroup* group,
                             int verify_ase_count = 0) {
                             int verify_ase_count = 0,
                             bool inject_disconnect_device = false) {
    ase_ctp_handlers[ascs::kAseCtpOpcodeRelease] =
        [group, verify_ase_count, this](LeAudioDevice* device,
                                        std::vector<uint8_t> value,
        [group, verify_ase_count, inject_disconnect_device, this](
            LeAudioDevice* device, std::vector<uint8_t> value,
            GATT_WRITE_OP_CB cb, void* cb_data) {
          auto num_ase = value[1];

@@ -1150,6 +1157,11 @@ class StateMachineTest : public Test {
          if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase);
          ASSERT_EQ(value.size(), 2ul + num_ase);

          if (inject_disconnect_device) {
            InjectAclDisconnected(group, device);
            return;
          }

          // Inject Releasing & Idle ASE state notification for each ASE
          auto* ase_p = &value[2];
          for (auto i = 0u; i < num_ase; ++i) {
@@ -2514,6 +2526,11 @@ TEST_F(StateMachineTest, testReleaseMultiple) {

  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,
@@ -2535,6 +2552,10 @@ TEST_F(StateMachineTest, testReleaseMultiple) {
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::IDLE));
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::STREAMING))
      .Times(0);

  // Stop the stream
  LeAudioGroupStateMachine::Get()->StopStream(group);
@@ -2544,6 +2565,89 @@ TEST_F(StateMachineTest, testReleaseMultiple) {
  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}

TEST_F(StateMachineTest, testReleaseMultiple_DeviceDisconnectedDuringRelease) {
  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);
  PrepareDisableHandler(group);

  /* Here we inject device disconnection during release */
  PrepareReleaseHandler(group, 0, true);

  auto* leAudioDevice = group->GetFirstDevice();
  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(AtLeast(4));
    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);
  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
  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, mock_function_count_map["alarm_cancel"]);
  mock_function_count_map["alarm_cancel"] = 0;

  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);

  // 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));
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::STREAMING))
      .Times(0);

  // Stop the stream
  LeAudioGroupStateMachine::Get()->StopStream(group);

  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);

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

TEST_F(StateMachineTest, testReleaseBidirectional) {
  /* Device is banded headphones with 2x snk + 1x src ase
   * (1x bidirectional + 1xunidirectional CIS)
@@ -3026,12 +3130,6 @@ TEST_F(StateMachineTest, testConfigureDataPathForAdsp) {
       .source = types::AudioContexts(context_type)}));
}

static void InjectAclDisconnected(LeAudioDeviceGroup* group,
                                  LeAudioDevice* leAudioDevice) {
  LeAudioGroupStateMachine::Get()->ProcessHciNotifAclDisconnected(
      group, leAudioDevice);
}

TEST_F(StateMachineTest, testStreamConfigurationAdspDownMix) {
  const auto context_type = kContextTypeConversational;
  const int leaudio_group_id = 4;