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

Commit a9731545 authored by Łukasz Rymanowski (xWF)'s avatar Łukasz Rymanowski (xWF) Committed by Gerrit Code Review
Browse files

Merge "leaudio: Read Available Context Type after reconnect" into main

parents 65962427 35bc98bd
Loading
Loading
Loading
Loading
+50 −44
Original line number Diff line number Diff line
@@ -1812,6 +1812,24 @@ public:
    }
  }

  void handleInitialCtpCccRead(LeAudioDevice* leAudioDevice, uint16_t len, uint8_t* value) {
    if (len != 2) {
      log::error("Could not read CCC for {}, disconnecting", leAudioDevice->address_);
      instance->Disconnect(leAudioDevice->address_);
      return;
    }

    uint16_t val = *(uint16_t*)value;
    if (val == 0) {
      log::warn("{} forgot CCC values. Re-subscribing", leAudioDevice->address_);
      RegisterKnownNotifications(leAudioDevice, false, true);
      return;
    }

    log::verbose("{}, ASCS ctp ccc: {:#x}", leAudioDevice->address_, val);
    connectionReady(leAudioDevice);
  }

  /* This is a generic read/notify/indicate handler for gatt. Here messages
   * are dispatched to correct elements e.g. ASEs, PACs, audio locations etc.
   */
@@ -1826,11 +1844,15 @@ public:
    }

    ase = leAudioDevice->GetAseByValHandle(hdl);

    LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
    if (ase) {
      groupStateMachine_->ProcessGattNotifEvent(value, len, ase, leAudioDevice, group);
      return;
    }

    /* Initial CCC read to check if remote device properly keeps CCC values */
    if (hdl == leAudioDevice->ctp_hdls_.ccc_hdl) {
      handleInitialCtpCccRead(leAudioDevice, len, value);
      return;
    }

@@ -2218,6 +2240,32 @@ public:
    }
  }

  void ReadMustHaveAttributesOnReconnect(LeAudioDevice* leAudioDevice) {
    log::verbose("{}", leAudioDevice->address_);
    /* Here we read
     * 1) ASCS Control Point CCC descriptor in order to validate proper
     *    behavior of remote device which should store CCC values for bonded device.
     * 2) Available Context Types which normally should be notified by the server,
     *    but since it is crucial for proper streaming experiance, and in the same time
     *    it can change very often which, as we observed, might lead to not being sent by
     *    remote devices
     */
    if (!com::android::bluetooth::flags::le_ase_read_multiple_variable()) {
      BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_,
                                       leAudioDevice->audio_avail_hdls_.val_hdl,
                                       OnGattReadRspStatic, NULL);
      BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.ccc_hdl,
                                       OnGattReadRspStatic, NULL);
    } else {
      tBTA_GATTC_MULTI multi_read = {.num_attr = 2,
                                     .handles = {leAudioDevice->audio_avail_hdls_.val_hdl,
                                                 leAudioDevice->ctp_hdls_.ccc_hdl}};

      BtaGattQueue::ReadMultiCharacteristic(leAudioDevice->conn_id_, multi_read,
                                            OnGattReadMultiRspStatic, NULL);
    }
  }

  void OnEncryptionComplete(const RawAddress& address, tBTM_STATUS status) {
    log::info("{} status 0x{:02x}", address, status);
    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
@@ -2268,13 +2316,7 @@ public:
       * assume remote device keeps bonded CCC values.
       */
      RegisterKnownNotifications(leAudioDevice, true, false);

      /* Make sure remote keeps CCC values as per specification.
       * We read only ctp_ccc value. If that one is good, we assume
       * remote keeps CCC values correctly.
       */
      BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.ccc_hdl,
                                       OnGattCtpCccReadRspStatic, NULL);
      ReadMustHaveAttributesOnReconnect(leAudioDevice);
    }

    /* If we know services and read is not ongoing, this is reconnection and
@@ -4960,42 +5002,6 @@ public:
    return false;
  }

  static void OnGattCtpCccReadRspStatic(tCONN_ID conn_id, tGATT_STATUS status, uint16_t hdl,
                                        uint16_t len, uint8_t* value, void* data) {
    if (!instance) {
      return;
    }

    log::debug("conn_id: 0x{:04x}, status: 0x{:02x}", conn_id, status);

    LeAudioDevice* leAudioDevice = instance->leAudioDevices_.FindByConnId(conn_id);

    if (!leAudioDevice) {
      log::error("LeAudioDevice not found");
      return;
    }

    if (status == GATT_DATABASE_OUT_OF_SYNC) {
      log::info("Database out of sync for {}, re-discovering", leAudioDevice->address_);
      instance->ClearDeviceInformationAndStartSearch(leAudioDevice);
      return;
    }

    if (status != GATT_SUCCESS || len != 2) {
      log::error("Could not read CCC for {}, disconnecting", leAudioDevice->address_);
      instance->Disconnect(leAudioDevice->address_);
      return;
    }

    uint16_t val = *(uint16_t*)value;
    if (val == 0) {
      log::info("{} forgot CCC values. Re-subscribing", leAudioDevice->address_);
      instance->RegisterKnownNotifications(leAudioDevice, false, true);
    } else {
      instance->connectionReady(leAudioDevice);
    }
  }

  static void OnGattReadRspStatic(tCONN_ID conn_id, tGATT_STATUS status, uint16_t hdl, uint16_t len,
                                  uint8_t* value, void* data) {
    if (!instance) {
+107 −1
Original line number Diff line number Diff line
@@ -8677,6 +8677,107 @@ TEST_F(UnicastTest, CheckDeviceIsNotAttachedToStreamWhenNotNeeded) {
  SyncOnMainLoop();
}
TEST_F(UnicastTest, ReconnectedDeviceAndAttachedToStreamBecauseOfAvailableContextTypeChange) {
  com::android::bluetooth::flags::provider_->le_ase_read_multiple_variable(true);
  uint8_t group_size = 2;
  int group_id = 2;
  /* Scenario
   * 1. Two devices A and B are streaming
   * 2. Device A Release ASE and removes all available context types
   * 3. Device B keeps streaming
   * 4. Device A disconnectes
   * 5. Device A reconnect
   * 6. Device A has context available for streaming  and should be attached to the stream
   */
  // Report working CSIS
  ON_CALL(mock_csis_client_module_, IsCsisClientRunning()).WillByDefault(Return(true));
  ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id))
          .WillByDefault(Invoke([&](int group_id) { return group_size; }));
  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);
  SyncOnMainLoop();
  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);
  /* Get group and Device A */
  ASSERT_NE(0lu, streaming_groups.count(group_id));
  auto group = streaming_groups.at(group_id);
  ASSERT_NE(group, nullptr);
  auto device = group->GetFirstDevice();
  /* Simulate available context type being cleared */
  InjectAvailableContextTypes(device->address_, device->conn_id_, types::AudioContexts(0),
                              types::AudioContexts(0));
  /* Simulate ASE releasing and CIS Disconnection */
  for (auto& ase : device->ases_) {
    /* Releasing state */
    if (!ase.active) {
      continue;
    }
    std::vector<uint8_t> releasing_state = {
            ase.id, static_cast<uint8_t>(types::AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING)};
    InjectNotificationEvent(device->address_, device->conn_id_, ase.hdls.val_hdl, releasing_state);
    SyncOnMainLoop();
    InjectCisDisconnected(group_id, ase.cis_conn_hdl);
    SyncOnMainLoop();
  }
  cis_count_out = 1;
  cis_count_in = 0;
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
  /* Device A will disconnect, and do not reconnect automatically */
  ON_CALL(mock_gatt_interface_, Open(_, device->address_, BTM_BLE_DIRECT_CONNECTION, _))
          .WillByDefault(Return());
  /* Disconnect first device */
  auto conn_id = device->conn_id_;
  InjectDisconnectedEvent(conn_id, GATT_CONN_TERMINATE_PEER_USER);
  SyncOnMainLoop();
  /* For background connect, test needs to Inject Connected Event.
   * Note that initial available_snk_context_types_ available_src_context_types_ will
   * be read after reconnection, which should bring device to the stream again. */
  InjectConnectedEvent(device->address_, conn_id);
  SyncOnMainLoop();
  /* Check single device is streaming */
  cis_count_out = 2;
  cis_count_in = 0;
  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
}
TEST_F(UnicastTest, ReconnectedDeviceNotAttachedToStreamBecauseOfNotAvailableContext) {
  uint8_t group_size = 2;
  int group_id = 2;
@@ -8762,7 +8863,12 @@ TEST_F(UnicastTest, ReconnectedDeviceNotAttachedToStreamBecauseOfNotAvailableCon
  InjectDisconnectedEvent(conn_id, GATT_CONN_TERMINATE_PEER_USER);
  SyncOnMainLoop();
  /* For background connect, test needs to Inject Connected Event */
  /* For background connect, test needs to Inject Connected Event.
   * Since after reconnect Android reads available context types, make sure
   * 0 is read */
  available_snk_context_types_ = 0;
  available_src_context_types_ = 0;
  InjectConnectedEvent(device->address_, conn_id);
  SyncOnMainLoop();