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

Commit ed515005 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Jakub Pawłowski
Browse files

leaudio: Add graceful disconnect support.

With this patch, if user request to disconnect profile, streaming
is closed first and later profile disconnects from GATT.

We need to do it as disconnection from GATT does not assure ACL will be
disconnected, as there might be other profiles enabled on that ACL.

Sponsor: jpawlowski@
Bug: 205791687
Test: atest bluetooth_le_audio_client_test
Change-Id: Idf5f22c9bc5fe35e2ab42679a3db93115f5987ad
parent 4c559101
Loading
Loading
Loading
Loading
+27 −1
Original line number Original line Diff line number Diff line
@@ -883,6 +883,14 @@ class LeAudioClientImpl : public LeAudioClient {
    BTA_GATTC_CancelOpen(0, address, false);
    BTA_GATTC_CancelOpen(0, address, false);


    if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) {
    if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) {
      auto group = aseGroups_.FindById(leAudioDevice->group_id_);
      if (group &&
          group->GetState() ==
              le_audio::types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
        leAudioDevice->closing_stream_for_disconnection_ = true;
        groupStateMachine_->StopStream(group);
        return;
      }
      DisconnectDevice(leAudioDevice);
      DisconnectDevice(leAudioDevice);
      return;
      return;
    }
    }
@@ -1308,6 +1316,7 @@ class LeAudioClientImpl : public LeAudioClient {


    callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
    callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
    leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
    leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
    leAudioDevice->closing_stream_for_disconnection_ = false;
    leAudioDevice->encrypted_ = false;
    leAudioDevice->encrypted_ = false;


    if (leAudioDevice->removing_device_) {
    if (leAudioDevice->removing_device_) {
@@ -3233,6 +3242,20 @@ class LeAudioClientImpl : public LeAudioClient {
    }
    }
  }
  }


  void HandlePendingDeviceDisconnection(LeAudioDeviceGroup* group) {
    LOG_DEBUG();
    auto leAudioDevice = group->GetFirstDevice();
    while (leAudioDevice) {
      if (leAudioDevice->closing_stream_for_disconnection_) {
        leAudioDevice->closing_stream_for_disconnection_ = false;
        LOG_DEBUG("Disconnecting group id: %d, address: %s", group->group_id_,
                  leAudioDevice->address_.ToString().c_str());
        DisconnectDevice(leAudioDevice);
      }
      leAudioDevice = group->GetNextDevice(leAudioDevice);
    }
  }

  void StatusReportCb(int group_id, GroupStreamStatus status) {
  void StatusReportCb(int group_id, GroupStreamStatus status) {
    LOG(INFO) << __func__ << "status: " << static_cast<int>(status)
    LOG(INFO) << __func__ << "status: " << static_cast<int>(status)
              << " audio_sender_state_: " << audio_sender_state_
              << " audio_sender_state_: " << audio_sender_state_
@@ -3284,7 +3307,10 @@ class LeAudioClientImpl : public LeAudioClient {
          }
          }
        }
        }
        CancelStreamingRequest();
        CancelStreamingRequest();
        if (group) {
          HandlePendingAvailableContexts(group);
          HandlePendingAvailableContexts(group);
          HandlePendingDeviceDisconnection(group);
        }
        break;
        break;
      }
      }
      case GroupStreamStatus::RELEASING:
      case GroupStreamStatus::RELEASING:
+2 −0
Original line number Original line Diff line number Diff line
@@ -57,6 +57,7 @@ class LeAudioDevice {
   * This is true only during initial phase of first connection. */
   * This is true only during initial phase of first connection. */
  bool first_connection_;
  bool first_connection_;
  bool connecting_actively_;
  bool connecting_actively_;
  bool closing_stream_for_disconnection_;
  uint16_t conn_id_;
  uint16_t conn_id_;
  bool encrypted_;
  bool encrypted_;
  int group_id_;
  int group_id_;
@@ -87,6 +88,7 @@ class LeAudioDevice {
        removing_device_(false),
        removing_device_(false),
        first_connection_(first_connection),
        first_connection_(first_connection),
        connecting_actively_(first_connection),
        connecting_actively_(first_connection),
        closing_stream_for_disconnection_(false),
        conn_id_(GATT_INVALID_CONN_ID),
        conn_id_(GATT_INVALID_CONN_ID),
        encrypted_(false),
        encrypted_(false),
        group_id_(group_id),
        group_id_(group_id),
+74 −14
Original line number Original line Diff line number Diff line
@@ -437,7 +437,7 @@ class UnicastTestNoInit : public Test {
        }));
        }));


    global_conn_id = 1;
    global_conn_id = 1;
    ON_CALL(mock_gatt_interface_, Open(_, _, _, _))
    ON_CALL(mock_gatt_interface_, Open(_, _, true, _))
        .WillByDefault(
        .WillByDefault(
            Invoke([&](tGATT_IF client_if, const RawAddress& remote_bda,
            Invoke([&](tGATT_IF client_if, const RawAddress& remote_bda,
                       bool is_direct, bool opportunistic) {
                       bool is_direct, bool opportunistic) {
@@ -1094,6 +1094,8 @@ class UnicastTestNoInit : public Test {
    tracks_[0].content_type = content_type;
    tracks_[0].content_type = content_type;


    if (reconfigure_existing_stream) {
    if (reconfigure_existing_stream) {
      EXPECT_CALL(*mock_unicast_audio_source_, SuspendedForReconfiguration())
          .Times(1);
      EXPECT_CALL(*mock_unicast_audio_source_, ConfirmStreamingRequest())
      EXPECT_CALL(*mock_unicast_audio_source_, ConfirmStreamingRequest())
          .Times(1);
          .Times(1);
    } else {
    } else {
@@ -1930,11 +1932,16 @@ TEST_F(UnicastTest, ConnectRemoteDisconnectOneEarbud) {
  /* For remote disconnection, expect stack to try background re-connect */
  /* For remote disconnection, expect stack to try background re-connect */
  EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, false, _))
  EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, false, _))
      .Times(1);
      .Times(1);
  global_conn_id = 1; /* Reset to keep conn_id same during re-connect */

  EXPECT_CALL(mock_client_callbacks_,
  EXPECT_CALL(mock_client_callbacks_,
              OnConnectionState(ConnectionState::CONNECTED, test_address0))
              OnConnectionState(ConnectionState::CONNECTED, test_address0))
      .Times(1);
      .Times(1);
  InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER);
  InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER);
  SyncOnMainLoop();

  /* For background connect, test needs to Inject Connected Event */
  InjectConnectedEvent(test_address0, 1);
  SyncOnMainLoop();
}
}


TEST_F(UnicastTest, ConnectTwoEarbudsCsisGrouped) {
TEST_F(UnicastTest, ConnectTwoEarbudsCsisGrouped) {
@@ -2084,6 +2091,10 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGrouped) {
      framework_encode_preference);
      framework_encode_preference);
  if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS);
  if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS);


  /* For background connect, test needs to Inject Connected Event */
  InjectConnectedEvent(test_address0, 1);
  InjectConnectedEvent(test_address1, 2);

  // We need to wait for the storage callback before verifying stuff
  // We need to wait for the storage callback before verifying stuff
  SyncOnMainLoop();
  SyncOnMainLoop();
  ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning());
  ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning());
@@ -2174,6 +2185,9 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGroupedDifferently) {
      framework_encode_preference);
      framework_encode_preference);
  if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS);
  if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS);


 /* For background connect, test needs to Inject Connected Event */
  InjectConnectedEvent(test_address0, 1);

  // We need to wait for the storage callback before verifying stuff
  // We need to wait for the storage callback before verifying stuff
  SyncOnMainLoop();
  SyncOnMainLoop();
  ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning());
  ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning());
@@ -2862,7 +2876,7 @@ TEST_F(UnicastTest, TwoEarbuds2ndLateConnect) {
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
}
}


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


@@ -2907,7 +2921,12 @@ TEST_F(UnicastTest, TwoEarbuds2ndDisconnect) {
  for (auto& ase : device->ases_) {
  for (auto& ase : device->ases_) {
    InjectCisDisconnected(group_id, ase.cis_conn_hdl);
    InjectCisDisconnected(group_id, ase.cis_conn_hdl);
  }
  }
  DisconnectLeAudio(device->address_, 1);

  EXPECT_CALL(mock_gatt_interface_, Open(_, device->address_, false, false))
      .Times(1);

  auto conn_id = device->conn_id_;
  InjectDisconnectedEvent(device->conn_id_, GATT_CONN_TERMINATE_PEER_USER);
  SyncOnMainLoop();
  SyncOnMainLoop();
  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);


@@ -2916,16 +2935,8 @@ TEST_F(UnicastTest, TwoEarbuds2ndDisconnect) {
  cis_count_in = 0;
  cis_count_in = 0;
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);


  // Reconnect the disconnected device
  InjectConnectedEvent(device->address_, conn_id);
  auto rank = 1;
  SyncOnMainLoop();
  auto location = codec_spec_conf::kLeAudioLocationFrontLeft;
  if (device->address_ == test_address1) {
    rank = 2;
    location = codec_spec_conf::kLeAudioLocationFrontRight;
  }
  ConnectCsisDevice(device->address_, 3 /*conn_id*/, location, location,
                    group_size, group_id, rank, true /*connect_through_csis*/,
                    false /* New device */);


  // Expect two iso channels to be fed with data
  // Expect two iso channels to be fed with data
  cis_count_out = 2;
  cis_count_out = 2;
@@ -2933,5 +2944,54 @@ TEST_F(UnicastTest, TwoEarbuds2ndDisconnect) {
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
}
}


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

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

  // First earbud
  const RawAddress test_address0 = GetTestAddress(0);
  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
                    codec_spec_conf::kLeAudioLocationFrontLeft,
                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
                    group_id, 1 /* rank*/);

  // Second earbud
  const RawAddress test_address1 = GetTestAddress(1);
  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
                    codec_spec_conf::kLeAudioLocationFrontRight,
                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
                    group_id, 2 /* rank*/, true /*connect_through_csis*/);

  // Audio sessions are started only when device gets active
  EXPECT_CALL(*mock_unicast_audio_source_, Start(_, _)).Times(1);
  EXPECT_CALL(*mock_audio_sink_, Start(_, _)).Times(1);
  LeAudioClient::Get()->GroupSetActive(group_id);

  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);

  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
  Mock::VerifyAndClearExpectations(mock_unicast_audio_source_);
  SyncOnMainLoop();

  // Expect two iso channels 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);

  // Disconnect one device and expect the group to keep on streaming
  EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1);
  EXPECT_CALL(mock_gatt_interface_, Open(_, _, _, _)).Times(0);

  DisconnectLeAudio(test_address0, 1);
  DisconnectLeAudio(test_address1, 2);

  SyncOnMainLoop();
  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
}

}  // namespace
}  // namespace
}  // namespace le_audio
}  // namespace le_audio