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

Commit 339b01cb authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Make sure context and locations are updated correctly

With this patch available group contexts and locations are updated in following
points of time
1) When device got disconnected
2) When notification arrives but group is not streaming
3) when streaming ends
4) when device got connected

Also note, that whenever leaudio code get latest available context type
it just look into connected devices

Bug: 281957456
Tag: #feature
Test: atest bluetooth_le_audio_client_test

Change-Id: I7a86c6520a708291f2f05c1750c005732bc379d6
parent aee005b7
Loading
Loading
Loading
Loading
+38 −29
Original line number Diff line number Diff line
@@ -1761,12 +1761,10 @@ class LeAudioClientImpl : public LeAudioClient {
            leAudioDevice->address_,
            leAudioDevice->snk_audio_locations_.to_ulong(),
            leAudioDevice->src_audio_locations_.to_ulong());
      }

      /* Read of source audio locations during initial attribute discovery.
       * Group would be assigned once service search is completed.
       */
        if (group && group->IsReleasingOrIdle()) {
          UpdateLocationsAndContextsAvailability(leAudioDevice->group_id_);
        }
      }
    } else if (hdl == leAudioDevice->src_audio_locations_hdls_.val_hdl) {
      AudioLocations src_audio_locations;

@@ -1791,12 +1789,10 @@ class LeAudioClientImpl : public LeAudioClient {
            leAudioDevice->address_,
            leAudioDevice->snk_audio_locations_.to_ulong(),
            leAudioDevice->src_audio_locations_.to_ulong());
      }

      /* Read of source audio locations during initial attribute discovery.
       * Group would be assigned once service search is completed.
       */
        if (group && group->IsReleasingOrIdle()) {
          UpdateLocationsAndContextsAvailability(leAudioDevice->group_id_);
        }
      }
    } else if (hdl == leAudioDevice->audio_avail_hdls_.val_hdl) {
      BidirectionalPair<AudioContexts> contexts;
      if (!le_audio::client_parser::pacs::ParseAvailableAudioContexts(
@@ -1810,8 +1806,16 @@ class LeAudioClientImpl : public LeAudioClient {
        return;
      }

      /* Check if we should attach to stream this device */
      if (group->IsInTransition() || !group->IsStreaming()) {
      if (group->IsReleasingOrIdle()) {
        /* Group is not streaming. Device does not have to be attach to the
         * stream, and we can update context availability for the group
         */
        UpdateLocationsAndContextsAvailability(group);
        return;
      }

      if (group->IsInTransition()) {
        /* Group is in transition, do not take any actions now.*/
        return;
      }

@@ -5308,7 +5312,9 @@ class LeAudioClientImpl : public LeAudioClient {
         */
        FALLTHROUGH;
      case GroupStreamStatus::IDLE: {
        if (group && group->IsPendingConfiguration()) {
        if (group) {
          UpdateLocationsAndContextsAvailability(group->group_id_);
          if (group->IsPendingConfiguration()) {
            SuspendedForReconfiguration();
            auto remote_direction =
                kLeAudioContextAllRemoteSource.test(configuration_context_type_)
@@ -5327,9 +5333,12 @@ class LeAudioClientImpl : public LeAudioClient {
                    group->group_id_);
            group->ClearPendingConfiguration();
          }
        }

        stream_setup_end_timestamp_ = 0;
        stream_setup_start_timestamp_ = 0;
        CancelStreamingRequest();

        if (group) {
          NotifyUpperLayerGroupTurnedIdleDuringCall(group->group_id_);
          HandlePendingDeviceRemove(group);
+3 −6
Original line number Diff line number Diff line
@@ -798,11 +798,7 @@ uint16_t LeAudioDeviceGroup::GetRemoteDelay(uint8_t direction) const {
}

bool LeAudioDeviceGroup::UpdateAudioContextAvailability(void) {
  LOG_DEBUG(
      " group id: %d, available contexts sink: %s, available contexts source: "
      "%s",
      group_id_, group_available_contexts_.sink.to_string().c_str(),
      group_available_contexts_.source.to_string().c_str());
  LOG_DEBUG("%d", group_id_);
  auto old_contexts = GetAvailableContexts();
  SetAvailableContexts(GetLatestAvailableContexts());
  return old_contexts != GetAvailableContexts();
@@ -842,7 +838,8 @@ LeAudioDeviceGroup::GetLatestAvailableContexts() const {
  types::BidirectionalPair<types::AudioContexts> contexts;
  for (const auto& device : leAudioDevices_) {
    auto shared_ptr = device.lock();
    if (shared_ptr) {
    if (shared_ptr &&
        shared_ptr->GetConnectionState() == DeviceConnectState::CONNECTED) {
      contexts.sink |=
          shared_ptr->GetAvailableContexts(types::kLeAudioDirectionSink);
      contexts.source |=
+13 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@
#include "le_audio_log_history.h"
#include "le_audio_types.h"
#include "osi/include/alarm.h"
#include "osi/include/log.h"
#include "osi/include/properties.h"
#include "raw_address.h"

@@ -466,11 +467,23 @@ class LeAudioDeviceGroup {
  inline void SetAvailableContexts(
      types::BidirectionalPair<types::AudioContexts> new_contexts) {
    group_available_contexts_ = new_contexts;
    LOG_DEBUG(
        " group id: %d, available contexts sink: %s, available contexts "
        "source: "
        "%s",
        group_id_, group_available_contexts_.sink.to_string().c_str(),
        group_available_contexts_.source.to_string().c_str());
  }

  inline types::AudioContexts GetAvailableContexts(
      int direction = (types::kLeAudioDirectionSink |
                       types::kLeAudioDirectionSource)) const {
    LOG_DEBUG(
        " group id: %d, available contexts sink: %s, available contexts "
        "source: "
        "%s",
        group_id_, group_available_contexts_.sink.to_string().c_str(),
        group_available_contexts_.source.to_string().c_str());
    return group_available_contexts_.get(direction);
  }

+170 −0
Original line number Diff line number Diff line
@@ -73,6 +73,8 @@ using le_audio::LeAudioSinkAudioHalClient;
using le_audio::LeAudioSourceAudioHalClient;

using le_audio::types::AudioContexts;
using le_audio::types::BidirectionalPair;
using le_audio::types::LeAudioContextType;

extern struct fake_osi_alarm_set_on_mloop fake_osi_alarm_set_on_mloop_;

@@ -4974,6 +4976,174 @@ TEST_F(UnicastTest, TwoEarbuds2ndReleaseAseRemoveAvailableContextAndBack) {
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
}

TEST_F(UnicastTest, StartStream_AvailableContextTypeNotifiedLater) {
  uint8_t group_size = 2;
  int group_id = 2;

  available_snk_context_types_ = 0;

  /* Scenario (Devices A and B called "Remote")
   * 1. Remote  does supports all the context types, but has NO available
   * contexts at the beginning
   * 2. After connection Remote add Available context types
   * 3. Android start stream with MEDIA
   * 4. Make sure stream will be started
   */

  // Report working CSIS
  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
      .WillByDefault(Return(true));

  const RawAddress test_address0 = GetTestAddress(0);
  const RawAddress test_address1 = GetTestAddress(1);

  // First earbud connects
  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
                    codec_spec_conf::kLeAudioLocationFrontLeft,
                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
                    group_id, 1 /* rank*/);

  // Second earbud connects
  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
                    codec_spec_conf::kLeAudioLocationFrontRight,
                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
                    group_id, 2 /* rank*/, true /*connect_through_csis*/);

  // Inject Supported and available context types
  auto sink_available_contexts = types::kLeAudioContextAllRemoteSinkOnly;
  auto source_available_contexts = types::kLeAudioContextAllRemoteSource;

  InjectAvailableContextTypes(test_address0, 1, sink_available_contexts,
                              source_available_contexts);
  InjectAvailableContextTypes(test_address1, 2, sink_available_contexts,
                              source_available_contexts);
  // Start streaming
  EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _)).Times(1);
  EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1);
  LeAudioClient::Get()->GroupSetActive(group_id);

  ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id))
      .WillByDefault(Invoke([&](int group_id) { return 2; }));

  BidirectionalPair<AudioContexts> contexts = {
      .sink = types::AudioContexts(types::LeAudioContextType::MEDIA),
      .source = types::AudioContexts()};

  EXPECT_CALL(
      mock_state_machine_,
      StartStream(_, le_audio::types::LeAudioContextType::MEDIA, contexts, _))
      .Times(1);

  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);

  Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);
  Mock::VerifyAndClearExpectations(&mock_le_audio_source_hal_client_);
  SyncOnMainLoop();

  // Expect two iso channel to be fed with data
  uint8_t cis_count_out = 2;
  uint8_t cis_count_in = 0;
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
}

TEST_F(UnicastTest, ModifyContextTypeOnDeviceA_WhileDeviceB_IsDisconnected) {
  uint8_t group_size = 2;
  int group_id = 2;

  /* Scenario (Device A and B called Remote)
   * 1. Remote set does supports all the context types and make them available
   * 2. Android start stream with MEDIA, verify it works.
   * 3. Android stops the stream
   * 4. Device B disconnects
   * 5. Device A removes Media from Available Contexts
   * 6. Android start stream with MEDIA, verify it will be started without
   * context Note: This behaviour will change in next patch.
   */

  // Report working CSIS
  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
      .WillByDefault(Return(true));

  const RawAddress test_address0 = GetTestAddress(0);
  const RawAddress test_address1 = GetTestAddress(1);

  // First earbud connects
  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
                    codec_spec_conf::kLeAudioLocationFrontLeft,
                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
                    group_id, 1 /* rank*/);

  // Second earbud connects
  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
                    codec_spec_conf::kLeAudioLocationFrontRight,
                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
                    group_id, 2 /* rank*/, true /*connect_through_csis*/);

  // Start streaming
  EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _)).Times(1);
  EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1);
  LeAudioClient::Get()->GroupSetActive(group_id);

  ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id))
      .WillByDefault(Invoke([&](int group_id) { return 2; }));

  BidirectionalPair<AudioContexts> contexts = {
      .sink = types::AudioContexts(types::LeAudioContextType::MEDIA),
      .source = types::AudioContexts()};

  EXPECT_CALL(
      mock_state_machine_,
      StartStream(_, le_audio::types::LeAudioContextType::MEDIA, contexts, _))
      .Times(1);

  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);

  SyncOnMainLoop();
  Mock::VerifyAndClearExpectations(&mock_state_machine_);
  Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);
  Mock::VerifyAndClearExpectations(&mock_le_audio_source_hal_client_);

  // Expect two iso channel to be fed with data
  uint8_t cis_count_out = 2;
  uint8_t cis_count_in = 0;
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);

  // Stop
  StopStreaming(group_id);
  // simulate suspend timeout passed, alarm executing
  fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data);
  Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);

  // Device B got disconnected
  InjectDisconnectedEvent(2, GATT_CONN_TERMINATE_PEER_USER);
  SyncOnMainLoop();

  // Device A changes available context type
  // Inject Supported and available context types
  auto sink_supported_context = types::kLeAudioContextAllRemoteSinkOnly;
  sink_supported_context.unset(LeAudioContextType::MEDIA);
  sink_supported_context.set(LeAudioContextType::UNSPECIFIED);

  auto source_supported_context = types::kLeAudioContextAllRemoteSource;
  source_supported_context.set(LeAudioContextType::UNSPECIFIED);

  InjectSupportedContextTypes(test_address0, 1, sink_supported_context,
                              source_supported_context);
  InjectAvailableContextTypes(test_address0, 1, sink_supported_context,
                              source_supported_context);
  SyncOnMainLoop();

  contexts = {.sink = types::AudioContexts(), .source = types::AudioContexts()};

  /* Android starts stream. */
  EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(1);

  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
  SyncOnMainLoop();

  Mock::VerifyAndClearExpectations(&mock_state_machine_);
}

TEST_F(UnicastTest, TwoEarbuds2ndDisconnected) {
  uint8_t group_size = 2;
  int group_id = 2;