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

Commit 35bc98bd authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Read Available Context Type after reconnect

As per PACs specification,
"3.5.1. Available Audio Contexts behavior
<snip>

If the characteristic value exposed for a client changes when not in a connection,
and the value of the Client Characteristic Configuration descriptor is configured
for notifications, the server shall notify the new characteristic value when
reconnecting to a bonded client.
"

However it was observed that some audio servers are not doing that, which
leads to unexpected behavior.

This value changes quite often when multipoint scenario is used.

For this reason, and since Android already reads ASCS Control Point CCC
descriptor on reconnection, Android will read this value after
reconnection.
Since for EATT supported devices, Read Multiple Variable Len is going to
be used, there is almost no cost for this read.

This patch also moves handling of ASCS CTP CCC descriptor to other
place - this is mechanical refactor.

Bug: 366062501
Test: atest bluetooth_le_audio_test
Flag: EXEMPT, regression test with unit tests, new test added
Change-Id: I71d058343ef4408ad3f730261ee26563728a8d4f
parent 292efd07
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();