/* * 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. */ #pragma once #include #include #include #include #include "bta/include/bta_gatt_api.h" #include "bta/vc/types.h" #include "include/hardware/bt_vc.h" #include "types/raw_address.h" namespace bluetooth { namespace vc { namespace internal { class VolumeControlDevice { public: RawAddress address; /* This is true only during first connection to profile, until we store the * device */ bool first_connection; /* we are making active attempt to connect to this device, 'direct connect'. * This is true only during initial phase of first connection. */ bool connecting_actively; bool service_changed_rcvd; uint8_t volume; uint8_t change_counter; bool mute; uint8_t flags; uint16_t connection_id; /* Volume Control Service */ uint16_t volume_state_handle; uint16_t volume_state_ccc_handle; uint16_t volume_control_point_handle; uint16_t volume_flags_handle; uint16_t volume_flags_ccc_handle; VolumeOffsets audio_offsets; bool device_ready; /* Set when device read server status and registgered for notifications */ VolumeControlDevice(const RawAddress& address, bool first_connection) : address(address), first_connection(first_connection), connecting_actively(first_connection), service_changed_rcvd(false), volume(0), change_counter(0), mute(false), flags(0), connection_id(GATT_INVALID_CONN_ID), volume_state_handle(0), volume_state_ccc_handle(0), volume_control_point_handle(0), volume_flags_handle(0), volume_flags_ccc_handle(0), device_ready(false) {} ~VolumeControlDevice() = default; inline std::string ToString() { return address.ToString(); } void DebugDump(int fd) { std::stringstream stream; stream << " == device address: " << address << " == \n"; if (connection_id == GATT_INVALID_CONN_ID) stream << " Not connected\n"; else stream << " Connected. Conn_id = " << connection_id << "\n"; stream << " volume: " << +volume << "\n" << " mute: " << +mute << "\n" << " flags: " << +flags << "\n" << " device read: " << device_ready << "\n" << " first_connection_: " << first_connection << "\n" << " connecting_actively_: " << connecting_actively << "\n"; dprintf(fd, "%s", stream.str().c_str()); audio_offsets.Dump(fd); } bool IsConnected() { return connection_id != GATT_INVALID_CONN_ID; } void Disconnect(tGATT_IF gatt_if); void DeregisterNotifications(tGATT_IF gatt_if); bool UpdateHandles(void); void ResetHandles(void); bool HasHandles(void) { return GATT_HANDLE_IS_VALID(volume_state_handle); } void ControlPointOperation(uint8_t opcode, const std::vector* arg, GATT_WRITE_OP_CB cb, void* cb_data); void GetExtAudioOutVolumeOffset(uint8_t ext_output_id, GATT_READ_OP_CB cb, void* cb_data); void SetExtAudioOutLocation(uint8_t ext_output_id, uint32_t location); void GetExtAudioOutLocation(uint8_t ext_output_id, GATT_READ_OP_CB cb, void* cb_data); void GetExtAudioOutDescription(uint8_t ext_output_id, GATT_READ_OP_CB cb, void* cb_data); void SetExtAudioOutDescription(uint8_t ext_output_id, std::string& descr); void ExtAudioOutControlPointOperation(uint8_t ext_output_id, uint8_t opcode, const std::vector* arg, GATT_WRITE_OP_CB cb, void* cb_data); bool IsEncryptionEnabled(); void EnableEncryption(); bool EnqueueInitialRequests(tGATT_IF gatt_if, GATT_READ_OP_CB chrc_read_cb, GATT_WRITE_OP_CB cccd_write_cb); void EnqueueRemainingRequests(tGATT_IF gatt_if, GATT_READ_OP_CB chrc_read_cb, GATT_WRITE_OP_CB cccd_write_cb); bool VerifyReady(uint16_t handle); bool IsReady() { return device_ready; } private: /* * This is used to track the pending GATT operation handles. Once the list is * empty the device is assumed ready and connected. We are doing it because we * want to make sure all the required characteristics and descritors are * available on server side. */ std::unordered_set handles_pending; uint16_t find_ccc_handle(uint16_t chrc_handle); bool set_volume_control_service_handles(const gatt::Service& service); void set_volume_offset_control_service_handles(const gatt::Service& service); bool subscribe_for_notifications(tGATT_IF gatt_if, uint16_t handle, uint16_t ccc_handle, GATT_WRITE_OP_CB cb); }; class VolumeControlDevices { public: void Add(const RawAddress& address, bool first_connection) { if (FindByAddress(address) != nullptr) return; devices_.emplace_back(address, first_connection); } void Remove(const RawAddress& address) { for (auto it = devices_.begin(); it != devices_.end(); it++) { if (it->address == address) { it = devices_.erase(it); break; } } } VolumeControlDevice* FindByAddress(const RawAddress& address) { auto iter = std::find_if(devices_.begin(), devices_.end(), [&address](const VolumeControlDevice& device) { return device.address == address; }); return (iter == devices_.end()) ? nullptr : &(*iter); } VolumeControlDevice* FindByConnId(uint16_t connection_id) { auto iter = std::find_if(devices_.begin(), devices_.end(), [&connection_id](const VolumeControlDevice& device) { return device.connection_id == connection_id; }); return (iter == devices_.end()) ? nullptr : &(*iter); } size_t Size() { return (devices_.size()); } void Clear() { devices_.clear(); } void DebugDump(int fd) { if (devices_.empty()) { dprintf(fd, " No VC devices:\n"); } else { dprintf(fd, " Devices:\n"); for (auto& device : devices_) { device.DebugDump(fd); } } } void Disconnect(tGATT_IF gatt_if) { for (auto& device : devices_) { device.Disconnect(gatt_if); } } void ControlPointOperation(std::vector& devices, uint8_t opcode, const std::vector* arg, GATT_WRITE_OP_CB cb, void* cb_data) { for (auto& addr : devices) { VolumeControlDevice* device = FindByAddress(addr); if (device && device->IsConnected()) device->ControlPointOperation(opcode, arg, cb, cb_data); } } private: std::vector devices_; }; } // namespace internal } // namespace vc } // namespace bluetooth