/* * Copyright 2021 HIMSA II K/S - www.himsa.com. * Represented by EHIMA - www.ehima.com * * 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 #include #include #include #include #include #include #include "bta_gatt_api.h" #include "bta_gatt_queue.h" #include "bta_vc_api.h" #include "btif_storage.h" #include "devices.h" using base::Closure; using bluetooth::Uuid; using bluetooth::vc::ConnectionState; using namespace bluetooth::vc::internal; namespace { class VolumeControlImpl; VolumeControlImpl* instance; /** * Overview: * * This is Volume Control Implementation class which realize Volume Control * Profile (VCP) * * Each connected peer device supporting Volume Control Service (VCS) is on the * list of devices (volume_control_devices_). * * Once all the mandatory characteristis for all the services are discovered, * Fluoride calls ON_CONNECTED callback. * * It is assumed that whenever application changes general audio options in this * profile e.g. Volume up/down, mute/unmute etc, profile configures all the * devices which are active Le Audio devices. * * */ class VolumeControlImpl : public VolumeControl { public: ~VolumeControlImpl() override = default; VolumeControlImpl(bluetooth::vc::VolumeControlCallbacks* callbacks) : gatt_if_(0), callbacks_(callbacks) { BTA_GATTC_AppRegister( gattc_callback_static, base::Bind([](uint8_t client_id, uint8_t status) { if (status != GATT_SUCCESS) { LOG(ERROR) << "Can't start Volume Control profile - no gatt " "clients left!"; return; } instance->gatt_if_ = client_id; }), true); } void Connect(const RawAddress& address) override { LOG(INFO) << __func__ << " " << address; auto device = volume_control_devices_.FindByAddress(address); if (!device) { volume_control_devices_.Add(address, true); } else { device->connecting_actively = true; } BTA_GATTC_Open(gatt_if_, address, true, false); } void AddFromStorage(const RawAddress& address, bool auto_connect) { LOG(INFO) << __func__ << " " << address << ", auto_connect=" << auto_connect; if (auto_connect) { volume_control_devices_.Add(address, false); /* Add device into BG connection to accept remote initiated connection */ BTA_GATTC_Open(gatt_if_, address, false, false); } } void OnGattConnected(tGATT_STATUS status, uint16_t connection_id, tGATT_IF /*client_if*/, RawAddress address, tBT_TRANSPORT /*transport*/, uint16_t /*mtu*/) { LOG(INFO) << __func__ << ": address=" << address << ", connection_id=" << connection_id; VolumeControlDevice* device = volume_control_devices_.FindByAddress(address); if (!device) { LOG(ERROR) << __func__ << "Skipping unknown device, address=" << address; return; } if (status != GATT_SUCCESS) { LOG(INFO) << "Failed to connect to Volume Control device"; device_cleanup_helper(device, device->connecting_actively); return; } device->connection_id = connection_id; if (device->IsEncryptionEnabled()) { OnEncryptionComplete(address, BTM_SUCCESS); return; } if (!device->EnableEncryption(enc_callback_static)) { device_cleanup_helper(device, device->connecting_actively); } } void OnEncryptionComplete(const RawAddress& address, uint8_t success) { VolumeControlDevice* device = volume_control_devices_.FindByAddress(address); if (!device) { LOG(ERROR) << __func__ << "Skipping unknown device" << address; return; } if (success != BTM_SUCCESS) { LOG(ERROR) << "encryption failed " << "status: " << int{success}; // If the encryption failed, do not remove the device. // Disconnect only, since the Android will try to re-enable encryption // after disconnection device->Disconnect(gatt_if_); if (device->connecting_actively) callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, device->address); return; } LOG(INFO) << __func__ << " " << address << "status: " << success; if (device->HasHandles()) { device->EnqueueInitialRequests(gatt_if_, chrc_read_callback_static, OnGattWriteCccStatic); } else { device->first_connection = true; BTA_GATTC_ServiceSearchRequest(device->connection_id, &kVolumeControlUuid); } } void OnServiceChangeEvent(const RawAddress& address) { VolumeControlDevice* device = volume_control_devices_.FindByAddress(address); if (!device) { LOG(ERROR) << __func__ << "Skipping unknown device" << address; return; } LOG(INFO) << __func__ << ": address=" << address; device->first_connection = true; device->service_changed_rcvd = true; BtaGattQueue::Clean(device->connection_id); } void OnServiceDiscDoneEvent(const RawAddress& address) { VolumeControlDevice* device = volume_control_devices_.FindByAddress(address); if (!device) { LOG(ERROR) << __func__ << "Skipping unknown device" << address; return; } if (device->service_changed_rcvd) BTA_GATTC_ServiceSearchRequest(device->connection_id, &kVolumeControlUuid); } void OnServiceSearchComplete(uint16_t connection_id, tGATT_STATUS status) { VolumeControlDevice* device = volume_control_devices_.FindByConnId(connection_id); if (!device) { LOG(ERROR) << __func__ << "Skipping unknown device, connection_id=" << loghex(connection_id); return; } /* Known device, nothing to do */ if (!device->first_connection) return; if (status != GATT_SUCCESS) { /* close connection and report service discovery complete with error */ LOG(ERROR) << "Service discovery failed"; device_cleanup_helper(device, device->first_connection); return; } bool success = device->UpdateHandles(); if (!success) { LOG(ERROR) << "Incomplete service database"; device_cleanup_helper(device, true); return; } device->EnqueueInitialRequests(gatt_if_, chrc_read_callback_static, OnGattWriteCccStatic); } void OnCharacteristicValueChanged(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, uint8_t* value, void* /* data */) { VolumeControlDevice* device = volume_control_devices_.FindByConnId(conn_id); if (!device) { LOG(INFO) << __func__ << ": unknown conn_id=" << loghex(conn_id); return; } if (status != GATT_SUCCESS) { LOG(INFO) << __func__ << ": status=" << static_cast(status); return; } if (handle == device->volume_state_handle) { OnVolumeControlStateChanged(device, len, value); verify_device_ready(device, handle); return; } if (handle == device->volume_flags_handle) { OnVolumeControlFlagsChanged(device, len, value); verify_device_ready(device, handle); return; } LOG(ERROR) << __func__ << ": unknown handle=" << loghex(handle); } void OnNotificationEvent(uint16_t conn_id, uint16_t handle, uint16_t len, uint8_t* value) { LOG(INFO) << __func__ << ": handle=" << loghex(handle); OnCharacteristicValueChanged(conn_id, GATT_SUCCESS, handle, len, value, nullptr); } void VolumeControlReadCommon(uint16_t conn_id, uint16_t handle) { BtaGattQueue::ReadCharacteristic(conn_id, handle, chrc_read_callback_static, nullptr); } void OnVolumeControlStateChanged(VolumeControlDevice* device, uint16_t len, uint8_t* value) { if (len != 3) { LOG(INFO) << __func__ << ": malformed len=" << loghex(len); return; } uint8_t* pp = value; STREAM_TO_UINT8(device->volume, pp); STREAM_TO_UINT8(device->mute, pp); STREAM_TO_UINT8(device->change_counter, pp); LOG(INFO) << __func__ << " " << base::HexEncode(value, len); LOG(INFO) << __func__ << "volume " << loghex(device->volume) << "mute" << loghex(device->mute) << "change_counter" << loghex(device->change_counter); if (!device->device_ready) return; callbacks_->OnVolumeStateChanged(device->address, device->volume, device->mute); } void OnVolumeControlFlagsChanged(VolumeControlDevice* device, uint16_t len, uint8_t* value) { device->flags = *value; LOG(INFO) << __func__ << " " << base::HexEncode(value, len); LOG(INFO) << __func__ << "flags " << loghex(device->flags); } void OnGattWriteCcc(uint16_t connection_id, tGATT_STATUS status, uint16_t handle, void* /*data*/) { VolumeControlDevice* device = volume_control_devices_.FindByConnId(connection_id); if (!device) { LOG(INFO) << __func__ << "unknown connection_id=" << loghex(connection_id); BtaGattQueue::Clean(connection_id); return; } if (status != GATT_SUCCESS) { LOG(ERROR) << __func__ << "Failed to register for notification: " << loghex(handle) << " status: " << status; device_cleanup_helper(device, true); return; } LOG(INFO) << __func__ << "Successfully register for indications: " << loghex(handle); verify_device_ready(device, handle); } static void OnGattWriteCccStatic(uint16_t connection_id, tGATT_STATUS status, uint16_t handle, void* data) { if (!instance) { LOG(ERROR) << __func__ << "No instance=" << handle; return; } instance->OnGattWriteCcc(connection_id, status, handle, data); } void Dump(int fd) { volume_control_devices_.DebugDump(fd); } void Disconnect(const RawAddress& address) override { VolumeControlDevice* device = volume_control_devices_.FindByAddress(address); if (!device) { LOG(INFO) << "Device not connected to profile" << address; return; } LOG(INFO) << __func__ << ": " << address; LOG(INFO) << "GAP_EVT_CONN_CLOSED: " << device->address; device_cleanup_helper(device, true); } void OnGattDisconnected(uint16_t connection_id, tGATT_IF /*client_if*/, RawAddress remote_bda, tGATT_DISCONN_REASON /*reason*/) { VolumeControlDevice* device = volume_control_devices_.FindByConnId(connection_id); if (!device) { LOG(ERROR) << __func__ << " Skipping unknown device disconnect, connection_id=" << loghex(connection_id); return; } // If we get here, it means, device has not been exlicitly disconnected. bool device_ready = device->device_ready; device_cleanup_helper(device, device->connecting_actively); if (device_ready) { volume_control_devices_.Add(remote_bda, true); /* Add device into BG connection to accept remote initiated connection */ BTA_GATTC_Open(gatt_if_, remote_bda, false, false); } } void OnWriteControlResponse(uint16_t connection_id, tGATT_STATUS status, uint16_t handle, void* /*data*/) { VolumeControlDevice* device = volume_control_devices_.FindByConnId(connection_id); if (!device) { LOG(ERROR) << __func__ << "Skipping unknown device disconnect, connection_id=" << loghex(connection_id); return; } LOG(INFO) << "Write response handle: " << loghex(handle) << " status: " << loghex((int)(status)); } void SetVolume(std::variant addr_or_group_id, uint8_t volume) override { LOG(INFO) << __func__ << "vol: " << +volume; if (std::holds_alternative(addr_or_group_id)) { std::vector devices = { std::get(addr_or_group_id)}; std::vector arg({volume}); devices_control_point_helper(devices, kControlPointOpcodeSetAbsoluteVolume, &arg); return; } /* TODO implement handling group request */ } void CleanUp() { LOG(INFO) << __func__; volume_control_devices_.Disconnect(gatt_if_); volume_control_devices_.Clear(); BTA_GATTC_AppDeregister(gatt_if_); } private: tGATT_IF gatt_if_; bluetooth::vc::VolumeControlCallbacks* callbacks_; VolumeControlDevices volume_control_devices_; void verify_device_ready(VolumeControlDevice* device, uint16_t handle) { if (device->device_ready) return; // VerifyReady sets the device_ready flag if all remaining GATT operations // are completed if (device->VerifyReady(handle)) { LOG(INFO) << __func__ << "Outstanding reads completed "; callbacks_->OnConnectionState(ConnectionState::CONNECTED, device->address); device->connecting_actively = true; device->first_connection = false; // once profile connected we can notify current states callbacks_->OnVolumeStateChanged(device->address, device->volume, device->mute); device->EnqueueRemainingRequests(gatt_if_, chrc_read_callback_static, OnGattWriteCccStatic); } } void device_cleanup_helper(VolumeControlDevice* device, bool notify) { device->Disconnect(gatt_if_); if (notify) callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, device->address); volume_control_devices_.Remove(device->address); } void devices_control_point_helper(std::vector& devices, uint8_t opcode, const std::vector* arg) { volume_control_devices_.ControlPointOperation( devices, opcode, arg, [](uint16_t connection_id, tGATT_STATUS status, uint16_t handle, void* data) { if (instance) instance->OnWriteControlResponse(connection_id, status, handle, data); }, nullptr); } void gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { LOG(INFO) << __func__ << " event = " << static_cast(event); if (p_data == nullptr) return; switch (event) { case BTA_GATTC_OPEN_EVT: { tBTA_GATTC_OPEN& o = p_data->open; OnGattConnected(o.status, o.conn_id, o.client_if, o.remote_bda, o.transport, o.mtu); } break; case BTA_GATTC_CLOSE_EVT: { tBTA_GATTC_CLOSE& c = p_data->close; OnGattDisconnected(c.conn_id, c.client_if, c.remote_bda, c.reason); } break; case BTA_GATTC_SEARCH_CMPL_EVT: OnServiceSearchComplete(p_data->search_cmpl.conn_id, p_data->search_cmpl.status); break; case BTA_GATTC_NOTIF_EVT: { tBTA_GATTC_NOTIFY& n = p_data->notify; if (!n.is_notify || n.len > GATT_MAX_ATTR_LEN) { LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify=" << n.is_notify << ", len=" << static_cast(n.len); break; } OnNotificationEvent(n.conn_id, n.handle, n.len, n.value); } break; case BTA_GATTC_ENC_CMPL_CB_EVT: OnEncryptionComplete(p_data->enc_cmpl.remote_bda, true); break; case BTA_GATTC_SRVC_CHG_EVT: OnServiceChangeEvent(p_data->remote_bda); break; case BTA_GATTC_SRVC_DISC_DONE_EVT: OnServiceDiscDoneEvent(p_data->remote_bda); break; default: break; } } static void gattc_callback_static(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { if (instance) instance->gattc_callback(event, p_data); } static void enc_callback_static(const RawAddress* address, tBT_TRANSPORT, void*, tBTM_STATUS status) { if (instance) instance->OnEncryptionComplete(*address, status); } static void chrc_read_callback_static(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, uint8_t* value, void* data) { if (instance) instance->OnCharacteristicValueChanged(conn_id, status, handle, len, value, data); } }; } // namespace void VolumeControl::Initialize( bluetooth::vc::VolumeControlCallbacks* callbacks) { if (instance) { LOG(ERROR) << "Already initialized!"; return; } instance = new VolumeControlImpl(callbacks); } bool VolumeControl::IsVolumeControlRunning() { return instance; } VolumeControl* VolumeControl::Get(void) { CHECK(instance); return instance; }; void VolumeControl::AddFromStorage(const RawAddress& address, bool auto_connect) { if (!instance) { LOG(ERROR) << "Not initialized yet"; return; } instance->AddFromStorage(address, auto_connect); }; void VolumeControl::CleanUp() { if (!instance) { LOG(ERROR) << "not initialized!"; return; } VolumeControlImpl* ptr = instance; instance = nullptr; ptr->CleanUp(); delete ptr; }; void VolumeControl::DebugDump(int fd) { dprintf(fd, "Volume Control Manager:\n"); if (instance) instance->Dump(fd); dprintf(fd, "\n"); }