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

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

add Gmap Server

Bug: 350102910
Bug: 353978074
Test: atest --host bluetooth_le_audio_client_test
Flag: com.android.bluetooth.flags.leaudio_gmap_client
Change-Id: I9b2249eb1ef57f7ef56d8f654d6141b962b2a690
parent 5f53e096
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ cc_library_static {
        "av/bta_av_ssm.cc",
        "csis/csis_client.cc",
        "gmap/gmap_client.cc",
        "gmap/gmap_server.cc",
        "groups/groups.cc",
        "has/has_client.cc",
        "has/has_ctp.cc",
@@ -980,6 +981,8 @@ cc_test {
        "gatt/database_builder.cc",
        "gmap/gmap_client.cc",
        "gmap/gmap_client_test.cc",
        "gmap/gmap_server.cc",
        "gmap/gmap_server_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
@@ -65,6 +65,7 @@ static_library("bta") {
    "gatt/database_builder.cc",
    "groups/groups.cc",
    "gmap/gmap_client.cc",
    "gmap/gmap_server.cc",
    "has/has_client.cc",
    "has/has_ctp.cc",
    "has/has_preset.cc",
+278 −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_server.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/le_audio/le_audio_types.h"
#include "bta_gatt_api.h"
#include "bta_gatt_queue.h"
#include "bta_le_audio_uuids.h"
#include "btm_sec.h"
#include "gap_api.h"
#include "gatt_api.h"
#include "gd/hci/uuid.h"
#include "gd/os/rand.h"
#include "include/hardware/bt_gmap.h"
#include "internal_include/bt_trace.h"
#include "os/log.h"
#include "os/logging/log_adapter.h"
#include "osi/include/properties.h"
#include "stack/include/bt_types.h"
#include "stack/include/btm_ble_addr.h"

using bluetooth::Uuid;
using namespace bluetooth;
using bluetooth::le_audio::GmapCharacteristic;
using bluetooth::le_audio::GmapServer;

bool GmapServer::is_offloader_support_gmap_ = false;
uint16_t GmapServer::server_if_ = 0;
std::unordered_map<uint16_t, GmapCharacteristic> GmapServer::characteristics_ =
        std::unordered_map<uint16_t, GmapCharacteristic>();
// default role is UGG
std::bitset<8> GmapServer::role_ = 0b0001;
// AOSP's LE Audio source support multi-sink on default
std::bitset<8> GmapServer::UGG_feature_ =
        static_cast<uint8_t>(bluetooth::gmap::UGGFeatureBitMask::MultisinkFeatureSupport);

bool GmapServer::IsGmapServerEnabled() {
  // for UGG, both GMAP Server and Client are needed. So server and client share the same flag.
  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("GmapServerEnabled={}, flag={}, system_prop={}, offloader_support={}", result,
            system_prop, flag, GmapServer::is_offloader_support_gmap_);
  return result;
}

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

void GmapServer::DebugDump(int fd) {
  std::stringstream stream;
  stream << "GmapServer is enabled: " << IsGmapServerEnabled() << "\n";
  if (IsGmapServerEnabled()) {
    stream << "GmapServer Role: " << role_ << ", UGG Feature: " << UGG_feature_ << "\n";
  }

  dprintf(fd, "%s", stream.str().c_str());
}

void GmapServer::Initialize(std::bitset<8> role, std::bitset<8> UGG_feature) {
  GmapServer::role_ = role;
  GmapServer::Initialize(UGG_feature);
}

void GmapServer::Initialize(std::bitset<8> UGG_feature) {
  GmapServer::UGG_feature_ = UGG_feature;
  log::info("GmapServer initialized, role={}, UGG_feature={}", GmapServer::role_.to_string(),
            UGG_feature.to_string());
  characteristics_.clear();

  BTA_GATTS_AppRegister(
          bluetooth::le_audio::uuid::kGamingAudioServiceUuid,
          [](tBTA_GATTS_EVT event, tBTA_GATTS *p_data) {
            if (p_data) {
              GmapServer::GattsCallback(event, p_data);
            }
          },
          false);
}

std::bitset<8> GmapServer::GetRole() { return GmapServer::role_; }

uint16_t GmapServer::GetRoleHandle() {
  for (auto &[attribute_handle, characteristic] : characteristics_) {
    if (characteristic.uuid_ == bluetooth::le_audio::uuid::kRoleCharacteristicUuid) {
      return attribute_handle;
    }
  }
  log::warn("no valid UGG feature handle");
  return 0;
}

std::bitset<8> GmapServer::GetUGGFeature() { return GmapServer::UGG_feature_; }

uint16_t GmapServer::GetUGGFeatureHandle() {
  for (auto &[attribute_handle, characteristic] : characteristics_) {
    if (characteristic.uuid_ == bluetooth::le_audio::uuid::kUnicastGameGatewayCharacteristicUuid) {
      return attribute_handle;
    }
  }
  log::warn("no valid UGG feature handle");
  return 0;
}

std::unordered_map<uint16_t, GmapCharacteristic> &GmapServer::GetCharacteristics() {
  return GmapServer::characteristics_;
}

void GmapServer::GattsCallback(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) {
  log::info("event: {}", gatt_server_event_text(event));
  switch (event) {
    case BTA_GATTS_CONNECT_EVT: {
      OnGattConnect(p_data);
      break;
    }
    case BTA_GATTS_DEREG_EVT: {
      BTA_GATTS_AppDeregister(server_if_);
      break;
    }
    case BTA_GATTS_DISCONNECT_EVT: {
      OnGattDisconnect(p_data);
      break;
    }
    case BTA_GATTS_REG_EVT: {
      OnGattServerRegister(p_data);
      break;
    }
    case BTA_GATTS_READ_CHARACTERISTIC_EVT: {
      OnReadCharacteristic(p_data);
      break;
    }
    default:
      log::warn("Unhandled event {}", gatt_server_event_text(event));
  }
}

void GmapServer::OnGattConnect(tBTA_GATTS *p_data) {
  if (p_data == nullptr) {
    log::warn("invalid p_data");
  }
  auto address = p_data->conn.remote_bda;
  log::info("Address: {}, conn_id:{}", address, p_data->conn.conn_id);
  if (p_data->conn.transport == BT_TRANSPORT_BR_EDR) {
    log::warn("Skip BE/EDR connection");
    return;
  }
}

void GmapServer::OnGattDisconnect(tBTA_GATTS *p_data) {
  if (p_data == nullptr) {
    log::warn("invalid p_data");
  }
  auto address = p_data->conn.remote_bda;
  log::info("Address: {}, conn_id:{}", address, p_data->conn.conn_id);
}

void GmapServer::OnGattServerRegister(tBTA_GATTS *p_data) {
  if (p_data == nullptr) {
    log::warn("invalid p_data");
  }
  tGATT_STATUS status = p_data->reg_oper.status;
  log::info("status: {}", gatt_status_text(p_data->reg_oper.status));

  if (status != tGATT_STATUS::GATT_SUCCESS) {
    log::warn("Register Server fail");
    return;
  }
  server_if_ = p_data->reg_oper.server_if;

  std::vector<btgatt_db_element_t> service;

  // GMAP service
  btgatt_db_element_t gmap_service;
  gmap_service.uuid = bluetooth::le_audio::uuid::kGamingAudioServiceUuid;
  gmap_service.type = BTGATT_DB_PRIMARY_SERVICE;
  service.push_back(gmap_service);

  // GMAP role
  btgatt_db_element_t role_characteristic;
  role_characteristic.uuid = bluetooth::le_audio::uuid::kRoleCharacteristicUuid;
  role_characteristic.type = BTGATT_DB_CHARACTERISTIC;
  role_characteristic.properties = GATT_CHAR_PROP_BIT_READ;
  role_characteristic.permissions = GATT_PERM_READ;
  service.push_back(role_characteristic);

  // GMAP UGG feature
  btgatt_db_element_t UGG_feature_characteristic;
  UGG_feature_characteristic.uuid =
          bluetooth::le_audio::uuid::kUnicastGameGatewayCharacteristicUuid;
  UGG_feature_characteristic.type = BTGATT_DB_CHARACTERISTIC;
  UGG_feature_characteristic.properties = GATT_CHAR_PROP_BIT_READ;
  UGG_feature_characteristic.permissions = GATT_PERM_READ;
  service.push_back(UGG_feature_characteristic);

  log::info("add service");
  BTA_GATTS_AddService(server_if_, service,
                       base::BindRepeating([](tGATT_STATUS status, int server_if,
                                              std::vector<btgatt_db_element_t> service) {
                         OnServiceAdded(status, server_if, service);
                       }));
}

void GmapServer::OnServiceAdded(tGATT_STATUS status, int server_if,
                                std::vector<btgatt_db_element_t> services) {
  log::info("status: {}, server_if: {}", gatt_status_text(status), server_if);
  for (const auto &service : services) {
    uint16_t attribute_handle = service.attribute_handle;
    Uuid uuid = service.uuid;
    if (service.type == BTGATT_DB_CHARACTERISTIC) {
      log::info("Characteristic uuid: 0x{:04x}, handle:0x{:04x}", uuid.As16Bit(), attribute_handle);
      GmapCharacteristic characteristic{.uuid_ = uuid, .attribute_handle_ = attribute_handle};
      characteristics_[attribute_handle] = characteristic;
    }
  }
}

void GmapServer::OnReadCharacteristic(tBTA_GATTS *p_data) {
  uint16_t read_req_handle = p_data->req_data.p_data->read_req.handle;
  log::info("read_req_handle: 0x{:04x},", read_req_handle);

  tGATTS_RSP p_msg;
  p_msg.attr_value.handle = read_req_handle;
  auto it = characteristics_.find(read_req_handle);
  if (it == characteristics_.end()) {
    log::error("Invalid handle 0x{:04x}", read_req_handle);
    BTA_GATTS_SendRsp(p_data->req_data.conn_id, p_data->req_data.trans_id, GATT_INVALID_HANDLE,
                      &p_msg);
    return;
  }

  auto uuid = it->second.uuid_;

  log::info("Read uuid, 0x{:04x}", uuid.As16Bit());
  // Check Characteristic UUID
  if (bluetooth::le_audio::uuid::kRoleCharacteristicUuid == uuid) {
    p_msg.attr_value.len = GmapServer::kGmapRoleLen;
    auto role = GmapServer::GetRole();
    p_msg.attr_value.value[0] = static_cast<uint8_t>(role.to_ulong());
  } else if (bluetooth::le_audio::uuid::kUnicastGameGatewayCharacteristicUuid == uuid) {
    p_msg.attr_value.len = GmapServer::kGmapUGGFeatureLen;
    auto UGGFeature = GmapServer::GetUGGFeature();
    p_msg.attr_value.value[0] = static_cast<uint8_t>(UGGFeature.to_ulong());
  } else {
    log::warn("Unhandled uuid {}", uuid.ToString());
    BTA_GATTS_SendRsp(p_data->req_data.conn_id, p_data->req_data.trans_id, GATT_ILLEGAL_PARAMETER,
                      &p_msg);
    return;
  }

  BTA_GATTS_SendRsp(p_data->req_data.conn_id, p_data->req_data.trans_id, GATT_SUCCESS, &p_msg);
}
+167 −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_server.h"

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

#include "bta/le_audio/le_audio_types.h"
#include "bta_gatt_api_mock.h"
#include "test/common/mock_functions.h"

using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::DoDefault;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::Sequence;
using ::testing::SetArgPointee;
using ::testing::WithArg;

using ::testing::NiceMock;

using bluetooth::Uuid;
using namespace bluetooth;
using bluetooth::le_audio::GmapCharacteristic;
using bluetooth::le_audio::GmapServer;

class GmapServerTest : public ::testing::Test {
public:
  RawAddress addr = RawAddress({0x11, 0x22, 0x33, 0x44, 0x55, 0x66});
  NiceMock<gatt::MockBtaGattServerInterface> gatt_server_interface;
  uint8_t role = 0b1;
  uint8_t UGG_feature = 0b111;

  void SetUp(void) override {
    reset_mock_function_count_map();
    gatt::SetMockBtaGattServerInterface(&gatt_server_interface);
    EXPECT_CALL(gatt_server_interface, AppRegister(_, _, _)).Times(1);
    GmapServer::Initialize(role, UGG_feature);
  }
};

TEST_F(GmapServerTest, test_get_role) { ASSERT_EQ(GmapServer::GetRole(), role); }

TEST_F(GmapServerTest, test_get_UGG_feature) {
  ASSERT_EQ(GmapServer::GetUGGFeature(), UGG_feature);
}

TEST_F(GmapServerTest, test_add_service) {
  tBTA_GATTS gatts_cb_data;
  uint8_t server_if = 10;
  gatts_cb_data.reg_oper.status = GATT_SUCCESS;
  gatts_cb_data.reg_oper.server_if = server_if;

  EXPECT_CALL(gatt_server_interface, AddService(_, _, _)).Times(1);
  GmapServer::GattsCallback(BTA_GATTS_REG_EVT, &gatts_cb_data);
}

TEST_F(GmapServerTest, test_app_deregister) {
  tBTA_GATTS gatts_cb_data;
  EXPECT_CALL(gatt_server_interface, AppDeregister(_)).Times(1);
  GmapServer::GattsCallback(BTA_GATTS_DEREG_EVT, &gatts_cb_data);
}

TEST_F(GmapServerTest, test_read_invalid_characteristic) {
  uint16_t handle = 10;
  tGATTS_DATA gatts_data;
  gatts_data.read_req.handle = handle;
  tBTA_GATTS gatts_cb_data;
  gatts_cb_data.req_data.p_data = &gatts_data;

  EXPECT_CALL(gatt_server_interface, SendRsp(_, _, GATT_INVALID_HANDLE, _)).Times(1);
  GmapServer::GattsCallback(BTA_GATTS_READ_CHARACTERISTIC_EVT, &gatts_cb_data);
}

TEST_F(GmapServerTest, test_read_invalid_role_characteristic) {
  uint16_t handle = 10;
  GmapCharacteristic invalidGmapCharacteristic{
          .uuid_ = bluetooth::le_audio::uuid::kTelephonyMediaAudioProfileRoleCharacteristicUuid,
          .attribute_handle_ = handle};
  GmapServer::GetCharacteristics()[handle] = invalidGmapCharacteristic;

  tGATTS_DATA gatts_data;
  gatts_data.read_req.handle = handle;
  tBTA_GATTS gatts_cb_data;
  gatts_cb_data.req_data.p_data = &gatts_data;

  EXPECT_CALL(gatt_server_interface, SendRsp(_, _, GATT_ILLEGAL_PARAMETER, _)).Times(1);
  GmapServer::GattsCallback(BTA_GATTS_READ_CHARACTERISTIC_EVT, &gatts_cb_data);
}

TEST_F(GmapServerTest, test_read_valid_role_characteristic) {
  uint16_t handle = 10;
  GmapCharacteristic gmapCharacteristic{.uuid_ = bluetooth::le_audio::uuid::kRoleCharacteristicUuid,
                                        .attribute_handle_ = handle};
  GmapServer::GetCharacteristics()[handle] = gmapCharacteristic;

  tGATTS_DATA gatts_data;
  gatts_data.read_req.handle = handle;
  tBTA_GATTS gatts_cb_data;
  gatts_cb_data.req_data.p_data = &gatts_data;

  EXPECT_CALL(gatt_server_interface, SendRsp(_, _, GATT_SUCCESS, _)).Times(1);
  GmapServer::GattsCallback(BTA_GATTS_READ_CHARACTERISTIC_EVT, &gatts_cb_data);
}

TEST_F(GmapServerTest, test_read_valid_ugg_feature_characteristic) {
  uint16_t handle = 10;
  GmapCharacteristic gmapCharacteristic{
          .uuid_ = bluetooth::le_audio::uuid::kUnicastGameGatewayCharacteristicUuid,
          .attribute_handle_ = handle};
  GmapServer::GetCharacteristics()[handle] = gmapCharacteristic;

  tGATTS_DATA gatts_data;
  gatts_data.read_req.handle = handle;
  tBTA_GATTS gatts_cb_data;
  gatts_cb_data.req_data.p_data = &gatts_data;

  EXPECT_CALL(gatt_server_interface, SendRsp(_, _, GATT_SUCCESS, _)).Times(1);
  GmapServer::GattsCallback(BTA_GATTS_READ_CHARACTERISTIC_EVT, &gatts_cb_data);
}

TEST_F(GmapServerTest, test_get_UGG_feature_handle) {
  uint16_t handle = 10;
  GmapCharacteristic gmapCharacteristic{
          .uuid_ = bluetooth::le_audio::uuid::kUnicastGameGatewayCharacteristicUuid,
          .attribute_handle_ = handle};
  GmapServer::GetCharacteristics()[handle] = gmapCharacteristic;

  ASSERT_EQ(GmapServer::GetUGGFeatureHandle(), handle);
}

TEST_F(GmapServerTest, test_read_invalid_UGG_feature_handle) {
  uint16_t handle = 10;
  GmapServer::GetCharacteristics().clear();

  ASSERT_NE(GmapServer::GetUGGFeatureHandle(), handle);
}

TEST_F(GmapServerTest, test_get_role_handle) {
  uint16_t handle = 10;
  GmapCharacteristic gmapCharacteristic{.uuid_ = bluetooth::le_audio::uuid::kRoleCharacteristicUuid,
                                        .attribute_handle_ = handle};
  GmapServer::GetCharacteristics()[handle] = gmapCharacteristic;

  ASSERT_EQ(GmapServer::GetRoleHandle(), handle);
}
+17 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@
#include "devices.h"
#include "gatt_api.h"
#include "gmap_client.h"
#include "gmap_server.h"
#include "hci/controller_interface.h"
#include "include/hardware/bt_gmap.h"
#include "internal_include/stack_config.h"
@@ -87,7 +88,9 @@ using bluetooth::le_audio::ContentControlIdKeeper;
using bluetooth::le_audio::DeviceConnectState;
using bluetooth::le_audio::DsaMode;
using bluetooth::le_audio::DsaModes;
using bluetooth::le_audio::GmapCharacteristic;
using bluetooth::le_audio::GmapClient;
using bluetooth::le_audio::GmapServer;
using bluetooth::le_audio::GroupNodeStatus;
using bluetooth::le_audio::GroupStatus;
using bluetooth::le_audio::GroupStreamStatus;
@@ -121,6 +124,7 @@ 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 */
@@ -6152,6 +6156,19 @@ void LeAudioClient::Initialize(
  auto cm = CodecManager::GetInstance();
  callbacks_->OnAudioLocalCodecCapabilities(cm->GetLocalAudioInputCodecCapa(),
                                            cm->GetLocalAudioOutputCodecCapa());

  if (GmapServer::IsGmapServerEnabled()) {
    auto capabilities = cm->GetLocalAudioOutputCodecCapa();
    std::bitset<8> UGG_feature = GmapServer::GetUGGFeature();
    for (auto& capa : capabilities) {
      if (capa.sample_rate == bluetooth::le_audio::LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ) {
        UGG_feature |= static_cast<uint8_t>(
                bluetooth::gmap::UGGFeatureBitMask::NinetySixKbpsSourceFeatureSupport);
        break;
      }
    }
    GmapServer::Initialize(UGG_feature);
  }
}

void LeAudioClient::DebugDump(int fd) {
Loading