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 package com.android.bluetooth.vc; 19 20 import static android.Manifest.permission.BLUETOOTH_CONNECT; 21 22 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; 23 24 import android.annotation.RequiresPermission; 25 import android.bluetooth.BluetoothDevice; 26 import android.bluetooth.BluetoothProfile; 27 import android.bluetooth.BluetoothUuid; 28 import android.bluetooth.BluetoothVolumeControl; 29 import android.bluetooth.IBluetoothCsipSetCoordinator; 30 import android.bluetooth.IBluetoothLeAudio; 31 import android.bluetooth.IBluetoothVolumeControl; 32 import android.bluetooth.IBluetoothVolumeControlCallback; 33 import android.content.AttributionSource; 34 import android.content.BroadcastReceiver; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.IntentFilter; 38 import android.media.AudioManager; 39 import android.os.HandlerThread; 40 import android.os.ParcelUuid; 41 import android.os.RemoteCallbackList; 42 import android.os.RemoteException; 43 import android.sysprop.BluetoothProperties; 44 import android.util.Log; 45 46 import com.android.bluetooth.Utils; 47 import com.android.bluetooth.btservice.AdapterService; 48 import com.android.bluetooth.btservice.ProfileService; 49 import com.android.bluetooth.btservice.ServiceFactory; 50 import com.android.bluetooth.btservice.storage.DatabaseManager; 51 import com.android.bluetooth.csip.CsipSetCoordinatorService; 52 import com.android.bluetooth.le_audio.LeAudioService; 53 import com.android.internal.annotations.VisibleForTesting; 54 import com.android.modules.utils.SynchronousResultReceiver; 55 56 import java.util.ArrayList; 57 import java.util.HashMap; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Objects; 61 62 public class VolumeControlService extends ProfileService { 63 private static final boolean DBG = false; 64 private static final String TAG = "VolumeControlService"; 65 66 // Timeout for state machine thread join, to prevent potential ANR. 67 private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1000; 68 69 // Upper limit of all VolumeControl devices: Bonded or Connected 70 private static final int MAX_VC_STATE_MACHINES = 10; 71 private static final int LE_AUDIO_MAX_VOL = 255; 72 private static final int LE_AUDIO_MIN_VOL = 0; 73 74 private static VolumeControlService sVolumeControlService; 75 76 private AdapterService mAdapterService; 77 private DatabaseManager mDatabaseManager; 78 private HandlerThread mStateMachinesThread; 79 private BluetoothDevice mPreviousAudioDevice; 80 81 @VisibleForTesting 82 RemoteCallbackList<IBluetoothVolumeControlCallback> mCallbacks; 83 84 @VisibleForTesting 85 static class VolumeControlOffsetDescriptor { 86 Map<Integer, Descriptor> mVolumeOffsets; 87 88 private class Descriptor { Descriptor()89 Descriptor() { 90 mValue = 0; 91 mLocation = 0; 92 mDescription = null; 93 } 94 int mValue; 95 int mLocation; 96 String mDescription; 97 }; 98 VolumeControlOffsetDescriptor()99 VolumeControlOffsetDescriptor() { 100 mVolumeOffsets = new HashMap<>(); 101 } 102 size()103 int size() { 104 return mVolumeOffsets.size(); 105 } 106 add(int id)107 void add(int id) { 108 Descriptor d = mVolumeOffsets.get(id); 109 if (d == null) { 110 mVolumeOffsets.put(id, new Descriptor()); 111 } 112 } 113 setValue(int id, int value)114 boolean setValue(int id, int value) { 115 Descriptor d = mVolumeOffsets.get(id); 116 if (d == null) { 117 return false; 118 } 119 d.mValue = value; 120 return true; 121 } 122 getValue(int id)123 int getValue(int id) { 124 Descriptor d = mVolumeOffsets.get(id); 125 if (d == null) { 126 return 0; 127 } 128 return d.mValue; 129 } 130 setDescription(int id, String desc)131 boolean setDescription(int id, String desc) { 132 Descriptor d = mVolumeOffsets.get(id); 133 if (d == null) { 134 return false; 135 } 136 d.mDescription = desc; 137 return true; 138 } 139 getDescription(int id)140 String getDescription(int id) { 141 Descriptor d = mVolumeOffsets.get(id); 142 if (d == null) { 143 return null; 144 } 145 return d.mDescription; 146 } 147 setLocation(int id, int location)148 boolean setLocation(int id, int location) { 149 Descriptor d = mVolumeOffsets.get(id); 150 if (d == null) { 151 return false; 152 } 153 d.mLocation = location; 154 return true; 155 } 156 getLocation(int id)157 int getLocation(int id) { 158 Descriptor d = mVolumeOffsets.get(id); 159 if (d == null) { 160 return 0; 161 } 162 return d.mLocation; 163 } 164 remove(int id)165 void remove(int id) { 166 mVolumeOffsets.remove(id); 167 } 168 clear()169 void clear() { 170 mVolumeOffsets.clear(); 171 } 172 dump(StringBuilder sb)173 void dump(StringBuilder sb) { 174 for (Map.Entry<Integer, Descriptor> entry : mVolumeOffsets.entrySet()) { 175 Descriptor descriptor = entry.getValue(); 176 Integer id = entry.getKey(); 177 ProfileService.println(sb, " Id: " + id); 178 ProfileService.println(sb, " value: " + descriptor.mValue); 179 ProfileService.println(sb, " location: " + descriptor.mLocation); 180 ProfileService.println(sb, " description: " + descriptor.mDescription); 181 } 182 } 183 } 184 185 @VisibleForTesting 186 VolumeControlNativeInterface mVolumeControlNativeInterface; 187 @VisibleForTesting 188 AudioManager mAudioManager; 189 190 private final Map<BluetoothDevice, VolumeControlStateMachine> mStateMachines = new HashMap<>(); 191 private final Map<BluetoothDevice, VolumeControlOffsetDescriptor> mAudioOffsets = 192 new HashMap<>(); 193 private final Map<Integer, Integer> mGroupVolumeCache = new HashMap<>(); 194 195 private BroadcastReceiver mBondStateChangedReceiver; 196 private BroadcastReceiver mConnectionStateChangedReceiver; 197 198 @VisibleForTesting 199 ServiceFactory mFactory = new ServiceFactory(); 200 isEnabled()201 public static boolean isEnabled() { 202 return BluetoothProperties.isProfileVcpControllerEnabled().orElse(false); 203 } 204 205 @Override initBinder()206 protected IProfileServiceBinder initBinder() { 207 return new BluetoothVolumeControlBinder(this); 208 } 209 210 @Override create()211 protected void create() { 212 if (DBG) { 213 Log.d(TAG, "create()"); 214 } 215 } 216 217 @Override start()218 protected boolean start() { 219 if (DBG) { 220 Log.d(TAG, "start()"); 221 } 222 if (sVolumeControlService != null) { 223 throw new IllegalStateException("start() called twice"); 224 } 225 226 // Get AdapterService, VolumeControlNativeInterface, DatabaseManager, AudioManager. 227 // None of them can be null. 228 mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), 229 "AdapterService cannot be null when VolumeControlService starts"); 230 mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(), 231 "DatabaseManager cannot be null when VolumeControlService starts"); 232 mVolumeControlNativeInterface = Objects.requireNonNull( 233 VolumeControlNativeInterface.getInstance(), 234 "VolumeControlNativeInterface cannot be null when VolumeControlService starts"); 235 mAudioManager = getSystemService(AudioManager.class); 236 Objects.requireNonNull(mAudioManager, 237 "AudioManager cannot be null when VolumeControlService starts"); 238 239 // Start handler thread for state machines 240 mStateMachines.clear(); 241 mStateMachinesThread = new HandlerThread("VolumeControlService.StateMachines"); 242 mStateMachinesThread.start(); 243 244 // Setup broadcast receivers 245 IntentFilter filter = new IntentFilter(); 246 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 247 mBondStateChangedReceiver = new BondStateChangedReceiver(); 248 registerReceiver(mBondStateChangedReceiver, filter); 249 filter = new IntentFilter(); 250 filter.addAction(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED); 251 mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver(); 252 registerReceiver(mConnectionStateChangedReceiver, filter); 253 254 mAudioOffsets.clear(); 255 mGroupVolumeCache.clear(); 256 mCallbacks = new RemoteCallbackList<IBluetoothVolumeControlCallback>(); 257 258 // Mark service as started 259 setVolumeControlService(this); 260 261 // Initialize native interface 262 mVolumeControlNativeInterface.init(); 263 264 return true; 265 } 266 267 @Override stop()268 protected boolean stop() { 269 if (DBG) { 270 Log.d(TAG, "stop()"); 271 } 272 if (sVolumeControlService == null) { 273 Log.w(TAG, "stop() called before start()"); 274 return true; 275 } 276 277 // Mark service as stopped 278 setVolumeControlService(null); 279 280 // Unregister broadcast receivers 281 unregisterReceiver(mBondStateChangedReceiver); 282 mBondStateChangedReceiver = null; 283 unregisterReceiver(mConnectionStateChangedReceiver); 284 mConnectionStateChangedReceiver = null; 285 286 // Destroy state machines and stop handler thread 287 synchronized (mStateMachines) { 288 for (VolumeControlStateMachine sm : mStateMachines.values()) { 289 sm.doQuit(); 290 sm.cleanup(); 291 } 292 mStateMachines.clear(); 293 } 294 295 if (mStateMachinesThread != null) { 296 try { 297 mStateMachinesThread.quitSafely(); 298 mStateMachinesThread.join(SM_THREAD_JOIN_TIMEOUT_MS); 299 mStateMachinesThread = null; 300 } catch (InterruptedException e) { 301 // Do not rethrow as we are shutting down anyway 302 } 303 } 304 305 // Cleanup native interface 306 mVolumeControlNativeInterface.cleanup(); 307 mVolumeControlNativeInterface = null; 308 309 mAudioOffsets.clear(); 310 mGroupVolumeCache.clear(); 311 312 // Clear AdapterService, VolumeControlNativeInterface 313 mAudioManager = null; 314 mVolumeControlNativeInterface = null; 315 mAdapterService = null; 316 317 if (mCallbacks != null) { 318 mCallbacks.kill(); 319 } 320 321 return true; 322 } 323 324 @Override cleanup()325 protected void cleanup() { 326 if (DBG) { 327 Log.d(TAG, "cleanup()"); 328 } 329 } 330 331 /** 332 * Get the VolumeControlService instance 333 * @return VolumeControlService instance 334 */ getVolumeControlService()335 public static synchronized VolumeControlService getVolumeControlService() { 336 if (sVolumeControlService == null) { 337 Log.w(TAG, "getVolumeControlService(): service is NULL"); 338 return null; 339 } 340 341 if (!sVolumeControlService.isAvailable()) { 342 Log.w(TAG, "getVolumeControlService(): service is not available"); 343 return null; 344 } 345 return sVolumeControlService; 346 } 347 348 @VisibleForTesting setVolumeControlService(VolumeControlService instance)349 static synchronized void setVolumeControlService(VolumeControlService instance) { 350 if (DBG) { 351 Log.d(TAG, "setVolumeControlService(): set to: " + instance); 352 } 353 sVolumeControlService = instance; 354 } 355 356 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) connect(BluetoothDevice device)357 public boolean connect(BluetoothDevice device) { 358 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 359 "Need BLUETOOTH_PRIVILEGED permission"); 360 if (DBG) { 361 Log.d(TAG, "connect(): " + device); 362 } 363 if (device == null) { 364 return false; 365 } 366 367 if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 368 return false; 369 } 370 ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device); 371 if (!Utils.arrayContains(featureUuids, BluetoothUuid.VOLUME_CONTROL)) { 372 Log.e(TAG, "Cannot connect to " + device 373 + " : Remote does not have Volume Control UUID"); 374 return false; 375 } 376 377 378 synchronized (mStateMachines) { 379 VolumeControlStateMachine smConnect = getOrCreateStateMachine(device); 380 if (smConnect == null) { 381 Log.e(TAG, "Cannot connect to " + device + " : no state machine"); 382 } 383 smConnect.sendMessage(VolumeControlStateMachine.CONNECT); 384 } 385 386 return true; 387 } 388 389 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) disconnect(BluetoothDevice device)390 public boolean disconnect(BluetoothDevice device) { 391 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 392 "Need BLUETOOTH_PRIVILEGED permission"); 393 if (DBG) { 394 Log.d(TAG, "disconnect(): " + device); 395 } 396 if (device == null) { 397 return false; 398 } 399 synchronized (mStateMachines) { 400 VolumeControlStateMachine sm = getOrCreateStateMachine(device); 401 if (sm != null) { 402 sm.sendMessage(VolumeControlStateMachine.DISCONNECT); 403 } 404 } 405 406 return true; 407 } 408 409 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectedDevices()410 public List<BluetoothDevice> getConnectedDevices() { 411 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 412 "Need BLUETOOTH_PRIVILEGED permission"); 413 synchronized (mStateMachines) { 414 List<BluetoothDevice> devices = new ArrayList<>(); 415 for (VolumeControlStateMachine sm : mStateMachines.values()) { 416 if (sm.isConnected()) { 417 devices.add(sm.getDevice()); 418 } 419 } 420 return devices; 421 } 422 } 423 424 /** 425 * Check whether can connect to a peer device. 426 * The check considers a number of factors during the evaluation. 427 * 428 * @param device the peer device to connect to 429 * @return true if connection is allowed, otherwise false 430 */ 431 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) okToConnect(BluetoothDevice device)432 public boolean okToConnect(BluetoothDevice device) { 433 /* Make sure device is valid */ 434 if (device == null) { 435 Log.e(TAG, "okToConnect: Invalid device"); 436 return false; 437 } 438 // Check if this is an incoming connection in Quiet mode. 439 if (mAdapterService.isQuietModeEnabled()) { 440 Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); 441 return false; 442 } 443 // Check connectionPolicy and accept or reject the connection. 444 int connectionPolicy = getConnectionPolicy(device); 445 int bondState = mAdapterService.getBondState(device); 446 // Allow this connection only if the device is bonded. Any attempt to connect while 447 // bonding would potentially lead to an unauthorized connection. 448 if (bondState != BluetoothDevice.BOND_BONDED) { 449 Log.w(TAG, "okToConnect: return false, bondState=" + bondState); 450 return false; 451 } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN 452 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 453 // Otherwise, reject the connection if connectionPolicy is not valid. 454 Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy); 455 return false; 456 } 457 return true; 458 } 459 460 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getDevicesMatchingConnectionStates(int[] states)461 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 462 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 463 "Need BLUETOOTH_PRIVILEGED permission"); 464 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 465 if (states == null) { 466 return devices; 467 } 468 final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 469 if (bondedDevices == null) { 470 return devices; 471 } 472 synchronized (mStateMachines) { 473 for (BluetoothDevice device : bondedDevices) { 474 final ParcelUuid[] featureUuids = device.getUuids(); 475 if (!Utils.arrayContains(featureUuids, BluetoothUuid.VOLUME_CONTROL)) { 476 continue; 477 } 478 int connectionState = BluetoothProfile.STATE_DISCONNECTED; 479 VolumeControlStateMachine sm = mStateMachines.get(device); 480 if (sm != null) { 481 connectionState = sm.getConnectionState(); 482 } 483 for (int state : states) { 484 if (connectionState == state) { 485 devices.add(device); 486 break; 487 } 488 } 489 } 490 return devices; 491 } 492 } 493 494 /** 495 * Get the list of devices that have state machines. 496 * 497 * @return the list of devices that have state machines 498 */ 499 @VisibleForTesting getDevices()500 List<BluetoothDevice> getDevices() { 501 List<BluetoothDevice> devices = new ArrayList<>(); 502 synchronized (mStateMachines) { 503 for (VolumeControlStateMachine sm : mStateMachines.values()) { 504 devices.add(sm.getDevice()); 505 } 506 return devices; 507 } 508 } 509 510 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(BluetoothDevice device)511 public int getConnectionState(BluetoothDevice device) { 512 enforceCallingOrSelfPermission(BLUETOOTH_CONNECT, 513 "Need BLUETOOTH_CONNECT permission"); 514 synchronized (mStateMachines) { 515 VolumeControlStateMachine sm = mStateMachines.get(device); 516 if (sm == null) { 517 return BluetoothProfile.STATE_DISCONNECTED; 518 } 519 return sm.getConnectionState(); 520 } 521 } 522 523 /** 524 * Set connection policy of the profile and connects it if connectionPolicy is 525 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is 526 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 527 * 528 * <p> The device should already be paired. 529 * Connection policy can be one of: 530 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 531 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 532 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 533 * 534 * @param device the remote device 535 * @param connectionPolicy is the connection policy to set to for this profile 536 * @return true on success, otherwise false 537 */ 538 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(BluetoothDevice device, int connectionPolicy)539 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 540 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 541 "Need BLUETOOTH_PRIVILEGED permission"); 542 if (DBG) { 543 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 544 } 545 mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.VOLUME_CONTROL, 546 connectionPolicy); 547 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 548 connect(device); 549 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 550 disconnect(device); 551 } 552 return true; 553 } 554 555 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectionPolicy(BluetoothDevice device)556 public int getConnectionPolicy(BluetoothDevice device) { 557 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 558 "Need BLUETOOTH_PRIVILEGED permission"); 559 return mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.VOLUME_CONTROL); 560 } 561 isVolumeOffsetAvailable(BluetoothDevice device)562 boolean isVolumeOffsetAvailable(BluetoothDevice device) { 563 VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device); 564 if (offsets == null) { 565 Log.i(TAG, " There is no offset service for device: " + device); 566 return false; 567 } 568 Log.i(TAG, " Offset service available for device: " + device); 569 return true; 570 } 571 setVolumeOffset(BluetoothDevice device, int volumeOffset)572 void setVolumeOffset(BluetoothDevice device, int volumeOffset) { 573 VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device); 574 if (offsets == null) { 575 Log.e(TAG, " There is no offset service for device: " + device); 576 return; 577 } 578 579 /* Use first offset always */ 580 int value = offsets.getValue(1); 581 if (value == volumeOffset) { 582 /* Nothing to do - offset already applied */ 583 return; 584 } 585 586 mVolumeControlNativeInterface.setExtAudioOutVolumeOffset(device, 1, volumeOffset); 587 } 588 589 /** 590 * {@hide} 591 */ setGroupVolume(int groupId, int volume)592 public void setGroupVolume(int groupId, int volume) { 593 if (volume < 0) { 594 Log.w(TAG, "Tried to set invalid volume " + volume + ". Ignored."); 595 return; 596 } 597 598 mGroupVolumeCache.put(groupId, volume); 599 mVolumeControlNativeInterface.setGroupVolume(groupId, volume); 600 } 601 602 /** 603 * {@hide} 604 * @param groupId 605 */ getGroupVolume(int groupId)606 public int getGroupVolume(int groupId) { 607 return mGroupVolumeCache.getOrDefault(groupId, 608 IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME); 609 } 610 611 /** 612 * {@hide} 613 */ mute(BluetoothDevice device)614 public void mute(BluetoothDevice device) { 615 mVolumeControlNativeInterface.mute(device); 616 } 617 618 /** 619 * {@hide} 620 */ muteGroup(int groupId)621 public void muteGroup(int groupId) { 622 mVolumeControlNativeInterface.muteGroup(groupId); 623 } 624 625 /** 626 * {@hide} 627 */ unmute(BluetoothDevice device)628 public void unmute(BluetoothDevice device) { 629 mVolumeControlNativeInterface.unmute(device); 630 } 631 632 /** 633 * {@hide} 634 */ unmuteGroup(int groupId)635 public void unmuteGroup(int groupId) { 636 mVolumeControlNativeInterface.unmuteGroup(groupId); 637 } 638 639 /** 640 * {@hide} 641 */ handleGroupNodeAdded(int groupId, BluetoothDevice device)642 public void handleGroupNodeAdded(int groupId, BluetoothDevice device) { 643 // Ignore disconnected device, its volume will be set once it connects 644 synchronized (mStateMachines) { 645 VolumeControlStateMachine sm = mStateMachines.get(device); 646 if (sm == null) { 647 return; 648 } 649 if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { 650 return; 651 } 652 } 653 654 // If group volume has already changed, the new group member should set it 655 Integer groupVolume = mGroupVolumeCache.getOrDefault(groupId, 656 IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME); 657 if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) { 658 // Correct the volume level only if device was already reported as connected. 659 boolean can_change_volume = false; 660 synchronized (mStateMachines) { 661 VolumeControlStateMachine sm = mStateMachines.get(device); 662 if (sm != null) { 663 can_change_volume = 664 (sm.getConnectionState() == BluetoothProfile.STATE_CONNECTED); 665 } 666 } 667 if (can_change_volume) { 668 Log.i(TAG, "Setting value:" + groupVolume + " to " + device); 669 mVolumeControlNativeInterface.setVolume(device, groupVolume); 670 } 671 } 672 } 673 handleVolumeControlChanged(BluetoothDevice device, int groupId, int volume, boolean mute, boolean isAutonomous)674 void handleVolumeControlChanged(BluetoothDevice device, int groupId, 675 int volume, boolean mute, boolean isAutonomous) { 676 677 if (isAutonomous && device != null) { 678 Log.e(TAG, "We expect only group notification for autonomous updates"); 679 return; 680 } 681 682 if (groupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID) { 683 LeAudioService leAudioService = mFactory.getLeAudioService(); 684 if (leAudioService == null) { 685 Log.e(TAG, "leAudioService not available"); 686 return; 687 } 688 groupId = leAudioService.getGroupId(device); 689 } 690 691 if (groupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID) { 692 Log.e(TAG, "Device not a part of the group"); 693 return; 694 } 695 696 int groupVolume = getGroupVolume(groupId); 697 698 if (!isAutonomous) { 699 /* If the change is triggered by Android device, the stream is already changed. 700 * However it might be called with isAutonomous, one the first read of after 701 * reconnection. Make sure device has group volume. Also it might happen that 702 * remote side send us wrong value - lets check it. 703 */ 704 705 if (groupVolume == volume) { 706 Log.i(TAG, " Volume:" + volume + " confirmed by remote side."); 707 return; 708 } 709 710 if (device != null && groupVolume 711 != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) { 712 // Correct the volume level only if device was already reported as connected. 713 boolean can_change_volume = false; 714 synchronized (mStateMachines) { 715 VolumeControlStateMachine sm = mStateMachines.get(device); 716 if (sm != null) { 717 can_change_volume = 718 (sm.getConnectionState() == BluetoothProfile.STATE_CONNECTED); 719 } 720 } 721 if (can_change_volume) { 722 Log.i(TAG, "Setting value:" + groupVolume + " to " + device); 723 mVolumeControlNativeInterface.setVolume(device, groupVolume); 724 } 725 } else { 726 Log.e(TAG, "Volume changed did not succeed. Volume: " + volume 727 + " expected volume: " + groupVolume); 728 } 729 } else { 730 // TODO: Handle the other arguments: mute. 731 732 /* Received group notification for autonomous change. Update cache and audio system. */ 733 mGroupVolumeCache.put(groupId, volume); 734 735 int streamType = getBluetoothContextualVolumeStream(); 736 mAudioManager.setStreamVolume(streamType, getDeviceVolume(streamType, volume), 737 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME); 738 } 739 } 740 741 /** 742 * {@hide} 743 */ getAudioDeviceGroupVolume(int groupId)744 public int getAudioDeviceGroupVolume(int groupId) { 745 int volume = getGroupVolume(groupId); 746 if (volume == IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) return -1; 747 return getDeviceVolume(getBluetoothContextualVolumeStream(), volume); 748 } 749 getDeviceVolume(int streamType, int bleVolume)750 int getDeviceVolume(int streamType, int bleVolume) { 751 int deviceMaxVolume = mAudioManager.getStreamMaxVolume(streamType); 752 753 // TODO: Investigate what happens in classic BT when BT volume is changed to zero. 754 double deviceVolume = (double) (bleVolume * deviceMaxVolume) / LE_AUDIO_MAX_VOL; 755 return (int) Math.round(deviceVolume); 756 } 757 758 // Copied from AudioService.getBluetoothContextualVolumeStream() and modified it. getBluetoothContextualVolumeStream()759 int getBluetoothContextualVolumeStream() { 760 int mode = mAudioManager.getMode(); 761 switch (mode) { 762 case AudioManager.MODE_IN_COMMUNICATION: 763 case AudioManager.MODE_IN_CALL: 764 return AudioManager.STREAM_VOICE_CALL; 765 case AudioManager.MODE_NORMAL: 766 default: 767 // other conditions will influence the stream type choice, read on... 768 break; 769 } 770 return AudioManager.STREAM_MUSIC; 771 } 772 handleDeviceAvailable(BluetoothDevice device, int numberOfExternalOutputs)773 void handleDeviceAvailable(BluetoothDevice device, int numberOfExternalOutputs) { 774 if (numberOfExternalOutputs == 0) { 775 Log.i(TAG, "Volume offset not available"); 776 return; 777 } 778 779 VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device); 780 if (offsets == null) { 781 offsets = new VolumeControlOffsetDescriptor(); 782 mAudioOffsets.put(device, offsets); 783 } else if (offsets.size() != numberOfExternalOutputs) { 784 Log.i(TAG, "Number of offset changed: "); 785 offsets.clear(); 786 } 787 788 /* Stack delivers us number of audio outputs. 789 * Offset ids a countinous from 1 to number_of_ext_outputs*/ 790 for (int i = 1; i <= numberOfExternalOutputs; i++) { 791 offsets.add(i); 792 mVolumeControlNativeInterface.getExtAudioOutVolumeOffset(device, i); 793 mVolumeControlNativeInterface.getExtAudioOutDescription(device, i); 794 } 795 } 796 handleDeviceExtAudioOffsetChanged(BluetoothDevice device, int id, int value)797 void handleDeviceExtAudioOffsetChanged(BluetoothDevice device, int id, int value) { 798 if (DBG) { 799 Log.d(TAG, " device: " + device + " offset_id: " + id + " value: " + value); 800 } 801 VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device); 802 if (offsets == null) { 803 Log.e(TAG, " Offsets not found for device: " + device); 804 return; 805 } 806 offsets.setValue(id, value); 807 808 if (mCallbacks == null) { 809 return; 810 } 811 812 int n = mCallbacks.beginBroadcast(); 813 for (int i = 0; i < n; i++) { 814 try { 815 mCallbacks.getBroadcastItem(i).onVolumeOffsetChanged(device, value); 816 } catch (RemoteException e) { 817 continue; 818 } 819 } 820 mCallbacks.finishBroadcast(); 821 } 822 handleDeviceExtAudioLocationChanged(BluetoothDevice device, int id, int location)823 void handleDeviceExtAudioLocationChanged(BluetoothDevice device, int id, int location) { 824 if (DBG) { 825 Log.d(TAG, " device: " + device + " offset_id: " 826 + id + " location: " + location); 827 } 828 829 VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device); 830 if (offsets == null) { 831 Log.e(TAG, " Offsets not found for device: " + device); 832 return; 833 } 834 offsets.setLocation(id, location); 835 } 836 handleDeviceExtAudioDescriptionChanged(BluetoothDevice device, int id, String description)837 void handleDeviceExtAudioDescriptionChanged(BluetoothDevice device, int id, 838 String description) { 839 if (DBG) { 840 Log.d(TAG, " device: " + device + " offset_id: " 841 + id + " description: " + description); 842 } 843 844 VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device); 845 if (offsets == null) { 846 Log.e(TAG, " Offsets not found for device: " + device); 847 return; 848 } 849 offsets.setDescription(id, description); 850 } 851 messageFromNative(VolumeControlStackEvent stackEvent)852 void messageFromNative(VolumeControlStackEvent stackEvent) { 853 854 if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED) { 855 handleVolumeControlChanged(stackEvent.device, stackEvent.valueInt1, 856 stackEvent.valueInt2, stackEvent.valueBool1, 857 stackEvent.valueBool2); 858 return; 859 } 860 861 Objects.requireNonNull(stackEvent.device, 862 "Device should never be null, event: " + stackEvent); 863 864 Intent intent = null; 865 866 if (intent != null) { 867 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 868 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 869 sendBroadcast(intent, BLUETOOTH_CONNECT); 870 return; 871 } 872 873 BluetoothDevice device = stackEvent.device; 874 if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE) { 875 handleDeviceAvailable(device, stackEvent.valueInt1); 876 return; 877 } 878 879 if (stackEvent.type 880 == VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED) { 881 handleDeviceExtAudioOffsetChanged(device, stackEvent.valueInt1, stackEvent.valueInt2); 882 return; 883 } 884 885 if (stackEvent.type 886 == VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED) { 887 handleDeviceExtAudioLocationChanged(device, stackEvent.valueInt1, 888 stackEvent.valueInt2); 889 return; 890 } 891 892 if (stackEvent.type 893 == VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED) { 894 handleDeviceExtAudioDescriptionChanged(device, stackEvent.valueInt1, 895 stackEvent.valueString1); 896 return; 897 } 898 899 synchronized (mStateMachines) { 900 VolumeControlStateMachine sm = mStateMachines.get(device); 901 if (sm == null) { 902 if (stackEvent.type 903 == VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { 904 switch (stackEvent.valueInt1) { 905 case VolumeControlStackEvent.CONNECTION_STATE_CONNECTED: 906 case VolumeControlStackEvent.CONNECTION_STATE_CONNECTING: 907 sm = getOrCreateStateMachine(device); 908 break; 909 default: 910 break; 911 } 912 } 913 } 914 if (sm == null) { 915 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); 916 return; 917 } 918 sm.sendMessage(VolumeControlStateMachine.STACK_EVENT, stackEvent); 919 } 920 } 921 getOrCreateStateMachine(BluetoothDevice device)922 private VolumeControlStateMachine getOrCreateStateMachine(BluetoothDevice device) { 923 if (device == null) { 924 Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); 925 return null; 926 } 927 synchronized (mStateMachines) { 928 VolumeControlStateMachine sm = mStateMachines.get(device); 929 if (sm != null) { 930 return sm; 931 } 932 // Limit the maximum number of state machines to avoid DoS attack 933 if (mStateMachines.size() >= MAX_VC_STATE_MACHINES) { 934 Log.e(TAG, "Maximum number of VolumeControl state machines reached: " 935 + MAX_VC_STATE_MACHINES); 936 return null; 937 } 938 if (DBG) { 939 Log.d(TAG, "Creating a new state machine for " + device); 940 } 941 sm = VolumeControlStateMachine.make(device, this, 942 mVolumeControlNativeInterface, mStateMachinesThread.getLooper()); 943 mStateMachines.put(device, sm); 944 return sm; 945 } 946 } 947 948 // Remove state machine if the bonding for a device is removed 949 private class BondStateChangedReceiver extends BroadcastReceiver { 950 @Override onReceive(Context context, Intent intent)951 public void onReceive(Context context, Intent intent) { 952 if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { 953 return; 954 } 955 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 956 BluetoothDevice.ERROR); 957 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 958 Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 959 bondStateChanged(device, state); 960 } 961 } 962 963 /** 964 * Process a change in the bonding state for a device. 965 * 966 * @param device the device whose bonding state has changed 967 * @param bondState the new bond state for the device. Possible values are: 968 * {@link BluetoothDevice#BOND_NONE}, 969 * {@link BluetoothDevice#BOND_BONDING}, 970 * {@link BluetoothDevice#BOND_BONDED}. 971 */ 972 @VisibleForTesting bondStateChanged(BluetoothDevice device, int bondState)973 void bondStateChanged(BluetoothDevice device, int bondState) { 974 if (DBG) { 975 Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); 976 } 977 // Remove state machine if the bonding for a device is removed 978 if (bondState != BluetoothDevice.BOND_NONE) { 979 return; 980 } 981 982 synchronized (mStateMachines) { 983 VolumeControlStateMachine sm = mStateMachines.get(device); 984 if (sm == null) { 985 return; 986 } 987 if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { 988 Log.i(TAG, "Disconnecting device because it was unbonded."); 989 disconnect(device); 990 return; 991 } 992 removeStateMachine(device); 993 } 994 } 995 removeStateMachine(BluetoothDevice device)996 private void removeStateMachine(BluetoothDevice device) { 997 synchronized (mStateMachines) { 998 VolumeControlStateMachine sm = mStateMachines.get(device); 999 if (sm == null) { 1000 Log.w(TAG, "removeStateMachine: device " + device 1001 + " does not have a state machine"); 1002 return; 1003 } 1004 Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); 1005 sm.doQuit(); 1006 sm.cleanup(); 1007 mStateMachines.remove(device); 1008 } 1009 } 1010 1011 @VisibleForTesting connectionStateChanged(BluetoothDevice device, int fromState, int toState)1012 synchronized void connectionStateChanged(BluetoothDevice device, int fromState, 1013 int toState) { 1014 if ((device == null) || (fromState == toState)) { 1015 Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device 1016 + " fromState=" + fromState + " toState=" + toState); 1017 return; 1018 } 1019 1020 // Check if the device is disconnected - if unbond, remove the state machine 1021 if (toState == BluetoothProfile.STATE_DISCONNECTED) { 1022 int bondState = mAdapterService.getBondState(device); 1023 if (bondState == BluetoothDevice.BOND_NONE) { 1024 if (DBG) { 1025 Log.d(TAG, device + " is unbond. Remove state machine"); 1026 } 1027 removeStateMachine(device); 1028 } 1029 } else if (toState == BluetoothProfile.STATE_CONNECTED) { 1030 // Restore the group volume if it was changed while the device was not yet connected. 1031 CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService(); 1032 Integer groupId = csipClient.getGroupId(device, BluetoothUuid.CAP); 1033 if (groupId != IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID) { 1034 Integer groupVolume = mGroupVolumeCache.getOrDefault(groupId, 1035 IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME); 1036 if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) { 1037 mVolumeControlNativeInterface.setVolume(device, groupVolume); 1038 } 1039 } 1040 } 1041 } 1042 1043 private class ConnectionStateChangedReceiver extends BroadcastReceiver { 1044 @Override onReceive(Context context, Intent intent)1045 public void onReceive(Context context, Intent intent) { 1046 if (!BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 1047 return; 1048 } 1049 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 1050 int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 1051 int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 1052 connectionStateChanged(device, fromState, toState); 1053 } 1054 } 1055 1056 /** 1057 * Binder object: must be a static class or memory leak may occur 1058 */ 1059 @VisibleForTesting 1060 static class BluetoothVolumeControlBinder extends IBluetoothVolumeControl.Stub 1061 implements IProfileServiceBinder { 1062 @VisibleForTesting 1063 boolean mIsTesting = false; 1064 private VolumeControlService mService; 1065 1066 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)1067 private VolumeControlService getService(AttributionSource source) { 1068 if (mIsTesting) { 1069 return mService; 1070 } 1071 if (!Utils.checkServiceAvailable(mService, TAG) 1072 || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG) 1073 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 1074 return null; 1075 } 1076 return mService; 1077 } 1078 BluetoothVolumeControlBinder(VolumeControlService svc)1079 BluetoothVolumeControlBinder(VolumeControlService svc) { 1080 mService = svc; 1081 } 1082 1083 @Override cleanup()1084 public void cleanup() { 1085 mService = null; 1086 } 1087 1088 @Override connect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1089 public void connect(BluetoothDevice device, AttributionSource source, 1090 SynchronousResultReceiver receiver) { 1091 try { 1092 Objects.requireNonNull(device, "device cannot be null"); 1093 Objects.requireNonNull(source, "source cannot be null"); 1094 Objects.requireNonNull(receiver, "receiver cannot be null"); 1095 1096 VolumeControlService service = getService(source); 1097 boolean defaultValue = false; 1098 if (service != null) { 1099 defaultValue = service.connect(device); 1100 } 1101 receiver.send(defaultValue); 1102 } catch (RuntimeException e) { 1103 receiver.propagateException(e); 1104 } 1105 } 1106 1107 @Override disconnect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1108 public void disconnect(BluetoothDevice device, AttributionSource source, 1109 SynchronousResultReceiver receiver) { 1110 try { 1111 Objects.requireNonNull(device, "device cannot be null"); 1112 Objects.requireNonNull(source, "source cannot be null"); 1113 Objects.requireNonNull(receiver, "receiver cannot be null"); 1114 1115 VolumeControlService service = getService(source); 1116 boolean defaultValue = false; 1117 if (service != null) { 1118 defaultValue = service.disconnect(device); 1119 } 1120 receiver.send(defaultValue); 1121 } catch (RuntimeException e) { 1122 receiver.propagateException(e); 1123 } 1124 } 1125 1126 @Override getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)1127 public void getConnectedDevices(AttributionSource source, 1128 SynchronousResultReceiver receiver) { 1129 try { 1130 Objects.requireNonNull(source, "source cannot be null"); 1131 Objects.requireNonNull(receiver, "receiver cannot be null"); 1132 1133 VolumeControlService service = getService(source); 1134 List<BluetoothDevice> defaultValue = new ArrayList<>(); 1135 if (service != null) { 1136 enforceBluetoothPrivilegedPermission(service); 1137 defaultValue = service.getConnectedDevices(); 1138 } 1139 receiver.send(defaultValue); 1140 } catch (RuntimeException e) { 1141 receiver.propagateException(e); 1142 } 1143 } 1144 1145 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)1146 public void getDevicesMatchingConnectionStates(int[] states, 1147 AttributionSource source, SynchronousResultReceiver receiver) { 1148 try { 1149 Objects.requireNonNull(source, "source cannot be null"); 1150 Objects.requireNonNull(receiver, "receiver cannot be null"); 1151 1152 VolumeControlService service = getService(source); 1153 List<BluetoothDevice> defaultValue = new ArrayList<>(); 1154 if (service != null) { 1155 defaultValue = service.getDevicesMatchingConnectionStates(states); 1156 } 1157 receiver.send(defaultValue); 1158 } catch (RuntimeException e) { 1159 receiver.propagateException(e); 1160 } 1161 } 1162 1163 @Override getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1164 public void getConnectionState(BluetoothDevice device, AttributionSource source, 1165 SynchronousResultReceiver receiver) { 1166 try { 1167 Objects.requireNonNull(device, "device cannot be null"); 1168 Objects.requireNonNull(source, "source cannot be null"); 1169 Objects.requireNonNull(receiver, "receiver cannot be null"); 1170 1171 VolumeControlService service = getService(source); 1172 int defaultValue = BluetoothProfile.STATE_DISCONNECTED; 1173 if (service != null) { 1174 defaultValue = service.getConnectionState(device); 1175 } 1176 receiver.send(defaultValue); 1177 } catch (RuntimeException e) { 1178 receiver.propagateException(e); 1179 } 1180 } 1181 1182 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)1183 public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 1184 AttributionSource source, SynchronousResultReceiver receiver) { 1185 try { 1186 Objects.requireNonNull(device, "device cannot be null"); 1187 Objects.requireNonNull(source, "source cannot be null"); 1188 Objects.requireNonNull(receiver, "receiver cannot be null"); 1189 1190 VolumeControlService service = getService(source); 1191 boolean defaultValue = false; 1192 if (service != null) { 1193 defaultValue = service.setConnectionPolicy(device, connectionPolicy); 1194 } 1195 receiver.send(defaultValue); 1196 } catch (RuntimeException e) { 1197 receiver.propagateException(e); 1198 } 1199 } 1200 1201 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1202 public void getConnectionPolicy(BluetoothDevice device, AttributionSource source, 1203 SynchronousResultReceiver receiver) { 1204 try { 1205 Objects.requireNonNull(device, "device cannot be null"); 1206 Objects.requireNonNull(source, "source cannot be null"); 1207 Objects.requireNonNull(receiver, "receiver cannot be null"); 1208 1209 VolumeControlService service = getService(source); 1210 int defaultValue = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 1211 if (service != null) { 1212 defaultValue = service.getConnectionPolicy(device); 1213 } 1214 receiver.send(defaultValue); 1215 } catch (RuntimeException e) { 1216 receiver.propagateException(e); 1217 } 1218 } 1219 1220 @Override isVolumeOffsetAvailable(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1221 public void isVolumeOffsetAvailable(BluetoothDevice device, 1222 AttributionSource source, SynchronousResultReceiver receiver) { 1223 try { 1224 Objects.requireNonNull(device, "device cannot be null"); 1225 Objects.requireNonNull(source, "source cannot be null"); 1226 Objects.requireNonNull(receiver, "receiver cannot be null"); 1227 1228 boolean defaultValue = false; 1229 VolumeControlService service = getService(source); 1230 if (service != null) { 1231 defaultValue = service.isVolumeOffsetAvailable(device); 1232 } 1233 receiver.send(defaultValue); 1234 } catch (RuntimeException e) { 1235 receiver.propagateException(e); 1236 } 1237 } 1238 1239 @Override setVolumeOffset(BluetoothDevice device, int volumeOffset, AttributionSource source, SynchronousResultReceiver receiver)1240 public void setVolumeOffset(BluetoothDevice device, int volumeOffset, 1241 AttributionSource source, SynchronousResultReceiver receiver) { 1242 try { 1243 Objects.requireNonNull(device, "device cannot be null"); 1244 Objects.requireNonNull(source, "source cannot be null"); 1245 Objects.requireNonNull(receiver, "receiver cannot be null"); 1246 1247 VolumeControlService service = getService(source); 1248 if (service != null) { 1249 service.setVolumeOffset(device, volumeOffset); 1250 } 1251 receiver.send(null); 1252 } catch (RuntimeException e) { 1253 receiver.propagateException(e); 1254 } 1255 } 1256 1257 @Override setGroupVolume(int groupId, int volume, AttributionSource source, SynchronousResultReceiver receiver)1258 public void setGroupVolume(int groupId, int volume, AttributionSource source, 1259 SynchronousResultReceiver receiver) { 1260 try { 1261 Objects.requireNonNull(source, "source cannot be null"); 1262 Objects.requireNonNull(receiver, "receiver cannot be null"); 1263 1264 VolumeControlService service = getService(source); 1265 if (service != null) { 1266 service.setGroupVolume(groupId, volume); 1267 } 1268 receiver.send(null); 1269 } catch (RuntimeException e) { 1270 receiver.propagateException(e); 1271 } 1272 } 1273 1274 @Override getGroupVolume(int groupId, AttributionSource source, SynchronousResultReceiver receiver)1275 public void getGroupVolume(int groupId, AttributionSource source, 1276 SynchronousResultReceiver receiver) { 1277 try { 1278 Objects.requireNonNull(source, "source cannot be null"); 1279 Objects.requireNonNull(receiver, "receiver cannot be null"); 1280 1281 int groupVolume = 0; 1282 VolumeControlService service = getService(source); 1283 if (service != null) { 1284 groupVolume = service.getGroupVolume(groupId); 1285 } 1286 receiver.send(groupVolume); 1287 } catch (RuntimeException e) { 1288 receiver.propagateException(e); 1289 } 1290 } 1291 1292 1293 @Override mute(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1294 public void mute(BluetoothDevice device, AttributionSource source, 1295 SynchronousResultReceiver receiver) { 1296 try { 1297 Objects.requireNonNull(device, "device cannot be null"); 1298 Objects.requireNonNull(source, "source cannot be null"); 1299 Objects.requireNonNull(receiver, "receiver cannot be null"); 1300 1301 VolumeControlService service = getService(source); 1302 if (service != null) { 1303 service.mute(device); 1304 } 1305 receiver.send(null); 1306 } catch (RuntimeException e) { 1307 receiver.propagateException(e); 1308 } 1309 } 1310 1311 @Override muteGroup(int groupId, AttributionSource source, SynchronousResultReceiver receiver)1312 public void muteGroup(int groupId, AttributionSource source, 1313 SynchronousResultReceiver receiver) { 1314 try { 1315 Objects.requireNonNull(source, "source cannot be null"); 1316 Objects.requireNonNull(receiver, "receiver cannot be null"); 1317 1318 VolumeControlService service = getService(source); 1319 if (service != null) { 1320 service.muteGroup(groupId); 1321 } 1322 receiver.send(null); 1323 } catch (RuntimeException e) { 1324 receiver.propagateException(e); 1325 } 1326 } 1327 1328 @Override unmute(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1329 public void unmute(BluetoothDevice device, AttributionSource source, 1330 SynchronousResultReceiver receiver) { 1331 try { 1332 Objects.requireNonNull(device, "device cannot be null"); 1333 Objects.requireNonNull(source, "source cannot be null"); 1334 Objects.requireNonNull(receiver, "receiver cannot be null"); 1335 1336 VolumeControlService service = getService(source); 1337 if (service != null) { 1338 service.unmute(device); 1339 } 1340 receiver.send(null); 1341 } catch (RuntimeException e) { 1342 receiver.propagateException(e); 1343 } 1344 } 1345 1346 @Override unmuteGroup(int groupId, AttributionSource source, SynchronousResultReceiver receiver)1347 public void unmuteGroup(int groupId, AttributionSource source, 1348 SynchronousResultReceiver receiver) { 1349 try { 1350 Objects.requireNonNull(source, "source cannot be null"); 1351 Objects.requireNonNull(receiver, "receiver cannot be null"); 1352 1353 VolumeControlService service = getService(source); 1354 if (service != null) { 1355 service.unmuteGroup(groupId); 1356 } 1357 receiver.send(null); 1358 } catch (RuntimeException e) { 1359 receiver.propagateException(e); 1360 } 1361 } 1362 1363 @Override registerCallback(IBluetoothVolumeControlCallback callback, AttributionSource source, SynchronousResultReceiver receiver)1364 public void registerCallback(IBluetoothVolumeControlCallback callback, 1365 AttributionSource source, SynchronousResultReceiver receiver) { 1366 try { 1367 Objects.requireNonNull(callback, "callback cannot be null"); 1368 Objects.requireNonNull(source, "source cannot be null"); 1369 Objects.requireNonNull(receiver, "receiver cannot be null"); 1370 1371 VolumeControlService service = getService(source); 1372 if (service == null) { 1373 throw new IllegalStateException("Service is unavailable"); 1374 } 1375 1376 enforceBluetoothPrivilegedPermission(service); 1377 1378 service.mCallbacks.register(callback); 1379 receiver.send(null); 1380 } catch (RuntimeException e) { 1381 receiver.propagateException(e); 1382 } 1383 } 1384 1385 @Override unregisterCallback(IBluetoothVolumeControlCallback callback, AttributionSource source, SynchronousResultReceiver receiver)1386 public void unregisterCallback(IBluetoothVolumeControlCallback callback, 1387 AttributionSource source, SynchronousResultReceiver receiver) { 1388 try { 1389 Objects.requireNonNull(callback, "callback cannot be null"); 1390 Objects.requireNonNull(source, "source cannot be null"); 1391 Objects.requireNonNull(receiver, "receiver cannot be null"); 1392 1393 VolumeControlService service = getService(source); 1394 if (service == null) { 1395 throw new IllegalStateException("Service is unavailable"); 1396 } 1397 1398 enforceBluetoothPrivilegedPermission(service); 1399 1400 service.mCallbacks.unregister(callback); 1401 receiver.send(null); 1402 } catch (RuntimeException e) { 1403 receiver.propagateException(e); 1404 } 1405 } 1406 } 1407 1408 @Override dump(StringBuilder sb)1409 public void dump(StringBuilder sb) { 1410 super.dump(sb); 1411 for (VolumeControlStateMachine sm : mStateMachines.values()) { 1412 sm.dump(sb); 1413 } 1414 1415 for (Map.Entry<BluetoothDevice, VolumeControlOffsetDescriptor> entry : 1416 mAudioOffsets.entrySet()) { 1417 VolumeControlOffsetDescriptor descriptor = entry.getValue(); 1418 BluetoothDevice device = entry.getKey(); 1419 ProfileService.println(sb, " Device: " + device); 1420 ProfileService.println(sb, " Volume offset cnt: " + descriptor.size()); 1421 descriptor.dump(sb); 1422 } 1423 for (Map.Entry<Integer, Integer> entry : mGroupVolumeCache.entrySet()) { 1424 ProfileService.println(sb, " GroupId: " + entry.getKey() + " volume: " 1425 + entry.getValue()); 1426 } 1427 } 1428 } 1429