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

Commit 9b1e8ec7 authored by Yuyang Huang's avatar Yuyang Huang Committed by Gerrit Code Review
Browse files

Merge "add Gmap Server" into main

parents b5d43ee3 a314bc93
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",
@@ -969,6 +970,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 */
@@ -6157,6 +6161,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