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 #include <base/bind.h>
19 #include <base/logging.h>
20 #include <base/strings/string_number_conversions.h>
21 #include <base/strings/string_util.h>
22 #include <hardware/bt_vc.h>
23
24 #include <string>
25 #include <vector>
26
27 #include "bind_helpers.h"
28 #include "bta/le_audio/le_audio_types.h"
29 #include "bta_csis_api.h"
30 #include "bta_gatt_api.h"
31 #include "bta_gatt_queue.h"
32 #include "bta_vc_api.h"
33 #include "btif_storage.h"
34 #include "devices.h"
35 #include "gd/common/strings.h"
36 #include "osi/include/log.h"
37 #include "osi/include/osi.h"
38 #include "stack/btm/btm_sec.h"
39 #include "types/bluetooth/uuid.h"
40 #include "types/raw_address.h"
41
42 using base::Closure;
43 using bluetooth::Uuid;
44 using bluetooth::csis::CsisClient;
45 using bluetooth::vc::ConnectionState;
46 using namespace bluetooth::vc::internal;
47
48 namespace {
49 class VolumeControlImpl;
50 VolumeControlImpl* instance;
51
52 /**
53 * Overview:
54 *
55 * This is Volume Control Implementation class which realize Volume Control
56 * Profile (VCP)
57 *
58 * Each connected peer device supporting Volume Control Service (VCS) is on the
59 * list of devices (volume_control_devices_). When VCS is discovered on the peer
60 * device, Android does search for all the instances Volume Offset Service
61 * (VOCS). Note that AIS and VOCS are optional.
62 *
63 * Once all the mandatory characteristis for all the services are discovered,
64 * Fluoride calls ON_CONNECTED callback.
65 *
66 * It is assumed that whenever application changes general audio options in this
67 * profile e.g. Volume up/down, mute/unmute etc, profile configures all the
68 * devices which are active Le Audio devices.
69 *
70 * Peer devices has at maximum one instance of VCS and 0 or more instance of
71 * VOCS. Android gets access to External Audio Outputs using appropriate ID.
72 * Also each of the External Device has description
73 * characteristic and Type which gives the application hint what it is a device.
74 * Examples of such devices:
75 * External Output: 1 instance to controller ballance between set of devices
76 * External Output: each of 5.1 speaker set etc.
77 */
78 class VolumeControlImpl : public VolumeControl {
79 public:
80 ~VolumeControlImpl() override = default;
81
VolumeControlImpl(bluetooth::vc::VolumeControlCallbacks * callbacks)82 VolumeControlImpl(bluetooth::vc::VolumeControlCallbacks* callbacks)
83 : gatt_if_(0), callbacks_(callbacks), latest_operation_id_(0) {
84 BTA_GATTC_AppRegister(
85 gattc_callback_static,
86 base::Bind([](uint8_t client_id, uint8_t status) {
87 if (status != GATT_SUCCESS) {
88 LOG(ERROR) << "Can't start Volume Control profile - no gatt "
89 "clients left!";
90 return;
91 }
92 instance->gatt_if_ = client_id;
93 }),
94 true);
95 }
96
Connect(const RawAddress & address)97 void Connect(const RawAddress& address) override {
98 LOG(INFO) << __func__ << " " << address;
99
100 auto device = volume_control_devices_.FindByAddress(address);
101 if (!device) {
102 volume_control_devices_.Add(address, true);
103 } else {
104 device->connecting_actively = true;
105
106 if (device->IsConnected()) {
107 LOG(WARNING) << __func__ << ": address=" << address
108 << ", connection_id=" << device->connection_id
109 << " already connected.";
110
111 if (device->IsReady()) {
112 callbacks_->OnConnectionState(ConnectionState::CONNECTED,
113 device->address);
114 } else {
115 OnGattConnected(GATT_SUCCESS, device->connection_id, gatt_if_,
116 device->address, BT_TRANSPORT_LE, GATT_MAX_MTU_SIZE);
117 }
118 return;
119 }
120 }
121
122 BTA_GATTC_Open(gatt_if_, address, BTM_BLE_DIRECT_CONNECTION, false);
123 }
124
AddFromStorage(const RawAddress & address,bool auto_connect)125 void AddFromStorage(const RawAddress& address, bool auto_connect) {
126 LOG(INFO) << __func__ << " " << address
127 << ", auto_connect=" << auto_connect;
128
129 if (auto_connect) {
130 volume_control_devices_.Add(address, false);
131
132 /* Add device into BG connection to accept remote initiated connection */
133 BTA_GATTC_Open(gatt_if_, address, BTM_BLE_BKG_CONNECT_ALLOW_LIST, false);
134 }
135 }
136
OnGattConnected(tGATT_STATUS status,uint16_t connection_id,tGATT_IF,RawAddress address,tBT_TRANSPORT,uint16_t)137 void OnGattConnected(tGATT_STATUS status, uint16_t connection_id,
138 tGATT_IF /*client_if*/, RawAddress address,
139 tBT_TRANSPORT /*transport*/, uint16_t /*mtu*/) {
140 LOG(INFO) << __func__ << ": address=" << address
141 << ", connection_id=" << connection_id;
142
143 VolumeControlDevice* device =
144 volume_control_devices_.FindByAddress(address);
145 if (!device) {
146 LOG(ERROR) << __func__ << "Skipping unknown device, address=" << address;
147 return;
148 }
149
150 if (status != GATT_SUCCESS) {
151 LOG(INFO) << "Failed to connect to Volume Control device";
152 device_cleanup_helper(device, device->connecting_actively);
153 return;
154 }
155
156 device->connection_id = connection_id;
157
158 if (device->IsEncryptionEnabled()) {
159 OnEncryptionComplete(address, BTM_SUCCESS);
160 return;
161 }
162
163 device->EnableEncryption();
164 }
165
OnEncryptionComplete(const RawAddress & address,uint8_t success)166 void OnEncryptionComplete(const RawAddress& address, uint8_t success) {
167 VolumeControlDevice* device =
168 volume_control_devices_.FindByAddress(address);
169 if (!device) {
170 LOG(ERROR) << __func__ << "Skipping unknown device " << address;
171 return;
172 }
173
174 if (success != BTM_SUCCESS) {
175 LOG(ERROR) << "encryption failed "
176 << "status: " << int{success};
177 // If the encryption failed, do not remove the device.
178 // Disconnect only, since the Android will try to re-enable encryption
179 // after disconnection
180 device->Disconnect(gatt_if_);
181 if (device->connecting_actively)
182 callbacks_->OnConnectionState(ConnectionState::DISCONNECTED,
183 device->address);
184 return;
185 }
186
187 LOG(INFO) << __func__ << " " << address << " status: " << +success;
188
189 if (device->HasHandles()) {
190 device->EnqueueInitialRequests(gatt_if_, chrc_read_callback_static,
191 OnGattWriteCccStatic);
192
193 } else {
194 device->first_connection = true;
195 BTA_GATTC_ServiceSearchRequest(device->connection_id,
196 &kVolumeControlUuid);
197 }
198 }
199
ClearDeviceInformationAndStartSearch(VolumeControlDevice * device)200 void ClearDeviceInformationAndStartSearch(VolumeControlDevice* device) {
201 if (!device) {
202 LOG_ERROR("Device is null");
203 return;
204 }
205
206 LOG_INFO(": address=%s", device->address.ToString().c_str());
207 if (device->service_changed_rcvd) {
208 LOG_INFO("Device already is waiting for new services");
209 return;
210 }
211
212 std::vector<RawAddress> devices = {device->address};
213 device->DeregisterNotifications(gatt_if_);
214
215 RemovePendingVolumeControlOperations(devices,
216 bluetooth::groups::kGroupUnknown);
217 device->first_connection = true;
218 device->service_changed_rcvd = true;
219 BtaGattQueue::Clean(device->connection_id);
220 BTA_GATTC_ServiceSearchRequest(device->connection_id, &kVolumeControlUuid);
221 }
222
OnServiceChangeEvent(const RawAddress & address)223 void OnServiceChangeEvent(const RawAddress& address) {
224 VolumeControlDevice* device =
225 volume_control_devices_.FindByAddress(address);
226 if (!device) {
227 LOG(ERROR) << __func__ << "Skipping unknown device " << address;
228 return;
229 }
230
231 ClearDeviceInformationAndStartSearch(device);
232 }
233
OnServiceDiscDoneEvent(const RawAddress & address)234 void OnServiceDiscDoneEvent(const RawAddress& address) {
235 VolumeControlDevice* device =
236 volume_control_devices_.FindByAddress(address);
237 if (!device) {
238 LOG(ERROR) << __func__ << "Skipping unknown device " << address;
239 return;
240 }
241
242 if (device->service_changed_rcvd)
243 BTA_GATTC_ServiceSearchRequest(device->connection_id,
244 &kVolumeControlUuid);
245 }
246
OnServiceSearchComplete(uint16_t connection_id,tGATT_STATUS status)247 void OnServiceSearchComplete(uint16_t connection_id, tGATT_STATUS status) {
248 VolumeControlDevice* device =
249 volume_control_devices_.FindByConnId(connection_id);
250 if (!device) {
251 LOG(ERROR) << __func__ << "Skipping unknown device, connection_id="
252 << loghex(connection_id);
253 return;
254 }
255
256 /* Known device, nothing to do */
257 if (!device->first_connection) return;
258
259 if (status != GATT_SUCCESS) {
260 /* close connection and report service discovery complete with error */
261 LOG(ERROR) << "Service discovery failed";
262 device_cleanup_helper(device, device->first_connection);
263 return;
264 }
265
266 bool success = device->UpdateHandles();
267 if (!success) {
268 LOG(ERROR) << "Incomplete service database";
269 device_cleanup_helper(device, true);
270 return;
271 }
272
273 device->EnqueueInitialRequests(gatt_if_, chrc_read_callback_static,
274 OnGattWriteCccStatic);
275 }
276
OnCharacteristicValueChanged(uint16_t conn_id,tGATT_STATUS status,uint16_t handle,uint16_t len,uint8_t * value,void * data,bool is_notification)277 void OnCharacteristicValueChanged(uint16_t conn_id, tGATT_STATUS status,
278 uint16_t handle, uint16_t len,
279 uint8_t* value, void* data,
280 bool is_notification) {
281 VolumeControlDevice* device = volume_control_devices_.FindByConnId(conn_id);
282 if (!device) {
283 LOG(INFO) << __func__ << ": unknown conn_id=" << loghex(conn_id);
284 return;
285 }
286
287 if (status != GATT_SUCCESS) {
288 LOG_INFO(": status=0x%02x", static_cast<int>(status));
289 if (status == GATT_DATABASE_OUT_OF_SYNC) {
290 LOG_INFO("Database out of sync for %s",
291 device->address.ToString().c_str());
292 ClearDeviceInformationAndStartSearch(device);
293 }
294 return;
295 }
296
297 if (handle == device->volume_state_handle) {
298 OnVolumeControlStateReadOrNotified(device, len, value, is_notification);
299 verify_device_ready(device, handle);
300 return;
301 }
302 if (handle == device->volume_flags_handle) {
303 OnVolumeControlFlagsChanged(device, len, value);
304 verify_device_ready(device, handle);
305 return;
306 }
307
308 const gatt::Service* service = BTA_GATTC_GetOwningService(conn_id, handle);
309 if (service == nullptr) return;
310
311 VolumeOffset* offset =
312 device->audio_offsets.FindByServiceHandle(service->handle);
313 if (offset != nullptr) {
314 if (handle == offset->state_handle) {
315 OnExtAudioOutStateChanged(device, offset, len, value);
316 } else if (handle == offset->audio_location_handle) {
317 OnExtAudioOutLocationChanged(device, offset, len, value);
318 } else if (handle == offset->audio_descr_handle) {
319 OnOffsetOutputDescChanged(device, offset, len, value);
320 } else {
321 LOG(ERROR) << __func__ << ": unknown offset handle=" << loghex(handle);
322 return;
323 }
324
325 verify_device_ready(device, handle);
326 return;
327 }
328
329 LOG(ERROR) << __func__ << ": unknown handle=" << loghex(handle);
330 }
331
OnNotificationEvent(uint16_t conn_id,uint16_t handle,uint16_t len,uint8_t * value)332 void OnNotificationEvent(uint16_t conn_id, uint16_t handle, uint16_t len,
333 uint8_t* value) {
334 LOG(INFO) << __func__ << ": handle=" << loghex(handle);
335 OnCharacteristicValueChanged(conn_id, GATT_SUCCESS, handle, len, value,
336 nullptr, true);
337 }
338
VolumeControlReadCommon(uint16_t conn_id,uint16_t handle)339 void VolumeControlReadCommon(uint16_t conn_id, uint16_t handle) {
340 BtaGattQueue::ReadCharacteristic(conn_id, handle, chrc_read_callback_static,
341 nullptr);
342 }
343
HandleAutonomusVolumeChange(VolumeControlDevice * device,bool is_volume_change,bool is_mute_change)344 void HandleAutonomusVolumeChange(VolumeControlDevice* device,
345 bool is_volume_change, bool is_mute_change) {
346 DLOG(INFO) << __func__ << device->address
347 << " is volume change: " << is_volume_change
348 << " is mute change: " << is_mute_change;
349
350 if (!is_volume_change && !is_mute_change) {
351 LOG(ERROR) << __func__
352 << "Autonomous change but volume and mute did not changed.";
353 return;
354 }
355
356 auto csis_api = CsisClient::Get();
357 if (!csis_api) {
358 DLOG(INFO) << __func__ << " Csis is not available";
359 callbacks_->OnVolumeStateChanged(device->address, device->volume,
360 device->mute, true);
361 return;
362 }
363
364 auto group_id =
365 csis_api->GetGroupId(device->address, le_audio::uuid::kCapServiceUuid);
366 if (group_id == bluetooth::groups::kGroupUnknown) {
367 DLOG(INFO) << __func__ << " No group for device " << device->address;
368 callbacks_->OnVolumeStateChanged(device->address, device->volume,
369 device->mute, true);
370 return;
371 }
372
373 auto devices = csis_api->GetDeviceList(group_id);
374 for (auto it = devices.begin(); it != devices.end();) {
375 auto dev = volume_control_devices_.FindByAddress(*it);
376 if (!dev || !dev->IsConnected() || (dev->address == device->address)) {
377 it = devices.erase(it);
378 } else {
379 it++;
380 }
381 }
382
383 if (devices.empty() && (is_volume_change || is_mute_change)) {
384 LOG_INFO("No more devices in the group right now");
385 callbacks_->OnGroupVolumeStateChanged(group_id, device->volume,
386 device->mute, true);
387 return;
388 }
389
390 if (is_volume_change) {
391 std::vector<uint8_t> arg({device->volume});
392 PrepareVolumeControlOperation(devices, group_id, true,
393 kControlPointOpcodeSetAbsoluteVolume, arg);
394 }
395
396 if (is_mute_change) {
397 std::vector<uint8_t> arg;
398 uint8_t opcode =
399 device->mute ? kControlPointOpcodeMute : kControlPointOpcodeUnmute;
400 PrepareVolumeControlOperation(devices, group_id, true, opcode, arg);
401 }
402
403 StartQueueOperation();
404 }
405
OnVolumeControlStateReadOrNotified(VolumeControlDevice * device,uint16_t len,uint8_t * value,bool is_notification)406 void OnVolumeControlStateReadOrNotified(VolumeControlDevice* device,
407 uint16_t len, uint8_t* value,
408 bool is_notification) {
409 if (len != 3) {
410 LOG(INFO) << __func__ << ": malformed len=" << loghex(len);
411 return;
412 }
413
414 uint8_t vol;
415 uint8_t mute;
416 uint8_t* pp = value;
417 STREAM_TO_UINT8(vol, pp);
418 STREAM_TO_UINT8(mute, pp);
419 STREAM_TO_UINT8(device->change_counter, pp);
420
421 bool is_volume_change = (device->volume != vol);
422 device->volume = vol;
423
424 bool is_mute_change = (device->mute != mute);
425 device->mute = mute;
426
427 LOG(INFO) << __func__ << " volume " << loghex(device->volume) << " mute "
428 << loghex(device->mute) << " change_counter "
429 << loghex(device->change_counter);
430
431 if (!device->IsReady()) {
432 LOG_INFO("Device: %s is not ready yet.",
433 device->address.ToString().c_str());
434 return;
435 }
436
437 /* This is just a read, send single notification */
438 if (!is_notification) {
439 callbacks_->OnVolumeStateChanged(device->address, device->volume,
440 device->mute, false);
441 return;
442 }
443
444 auto addr = device->address;
445 auto op = find_if(ongoing_operations_.begin(), ongoing_operations_.end(),
446 [addr](auto& operation) {
447 auto it = find(operation.devices_.begin(),
448 operation.devices_.end(), addr);
449 return it != operation.devices_.end();
450 });
451 if (op == ongoing_operations_.end()) {
452 DLOG(INFO) << __func__ << " Could not find operation id for device: "
453 << device->address << ". Autonomus change";
454 HandleAutonomusVolumeChange(device, is_volume_change, is_mute_change);
455 return;
456 }
457
458 DLOG(INFO) << __func__ << " operation found: " << op->operation_id_
459 << " for group id: " << op->group_id_;
460
461 /* Received notification from the device we do expect */
462 auto it = find(op->devices_.begin(), op->devices_.end(), device->address);
463 op->devices_.erase(it);
464 if (!op->devices_.empty()) {
465 DLOG(INFO) << __func__ << " wait for more responses for operation_id: "
466 << op->operation_id_;
467 return;
468 }
469
470 if (op->IsGroupOperation()) {
471 callbacks_->OnGroupVolumeStateChanged(op->group_id_, device->volume,
472 device->mute, op->is_autonomous_);
473 } else {
474 /* op->is_autonomous_ will always be false,
475 since we only make it true for group operations */
476 callbacks_->OnVolumeStateChanged(device->address, device->volume,
477 device->mute, false);
478 }
479
480 ongoing_operations_.erase(op);
481 StartQueueOperation();
482 }
483
OnVolumeControlFlagsChanged(VolumeControlDevice * device,uint16_t len,uint8_t * value)484 void OnVolumeControlFlagsChanged(VolumeControlDevice* device, uint16_t len,
485 uint8_t* value) {
486 device->flags = *value;
487
488 LOG(INFO) << __func__ << " flags " << loghex(device->flags);
489 }
490
OnExtAudioOutStateChanged(VolumeControlDevice * device,VolumeOffset * offset,uint16_t len,uint8_t * value)491 void OnExtAudioOutStateChanged(VolumeControlDevice* device,
492 VolumeOffset* offset, uint16_t len,
493 uint8_t* value) {
494 if (len != 3) {
495 LOG(INFO) << __func__ << ": malformed len=" << loghex(len);
496 return;
497 }
498
499 uint8_t* pp = value;
500 STREAM_TO_UINT16(offset->offset, pp);
501 STREAM_TO_UINT8(offset->change_counter, pp);
502
503 LOG(INFO) << __func__ << " " << base::HexEncode(value, len);
504 LOG(INFO) << __func__ << " id: " << loghex(offset->id)
505 << " offset: " << loghex(offset->offset)
506 << " counter: " << loghex(offset->change_counter);
507
508 if (!device->IsReady()) {
509 LOG_INFO("Device: %s is not ready yet.",
510 device->address.ToString().c_str());
511 return;
512 }
513
514 callbacks_->OnExtAudioOutVolumeOffsetChanged(device->address, offset->id,
515 offset->offset);
516 }
517
OnExtAudioOutLocationChanged(VolumeControlDevice * device,VolumeOffset * offset,uint16_t len,uint8_t * value)518 void OnExtAudioOutLocationChanged(VolumeControlDevice* device,
519 VolumeOffset* offset, uint16_t len,
520 uint8_t* value) {
521 if (len != 4) {
522 LOG(INFO) << __func__ << ": malformed len=" << loghex(len);
523 return;
524 }
525
526 uint8_t* pp = value;
527 STREAM_TO_UINT32(offset->location, pp);
528
529 LOG(INFO) << __func__ << " " << base::HexEncode(value, len);
530 LOG(INFO) << __func__ << "id " << loghex(offset->id) << "location "
531 << loghex(offset->location);
532
533 if (!device->IsReady()) {
534 LOG_INFO("Device: %s is not ready yet.",
535 device->address.ToString().c_str());
536 return;
537 }
538
539 callbacks_->OnExtAudioOutLocationChanged(device->address, offset->id,
540 offset->location);
541 }
542
OnExtAudioOutCPWrite(uint16_t connection_id,tGATT_STATUS status,uint16_t handle,void *)543 void OnExtAudioOutCPWrite(uint16_t connection_id, tGATT_STATUS status,
544 uint16_t handle, void* /*data*/) {
545 VolumeControlDevice* device =
546 volume_control_devices_.FindByConnId(connection_id);
547 if (!device) {
548 LOG(ERROR) << __func__
549 << "Skipping unknown device disconnect, connection_id="
550 << loghex(connection_id);
551 return;
552 }
553
554 LOG(INFO) << "Offset Control Point write response handle" << loghex(handle)
555 << " status: " << loghex((int)(status));
556
557 /* TODO Design callback API to notify about changes */
558 }
559
OnOffsetOutputDescChanged(VolumeControlDevice * device,VolumeOffset * offset,uint16_t len,uint8_t * value)560 void OnOffsetOutputDescChanged(VolumeControlDevice* device,
561 VolumeOffset* offset, uint16_t len,
562 uint8_t* value) {
563 std::string description = std::string(value, value + len);
564 if (!base::IsStringUTF8(description)) description = "<invalid utf8 string>";
565
566 LOG(INFO) << __func__ << " " << description;
567
568 if (!device->IsReady()) {
569 LOG_INFO("Device: %s is not ready yet.",
570 device->address.ToString().c_str());
571 return;
572 }
573
574 callbacks_->OnExtAudioOutDescriptionChanged(device->address, offset->id,
575 std::move(description));
576 }
577
OnGattWriteCcc(uint16_t connection_id,tGATT_STATUS status,uint16_t handle,uint16_t len,const uint8_t * value,void *)578 void OnGattWriteCcc(uint16_t connection_id, tGATT_STATUS status,
579 uint16_t handle, uint16_t len, const uint8_t* value,
580 void* /*data*/) {
581 VolumeControlDevice* device =
582 volume_control_devices_.FindByConnId(connection_id);
583 if (!device) {
584 LOG(INFO) << __func__
585 << "unknown connection_id=" << loghex(connection_id);
586 BtaGattQueue::Clean(connection_id);
587 return;
588 }
589
590 if (status != GATT_SUCCESS) {
591 if (status == GATT_DATABASE_OUT_OF_SYNC) {
592 LOG_INFO("Database out of sync for %s, conn_id: 0x%04x",
593 device->address.ToString().c_str(), connection_id);
594 ClearDeviceInformationAndStartSearch(device);
595 } else {
596 LOG_ERROR("Failed to register for notification: 0x%04x, status 0x%02x",
597 handle, status);
598 device_cleanup_helper(device, true);
599 }
600 return;
601 }
602
603 LOG(INFO) << __func__
604 << "Successfully register for indications: " << loghex(handle);
605
606 verify_device_ready(device, handle);
607 }
608
OnGattWriteCccStatic(uint16_t connection_id,tGATT_STATUS status,uint16_t handle,uint16_t len,const uint8_t * value,void * data)609 static void OnGattWriteCccStatic(uint16_t connection_id, tGATT_STATUS status,
610 uint16_t handle, uint16_t len,
611 const uint8_t* value, void* data) {
612 if (!instance) {
613 LOG(ERROR) << __func__ << "No instance=" << handle;
614 return;
615 }
616
617 instance->OnGattWriteCcc(connection_id, status, handle, len, value, data);
618 }
619
Dump(int fd)620 void Dump(int fd) { volume_control_devices_.DebugDump(fd); }
621
Disconnect(const RawAddress & address)622 void Disconnect(const RawAddress& address) override {
623 VolumeControlDevice* device =
624 volume_control_devices_.FindByAddress(address);
625 if (!device) {
626 LOG(INFO) << "Device not connected to profile " << address;
627 return;
628 }
629
630 LOG(INFO) << __func__ << " GAP_EVT_CONN_CLOSED: " << device->address;
631 device_cleanup_helper(device, true);
632 }
633
OnGattDisconnected(uint16_t connection_id,tGATT_IF,RawAddress remote_bda,tGATT_DISCONN_REASON)634 void OnGattDisconnected(uint16_t connection_id, tGATT_IF /*client_if*/,
635 RawAddress remote_bda,
636 tGATT_DISCONN_REASON /*reason*/) {
637 VolumeControlDevice* device =
638 volume_control_devices_.FindByConnId(connection_id);
639 if (!device) {
640 LOG(ERROR) << __func__
641 << " Skipping unknown device disconnect, connection_id="
642 << loghex(connection_id);
643 return;
644 }
645
646 if (!device->IsConnected()) {
647 LOG(ERROR) << __func__
648 << " Skipping disconnect of the already disconnected device, "
649 "connection_id="
650 << loghex(connection_id);
651 return;
652 }
653
654 // If we get here, it means, device has not been exlicitly disconnected.
655 bool device_ready = device->IsReady();
656
657 device_cleanup_helper(device, device->connecting_actively);
658
659 if (device_ready) {
660 device->first_connection = true;
661 device->connecting_actively = true;
662
663 /* Add device into BG connection to accept remote initiated connection */
664 BTA_GATTC_Open(gatt_if_, remote_bda, BTM_BLE_BKG_CONNECT_ALLOW_LIST,
665 false);
666 }
667 }
668
RemoveDeviceFromOperationList(const RawAddress & addr,int operation_id)669 void RemoveDeviceFromOperationList(const RawAddress& addr, int operation_id) {
670 auto op = find_if(ongoing_operations_.begin(), ongoing_operations_.end(),
671 [operation_id](auto& operation) {
672 return operation.operation_id_ == operation_id;
673 });
674
675 if (op == ongoing_operations_.end()) {
676 LOG(ERROR) << __func__
677 << " Could not find operation id: " << operation_id;
678 return;
679 }
680
681 auto it = find(op->devices_.begin(), op->devices_.end(), addr);
682 if (it != op->devices_.end()) {
683 op->devices_.erase(it);
684 if (op->devices_.empty()) {
685 ongoing_operations_.erase(op);
686 StartQueueOperation();
687 }
688 return;
689 }
690 }
691
RemovePendingVolumeControlOperations(std::vector<RawAddress> & devices,int group_id)692 void RemovePendingVolumeControlOperations(std::vector<RawAddress>& devices,
693 int group_id) {
694 for (auto op = ongoing_operations_.begin();
695 op != ongoing_operations_.end();) {
696 // We only remove operations that don't affect the mute field.
697 if (op->IsStarted() ||
698 (op->opcode_ != kControlPointOpcodeSetAbsoluteVolume &&
699 op->opcode_ != kControlPointOpcodeVolumeUp &&
700 op->opcode_ != kControlPointOpcodeVolumeDown)) {
701 op++;
702 continue;
703 }
704 if (group_id != bluetooth::groups::kGroupUnknown &&
705 op->group_id_ == group_id) {
706 op = ongoing_operations_.erase(op);
707 continue;
708 }
709 for (auto const& addr : devices) {
710 auto it = find(op->devices_.begin(), op->devices_.end(), addr);
711 if (it != op->devices_.end()) {
712 op->devices_.erase(it);
713 }
714 }
715 if (op->devices_.empty()) {
716 op = ongoing_operations_.erase(op);
717 } else {
718 op++;
719 }
720 }
721 }
722
OnWriteControlResponse(uint16_t connection_id,tGATT_STATUS status,uint16_t handle,void * data)723 void OnWriteControlResponse(uint16_t connection_id, tGATT_STATUS status,
724 uint16_t handle, void* data) {
725 VolumeControlDevice* device =
726 volume_control_devices_.FindByConnId(connection_id);
727 if (!device) {
728 LOG(ERROR) << __func__
729 << "Skipping unknown device disconnect, connection_id="
730 << loghex(connection_id);
731 return;
732 }
733
734 LOG(INFO) << "Write response handle: " << loghex(handle)
735 << " status: " << loghex((int)(status));
736
737 if (status == GATT_SUCCESS) return;
738
739 /* In case of error, remove device from the tracking operation list */
740 RemoveDeviceFromOperationList(device->address, PTR_TO_INT(data));
741
742 if (status == GATT_DATABASE_OUT_OF_SYNC) {
743 LOG_INFO("Database out of sync for %s",
744 device->address.ToString().c_str());
745 ClearDeviceInformationAndStartSearch(device);
746 }
747 }
748
operation_callback(void * data)749 static void operation_callback(void* data) {
750 instance->CancelVolumeOperation(PTR_TO_INT(data));
751 }
752
StartQueueOperation(void)753 void StartQueueOperation(void) {
754 LOG(INFO) << __func__;
755 if (ongoing_operations_.empty()) {
756 return;
757 };
758
759 auto op = &ongoing_operations_.front();
760
761 LOG(INFO) << __func__ << " operation_id: " << op->operation_id_;
762
763 if (op->IsStarted()) {
764 LOG(INFO) << __func__ << " wait until operation " << op->operation_id_
765 << " is complete";
766 return;
767 }
768
769 op->Start();
770
771 alarm_set_on_mloop(op->operation_timeout_, 3000, operation_callback,
772 INT_TO_PTR(op->operation_id_));
773 devices_control_point_helper(
774 op->devices_, op->opcode_,
775 op->arguments_.size() == 0 ? nullptr : &(op->arguments_));
776 }
777
CancelVolumeOperation(int operation_id)778 void CancelVolumeOperation(int operation_id) {
779 LOG(INFO) << __func__ << " canceling operation_id: " << operation_id;
780
781 auto op = find_if(
782 ongoing_operations_.begin(), ongoing_operations_.end(),
783 [operation_id](auto& it) { return it.operation_id_ == operation_id; });
784
785 if (op == ongoing_operations_.end()) {
786 LOG(ERROR) << __func__
787 << " Could not find operation_id: " << operation_id;
788 return;
789 }
790
791 /* Possibly close GATT operations */
792 ongoing_operations_.erase(op);
793 StartQueueOperation();
794 }
795
ProceedVolumeOperation(int operation_id)796 void ProceedVolumeOperation(int operation_id) {
797 auto op = find_if(ongoing_operations_.begin(), ongoing_operations_.end(),
798 [operation_id](auto& operation) {
799 return operation.operation_id_ == operation_id;
800 });
801
802 DLOG(INFO) << __func__ << " operation_id: " << operation_id;
803
804 if (op == ongoing_operations_.end()) {
805 LOG(ERROR) << __func__
806 << " Could not find operation_id: " << operation_id;
807 return;
808 }
809
810 DLOG(INFO) << __func__ << " procedure continued for operation_id: "
811 << op->operation_id_;
812
813 alarm_set_on_mloop(op->operation_timeout_, 3000, operation_callback,
814 INT_TO_PTR(op->operation_id_));
815 devices_control_point_helper(op->devices_, op->opcode_, &(op->arguments_));
816 }
817
PrepareVolumeControlOperation(std::vector<RawAddress> devices,int group_id,bool is_autonomous,uint8_t opcode,std::vector<uint8_t> & arguments)818 void PrepareVolumeControlOperation(std::vector<RawAddress> devices,
819 int group_id, bool is_autonomous,
820 uint8_t opcode,
821 std::vector<uint8_t>& arguments) {
822 LOG_DEBUG(
823 "num of devices: %zu, group_id: %d, is_autonomous: %s opcode: %d, arg "
824 "size: %zu",
825 devices.size(), group_id, is_autonomous ? "true" : "false", +opcode,
826 arguments.size());
827
828 if (std::find_if(ongoing_operations_.begin(), ongoing_operations_.end(),
829 [opcode, &devices, &arguments](const VolumeOperation& op) {
830 if (op.opcode_ != opcode) return false;
831 if (!std::equal(op.arguments_.begin(),
832 op.arguments_.end(), arguments.begin()))
833 return false;
834 // Filter out all devices which have the exact operation
835 // already scheduled
836 devices.erase(
837 std::remove_if(devices.begin(), devices.end(),
838 [&op](auto d) {
839 return find(op.devices_.begin(),
840 op.devices_.end(),
841 d) != op.devices_.end();
842 }),
843 devices.end());
844 return devices.empty();
845 }) == ongoing_operations_.end()) {
846 ongoing_operations_.emplace_back(latest_operation_id_++, group_id,
847 is_autonomous, opcode, arguments,
848 devices);
849 }
850 }
851
MuteUnmute(std::variant<RawAddress,int> addr_or_group_id,bool mute)852 void MuteUnmute(std::variant<RawAddress, int> addr_or_group_id, bool mute) {
853 std::vector<uint8_t> arg;
854
855 uint8_t opcode = mute ? kControlPointOpcodeMute : kControlPointOpcodeUnmute;
856
857 if (std::holds_alternative<RawAddress>(addr_or_group_id)) {
858 VolumeControlDevice* dev = volume_control_devices_.FindByAddress(
859 std::get<RawAddress>(addr_or_group_id));
860 if (dev != nullptr) {
861 LOG_DEBUG("Address: %s: isReady: %s", dev->address.ToString().c_str(),
862 dev->IsReady() ? "true" : "false");
863 if (dev->IsReady()) {
864 std::vector<RawAddress> devices = {dev->address};
865 PrepareVolumeControlOperation(
866 devices, bluetooth::groups::kGroupUnknown, false, opcode, arg);
867 }
868 }
869 } else {
870 /* Handle group change */
871 auto group_id = std::get<int>(addr_or_group_id);
872 LOG_DEBUG("group: %d", group_id);
873 auto csis_api = CsisClient::Get();
874 if (!csis_api) {
875 LOG(ERROR) << __func__ << " Csis is not there";
876 return;
877 }
878
879 auto devices = csis_api->GetDeviceList(group_id);
880 for (auto it = devices.begin(); it != devices.end();) {
881 auto dev = volume_control_devices_.FindByAddress(*it);
882 if (!dev || !dev->IsReady()) {
883 it = devices.erase(it);
884 } else {
885 it++;
886 }
887 }
888
889 if (devices.empty()) {
890 LOG(ERROR) << __func__ << " group id : " << group_id
891 << " is not connected? ";
892 return;
893 }
894
895 PrepareVolumeControlOperation(devices, group_id, false, opcode, arg);
896 }
897
898 StartQueueOperation();
899 }
900
Mute(std::variant<RawAddress,int> addr_or_group_id)901 void Mute(std::variant<RawAddress, int> addr_or_group_id) override {
902 LOG_DEBUG();
903 MuteUnmute(addr_or_group_id, true /* mute */);
904 }
905
UnMute(std::variant<RawAddress,int> addr_or_group_id)906 void UnMute(std::variant<RawAddress, int> addr_or_group_id) override {
907 LOG_DEBUG();
908 MuteUnmute(addr_or_group_id, false /* mute */);
909 }
910
SetVolume(std::variant<RawAddress,int> addr_or_group_id,uint8_t volume)911 void SetVolume(std::variant<RawAddress, int> addr_or_group_id,
912 uint8_t volume) override {
913 DLOG(INFO) << __func__ << " vol: " << +volume;
914
915 std::vector<uint8_t> arg({volume});
916 uint8_t opcode = kControlPointOpcodeSetAbsoluteVolume;
917
918 if (std::holds_alternative<RawAddress>(addr_or_group_id)) {
919 LOG_DEBUG("Address: %s: ",
920 std::get<RawAddress>(addr_or_group_id).ToString().c_str());
921 VolumeControlDevice* dev = volume_control_devices_.FindByAddress(
922 std::get<RawAddress>(addr_or_group_id));
923 if (dev != nullptr) {
924 LOG_DEBUG("Address: %s: isReady: %s", dev->address.ToString().c_str(),
925 dev->IsReady() ? "true" : "false");
926 if (dev->IsReady() && (dev->volume != volume)) {
927 std::vector<RawAddress> devices = {dev->address};
928 RemovePendingVolumeControlOperations(
929 devices, bluetooth::groups::kGroupUnknown);
930 PrepareVolumeControlOperation(
931 devices, bluetooth::groups::kGroupUnknown, false, opcode, arg);
932 }
933 }
934 } else {
935 /* Handle group change */
936 auto group_id = std::get<int>(addr_or_group_id);
937 DLOG(INFO) << __func__ << " group: " << group_id;
938 auto csis_api = CsisClient::Get();
939 if (!csis_api) {
940 LOG(ERROR) << __func__ << " Csis is not there";
941 return;
942 }
943
944 auto devices = csis_api->GetDeviceList(group_id);
945 for (auto it = devices.begin(); it != devices.end();) {
946 auto dev = volume_control_devices_.FindByAddress(*it);
947 if (!dev || !dev->IsReady()) {
948 it = devices.erase(it);
949 } else {
950 it++;
951 }
952 }
953
954 if (devices.empty()) {
955 LOG(ERROR) << __func__ << " group id : " << group_id
956 << " is not connected? ";
957 return;
958 }
959
960 RemovePendingVolumeControlOperations(devices, group_id);
961 PrepareVolumeControlOperation(devices, group_id, false, opcode, arg);
962 }
963
964 StartQueueOperation();
965 }
966
967 /* Methods to operate on Volume Control Offset Service (VOCS) */
GetExtAudioOutVolumeOffset(const RawAddress & address,uint8_t ext_output_id)968 void GetExtAudioOutVolumeOffset(const RawAddress& address,
969 uint8_t ext_output_id) override {
970 VolumeControlDevice* device =
971 volume_control_devices_.FindByAddress(address);
972 if (!device) {
973 LOG(ERROR) << __func__ << ", no such device!";
974 return;
975 }
976
977 device->GetExtAudioOutVolumeOffset(ext_output_id, chrc_read_callback_static,
978 nullptr);
979 }
980
SetExtAudioOutVolumeOffset(const RawAddress & address,uint8_t ext_output_id,int16_t offset_val)981 void SetExtAudioOutVolumeOffset(const RawAddress& address,
982 uint8_t ext_output_id,
983 int16_t offset_val) override {
984 std::vector<uint8_t> arg(2);
985 uint8_t* ptr = arg.data();
986 UINT16_TO_STREAM(ptr, offset_val);
987 ext_audio_out_control_point_helper(
988 address, ext_output_id, kVolumeOffsetControlPointOpcodeSet, &arg);
989 }
990
GetExtAudioOutLocation(const RawAddress & address,uint8_t ext_output_id)991 void GetExtAudioOutLocation(const RawAddress& address,
992 uint8_t ext_output_id) override {
993 VolumeControlDevice* device =
994 volume_control_devices_.FindByAddress(address);
995 if (!device) {
996 LOG(ERROR) << __func__ << ", no such device!";
997 return;
998 }
999
1000 device->GetExtAudioOutLocation(ext_output_id, chrc_read_callback_static,
1001 nullptr);
1002 }
1003
SetExtAudioOutLocation(const RawAddress & address,uint8_t ext_output_id,uint32_t location)1004 void SetExtAudioOutLocation(const RawAddress& address, uint8_t ext_output_id,
1005 uint32_t location) override {
1006 VolumeControlDevice* device =
1007 volume_control_devices_.FindByAddress(address);
1008 if (!device) {
1009 LOG(ERROR) << __func__ << ", no such device!";
1010 return;
1011 }
1012
1013 device->SetExtAudioOutLocation(ext_output_id, location);
1014 }
1015
GetExtAudioOutDescription(const RawAddress & address,uint8_t ext_output_id)1016 void GetExtAudioOutDescription(const RawAddress& address,
1017 uint8_t ext_output_id) override {
1018 VolumeControlDevice* device =
1019 volume_control_devices_.FindByAddress(address);
1020 if (!device) {
1021 LOG(ERROR) << __func__ << ", no such device!";
1022 return;
1023 }
1024
1025 device->GetExtAudioOutDescription(ext_output_id, chrc_read_callback_static,
1026 nullptr);
1027 }
1028
SetExtAudioOutDescription(const RawAddress & address,uint8_t ext_output_id,std::string descr)1029 void SetExtAudioOutDescription(const RawAddress& address,
1030 uint8_t ext_output_id,
1031 std::string descr) override {
1032 VolumeControlDevice* device =
1033 volume_control_devices_.FindByAddress(address);
1034 if (!device) {
1035 LOG(ERROR) << __func__ << ", no such device!";
1036 return;
1037 }
1038
1039 device->SetExtAudioOutDescription(ext_output_id, descr);
1040 }
1041
CleanUp()1042 void CleanUp() {
1043 LOG(INFO) << __func__;
1044 volume_control_devices_.Disconnect(gatt_if_);
1045 volume_control_devices_.Clear();
1046 ongoing_operations_.clear();
1047 BTA_GATTC_AppDeregister(gatt_if_);
1048 }
1049
1050 private:
1051 tGATT_IF gatt_if_;
1052 bluetooth::vc::VolumeControlCallbacks* callbacks_;
1053 VolumeControlDevices volume_control_devices_;
1054
1055 /* Used to track volume control operations */
1056 std::list<VolumeOperation> ongoing_operations_;
1057 int latest_operation_id_;
1058
verify_device_ready(VolumeControlDevice * device,uint16_t handle)1059 void verify_device_ready(VolumeControlDevice* device, uint16_t handle) {
1060 if (device->IsReady()) return;
1061
1062 // VerifyReady sets the device_ready flag if all remaining GATT operations
1063 // are completed
1064 if (device->VerifyReady(handle)) {
1065 LOG(INFO) << __func__ << " Outstanding reads completed.";
1066
1067 callbacks_->OnDeviceAvailable(device->address,
1068 device->audio_offsets.Size());
1069 callbacks_->OnConnectionState(ConnectionState::CONNECTED,
1070 device->address);
1071
1072 device->connecting_actively = true;
1073
1074 device->first_connection = false;
1075
1076 // once profile connected we can notify current states
1077 callbacks_->OnVolumeStateChanged(device->address, device->volume,
1078 device->mute, false);
1079
1080 for (auto const& offset : device->audio_offsets.volume_offsets) {
1081 callbacks_->OnExtAudioOutVolumeOffsetChanged(device->address, offset.id,
1082 offset.offset);
1083 }
1084
1085 device->EnqueueRemainingRequests(gatt_if_, chrc_read_callback_static,
1086 OnGattWriteCccStatic);
1087 }
1088 }
1089
device_cleanup_helper(VolumeControlDevice * device,bool notify)1090 void device_cleanup_helper(VolumeControlDevice* device, bool notify) {
1091 device->Disconnect(gatt_if_);
1092 if (notify)
1093 callbacks_->OnConnectionState(ConnectionState::DISCONNECTED,
1094 device->address);
1095 }
1096
devices_control_point_helper(std::vector<RawAddress> & devices,uint8_t opcode,const std::vector<uint8_t> * arg,int operation_id=-1)1097 void devices_control_point_helper(std::vector<RawAddress>& devices,
1098 uint8_t opcode,
1099 const std::vector<uint8_t>* arg,
1100 int operation_id = -1) {
1101 volume_control_devices_.ControlPointOperation(
1102 devices, opcode, arg,
1103 [](uint16_t connection_id, tGATT_STATUS status, uint16_t handle,
1104 uint16_t len, const uint8_t* value, void* data) {
1105 if (instance)
1106 instance->OnWriteControlResponse(connection_id, status, handle,
1107 data);
1108 },
1109 INT_TO_PTR(operation_id));
1110 }
1111
ext_audio_out_control_point_helper(const RawAddress & address,uint8_t ext_output_id,uint8_t opcode,const std::vector<uint8_t> * arg)1112 void ext_audio_out_control_point_helper(const RawAddress& address,
1113 uint8_t ext_output_id, uint8_t opcode,
1114 const std::vector<uint8_t>* arg) {
1115 LOG(INFO) << __func__ << ": " << address.ToString()
1116 << " id=" << loghex(ext_output_id) << " op=" << loghex(opcode);
1117 VolumeControlDevice* device =
1118 volume_control_devices_.FindByAddress(address);
1119 if (!device) {
1120 LOG(ERROR) << __func__ << ", no such device!";
1121 return;
1122 }
1123 device->ExtAudioOutControlPointOperation(
1124 ext_output_id, opcode, arg,
1125 [](uint16_t connection_id, tGATT_STATUS status, uint16_t handle,
1126 uint16_t len, const uint8_t* value, void* data) {
1127 if (instance)
1128 instance->OnExtAudioOutCPWrite(connection_id, status, handle, data);
1129 },
1130 nullptr);
1131 }
1132
gattc_callback(tBTA_GATTC_EVT event,tBTA_GATTC * p_data)1133 void gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
1134 LOG(INFO) << __func__ << " event = " << static_cast<int>(event);
1135
1136 if (p_data == nullptr) return;
1137
1138 switch (event) {
1139 case BTA_GATTC_OPEN_EVT: {
1140 tBTA_GATTC_OPEN& o = p_data->open;
1141 OnGattConnected(o.status, o.conn_id, o.client_if, o.remote_bda,
1142 o.transport, o.mtu);
1143
1144 } break;
1145
1146 case BTA_GATTC_CLOSE_EVT: {
1147 tBTA_GATTC_CLOSE& c = p_data->close;
1148 OnGattDisconnected(c.conn_id, c.client_if, c.remote_bda, c.reason);
1149 } break;
1150
1151 case BTA_GATTC_SEARCH_CMPL_EVT:
1152 OnServiceSearchComplete(p_data->search_cmpl.conn_id,
1153 p_data->search_cmpl.status);
1154 break;
1155
1156 case BTA_GATTC_NOTIF_EVT: {
1157 tBTA_GATTC_NOTIFY& n = p_data->notify;
1158 if (!n.is_notify || n.len > GATT_MAX_ATTR_LEN) {
1159 LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify="
1160 << n.is_notify << ", len=" << static_cast<int>(n.len);
1161 break;
1162 }
1163 OnNotificationEvent(n.conn_id, n.handle, n.len, n.value);
1164 } break;
1165
1166 case BTA_GATTC_ENC_CMPL_CB_EVT: {
1167 uint8_t encryption_status;
1168 if (BTM_IsEncrypted(p_data->enc_cmpl.remote_bda, BT_TRANSPORT_LE)) {
1169 encryption_status = BTM_SUCCESS;
1170 } else {
1171 encryption_status = BTM_FAILED_ON_SECURITY;
1172 }
1173 OnEncryptionComplete(p_data->enc_cmpl.remote_bda, encryption_status);
1174 } break;
1175
1176 case BTA_GATTC_SRVC_CHG_EVT:
1177 OnServiceChangeEvent(p_data->remote_bda);
1178 break;
1179
1180 case BTA_GATTC_SRVC_DISC_DONE_EVT:
1181 OnServiceDiscDoneEvent(p_data->remote_bda);
1182 break;
1183
1184 default:
1185 break;
1186 }
1187 }
1188
gattc_callback_static(tBTA_GATTC_EVT event,tBTA_GATTC * p_data)1189 static void gattc_callback_static(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
1190 if (instance) instance->gattc_callback(event, p_data);
1191 }
1192
chrc_read_callback_static(uint16_t conn_id,tGATT_STATUS status,uint16_t handle,uint16_t len,uint8_t * value,void * data)1193 static void chrc_read_callback_static(uint16_t conn_id, tGATT_STATUS status,
1194 uint16_t handle, uint16_t len,
1195 uint8_t* value, void* data) {
1196 if (instance)
1197 instance->OnCharacteristicValueChanged(conn_id, status, handle, len,
1198 value, data, false);
1199 }
1200 };
1201 } // namespace
1202
Initialize(bluetooth::vc::VolumeControlCallbacks * callbacks)1203 void VolumeControl::Initialize(
1204 bluetooth::vc::VolumeControlCallbacks* callbacks) {
1205 if (instance) {
1206 LOG(ERROR) << "Already initialized!";
1207 return;
1208 }
1209
1210 instance = new VolumeControlImpl(callbacks);
1211 }
1212
IsVolumeControlRunning()1213 bool VolumeControl::IsVolumeControlRunning() { return instance; }
1214
Get(void)1215 VolumeControl* VolumeControl::Get(void) {
1216 CHECK(instance);
1217 return instance;
1218 };
1219
AddFromStorage(const RawAddress & address,bool auto_connect)1220 void VolumeControl::AddFromStorage(const RawAddress& address,
1221 bool auto_connect) {
1222 if (!instance) {
1223 LOG(ERROR) << "Not initialized yet";
1224 return;
1225 }
1226
1227 instance->AddFromStorage(address, auto_connect);
1228 };
1229
CleanUp()1230 void VolumeControl::CleanUp() {
1231 if (!instance) {
1232 LOG(ERROR) << "Not initialized!";
1233 return;
1234 }
1235
1236 VolumeControlImpl* ptr = instance;
1237 instance = nullptr;
1238
1239 ptr->CleanUp();
1240
1241 delete ptr;
1242 };
1243
DebugDump(int fd)1244 void VolumeControl::DebugDump(int fd) {
1245 dprintf(fd, "Volume Control Manager:\n");
1246 if (instance) instance->Dump(fd);
1247 dprintf(fd, "\n");
1248 }
1249