1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.bluetooth; 18 19 import android.Manifest; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SdkConstant; 25 import android.annotation.SdkConstant.SdkConstantType; 26 import android.annotation.SystemApi; 27 import android.compat.annotation.UnsupportedAppUsage; 28 import android.content.Context; 29 import android.os.Binder; 30 import android.os.Build; 31 import android.os.IBinder; 32 import android.os.ParcelUuid; 33 import android.os.RemoteException; 34 import android.util.Log; 35 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.util.ArrayList; 39 import java.util.List; 40 41 42 /** 43 * This class provides the public APIs to control the Bluetooth A2DP 44 * profile. 45 * 46 * <p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP 47 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get 48 * the BluetoothA2dp proxy object. 49 * 50 * <p> Android only supports one connected Bluetooth A2dp device at a time. 51 * Each method is protected with its appropriate permission. 52 */ 53 public final class BluetoothA2dp implements BluetoothProfile { 54 private static final String TAG = "BluetoothA2dp"; 55 private static final boolean DBG = true; 56 private static final boolean VDBG = false; 57 58 /** 59 * Intent used to broadcast the change in connection state of the A2DP 60 * profile. 61 * 62 * <p>This intent will have 3 extras: 63 * <ul> 64 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 65 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> 66 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 67 * </ul> 68 * 69 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 70 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 71 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 72 * 73 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 74 * receive. 75 */ 76 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 77 public static final String ACTION_CONNECTION_STATE_CHANGED = 78 "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"; 79 80 /** 81 * Intent used to broadcast the change in the Playing state of the A2DP 82 * profile. 83 * 84 * <p>This intent will have 3 extras: 85 * <ul> 86 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 87 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 88 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 89 * </ul> 90 * 91 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 92 * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, 93 * 94 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 95 * receive. 96 */ 97 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 98 public static final String ACTION_PLAYING_STATE_CHANGED = 99 "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"; 100 101 /** @hide */ 102 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 103 public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED = 104 "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED"; 105 106 /** 107 * Intent used to broadcast the selection of a connected device as active. 108 * 109 * <p>This intent will have one extra: 110 * <ul> 111 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can 112 * be null if no device is active. </li> 113 * </ul> 114 * 115 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 116 * receive. 117 * 118 * @hide 119 */ 120 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 121 @UnsupportedAppUsage 122 public static final String ACTION_ACTIVE_DEVICE_CHANGED = 123 "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED"; 124 125 /** 126 * Intent used to broadcast the change in the Audio Codec state of the 127 * A2DP Source profile. 128 * 129 * <p>This intent will have 2 extras: 130 * <ul> 131 * <li> {@link BluetoothCodecStatus#EXTRA_CODEC_STATUS} - The codec status. </li> 132 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently 133 * connected, otherwise it is not included.</li> 134 * </ul> 135 * 136 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 137 * receive. 138 * 139 * @hide 140 */ 141 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 142 @UnsupportedAppUsage 143 public static final String ACTION_CODEC_CONFIG_CHANGED = 144 "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED"; 145 146 /** 147 * A2DP sink device is streaming music. This state can be one of 148 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 149 * {@link #ACTION_PLAYING_STATE_CHANGED} intent. 150 */ 151 public static final int STATE_PLAYING = 10; 152 153 /** 154 * A2DP sink device is NOT streaming music. This state can be one of 155 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 156 * {@link #ACTION_PLAYING_STATE_CHANGED} intent. 157 */ 158 public static final int STATE_NOT_PLAYING = 11; 159 160 /** @hide */ 161 @IntDef(prefix = "OPTIONAL_CODECS_", value = { 162 OPTIONAL_CODECS_SUPPORT_UNKNOWN, 163 OPTIONAL_CODECS_NOT_SUPPORTED, 164 OPTIONAL_CODECS_SUPPORTED 165 }) 166 @Retention(RetentionPolicy.SOURCE) 167 public @interface OptionalCodecsSupportStatus {} 168 169 /** 170 * We don't have a stored preference for whether or not the given A2DP sink device supports 171 * optional codecs. 172 * 173 * @hide 174 */ 175 @SystemApi 176 public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1; 177 178 /** 179 * The given A2DP sink device does not support optional codecs. 180 * 181 * @hide 182 */ 183 @SystemApi 184 public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0; 185 186 /** 187 * The given A2DP sink device does support optional codecs. 188 * 189 * @hide 190 */ 191 @SystemApi 192 public static final int OPTIONAL_CODECS_SUPPORTED = 1; 193 194 /** @hide */ 195 @IntDef(prefix = "OPTIONAL_CODECS_PREF_", value = { 196 OPTIONAL_CODECS_PREF_UNKNOWN, 197 OPTIONAL_CODECS_PREF_DISABLED, 198 OPTIONAL_CODECS_PREF_ENABLED 199 }) 200 @Retention(RetentionPolicy.SOURCE) 201 public @interface OptionalCodecsPreferenceStatus {} 202 203 /** 204 * We don't have a stored preference for whether optional codecs should be enabled or 205 * disabled for the given A2DP device. 206 * 207 * @hide 208 */ 209 @SystemApi 210 public static final int OPTIONAL_CODECS_PREF_UNKNOWN = -1; 211 212 /** 213 * Optional codecs should be disabled for the given A2DP device. 214 * 215 * @hide 216 */ 217 @SystemApi 218 public static final int OPTIONAL_CODECS_PREF_DISABLED = 0; 219 220 /** 221 * Optional codecs should be enabled for the given A2DP device. 222 * 223 * @hide 224 */ 225 @SystemApi 226 public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; 227 228 private BluetoothAdapter mAdapter; 229 private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector = 230 new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp", 231 IBluetoothA2dp.class.getName()) { 232 @Override 233 public IBluetoothA2dp getServiceInterface(IBinder service) { 234 return IBluetoothA2dp.Stub.asInterface(Binder.allowBlocking(service)); 235 } 236 }; 237 238 /** 239 * Create a BluetoothA2dp proxy object for interacting with the local 240 * Bluetooth A2DP service. 241 */ BluetoothA2dp(Context context, ServiceListener listener)242 /*package*/ BluetoothA2dp(Context context, ServiceListener listener) { 243 mAdapter = BluetoothAdapter.getDefaultAdapter(); 244 mProfileConnector.connect(context, listener); 245 } 246 247 @UnsupportedAppUsage close()248 /*package*/ void close() { 249 mProfileConnector.disconnect(); 250 } 251 getService()252 private IBluetoothA2dp getService() { 253 return mProfileConnector.getService(); 254 } 255 256 @Override finalize()257 public void finalize() { 258 // The empty finalize needs to be kept or the 259 // cts signature tests would fail. 260 } 261 262 /** 263 * Initiate connection to a profile of the remote Bluetooth device. 264 * 265 * <p> This API returns false in scenarios like the profile on the 266 * device is already connected or Bluetooth is not turned on. 267 * When this API returns true, it is guaranteed that 268 * connection state intent for the profile will be broadcasted with 269 * the state. Users can get the connection state of the profile 270 * from this intent. 271 * 272 * 273 * @param device Remote Bluetooth Device 274 * @return false on immediate error, true otherwise 275 * @hide 276 */ 277 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) 278 @UnsupportedAppUsage connect(BluetoothDevice device)279 public boolean connect(BluetoothDevice device) { 280 if (DBG) log("connect(" + device + ")"); 281 try { 282 final IBluetoothA2dp service = getService(); 283 if (service != null && isEnabled() && isValidDevice(device)) { 284 return service.connect(device); 285 } 286 if (service == null) Log.w(TAG, "Proxy not attached to service"); 287 return false; 288 } catch (RemoteException e) { 289 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 290 return false; 291 } 292 } 293 294 /** 295 * Initiate disconnection from a profile 296 * 297 * <p> This API will return false in scenarios like the profile on the 298 * Bluetooth device is not in connected state etc. When this API returns, 299 * true, it is guaranteed that the connection state change 300 * intent will be broadcasted with the state. Users can get the 301 * disconnection state of the profile from this intent. 302 * 303 * <p> If the disconnection is initiated by a remote device, the state 304 * will transition from {@link #STATE_CONNECTED} to 305 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 306 * host (local) device the state will transition from 307 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 308 * state {@link #STATE_DISCONNECTED}. The transition to 309 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 310 * two scenarios. 311 * 312 * 313 * @param device Remote Bluetooth Device 314 * @return false on immediate error, true otherwise 315 * @hide 316 */ 317 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) 318 @UnsupportedAppUsage disconnect(BluetoothDevice device)319 public boolean disconnect(BluetoothDevice device) { 320 if (DBG) log("disconnect(" + device + ")"); 321 try { 322 final IBluetoothA2dp service = getService(); 323 if (service != null && isEnabled() && isValidDevice(device)) { 324 return service.disconnect(device); 325 } 326 if (service == null) Log.w(TAG, "Proxy not attached to service"); 327 return false; 328 } catch (RemoteException e) { 329 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 330 return false; 331 } 332 } 333 334 /** 335 * {@inheritDoc} 336 */ 337 @Override getConnectedDevices()338 public List<BluetoothDevice> getConnectedDevices() { 339 if (VDBG) log("getConnectedDevices()"); 340 try { 341 final IBluetoothA2dp service = getService(); 342 if (service != null && isEnabled()) { 343 return service.getConnectedDevices(); 344 } 345 if (service == null) Log.w(TAG, "Proxy not attached to service"); 346 return new ArrayList<BluetoothDevice>(); 347 } catch (RemoteException e) { 348 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 349 return new ArrayList<BluetoothDevice>(); 350 } 351 } 352 353 /** 354 * {@inheritDoc} 355 */ 356 @Override getDevicesMatchingConnectionStates(int[] states)357 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 358 if (VDBG) log("getDevicesMatchingStates()"); 359 try { 360 final IBluetoothA2dp service = getService(); 361 if (service != null && isEnabled()) { 362 return service.getDevicesMatchingConnectionStates(states); 363 } 364 if (service == null) Log.w(TAG, "Proxy not attached to service"); 365 return new ArrayList<BluetoothDevice>(); 366 } catch (RemoteException e) { 367 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 368 return new ArrayList<BluetoothDevice>(); 369 } 370 } 371 372 /** 373 * {@inheritDoc} 374 */ 375 @Override getConnectionState(BluetoothDevice device)376 public @BtProfileState int getConnectionState(BluetoothDevice device) { 377 if (VDBG) log("getState(" + device + ")"); 378 try { 379 final IBluetoothA2dp service = getService(); 380 if (service != null && isEnabled() 381 && isValidDevice(device)) { 382 return service.getConnectionState(device); 383 } 384 if (service == null) Log.w(TAG, "Proxy not attached to service"); 385 return BluetoothProfile.STATE_DISCONNECTED; 386 } catch (RemoteException e) { 387 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 388 return BluetoothProfile.STATE_DISCONNECTED; 389 } 390 } 391 392 /** 393 * Select a connected device as active. 394 * 395 * The active device selection is per profile. An active device's 396 * purpose is profile-specific. For example, A2DP audio streaming 397 * is to the active A2DP Sink device. If a remote device is not 398 * connected, it cannot be selected as active. 399 * 400 * <p> This API returns false in scenarios like the profile on the 401 * device is not connected or Bluetooth is not turned on. 402 * When this API returns true, it is guaranteed that the 403 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted 404 * with the active device. 405 * 406 * @param device the remote Bluetooth device. Could be null to clear 407 * the active device and stop streaming audio to a Bluetooth device. 408 * @return false on immediate error, true otherwise 409 * @hide 410 */ 411 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) 412 @UnsupportedAppUsage setActiveDevice(@ullable BluetoothDevice device)413 public boolean setActiveDevice(@Nullable BluetoothDevice device) { 414 if (DBG) log("setActiveDevice(" + device + ")"); 415 try { 416 final IBluetoothA2dp service = getService(); 417 if (service != null && isEnabled() 418 && ((device == null) || isValidDevice(device))) { 419 return service.setActiveDevice(device); 420 } 421 if (service == null) Log.w(TAG, "Proxy not attached to service"); 422 return false; 423 } catch (RemoteException e) { 424 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 425 return false; 426 } 427 } 428 429 /** 430 * Get the connected device that is active. 431 * 432 * @return the connected device that is active or null if no device 433 * is active 434 * @hide 435 */ 436 @UnsupportedAppUsage 437 @Nullable 438 @RequiresPermission(Manifest.permission.BLUETOOTH) getActiveDevice()439 public BluetoothDevice getActiveDevice() { 440 if (VDBG) log("getActiveDevice()"); 441 try { 442 final IBluetoothA2dp service = getService(); 443 if (service != null && isEnabled()) { 444 return service.getActiveDevice(); 445 } 446 if (service == null) Log.w(TAG, "Proxy not attached to service"); 447 return null; 448 } catch (RemoteException e) { 449 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 450 return null; 451 } 452 } 453 454 /** 455 * Set priority of the profile 456 * 457 * <p> The device should already be paired. 458 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF} 459 * 460 * @param device Paired bluetooth device 461 * @param priority 462 * @return true if priority is set, false on error 463 * @hide 464 */ 465 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) setPriority(BluetoothDevice device, int priority)466 public boolean setPriority(BluetoothDevice device, int priority) { 467 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 468 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); 469 } 470 471 /** 472 * Set connection policy of the profile 473 * 474 * <p> The device should already be paired. 475 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 476 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 477 * 478 * @param device Paired bluetooth device 479 * @param connectionPolicy is the connection policy to set to for this profile 480 * @return true if connectionPolicy is set, false on error 481 * @hide 482 */ 483 @SystemApi 484 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)485 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 486 @ConnectionPolicy int connectionPolicy) { 487 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 488 try { 489 final IBluetoothA2dp service = getService(); 490 if (service != null && isEnabled() 491 && isValidDevice(device)) { 492 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 493 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 494 return false; 495 } 496 return service.setConnectionPolicy(device, connectionPolicy); 497 } 498 if (service == null) Log.w(TAG, "Proxy not attached to service"); 499 return false; 500 } catch (RemoteException e) { 501 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 502 return false; 503 } 504 } 505 506 /** 507 * Get the priority of the profile. 508 * 509 * <p> The priority can be any of: 510 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 511 * 512 * @param device Bluetooth device 513 * @return priority of the device 514 * @hide 515 */ 516 @RequiresPermission(Manifest.permission.BLUETOOTH) 517 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) getPriority(BluetoothDevice device)518 public int getPriority(BluetoothDevice device) { 519 if (VDBG) log("getPriority(" + device + ")"); 520 try { 521 final IBluetoothA2dp service = getService(); 522 if (service != null && isEnabled() 523 && isValidDevice(device)) { 524 return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device)); 525 } 526 if (service == null) Log.w(TAG, "Proxy not attached to service"); 527 return BluetoothProfile.PRIORITY_OFF; 528 } catch (RemoteException e) { 529 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 530 return BluetoothProfile.PRIORITY_OFF; 531 } 532 } 533 534 /** 535 * Get the connection policy of the profile. 536 * 537 * <p> The connection policy can be any of: 538 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, 539 * {@link #CONNECTION_POLICY_UNKNOWN} 540 * 541 * @param device Bluetooth device 542 * @return connection policy of the device 543 * @hide 544 */ 545 @SystemApi 546 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectionPolicy(@onNull BluetoothDevice device)547 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 548 if (VDBG) log("getConnectionPolicy(" + device + ")"); 549 try { 550 final IBluetoothA2dp service = getService(); 551 if (service != null && isEnabled() 552 && isValidDevice(device)) { 553 return service.getConnectionPolicy(device); 554 } 555 if (service == null) Log.w(TAG, "Proxy not attached to service"); 556 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 557 } catch (RemoteException e) { 558 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 559 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 560 } 561 } 562 563 /** 564 * Checks if Avrcp device supports the absolute volume feature. 565 * 566 * @return true if device supports absolute volume 567 * @hide 568 */ isAvrcpAbsoluteVolumeSupported()569 public boolean isAvrcpAbsoluteVolumeSupported() { 570 if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported"); 571 try { 572 final IBluetoothA2dp service = getService(); 573 if (service != null && isEnabled()) { 574 return service.isAvrcpAbsoluteVolumeSupported(); 575 } 576 if (service == null) Log.w(TAG, "Proxy not attached to service"); 577 return false; 578 } catch (RemoteException e) { 579 Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e); 580 return false; 581 } 582 } 583 584 /** 585 * Tells remote device to set an absolute volume. Only if absolute volume is supported 586 * 587 * @param volume Absolute volume to be set on AVRCP side 588 * @hide 589 */ setAvrcpAbsoluteVolume(int volume)590 public void setAvrcpAbsoluteVolume(int volume) { 591 if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume"); 592 try { 593 final IBluetoothA2dp service = getService(); 594 if (service != null && isEnabled()) { 595 service.setAvrcpAbsoluteVolume(volume); 596 } 597 if (service == null) Log.w(TAG, "Proxy not attached to service"); 598 } catch (RemoteException e) { 599 Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e); 600 } 601 } 602 603 /** 604 * Check if A2DP profile is streaming music. 605 * 606 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 607 * 608 * @param device BluetoothDevice device 609 */ isA2dpPlaying(BluetoothDevice device)610 public boolean isA2dpPlaying(BluetoothDevice device) { 611 try { 612 final IBluetoothA2dp service = getService(); 613 if (service != null && isEnabled() 614 && isValidDevice(device)) { 615 return service.isA2dpPlaying(device); 616 } 617 if (service == null) Log.w(TAG, "Proxy not attached to service"); 618 return false; 619 } catch (RemoteException e) { 620 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 621 return false; 622 } 623 } 624 625 /** 626 * This function checks if the remote device is an AVCRP 627 * target and thus whether we should send volume keys 628 * changes or not. 629 * 630 * @hide 631 */ shouldSendVolumeKeys(BluetoothDevice device)632 public boolean shouldSendVolumeKeys(BluetoothDevice device) { 633 if (isEnabled() && isValidDevice(device)) { 634 ParcelUuid[] uuids = device.getUuids(); 635 if (uuids == null) return false; 636 637 for (ParcelUuid uuid : uuids) { 638 if (uuid.equals(BluetoothUuid.AVRCP_TARGET)) { 639 return true; 640 } 641 } 642 } 643 return false; 644 } 645 646 /** 647 * Gets the current codec status (configuration and capability). 648 * 649 * @param device the remote Bluetooth device. If null, use the current 650 * active A2DP Bluetooth device. 651 * @return the current codec status 652 * @hide 653 */ 654 @UnsupportedAppUsage 655 @Nullable 656 @RequiresPermission(Manifest.permission.BLUETOOTH) getCodecStatus(@onNull BluetoothDevice device)657 public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { 658 if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); 659 verifyDeviceNotNull(device, "getCodecStatus"); 660 try { 661 final IBluetoothA2dp service = getService(); 662 if (service != null && isEnabled()) { 663 return service.getCodecStatus(device); 664 } 665 if (service == null) { 666 Log.w(TAG, "Proxy not attached to service"); 667 } 668 return null; 669 } catch (RemoteException e) { 670 Log.e(TAG, "Error talking to BT service in getCodecStatus()", e); 671 return null; 672 } 673 } 674 675 /** 676 * Sets the codec configuration preference. 677 * 678 * @param device the remote Bluetooth device. If null, use the current 679 * active A2DP Bluetooth device. 680 * @param codecConfig the codec configuration preference 681 * @hide 682 */ 683 @UnsupportedAppUsage 684 @RequiresPermission(Manifest.permission.BLUETOOTH) setCodecConfigPreference(@onNull BluetoothDevice device, @NonNull BluetoothCodecConfig codecConfig)685 public void setCodecConfigPreference(@NonNull BluetoothDevice device, 686 @NonNull BluetoothCodecConfig codecConfig) { 687 if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")"); 688 verifyDeviceNotNull(device, "setCodecConfigPreference"); 689 if (codecConfig == null) { 690 Log.e(TAG, "setCodecConfigPreference: Codec config can't be null"); 691 throw new IllegalArgumentException("codecConfig cannot be null"); 692 } 693 try { 694 final IBluetoothA2dp service = getService(); 695 if (service != null && isEnabled()) { 696 service.setCodecConfigPreference(device, codecConfig); 697 } 698 if (service == null) Log.w(TAG, "Proxy not attached to service"); 699 return; 700 } catch (RemoteException e) { 701 Log.e(TAG, "Error talking to BT service in setCodecConfigPreference()", e); 702 return; 703 } 704 } 705 706 /** 707 * Enables the optional codecs. 708 * 709 * @param device the remote Bluetooth device. If null, use the currect 710 * active A2DP Bluetooth device. 711 * @hide 712 */ 713 @UnsupportedAppUsage 714 @RequiresPermission(Manifest.permission.BLUETOOTH) enableOptionalCodecs(@onNull BluetoothDevice device)715 public void enableOptionalCodecs(@NonNull BluetoothDevice device) { 716 if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")"); 717 verifyDeviceNotNull(device, "enableOptionalCodecs"); 718 enableDisableOptionalCodecs(device, true); 719 } 720 721 /** 722 * Disables the optional codecs. 723 * 724 * @param device the remote Bluetooth device. If null, use the currect 725 * active A2DP Bluetooth device. 726 * @hide 727 */ 728 @UnsupportedAppUsage 729 @RequiresPermission(Manifest.permission.BLUETOOTH) disableOptionalCodecs(@onNull BluetoothDevice device)730 public void disableOptionalCodecs(@NonNull BluetoothDevice device) { 731 if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")"); 732 verifyDeviceNotNull(device, "disableOptionalCodecs"); 733 enableDisableOptionalCodecs(device, false); 734 } 735 736 /** 737 * Enables or disables the optional codecs. 738 * 739 * @param device the remote Bluetooth device. If null, use the currect 740 * active A2DP Bluetooth device. 741 * @param enable if true, enable the optional codecs, other disable them 742 */ enableDisableOptionalCodecs(BluetoothDevice device, boolean enable)743 private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) { 744 try { 745 final IBluetoothA2dp service = getService(); 746 if (service != null && isEnabled()) { 747 if (enable) { 748 service.enableOptionalCodecs(device); 749 } else { 750 service.disableOptionalCodecs(device); 751 } 752 } 753 if (service == null) Log.w(TAG, "Proxy not attached to service"); 754 return; 755 } catch (RemoteException e) { 756 Log.e(TAG, "Error talking to BT service in enableDisableOptionalCodecs()", e); 757 return; 758 } 759 } 760 761 /** 762 * Returns whether this device supports optional codecs. 763 * 764 * @param device The device to check 765 * @return one of OPTIONAL_CODECS_SUPPORT_UNKNOWN, OPTIONAL_CODECS_NOT_SUPPORTED, or 766 * OPTIONAL_CODECS_SUPPORTED. 767 * @hide 768 */ 769 @UnsupportedAppUsage 770 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) 771 @OptionalCodecsSupportStatus isOptionalCodecsSupported(@onNull BluetoothDevice device)772 public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) { 773 verifyDeviceNotNull(device, "isOptionalCodecsSupported"); 774 try { 775 final IBluetoothA2dp service = getService(); 776 if (service != null && isEnabled() && isValidDevice(device)) { 777 return service.supportsOptionalCodecs(device); 778 } 779 if (service == null) Log.w(TAG, "Proxy not attached to service"); 780 return OPTIONAL_CODECS_SUPPORT_UNKNOWN; 781 } catch (RemoteException e) { 782 Log.e(TAG, "Error talking to BT service in supportsOptionalCodecs()", e); 783 return OPTIONAL_CODECS_SUPPORT_UNKNOWN; 784 } 785 } 786 787 /** 788 * Returns whether this device should have optional codecs enabled. 789 * 790 * @param device The device in question. 791 * @return one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or 792 * OPTIONAL_CODECS_PREF_DISABLED. 793 * @hide 794 */ 795 @UnsupportedAppUsage 796 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) 797 @OptionalCodecsPreferenceStatus isOptionalCodecsEnabled(@onNull BluetoothDevice device)798 public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) { 799 verifyDeviceNotNull(device, "isOptionalCodecsEnabled"); 800 try { 801 final IBluetoothA2dp service = getService(); 802 if (service != null && isEnabled() && isValidDevice(device)) { 803 return service.getOptionalCodecsEnabled(device); 804 } 805 if (service == null) Log.w(TAG, "Proxy not attached to service"); 806 return OPTIONAL_CODECS_PREF_UNKNOWN; 807 } catch (RemoteException e) { 808 Log.e(TAG, "Error talking to BT service in getOptionalCodecsEnabled()", e); 809 return OPTIONAL_CODECS_PREF_UNKNOWN; 810 } 811 } 812 813 /** 814 * Sets a persistent preference for whether a given device should have optional codecs enabled. 815 * 816 * @param device The device to set this preference for. 817 * @param value Whether the optional codecs should be enabled for this device. This should be 818 * one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or 819 * OPTIONAL_CODECS_PREF_DISABLED. 820 * @hide 821 */ 822 @UnsupportedAppUsage 823 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) setOptionalCodecsEnabled(@onNull BluetoothDevice device, @OptionalCodecsPreferenceStatus int value)824 public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device, 825 @OptionalCodecsPreferenceStatus int value) { 826 verifyDeviceNotNull(device, "setOptionalCodecsEnabled"); 827 try { 828 if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN 829 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED 830 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { 831 Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value); 832 return; 833 } 834 final IBluetoothA2dp service = getService(); 835 if (service != null && isEnabled() 836 && isValidDevice(device)) { 837 service.setOptionalCodecsEnabled(device, value); 838 } 839 if (service == null) Log.w(TAG, "Proxy not attached to service"); 840 return; 841 } catch (RemoteException e) { 842 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 843 return; 844 } 845 } 846 847 /** 848 * Helper for converting a state to a string. 849 * 850 * For debug use only - strings are not internationalized. 851 * 852 * @hide 853 */ 854 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) stateToString(int state)855 public static String stateToString(int state) { 856 switch (state) { 857 case STATE_DISCONNECTED: 858 return "disconnected"; 859 case STATE_CONNECTING: 860 return "connecting"; 861 case STATE_CONNECTED: 862 return "connected"; 863 case STATE_DISCONNECTING: 864 return "disconnecting"; 865 case STATE_PLAYING: 866 return "playing"; 867 case STATE_NOT_PLAYING: 868 return "not playing"; 869 default: 870 return "<unknown state " + state + ">"; 871 } 872 } 873 isEnabled()874 private boolean isEnabled() { 875 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 876 return false; 877 } 878 verifyDeviceNotNull(BluetoothDevice device, String methodName)879 private void verifyDeviceNotNull(BluetoothDevice device, String methodName) { 880 if (device == null) { 881 Log.e(TAG, methodName + ": device param is null"); 882 throw new IllegalArgumentException("Device cannot be null"); 883 } 884 } 885 isValidDevice(BluetoothDevice device)886 private boolean isValidDevice(BluetoothDevice device) { 887 if (device == null) return false; 888 889 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 890 return false; 891 } 892 log(String msg)893 private static void log(String msg) { 894 Log.d(TAG, msg); 895 } 896 } 897