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

Commit b394a36f authored by Yuyang Huang's avatar Yuyang Huang
Browse files

add GMAP client

Bug: 350102910
Bug: 353978074
Test: atest --host bluetooth_le_audio_client_test:GmapClientTest
Flag: com.android.bluetooth.flags.leaudio_gmap_client
Change-Id: Ia0adc40410bb0d735b71d0dacb006dd620f5394b
parent 9d0a929c
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -74,6 +74,7 @@ cc_library_static {
        "av/bta_av_main.cc",
        "av/bta_av_ssm.cc",
        "csis/csis_client.cc",
        "gmap/gmap_client.cc",
        "groups/groups.cc",
        "has/has_client.cc",
        "has/has_ctp.cc",
@@ -876,6 +877,7 @@ cc_test {
        ":TestMockStackBtmIso",
        ":TestMockStackL2cap",
        ":TestStubOsi",
        "gmap/gmap_client.cc",
        "le_audio/audio_hal_client/audio_hal_client_test.cc",
        "le_audio/audio_hal_client/audio_sink_hal_client.cc",
        "le_audio/audio_hal_client/audio_source_hal_client.cc",
@@ -976,6 +978,8 @@ cc_test {
        ":TestStubOsi",
        "gatt/database.cc",
        "gatt/database_builder.cc",
        "gmap/gmap_client.cc",
        "gmap/gmap_client_test.cc",
        "le_audio/broadcaster/broadcast_configuration_provider.cc",
        "le_audio/broadcaster/broadcaster_types.cc",
        "le_audio/client.cc",
+1 −0
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ static_library("bta") {
    "gatt/database.cc",
    "gatt/database_builder.cc",
    "groups/groups.cc",
    "gmap/gmap_client.cc",
    "has/has_client.cc",
    "has/has_ctp.cc",
    "has/has_preset.cc",
+111 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "bta/le_audio/gmap_client.h"

#include <base/functional/bind.h>
#include <base/functional/callback.h>
#include <base/strings/string_number_conversions.h>
#include <bluetooth/log.h>
#include <com_android_bluetooth_flags.h>
#include <hardware/bt_gatt_types.h>

#include <bitset>
#include <string>
#include <vector>

#include "bta_gatt_api.h"
#include "bta_gatt_queue.h"
#include "bta_le_audio_uuids.h"
#include "gap_api.h"
#include "gatt_api.h"
#include "internal_include/bt_trace.h"
#include "os/log.h"
#include "osi/include/properties.h"
#include "stack/include/bt_types.h"

using bluetooth::Uuid;
using namespace bluetooth;
using bluetooth::le_audio::GmapClient;
bool GmapClient::is_offloader_support_gmap_ = false;

void GmapClient::AddFromStorage(const RawAddress &addr, const uint8_t role,
                                const uint16_t role_handle, const uint8_t UGT_feature,
                                const uint16_t UGT_feature_handle) {
  addr_ = addr;
  role_ = role;
  role_handle_ = role_handle;
  UGT_feature_ = UGT_feature;
  UGT_feature_handle_ = UGT_feature_handle;
}

void GmapClient::DebugDump(int fd) {
  std::stringstream stream;
  if (!IsGmapClientEnabled()) {
    dprintf(fd, "%s", "GmapClient not enabled");
    return;
  }
  stream << "GmapClient device: " << addr_ << ", Role: " << role_ << ", ";
  stream << "UGT Feature: " << UGT_feature_ << "\n";
  dprintf(fd, "%s", stream.str().c_str());
}

bool GmapClient::IsGmapClientEnabled() {
  bool flag = com::android::bluetooth::flags::leaudio_gmap_client();
  bool system_prop = osi_property_get_bool("bluetooth.profile.gmap.enabled", false);

  bool result = flag && system_prop && is_offloader_support_gmap_;
  log::info("GmapClientEnabled={}, flag={}, system_prop={}, offloader_support={}", result,
            system_prop, flag, GmapClient::is_offloader_support_gmap_);
  return result;
}

void GmapClient::UpdateGmapOffloaderSupport(bool value) {
  GmapClient::is_offloader_support_gmap_ = value;
}

bool GmapClient::parseAndSaveGmapRole(uint16_t len, const uint8_t *value) {
  if (len != GmapClient::kGmapRoleLen) {
    log::error("device: {}, Wrong len of GMAP Role characteristic", addr_);
    return false;
  }

  STREAM_TO_UINT8(role_, value);
  log::info("GMAP device: {}, Role: {}", addr_, role_.to_string());
  return true;
}

bool GmapClient::parseAndSaveUGTFeature(uint16_t len, const uint8_t *value) {
  if (len != kGmapUGTFeatureLen) {
    log::error("device: {}, Wrong len of GMAP UGT Feature characteristic", addr_);
    return false;
  }
  STREAM_TO_UINT8(UGT_feature_, value);
  log::info("GMAP device: {}, Feature: {}", addr_, UGT_feature_.to_string());
  return true;
}

std::bitset<8> GmapClient::getRole() { return role_; }

uint16_t GmapClient::getRoleHandle() { return role_handle_; }

void GmapClient::setRoleHandle(uint16_t handle) { role_handle_ = handle; }

std::bitset<8> GmapClient::getUGTFeature() { return UGT_feature_; }

uint16_t GmapClient::getUGTFeatureHandle() { return UGT_feature_handle_; }

void GmapClient::setUGTFeatureHandle(uint16_t handle) { UGT_feature_handle_ = handle; }
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "bta/le_audio/gmap_client.h"

#include <bluetooth/log.h>
#include <com_android_bluetooth_flags.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <hardware/bluetooth.h>

#include "bta/le_audio/le_audio_types.h"
#include "fake_osi.h"
#include "test/mock/mock_osi_properties.h"

using bluetooth::le_audio::GmapClient;
using ::testing::_;

static constexpr char kGmapEnabledSysProp[] = "bluetooth.profile.gmap.enabled";

void osi_property_set_bool(const char* key, bool value);

class GmapClientTest : public ::testing::Test {
public:
  RawAddress addr = RawAddress({0x11, 0x22, 0x33, 0x44, 0x55, 0x66});
  GmapClient gmapClient = GmapClient(addr);
};

TEST_F(GmapClientTest, test_parse_role) {
  const uint8_t role = 0b0001;
  gmapClient.parseAndSaveGmapRole(1, &role);

  ASSERT_EQ(gmapClient.getRole(), role);
}

TEST_F(GmapClientTest, test_parse_invalid_role) {
  const uint8_t role = 0b0001;
  ASSERT_FALSE(gmapClient.parseAndSaveGmapRole(2, &role));
}

TEST_F(GmapClientTest, test_parse_ugt_feature) {
  const uint8_t value = 0b0001;
  gmapClient.parseAndSaveUGTFeature(1, &value);

  ASSERT_EQ(gmapClient.getUGTFeature(), value);
}

TEST_F(GmapClientTest, test_parse_invalid_ugt_feature) {
  const uint8_t value = 0b0001;
  ASSERT_FALSE(gmapClient.parseAndSaveUGTFeature(2, &value));
}

TEST_F(GmapClientTest, test_add_from_storage) {
  const uint8_t role = 0b0001;
  const uint16_t role_handle = 2;
  const uint8_t UGT_feature = 0b0011;
  const uint16_t UGT_feature_handle = 4;
  gmapClient.AddFromStorage(addr, role, role_handle, UGT_feature, UGT_feature_handle);
  ASSERT_EQ(gmapClient.getRole(), role);
  ASSERT_EQ(gmapClient.getRoleHandle(), role_handle);
  ASSERT_EQ(gmapClient.getUGTFeature(), UGT_feature);
  ASSERT_EQ(gmapClient.getUGTFeatureHandle(), UGT_feature_handle);
}

TEST_F(GmapClientTest, test_role_handle) {
  const uint16_t handle = 5;
  gmapClient.setRoleHandle(handle);
  ASSERT_EQ(gmapClient.getRoleHandle(), handle);
}

TEST_F(GmapClientTest, test_ugt_feature_handle) {
  const uint16_t handle = 6;
  gmapClient.setUGTFeatureHandle(handle);
  ASSERT_EQ(gmapClient.getUGTFeatureHandle(), handle);
}

TEST_F(GmapClientTest, test_is_gmap_client_enabled) {
  GmapClient::UpdateGmapOffloaderSupport(false);
  ASSERT_EQ(GmapClient::IsGmapClientEnabled(), false);

  com::android::bluetooth::flags::provider_->leaudio_gmap_client(true);
  osi_property_set_bool(kGmapEnabledSysProp, true);

  GmapClient::UpdateGmapOffloaderSupport(true);

  ASSERT_EQ(GmapClient::IsGmapClientEnabled(), true);
  osi_property_set_bool(kGmapEnabledSysProp, false);
}
+35 −1
Original line number Diff line number Diff line
@@ -45,7 +45,9 @@
#include "content_control_id_keeper.h"
#include "devices.h"
#include "gatt_api.h"
#include "gmap_client.h"
#include "hci/controller_interface.h"
#include "include/hardware/bt_gmap.h"
#include "internal_include/stack_config.h"
#include "le_audio/device_groups.h"
#include "le_audio_health_status.h"
@@ -72,6 +74,7 @@
using base::Closure;
using bluetooth::Uuid;
using bluetooth::common::ToString;
using bluetooth::gmap::RolesBitMask;
using bluetooth::groups::DeviceGroups;
using bluetooth::groups::DeviceGroupsCallbacks;
using bluetooth::hci::IsoManager;
@@ -84,6 +87,7 @@ using bluetooth::le_audio::ContentControlIdKeeper;
using bluetooth::le_audio::DeviceConnectState;
using bluetooth::le_audio::DsaMode;
using bluetooth::le_audio::DsaModes;
using bluetooth::le_audio::GmapClient;
using bluetooth::le_audio::GroupNodeStatus;
using bluetooth::le_audio::GroupStatus;
using bluetooth::le_audio::GroupStreamStatus;
@@ -117,7 +121,6 @@ using bluetooth::le_audio::types::LeAudioContextType;
using bluetooth::le_audio::types::PublishedAudioCapabilities;
using bluetooth::le_audio::utils::GetAudioContextsFromSinkMetadata;
using bluetooth::le_audio::utils::GetAudioContextsFromSourceMetadata;

using namespace bluetooth;

/* Enums */
@@ -2052,6 +2055,12 @@ public:
    } else if (hdl == leAudioDevice->tmap_role_hdl_) {
      bluetooth::le_audio::client_parser::tmap::ParseTmapRole(leAudioDevice->tmap_role_, len,
                                                              value);
    } else if (leAudioDevice->gmap_client_ != nullptr && GmapClient::IsGmapClientEnabled() &&
               hdl == leAudioDevice->gmap_client_->getRoleHandle()) {
      leAudioDevice->gmap_client_->parseAndSaveGmapRole(len, value);
    } else if (leAudioDevice->gmap_client_ != nullptr && GmapClient::IsGmapClientEnabled() &&
               hdl == leAudioDevice->gmap_client_->getUGTFeatureHandle()) {
      leAudioDevice->gmap_client_->parseAndSaveUGTFeature(len, value);
    } else {
      log::error("Unknown attribute read: 0x{:x}", hdl);
    }
@@ -2767,6 +2776,7 @@ public:
    const gatt::Service* pac_svc = nullptr;
    const gatt::Service* ase_svc = nullptr;
    const gatt::Service* tmas_svc = nullptr;
    const gatt::Service* gmap_svc = nullptr;

    std::vector<uint16_t> csis_primary_handles;
    uint16_t cas_csis_included_handle = 0;
@@ -2805,6 +2815,10 @@ public:
        log::info("Found Telephony and Media Audio service, handle: 0x{:04x}, device: {}",
                  tmp.handle, leAudioDevice->address_);
        tmas_svc = &tmp;
      } else if (tmp.uuid == bluetooth::le_audio::uuid::kGamingAudioServiceUuid) {
        log::info("Found Gaming Audio service, handle: 0x{:04x}, device: {}", tmp.handle,
                  leAudioDevice->address_);
        gmap_svc = &tmp;
      }
    }

@@ -3057,6 +3071,26 @@ public:
      }
    }

    if (gmap_svc && GmapClient::IsGmapClientEnabled()) {
      leAudioDevice->gmap_client_ = std::make_unique<GmapClient>(leAudioDevice->address_);
      for (const gatt::Characteristic& charac : gmap_svc->characteristics) {
        if (charac.uuid == bluetooth::le_audio::uuid::kRoleCharacteristicUuid) {
          uint16_t handle = charac.value_handle;
          leAudioDevice->gmap_client_->setRoleHandle(handle);
          BtaGattQueue::ReadCharacteristic(conn_id, handle, OnGattReadRspStatic, NULL);
          log::info("Found Gmap Role characteristic, handle: 0x{:04x}, device: {}",
                    leAudioDevice->gmap_client_->getRoleHandle(), leAudioDevice->address_);
        }
        if (charac.uuid == bluetooth::le_audio::uuid::kUnicastGameTerminalCharacteristicUuid) {
          uint16_t handle = charac.value_handle;
          leAudioDevice->gmap_client_->setUGTFeatureHandle(handle);
          BtaGattQueue::ReadCharacteristic(conn_id, handle, OnGattReadRspStatic, NULL);
          log::info("Found Gmap UGT Feature characteristic, handle: 0x{:04x}, device: {}",
                    leAudioDevice->gmap_client_->getUGTFeatureHandle(), leAudioDevice->address_);
        }
      }
    }

    leAudioDevice->known_service_handles_ = true;
    leAudioDevice->notify_connected_after_read_ = true;
    if (leAudioHealthStatus_) {
Loading