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