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

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

leaudio: Do not start stream if context not available but supported

If remote device indicates a Context Type as supported but it is not in
Available context types, such stream will not be started by the Android

Bug: 281957456
Tag: #feature
Test: atest bluetooth_le_audio_client_test

Change-Id: I71f099aff1f93208d405143cfb8134b8be4bda79
parent 339b01cb
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -3816,6 +3816,12 @@ class LeAudioClientImpl : public LeAudioClient {
        DirectionalRealignMetadataAudioContexts(group, remote_direction);
    ApplyRemoteMetadataAudioContextPolicy(group, remote_contexts,
                                          remote_direction);

    if (!remote_contexts.sink.any() && !remote_contexts.source.any()) {
      LOG_WARN("Requested context type not available on the remote side");
      return false;
    }

    return GroupStream(active_group_id_, configuration_context_type_,
                       remote_contexts);
  }
+341 −41
Original line number Diff line number Diff line
@@ -1678,16 +1678,9 @@ class UnicastTestNoInit : public Test {
    ConnectLeAudio(addr);
  }

  void UpdateLocalSourceMetadata(audio_usage_t usage,
                                 audio_content_type_t content_type,
  void UpdateLocalSourceMetadata(
      std::vector<struct playback_track_metadata> tracks,
      bool reconfigure_existing_stream = false) {
    std::vector<struct playback_track_metadata> source_metadata = {
        {{AUDIO_USAGE_UNKNOWN, AUDIO_CONTENT_TYPE_UNKNOWN, 0},
         {AUDIO_USAGE_UNKNOWN, AUDIO_CONTENT_TYPE_UNKNOWN, 0}}};

    source_metadata[0].usage = usage;
    source_metadata[0].content_type = content_type;

    ASSERT_NE(nullptr, mock_le_audio_source_hal_client_);
    /* Local Source may reconfigure once the metadata is updated */
    if (reconfigure_existing_stream) {
@@ -1708,7 +1701,19 @@ class UnicastTestNoInit : public Test {
    }

    ASSERT_NE(unicast_source_hal_cb_, nullptr);
    unicast_source_hal_cb_->OnAudioMetadataUpdate(source_metadata);
    unicast_source_hal_cb_->OnAudioMetadataUpdate(tracks);
  }

  void UpdateLocalSourceMetadata(audio_usage_t usage,
                                 audio_content_type_t content_type,
                                 bool reconfigure_existing_stream = false) {
    std::vector<struct playback_track_metadata> tracks = {
        {{AUDIO_USAGE_UNKNOWN, AUDIO_CONTENT_TYPE_UNKNOWN, 0},
         {AUDIO_USAGE_UNKNOWN, AUDIO_CONTENT_TYPE_UNKNOWN, 0}}};

    tracks[0].usage = usage;
    tracks[0].content_type = content_type;
    UpdateLocalSourceMetadata(tracks, reconfigure_existing_stream);
  }

  void UpdateLocalSinkMetadata(audio_source_t audio_source) {
@@ -1765,7 +1770,8 @@ class UnicastTestNoInit : public Test {
  void StartStreaming(audio_usage_t usage, audio_content_type_t content_type,
                      int group_id,
                      audio_source_t audio_source = AUDIO_SOURCE_INVALID,
                      bool reconfigure_existing_stream = false) {
                      bool reconfigure_existing_stream = false,
                      bool expected_resume_confirmation = true) {
    ASSERT_NE(unicast_source_hal_cb_, nullptr);

    UpdateLocalSourceMetadata(usage, content_type, reconfigure_existing_stream);
@@ -1776,7 +1782,7 @@ class UnicastTestNoInit : public Test {
    /* Stream has been automatically restarted on UpdateLocalSourceMetadata */
    if (reconfigure_existing_stream) return;

    LocalAudioSourceResume();
    LocalAudioSourceResume(expected_resume_confirmation);
    SyncOnMainLoop();
    Mock::VerifyAndClearExpectations(&mock_state_machine_);

@@ -4425,12 +4431,20 @@ TEST_F(UnicastTest, SpeakerStreamingNonDefault) {
  const RawAddress test_address0 = GetTestAddress(0);
  int group_id = bluetooth::groups::kGroupUnknown;

  /**
   * Scenario test steps
   * 1. Set group active and stream VOICEASSISTANT
   * 2. Suspend group and resume with VOICEASSISTANT
   * 3. Stop Stream and make group inactive
   * 4. Start stream without setting metadata.
   * 5. Verify that UNSPECIFIED context type is used.
   */

  available_snk_context_types_ = (types::LeAudioContextType::VOICEASSISTANTS |
                                  types::LeAudioContextType::MEDIA)
                                     .value();
  supported_snk_context_types_ =
      (available_snk_context_types_ | types::LeAudioContextType::UNSPECIFIED)
                                  types::LeAudioContextType::MEDIA |
                                  types::LeAudioContextType::UNSPECIFIED)
                                     .value();
  supported_snk_context_types_ = available_snk_context_types_;

  SetSampleDatabaseEarbudsValid(
      1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
@@ -5056,8 +5070,7 @@ TEST_F(UnicastTest, ModifyContextTypeOnDeviceA_WhileDeviceB_IsDisconnected) {
   * 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.
   * 6. Android start stream with MEDIA, verify it will not be started
   */

  // Report working CSIS
@@ -5133,15 +5146,313 @@ TEST_F(UnicastTest, ModifyContextTypeOnDeviceA_WhileDeviceB_IsDisconnected) {
                              source_supported_context);
  SyncOnMainLoop();

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

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

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

  Mock::VerifyAndClearExpectations(&mock_state_machine_);
}

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

  /* Scenario (Devices A and B called "Remote")
   * 1. Remote  does supports all the context types and make them available
   * 2. Remote removes SoundEffect from the supported and available context
   * types
   * 3. Android start stream with SoundEffects
   * 4. Make sure stream will be started with Unspecified context type
   */

  // 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_supported_context = types::kLeAudioContextAllRemoteSinkOnly;
  sink_supported_context.unset(LeAudioContextType::SOUNDEFFECTS);
  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);
  InjectSupportedContextTypes(test_address1, 2, sink_supported_context,
                              source_supported_context);
  InjectAvailableContextTypes(test_address1, 2, sink_supported_context,
                              source_supported_context);
  // 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::UNSPECIFIED),
      .source = types::AudioContexts(0)};

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

  StartStreaming(AUDIO_USAGE_ASSISTANCE_SONIFICATION,
                 AUDIO_CONTENT_TYPE_SONIFICATION, 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,
       StartStreamToUnsupportedContextTypeUnspecifiedNotSupported) {
  uint8_t group_size = 2;
  int group_id = 2;

  /* Scenario (Device A and B called Remote)
   * 1. Remote does supports all the context types and make them available
   * 2. Remote removes SoundEffect from the Available Context Types
   * 3. Remote also removes UNSPECIFIED from the Available Context Types.
   * 4. Android start stream with SoundEffects
   * 5. Make sure stream will be NOT 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_supported_context = types::kLeAudioContextAllRemoteSinkOnly;
  sink_supported_context.unset(LeAudioContextType::SOUNDEFFECTS);
  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);
  InjectSupportedContextTypes(test_address1, 2, sink_supported_context,
                              source_supported_context);

  auto sink_available_context = sink_supported_context;
  sink_available_context.unset(LeAudioContextType::UNSPECIFIED);

  auto source_available_context = source_supported_context;
  source_available_context.unset(LeAudioContextType::UNSPECIFIED);

  InjectAvailableContextTypes(test_address0, 1, sink_available_context,
                              source_available_context);
  InjectAvailableContextTypes(test_address1, 2, sink_available_context,
                              source_available_context);
  // 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::UNSPECIFIED),
      .source = types::AudioContexts()};

  EXPECT_CALL(mock_state_machine_,
              StartStream(_, le_audio::types::LeAudioContextType::SOUNDEFFECTS,
                          contexts, _))
      .Times(0);

  StartStreaming(AUDIO_USAGE_ASSISTANCE_SONIFICATION,
                 AUDIO_CONTENT_TYPE_SONIFICATION, group_id,
                 AUDIO_SOURCE_INVALID, false, false);

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

TEST_F(UnicastTest, StartStreamToSupportedContextTypeThenMixUnavailable) {
  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. Abdriud start stream with MEDIA, verify it works.
   * 3. Stream becomes to be mixed with Soundeffect and Media - verify metadata
   *    update
   * 4. Android Stop stream.
   * 5. Remote removes SoundEffect from the supported and available context
   * types
   * 6. Android start stream with MEDIA, verify it works.
   * 7. Stream becomes to be mixed with Soundeffect and Media
   * 8. Make sure metadata updated does not contain unavailable context
   *    note: eventually, Audio framework should not give us unwanted context
   * types
   */

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

  contexts.sink = types::AudioContexts(types::LeAudioContextType::MEDIA |
                                       types::LeAudioContextType::SOUNDEFFECTS);
  EXPECT_CALL(
      mock_state_machine_,
      StartStream(_, le_audio::types::LeAudioContextType::MEDIA, contexts, _))
      .Times(1);

  /* Simulate metadata update, expect upadate , metadata */
  std::vector<struct playback_track_metadata> tracks = {
      {{AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, 0},
       {AUDIO_USAGE_ASSISTANCE_SONIFICATION, AUDIO_CONTENT_TYPE_SONIFICATION,
        0}}};
  UpdateLocalSourceMetadata(tracks);
  SyncOnMainLoop();

  Mock::VerifyAndClearExpectations(&mock_state_machine_);

  /* Stop stream */
  StopStreaming(group_id);
  // simulate suspend timeout passed, alarm executing
  fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data);
  SyncOnMainLoop();

  Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);

  // Inject Supported and available context types
  auto sink_supported_context = types::kLeAudioContextAllRemoteSinkOnly;
  sink_supported_context.unset(LeAudioContextType::SOUNDEFFECTS);
  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);
  InjectSupportedContextTypes(test_address1, 2, sink_supported_context,
                              source_supported_context);
  InjectAvailableContextTypes(test_address1, 2, sink_supported_context,
                              source_supported_context);

  SyncOnMainLoop();

  /* Start Media again */
  contexts.sink = types::AudioContexts(types::LeAudioContextType::MEDIA);
  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
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);

  /* Update metadata, and do not expect new context type*/
  EXPECT_CALL(
      mock_state_machine_,
      StartStream(_, le_audio::types::LeAudioContextType::MEDIA, contexts, _))
      .Times(1);

  /* Simulate metadata update */
  UpdateLocalSourceMetadata(tracks);
  SyncOnMainLoop();
  Mock::VerifyAndClearExpectations(&mock_state_machine_);
}

TEST_F(UnicastTest, TwoEarbuds2ndDisconnected) {
@@ -5956,27 +6267,20 @@ TEST_F(UnicastTest, StartNotAvailableSupportedContextType) {
  EXPECT_CALL(
      mock_state_machine_,
      StartStream(_, types::LeAudioContextType::EMERGENCYALARM, metadata, _))
      .Times(1);
      .Times(0);

  LeAudioClient::Get()->GroupSetActive(group_id);
  StartStreaming(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN, group_id);
  StartStreaming(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN, group_id,
                 AUDIO_SOURCE_INVALID, false, false);

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

  // Verify Data transfer on one audio source cis
  uint8_t cis_count_out = 1;
  uint8_t cis_count_in = 0;
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
}

/* When a certain context is unavailable and not supported and the UNSPECIFIED
 * is not available we should stop the stream, but that means IOP issues.
 * Since UNSPECIFIED is not available, do not put the UNSPECIFIED nor the
 * original unsupported context into the metadata.
 * What we can do now is to keep streaming (and reconfigure if needed for the
 * use case).
 * is not available we should stop the stream.
 * For now, stream will not be started in such a case.
 * In future we should be able to eliminate this context from the track mix.
 */
TEST_F(UnicastTest, StartNotAvailableUnsupportedContextTypeUnspecifiedUnavail) {
@@ -6021,19 +6325,15 @@ TEST_F(UnicastTest, StartNotAvailableUnsupportedContextTypeUnspecifiedUnavail) {
  EXPECT_CALL(
      mock_state_machine_,
      StartStream(_, types::LeAudioContextType::EMERGENCYALARM, metadata, _))
      .Times(1);
      .Times(0);

  LeAudioClient::Get()->GroupSetActive(group_id);
  StartStreaming(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN, group_id);
  StartStreaming(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN, group_id,
                 AUDIO_SOURCE_INVALID, false, false);

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

  // Verify Data transfer on one audio source cis
  uint8_t cis_count_out = 1;
  uint8_t cis_count_in = 0;
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
}

/* This test verifies if we use UNSPCIFIED context when another context is