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

Commit 18755bef authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Fix bad state on remote not going to Configured State

When ASE was reconfigured, and remote did not respond on Codec Configure
request, but instead state machine timeout happens and device got
disconected, the ASE state was not correctly cleared and reconfigure
flag stay On. This broke future Stream configuration.

This patch also fix some minor logging issues around the ASE state.

Bug: 326299571
Test: atest bluetooth_le_audio_test
Flag: Exempt, trivial fix, unit tested
Change-Id: Ibb37bae29d773aa2e2e15a63e30039220b2689c4
parent af3f35a9
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -177,6 +177,7 @@ void LeAudioDeviceGroup::Deactivate(void) {
    for (auto* ase = leAudioDevice->GetFirstActiveAse(); ase;
         ase = leAudioDevice->GetNextActiveAse(ase)) {
      ase->active = false;
      ase->reconfigure = 0;
    }
  }
}
+5 −2
Original line number Diff line number Diff line
@@ -796,9 +796,10 @@ void LeAudioDevice::PrintDebugState(void) {
      debug_str
          << "\n  id: " << +ase.id << ", active: " << ase.active << ", dir: "
          << (ase.direction == types::kLeAudioDirectionSink ? "sink" : "source")
          << ", state: " << bluetooth::common::ToString(ase.state)
          << ", cis_id: " << +ase.cis_id
          << ", cis_handle: " << +ase.cis_conn_hdl
          << ", state: " << bluetooth::common::ToString(ase.cis_state)
          << ", cis_state: " << bluetooth::common::ToString(ase.cis_state)
          << ", data_path_state: "
          << bluetooth::common::ToString(ase.data_path_state)
          << "\n ase max_latency: " << +ase.qos_config.max_transport_latency
@@ -808,7 +809,8 @@ void LeAudioDevice::PrintDebugState(void) {
          << ", presentation_delay: " << +ase.qos_config.presentation_delay
          << ", framing: " << +ase.qos_config.framing
          << ", phy: " << +ase.qos_config.phy
          << ", target latency: " << +ase.target_latency;
          << ", target latency: " << +ase.target_latency
          << ", reconfigure: " << ase.reconfigure << "\n";
    }
  }

@@ -1022,6 +1024,7 @@ void LeAudioDevice::DeactivateAllAses(void) {
    ase.cis_state = CisState::IDLE;
    ase.data_path_state = DataPathState::IDLE;
    ase.active = false;
    ase.reconfigure = 0;
    ase.cis_id = bluetooth::le_audio::kInvalidCisId;
    ase.cis_conn_hdl = 0;
  }
+174 −0
Original line number Diff line number Diff line
@@ -890,6 +890,19 @@ class StateMachineTestBase : public Test {
    }
  }

  void InjectInitialConfiguredNotification(LeAudioDeviceGroup* group) {
    for (auto* device = group->GetFirstDevice(); device != nullptr;
         device = group->GetNextDevice(device)) {
      for (auto& ase : device->ases_) {
        client_parser::ascs::ase_codec_configured_state_params
            codec_configured_state_params;
        InjectAseStateNotification(&ase, device, group,
                                   ascs::kAseStateCodecConfigured,
                                   &codec_configured_state_params);
      }
    }
  }

  void InjectInitialIdleAndConfiguredNotification(LeAudioDeviceGroup* group) {
    for (auto* device = group->GetFirstDevice(); device != nullptr;
         device = group->GetNextDevice(device)) {
@@ -4355,6 +4368,167 @@ TEST_F(StateMachineTest, testStateTransitionTimeout) {
  ASSERT_EQ(1, get_func_call_count("alarm_set_on_mloop"));
}

TEST_F(StateMachineTest,
       testStateTransitionTimeoutAndDisconnectWhenConfigured) {
  const auto context_type = kContextTypeMedia;
  const int leaudio_group_id = 4;
  channel_count_ = kLeAudioCodecChannelCountSingleChannel |
                   kLeAudioCodecChannelCountTwoChannel;

  // Prepare fake connected device group
  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);

  auto* leAudioDevice = group->GetFirstDevice();
  EXPECT_CALL(gatt_queue,
              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
                                  GATT_WRITE_NO_RSP, _, _))
      .Times(1);

  InjectInitialConfiguredNotification(group);

  group->PrintDebugState();

  // Start the configuration and stream Media content
  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
      group, context_type,
      {.sink = types::AudioContexts(context_type),
       .source = types::AudioContexts(context_type)}));

  group->PrintDebugState();

  // Check if timeout is fired
  EXPECT_CALL(mock_callbacks_, OnStateTransitionTimeout(leaudio_group_id));

  // simulate timeout seconds passed, alarm executing
  fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data);
  ASSERT_EQ(1, get_func_call_count("alarm_set_on_mloop"));

  LOG_INFO("OnStateTransitionTimeout");

  /* Simulate On State timeout */
  group->SetTargetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
  group->ClearAllCises();
  group->PrintDebugState();

  InjectAclDisconnected(group, leAudioDevice);

  /* Verify that all ASEs are inactive and reconfiguration flag is cleared.*/
  for (const auto& ase : leAudioDevice->ases_) {
    ASSERT_EQ(ase.state, types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
    ASSERT_EQ(ase.cis_state, types::CisState::IDLE);
    ASSERT_EQ(ase.data_path_state, types::DataPathState::IDLE);
    ASSERT_EQ(ase.reconfigure, 0);
  }
}

TEST_F(StateMachineTest,
       testStateTransitionTimeoutAndDisconnectWhenQoSConfigured) {
  const auto context_type = kContextTypeMedia;
  const int leaudio_group_id = 4;
  channel_count_ = kLeAudioCodecChannelCountSingleChannel |
                   kLeAudioCodecChannelCountTwoChannel;

  // Prepare fake connected device group
  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
  PrepareConfigureCodecHandler(group, 1);

  auto* leAudioDevice = group->GetFirstDevice();
  EXPECT_CALL(gatt_queue,
              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
                                  GATT_WRITE_NO_RSP, _, _))
      .Times(2);

  InjectInitialConfiguredNotification(group);

  group->PrintDebugState();

  // Start the configuration and stream Media content
  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
      group, context_type,
      {.sink = types::AudioContexts(context_type),
       .source = types::AudioContexts(context_type)}));

  group->PrintDebugState();

  // Check if timeout is fired
  EXPECT_CALL(mock_callbacks_, OnStateTransitionTimeout(leaudio_group_id));

  // simulate timeout seconds passed, alarm executing
  fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data);
  ASSERT_EQ(1, get_func_call_count("alarm_set_on_mloop"));

  LOG_INFO("OnStateTransitionTimeout");

  /* Simulate On State timeout */
  group->SetTargetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
  group->ClearAllCises();
  group->PrintDebugState();

  InjectAclDisconnected(group, leAudioDevice);

  /* Verify that all ASEs are inactive and reconfiguration flag is cleared.*/
  for (const auto& ase : leAudioDevice->ases_) {
    ASSERT_EQ(ase.state, types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
    ASSERT_EQ(ase.cis_state, types::CisState::IDLE);
    ASSERT_EQ(ase.data_path_state, types::DataPathState::IDLE);
    ASSERT_EQ(ase.reconfigure, 0);
  }
}

TEST_F(StateMachineTest, testStateTransitionTimeoutAndDisconnectWhenEnabling) {
  const auto context_type = kContextTypeMedia;
  const int leaudio_group_id = 4;
  channel_count_ = kLeAudioCodecChannelCountSingleChannel |
                   kLeAudioCodecChannelCountTwoChannel;

  // Prepare fake connected device group
  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
  PrepareConfigureCodecHandler(group, 1);
  PrepareConfigureQosHandler(group, 1);

  auto* leAudioDevice = group->GetFirstDevice();
  EXPECT_CALL(gatt_queue,
              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
                                  GATT_WRITE_NO_RSP, _, _))
      .Times(3);

  InjectInitialConfiguredNotification(group);

  group->PrintDebugState();

  // Start the configuration and stream Media content
  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
      group, context_type,
      {.sink = types::AudioContexts(context_type),
       .source = types::AudioContexts(context_type)}));

  group->PrintDebugState();

  // Check if timeout is fired
  EXPECT_CALL(mock_callbacks_, OnStateTransitionTimeout(leaudio_group_id));

  // simulate timeout seconds passed, alarm executing
  fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data);
  ASSERT_EQ(1, get_func_call_count("alarm_set_on_mloop"));

  LOG_INFO("OnStateTransitionTimeout");

  /* Simulate On State timeout */
  group->SetTargetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
  group->ClearAllCises();
  group->PrintDebugState();

  InjectAclDisconnected(group, leAudioDevice);

  /* Verify that all ASEs are inactive and reconfiguration flag is cleared.*/
  for (const auto& ase : leAudioDevice->ases_) {
    ASSERT_EQ(ase.state, types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
    ASSERT_EQ(ase.cis_state, types::CisState::IDLE);
    ASSERT_EQ(ase.data_path_state, types::DataPathState::IDLE);
    ASSERT_EQ(ase.reconfigure, 0);
  }
}

MATCHER_P(dataPathIsEq, expected, "") { return (arg.data_path_id == expected); }

TEST_F(StateMachineTest, testConfigureDataPathForHost) {