1 /* 2 * Copyright 2021 HIMSA II K/S - www.himsa.com. 3 * Represented by EHIMA - www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 #pragma once 19 20 #include <algorithm> 21 #include <cstdint> 22 #include <unordered_set> 23 #include <vector> 24 25 #include "bta/include/bta_gatt_api.h" 26 #include "bta/vc/types.h" 27 #include "common/interfaces/ILoggable.h" 28 #include "os/logging/log_adapter.h" 29 #include "types/raw_address.h" 30 31 namespace bluetooth { 32 namespace vc { 33 namespace internal { 34 35 class VolumeControlDevice : public bluetooth::common::IRedactableLoggable { 36 public: 37 RawAddress address; 38 39 /* We are making active attempt to connect to this device */ 40 bool connecting_actively; 41 42 bool known_service_handles_; 43 44 uint8_t volume; 45 uint8_t change_counter; 46 bool mute; 47 uint8_t flags; 48 49 uint16_t connection_id; 50 51 /* Volume Control Service */ 52 uint16_t volume_state_handle; 53 uint16_t volume_state_ccc_handle; 54 uint16_t volume_control_point_handle; 55 uint16_t volume_flags_handle; 56 uint16_t volume_flags_ccc_handle; 57 58 VolumeOffsets audio_offsets; 59 60 /* Set when device successfully reads server status and registers for 61 * notifications */ 62 bool device_ready; 63 VolumeControlDevice(const RawAddress & address,bool connecting_actively)64 VolumeControlDevice(const RawAddress& address, bool connecting_actively) 65 : address(address), 66 connecting_actively(connecting_actively), 67 known_service_handles_(false), 68 volume(0), 69 change_counter(0), 70 mute(false), 71 flags(0), 72 connection_id(GATT_INVALID_CONN_ID), 73 volume_state_handle(0), 74 volume_state_ccc_handle(0), 75 volume_control_point_handle(0), 76 volume_flags_handle(0), 77 volume_flags_ccc_handle(0), 78 device_ready(false) {} 79 80 ~VolumeControlDevice() = default; 81 82 // TODO: remove ToString()83 inline std::string ToString() { return address.ToString(); } 84 ToStringForLogging()85 std::string ToStringForLogging() const override { 86 return address.ToStringForLogging(); 87 } 88 ToRedactedStringForLogging()89 std::string ToRedactedStringForLogging() const override { 90 return address.ToRedactedStringForLogging(); 91 } 92 DebugDump(int fd)93 void DebugDump(int fd) { 94 std::stringstream stream; 95 stream << " == device address: " << ADDRESS_TO_LOGGABLE_STR(address) 96 << " == \n"; 97 98 if (connection_id == GATT_INVALID_CONN_ID) 99 stream << " Not connected\n"; 100 else 101 stream << " Connected. Conn_id = " << connection_id << "\n"; 102 103 stream << " volume: " << +volume << "\n" 104 << " mute: " << +mute << "\n" 105 << " flags: " << +flags << "\n" 106 << " device read: " << device_ready << "\n" 107 << " connecting_actively: " << connecting_actively << "\n" 108 << " change_counter: " << +change_counter << "\n"; 109 110 dprintf(fd, "%s", stream.str().c_str()); 111 audio_offsets.Dump(fd); 112 } 113 IsConnected()114 bool IsConnected() { return connection_id != GATT_INVALID_CONN_ID; } 115 116 void Disconnect(tGATT_IF gatt_if); 117 118 void DeregisterNotifications(tGATT_IF gatt_if); 119 120 bool UpdateHandles(void); 121 122 void ResetHandles(void); 123 HasHandles(void)124 bool HasHandles(void) { return GATT_HANDLE_IS_VALID(volume_state_handle); } 125 126 void ControlPointOperation(uint8_t opcode, const std::vector<uint8_t>* arg, 127 GATT_WRITE_OP_CB cb, void* cb_data); 128 void GetExtAudioOutVolumeOffset(uint8_t ext_output_id, GATT_READ_OP_CB cb, 129 void* cb_data); 130 void SetExtAudioOutLocation(uint8_t ext_output_id, uint32_t location); 131 void GetExtAudioOutLocation(uint8_t ext_output_id, GATT_READ_OP_CB cb, 132 void* cb_data); 133 void GetExtAudioOutDescription(uint8_t ext_output_id, GATT_READ_OP_CB cb, 134 void* cb_data); 135 void SetExtAudioOutDescription(uint8_t ext_output_id, std::string& descr); 136 void ExtAudioOutControlPointOperation(uint8_t ext_output_id, uint8_t opcode, 137 const std::vector<uint8_t>* arg, 138 GATT_WRITE_OP_CB cb, void* cb_data); 139 bool IsEncryptionEnabled(); 140 141 bool EnableEncryption(); 142 143 bool EnqueueInitialRequests(tGATT_IF gatt_if, GATT_READ_OP_CB chrc_read_cb, 144 GATT_WRITE_OP_CB cccd_write_cb); 145 void EnqueueRemainingRequests(tGATT_IF gatt_if, GATT_READ_OP_CB chrc_read_cb, 146 GATT_WRITE_OP_CB cccd_write_cb); 147 bool VerifyReady(uint16_t handle); IsReady()148 bool IsReady() { return device_ready; } 149 150 private: 151 /* 152 * This is used to track the pending GATT operation handles. Once the list is 153 * empty the device is assumed ready and connected. We are doing it because we 154 * want to make sure all the required characteristics and descritors are 155 * available on server side. 156 */ 157 std::unordered_set<uint16_t> handles_pending; 158 159 uint16_t find_ccc_handle(uint16_t chrc_handle); 160 bool set_volume_control_service_handles(const gatt::Service& service); 161 void set_volume_offset_control_service_handles(const gatt::Service& service); 162 bool subscribe_for_notifications(tGATT_IF gatt_if, uint16_t handle, 163 uint16_t ccc_handle, GATT_WRITE_OP_CB cb); 164 }; 165 166 class VolumeControlDevices { 167 public: Add(const RawAddress & address,bool connecting_actively)168 void Add(const RawAddress& address, bool connecting_actively) { 169 if (FindByAddress(address) != nullptr) return; 170 171 devices_.emplace_back(address, connecting_actively); 172 } 173 Remove(const RawAddress & address)174 void Remove(const RawAddress& address) { 175 for (auto it = devices_.begin(); it != devices_.end(); it++) { 176 if (it->address == address) { 177 it = devices_.erase(it); 178 break; 179 } 180 } 181 } 182 FindByAddress(const RawAddress & address)183 VolumeControlDevice* FindByAddress(const RawAddress& address) { 184 auto iter = std::find_if(devices_.begin(), devices_.end(), 185 [&address](const VolumeControlDevice& device) { 186 return device.address == address; 187 }); 188 189 return (iter == devices_.end()) ? nullptr : &(*iter); 190 } 191 FindByConnId(uint16_t connection_id)192 VolumeControlDevice* FindByConnId(uint16_t connection_id) { 193 auto iter = 194 std::find_if(devices_.begin(), devices_.end(), 195 [&connection_id](const VolumeControlDevice& device) { 196 return device.connection_id == connection_id; 197 }); 198 199 return (iter == devices_.end()) ? nullptr : &(*iter); 200 } 201 Size()202 size_t Size() { return (devices_.size()); } 203 Clear()204 void Clear() { devices_.clear(); } 205 DebugDump(int fd)206 void DebugDump(int fd) { 207 if (devices_.empty()) { 208 dprintf(fd, " No VC devices:\n"); 209 } else { 210 dprintf(fd, " Devices:\n"); 211 for (auto& device : devices_) { 212 device.DebugDump(fd); 213 } 214 } 215 } 216 Disconnect(tGATT_IF gatt_if)217 void Disconnect(tGATT_IF gatt_if) { 218 for (auto& device : devices_) { 219 device.Disconnect(gatt_if); 220 } 221 } 222 ControlPointOperation(std::vector<RawAddress> & devices,uint8_t opcode,const std::vector<uint8_t> * arg,GATT_WRITE_OP_CB cb,void * cb_data)223 void ControlPointOperation(std::vector<RawAddress>& devices, uint8_t opcode, 224 const std::vector<uint8_t>* arg, 225 GATT_WRITE_OP_CB cb, void* cb_data) { 226 for (auto& addr : devices) { 227 VolumeControlDevice* device = FindByAddress(addr); 228 if (device && device->IsConnected()) 229 device->ControlPointOperation(opcode, arg, cb, cb_data); 230 } 231 } 232 233 private: 234 std::vector<VolumeControlDevice> devices_; 235 }; 236 237 } // namespace internal 238 } // namespace vc 239 } // namespace bluetooth 240