/*
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "bta/le_audio/le_audio_types.h"
#include "bta_gatt_api.h"
#include "bta_gatt_queue.h"
#include "gatt_api.h"
#include "hardware/bt_common_types.h"
#include "include/hardware/bt_gmap.h"
#include "osi/include/properties.h"
#include "types/bluetooth/uuid.h"
#include "types/bt_transport.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 GmapServer::characteristics_ =
std::unordered_map();
// 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(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 &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 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 service) {
OnServiceAdded(status, server_if, service);
}));
}
void GmapServer::OnServiceAdded(tGATT_STATUS status, int server_if,
std::vector 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(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(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);
}