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.RequiresNoPermission; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SdkConstant; 26 import android.annotation.SdkConstant.SdkConstantType; 27 import android.annotation.SystemApi; 28 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 29 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; 30 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 31 import android.compat.annotation.UnsupportedAppUsage; 32 import android.content.Attributable; 33 import android.content.AttributionSource; 34 import android.content.Context; 35 import android.os.Binder; 36 import android.os.Build; 37 import android.os.IBinder; 38 import android.os.ParcelUuid; 39 import android.os.RemoteException; 40 import android.util.Log; 41 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.util.ArrayList; 45 import java.util.List; 46 47 48 /** 49 * This class provides the public APIs to control the Bluetooth A2DP 50 * profile. 51 * 52 * <p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP 53 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get 54 * the BluetoothA2dp proxy object. 55 * 56 * <p> Android only supports one connected Bluetooth A2dp device at a time. 57 * Each method is protected with its appropriate permission. 58 */ 59 public final class BluetoothA2dp implements BluetoothProfile { 60 private static final String TAG = "BluetoothA2dp"; 61 private static final boolean DBG = true; 62 private static final boolean VDBG = false; 63 64 /** 65 * Intent used to broadcast the change in connection state of the A2DP 66 * profile. 67 * 68 * <p>This intent will have 3 extras: 69 * <ul> 70 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 71 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> 72 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 73 * </ul> 74 * 75 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 76 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 77 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 78 */ 79 @RequiresLegacyBluetoothPermission 80 @RequiresBluetoothConnectPermission 81 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 82 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 83 public static final String ACTION_CONNECTION_STATE_CHANGED = 84 "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"; 85 86 /** 87 * Intent used to broadcast the change in the Playing state of the A2DP 88 * profile. 89 * 90 * <p>This intent will have 3 extras: 91 * <ul> 92 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 93 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 94 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 95 * </ul> 96 * 97 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 98 * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, 99 */ 100 @RequiresLegacyBluetoothPermission 101 @RequiresBluetoothConnectPermission 102 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 103 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 104 public static final String ACTION_PLAYING_STATE_CHANGED = 105 "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"; 106 107 /** @hide */ 108 @RequiresBluetoothConnectPermission 109 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 110 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 111 public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED = 112 "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED"; 113 114 /** 115 * Intent used to broadcast the selection of a connected device as active. 116 * 117 * <p>This intent will have one extra: 118 * <ul> 119 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can 120 * be null if no device is active. </li> 121 * </ul> 122 * 123 * @hide 124 */ 125 @RequiresLegacyBluetoothPermission 126 @RequiresBluetoothConnectPermission 127 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 128 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 129 @UnsupportedAppUsage(trackingBug = 171933273) 130 public static final String ACTION_ACTIVE_DEVICE_CHANGED = 131 "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED"; 132 133 /** 134 * Intent used to broadcast the change in the Audio Codec state of the 135 * A2DP Source profile. 136 * 137 * <p>This intent will have 2 extras: 138 * <ul> 139 * <li> {@link BluetoothCodecStatus#EXTRA_CODEC_STATUS} - The codec status. </li> 140 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently 141 * connected, otherwise it is not included.</li> 142 * </ul> 143 * 144 * @hide 145 */ 146 @RequiresLegacyBluetoothPermission 147 @RequiresBluetoothConnectPermission 148 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 149 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 150 @UnsupportedAppUsage(trackingBug = 181103983) 151 public static final String ACTION_CODEC_CONFIG_CHANGED = 152 "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED"; 153 154 /** 155 * A2DP sink device is streaming music. This state can be one of 156 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 157 * {@link #ACTION_PLAYING_STATE_CHANGED} intent. 158 */ 159 public static final int STATE_PLAYING = 10; 160 161 /** 162 * A2DP sink device is NOT streaming music. This state can be one of 163 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 164 * {@link #ACTION_PLAYING_STATE_CHANGED} intent. 165 */ 166 public static final int STATE_NOT_PLAYING = 11; 167 168 /** @hide */ 169 @IntDef(prefix = "OPTIONAL_CODECS_", value = { 170 OPTIONAL_CODECS_SUPPORT_UNKNOWN, 171 OPTIONAL_CODECS_NOT_SUPPORTED, 172 OPTIONAL_CODECS_SUPPORTED 173 }) 174 @Retention(RetentionPolicy.SOURCE) 175 public @interface OptionalCodecsSupportStatus {} 176 177 /** 178 * We don't have a stored preference for whether or not the given A2DP sink device supports 179 * optional codecs. 180 * 181 * @hide 182 */ 183 @SystemApi 184 public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1; 185 186 /** 187 * The given A2DP sink device does not support optional codecs. 188 * 189 * @hide 190 */ 191 @SystemApi 192 public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0; 193 194 /** 195 * The given A2DP sink device does support optional codecs. 196 * 197 * @hide 198 */ 199 @SystemApi 200 public static final int OPTIONAL_CODECS_SUPPORTED = 1; 201 202 /** @hide */ 203 @IntDef(prefix = "OPTIONAL_CODECS_PREF_", value = { 204 OPTIONAL_CODECS_PREF_UNKNOWN, 205 OPTIONAL_CODECS_PREF_DISABLED, 206 OPTIONAL_CODECS_PREF_ENABLED 207 }) 208 @Retention(RetentionPolicy.SOURCE) 209 public @interface OptionalCodecsPreferenceStatus {} 210 211 /** 212 * We don't have a stored preference for whether optional codecs should be enabled or 213 * disabled for the given A2DP device. 214 * 215 * @hide 216 */ 217 @SystemApi 218 public static final int OPTIONAL_CODECS_PREF_UNKNOWN = -1; 219 220 /** 221 * Optional codecs should be disabled for the given A2DP device. 222 * 223 * @hide 224 */ 225 @SystemApi 226 public static final int OPTIONAL_CODECS_PREF_DISABLED = 0; 227 228 /** 229 * Optional codecs should be enabled for the given A2DP device. 230 * 231 * @hide 232 */ 233 @SystemApi 234 public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; 235 236 /** @hide */ 237 @IntDef(prefix = "DYNAMIC_BUFFER_SUPPORT_", value = { 238 DYNAMIC_BUFFER_SUPPORT_NONE, 239 DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD, 240 DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING 241 }) 242 @Retention(RetentionPolicy.SOURCE) 243 public @interface Type {} 244 245 /** 246 * Indicates the supported type of Dynamic Audio Buffer is not supported. 247 * 248 * @hide 249 */ 250 @SystemApi 251 public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0; 252 253 /** 254 * Indicates the supported type of Dynamic Audio Buffer is A2DP offload. 255 * 256 * @hide 257 */ 258 @SystemApi 259 public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1; 260 261 /** 262 * Indicates the supported type of Dynamic Audio Buffer is A2DP software encoding. 263 * 264 * @hide 265 */ 266 @SystemApi 267 public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2; 268 269 private final BluetoothAdapter mAdapter; 270 private final AttributionSource mAttributionSource; 271 private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector = 272 new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp", 273 IBluetoothA2dp.class.getName()) { 274 @Override 275 public IBluetoothA2dp getServiceInterface(IBinder service) { 276 return IBluetoothA2dp.Stub.asInterface(Binder.allowBlocking(service)); 277 } 278 }; 279 280 /** 281 * Create a BluetoothA2dp proxy object for interacting with the local 282 * Bluetooth A2DP service. 283 */ BluetoothA2dp(Context context, ServiceListener listener, BluetoothAdapter adapter)284 /* package */ BluetoothA2dp(Context context, ServiceListener listener, 285 BluetoothAdapter adapter) { 286 mAdapter = adapter; 287 mAttributionSource = adapter.getAttributionSource(); 288 mProfileConnector.connect(context, listener); 289 } 290 291 @UnsupportedAppUsage close()292 /*package*/ void close() { 293 mProfileConnector.disconnect(); 294 } 295 getService()296 private IBluetoothA2dp getService() { 297 return mProfileConnector.getService(); 298 } 299 300 @Override finalize()301 public void finalize() { 302 // The empty finalize needs to be kept or the 303 // cts signature tests would fail. 304 } 305 306 /** 307 * Initiate connection to a profile of the remote Bluetooth device. 308 * 309 * <p> This API returns false in scenarios like the profile on the 310 * device is already connected or Bluetooth is not turned on. 311 * When this API returns true, it is guaranteed that 312 * connection state intent for the profile will be broadcasted with 313 * the state. Users can get the connection state of the profile 314 * from this intent. 315 * 316 * 317 * @param device Remote Bluetooth Device 318 * @return false on immediate error, true otherwise 319 * @hide 320 */ 321 @RequiresLegacyBluetoothAdminPermission 322 @RequiresBluetoothConnectPermission 323 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 324 @UnsupportedAppUsage connect(BluetoothDevice device)325 public boolean connect(BluetoothDevice device) { 326 if (DBG) log("connect(" + device + ")"); 327 try { 328 final IBluetoothA2dp service = getService(); 329 if (service != null && isEnabled() && isValidDevice(device)) { 330 return service.connect(device); 331 } 332 if (service == null) Log.w(TAG, "Proxy not attached to service"); 333 return false; 334 } catch (RemoteException e) { 335 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 336 return false; 337 } 338 } 339 340 /** 341 * Initiate disconnection from a profile 342 * 343 * <p> This API will return false in scenarios like the profile on the 344 * Bluetooth device is not in connected state etc. When this API returns, 345 * true, it is guaranteed that the connection state change 346 * intent will be broadcasted with the state. Users can get the 347 * disconnection state of the profile from this intent. 348 * 349 * <p> If the disconnection is initiated by a remote device, the state 350 * will transition from {@link #STATE_CONNECTED} to 351 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 352 * host (local) device the state will transition from 353 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 354 * state {@link #STATE_DISCONNECTED}. The transition to 355 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 356 * two scenarios. 357 * 358 * 359 * @param device Remote Bluetooth Device 360 * @return false on immediate error, true otherwise 361 * @hide 362 */ 363 @RequiresLegacyBluetoothAdminPermission 364 @RequiresBluetoothConnectPermission 365 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 366 @UnsupportedAppUsage disconnect(BluetoothDevice device)367 public boolean disconnect(BluetoothDevice device) { 368 if (DBG) log("disconnect(" + device + ")"); 369 try { 370 final IBluetoothA2dp service = getService(); 371 if (service != null && isEnabled() && isValidDevice(device)) { 372 return service.disconnect(device); 373 } 374 if (service == null) Log.w(TAG, "Proxy not attached to service"); 375 return false; 376 } catch (RemoteException e) { 377 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 378 return false; 379 } 380 } 381 382 /** 383 * {@inheritDoc} 384 */ 385 @Override 386 @RequiresBluetoothConnectPermission 387 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()388 public List<BluetoothDevice> getConnectedDevices() { 389 if (VDBG) log("getConnectedDevices()"); 390 try { 391 final IBluetoothA2dp service = getService(); 392 if (service != null && isEnabled()) { 393 return Attributable.setAttributionSource( 394 service.getConnectedDevicesWithAttribution(mAttributionSource), 395 mAttributionSource); 396 } 397 if (service == null) Log.w(TAG, "Proxy not attached to service"); 398 return new ArrayList<BluetoothDevice>(); 399 } catch (RemoteException e) { 400 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 401 return new ArrayList<BluetoothDevice>(); 402 } 403 } 404 405 /** 406 * {@inheritDoc} 407 */ 408 @Override 409 @RequiresBluetoothConnectPermission 410 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates(int[] states)411 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 412 if (VDBG) log("getDevicesMatchingStates()"); 413 try { 414 final IBluetoothA2dp service = getService(); 415 if (service != null && isEnabled()) { 416 return Attributable.setAttributionSource( 417 service.getDevicesMatchingConnectionStatesWithAttribution(states, 418 mAttributionSource), 419 mAttributionSource); 420 } 421 if (service == null) Log.w(TAG, "Proxy not attached to service"); 422 return new ArrayList<BluetoothDevice>(); 423 } catch (RemoteException e) { 424 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 425 return new ArrayList<BluetoothDevice>(); 426 } 427 } 428 429 /** 430 * {@inheritDoc} 431 */ 432 @Override 433 @RequiresBluetoothConnectPermission 434 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(BluetoothDevice device)435 public @BtProfileState int getConnectionState(BluetoothDevice device) { 436 if (VDBG) log("getState(" + device + ")"); 437 try { 438 final IBluetoothA2dp service = getService(); 439 if (service != null && isEnabled() 440 && isValidDevice(device)) { 441 return service.getConnectionState(device); 442 } 443 if (service == null) Log.w(TAG, "Proxy not attached to service"); 444 return BluetoothProfile.STATE_DISCONNECTED; 445 } catch (RemoteException e) { 446 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 447 return BluetoothProfile.STATE_DISCONNECTED; 448 } 449 } 450 451 /** 452 * Select a connected device as active. 453 * 454 * The active device selection is per profile. An active device's 455 * purpose is profile-specific. For example, A2DP audio streaming 456 * is to the active A2DP Sink device. If a remote device is not 457 * connected, it cannot be selected as active. 458 * 459 * <p> This API returns false in scenarios like the profile on the 460 * device is not connected or Bluetooth is not turned on. 461 * When this API returns true, it is guaranteed that the 462 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted 463 * with the active device. 464 * 465 * @param device the remote Bluetooth device. Could be null to clear 466 * the active device and stop streaming audio to a Bluetooth device. 467 * @return false on immediate error, true otherwise 468 * @hide 469 */ 470 @RequiresLegacyBluetoothAdminPermission 471 @RequiresBluetoothConnectPermission 472 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 473 @UnsupportedAppUsage(trackingBug = 171933273) setActiveDevice(@ullable BluetoothDevice device)474 public boolean setActiveDevice(@Nullable BluetoothDevice device) { 475 if (DBG) log("setActiveDevice(" + device + ")"); 476 try { 477 final IBluetoothA2dp service = getService(); 478 if (service != null && isEnabled() 479 && ((device == null) || isValidDevice(device))) { 480 return service.setActiveDevice(device, mAttributionSource); 481 } 482 if (service == null) Log.w(TAG, "Proxy not attached to service"); 483 return false; 484 } catch (RemoteException e) { 485 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 486 return false; 487 } 488 } 489 490 /** 491 * Get the connected device that is active. 492 * 493 * @return the connected device that is active or null if no device 494 * is active 495 * @hide 496 */ 497 @UnsupportedAppUsage(trackingBug = 171933273) 498 @Nullable 499 @RequiresLegacyBluetoothPermission 500 @RequiresBluetoothConnectPermission 501 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getActiveDevice()502 public BluetoothDevice getActiveDevice() { 503 if (VDBG) log("getActiveDevice()"); 504 try { 505 final IBluetoothA2dp service = getService(); 506 if (service != null && isEnabled()) { 507 return Attributable.setAttributionSource( 508 service.getActiveDevice(mAttributionSource), mAttributionSource); 509 } 510 if (service == null) Log.w(TAG, "Proxy not attached to service"); 511 return null; 512 } catch (RemoteException e) { 513 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 514 return null; 515 } 516 } 517 518 /** 519 * Set priority of the profile 520 * 521 * <p> The device should already be paired. 522 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF} 523 * 524 * @param device Paired bluetooth device 525 * @param priority 526 * @return true if priority is set, false on error 527 * @hide 528 */ 529 @RequiresBluetoothConnectPermission 530 @RequiresPermission(allOf = { 531 android.Manifest.permission.BLUETOOTH_CONNECT, 532 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 533 }) setPriority(BluetoothDevice device, int priority)534 public boolean setPriority(BluetoothDevice device, int priority) { 535 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 536 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); 537 } 538 539 /** 540 * Set connection policy of the profile 541 * 542 * <p> The device should already be paired. 543 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 544 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 545 * 546 * @param device Paired bluetooth device 547 * @param connectionPolicy is the connection policy to set to for this profile 548 * @return true if connectionPolicy is set, false on error 549 * @hide 550 */ 551 @SystemApi 552 @RequiresBluetoothConnectPermission 553 @RequiresPermission(allOf = { 554 android.Manifest.permission.BLUETOOTH_CONNECT, 555 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 556 }) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)557 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 558 @ConnectionPolicy int connectionPolicy) { 559 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 560 try { 561 final IBluetoothA2dp service = getService(); 562 if (service != null && isEnabled() 563 && isValidDevice(device)) { 564 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 565 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 566 return false; 567 } 568 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 569 } 570 if (service == null) Log.w(TAG, "Proxy not attached to service"); 571 return false; 572 } catch (RemoteException e) { 573 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 574 return false; 575 } 576 } 577 578 /** 579 * Get the priority of the profile. 580 * 581 * <p> The priority can be any of: 582 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 583 * 584 * @param device Bluetooth device 585 * @return priority of the device 586 * @hide 587 */ 588 @RequiresLegacyBluetoothPermission 589 @RequiresBluetoothConnectPermission 590 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 591 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) getPriority(BluetoothDevice device)592 public int getPriority(BluetoothDevice device) { 593 if (VDBG) log("getPriority(" + device + ")"); 594 try { 595 final IBluetoothA2dp service = getService(); 596 if (service != null && isEnabled() 597 && isValidDevice(device)) { 598 return BluetoothAdapter.connectionPolicyToPriority( 599 service.getPriority(device, mAttributionSource)); 600 } 601 if (service == null) Log.w(TAG, "Proxy not attached to service"); 602 return BluetoothProfile.PRIORITY_OFF; 603 } catch (RemoteException e) { 604 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 605 return BluetoothProfile.PRIORITY_OFF; 606 } 607 } 608 609 /** 610 * Get the connection policy of the profile. 611 * 612 * <p> The connection policy can be any of: 613 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, 614 * {@link #CONNECTION_POLICY_UNKNOWN} 615 * 616 * @param device Bluetooth device 617 * @return connection policy of the device 618 * @hide 619 */ 620 @SystemApi 621 @RequiresBluetoothConnectPermission 622 @RequiresPermission(allOf = { 623 android.Manifest.permission.BLUETOOTH_CONNECT, 624 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 625 }) getConnectionPolicy(@onNull BluetoothDevice device)626 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 627 if (VDBG) log("getConnectionPolicy(" + device + ")"); 628 try { 629 final IBluetoothA2dp service = getService(); 630 if (service != null && isEnabled() 631 && isValidDevice(device)) { 632 return service.getConnectionPolicy(device, mAttributionSource); 633 } 634 if (service == null) Log.w(TAG, "Proxy not attached to service"); 635 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 636 } catch (RemoteException e) { 637 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 638 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 639 } 640 } 641 642 /** 643 * Checks if Avrcp device supports the absolute volume feature. 644 * 645 * @return true if device supports absolute volume 646 * @hide 647 */ 648 @RequiresNoPermission isAvrcpAbsoluteVolumeSupported()649 public boolean isAvrcpAbsoluteVolumeSupported() { 650 if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported"); 651 try { 652 final IBluetoothA2dp service = getService(); 653 if (service != null && isEnabled()) { 654 return service.isAvrcpAbsoluteVolumeSupported(); 655 } 656 if (service == null) Log.w(TAG, "Proxy not attached to service"); 657 return false; 658 } catch (RemoteException e) { 659 Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e); 660 return false; 661 } 662 } 663 664 /** 665 * Tells remote device to set an absolute volume. Only if absolute volume is supported 666 * 667 * @param volume Absolute volume to be set on AVRCP side 668 * @hide 669 */ 670 @RequiresBluetoothConnectPermission 671 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setAvrcpAbsoluteVolume(int volume)672 public void setAvrcpAbsoluteVolume(int volume) { 673 if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume"); 674 try { 675 final IBluetoothA2dp service = getService(); 676 if (service != null && isEnabled()) { 677 service.setAvrcpAbsoluteVolume(volume, mAttributionSource); 678 } 679 if (service == null) Log.w(TAG, "Proxy not attached to service"); 680 } catch (RemoteException e) { 681 Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e); 682 } 683 } 684 685 /** 686 * Check if A2DP profile is streaming music. 687 * 688 * @param device BluetoothDevice device 689 */ 690 @RequiresLegacyBluetoothPermission 691 @RequiresBluetoothConnectPermission 692 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isA2dpPlaying(BluetoothDevice device)693 public boolean isA2dpPlaying(BluetoothDevice device) { 694 try { 695 final IBluetoothA2dp service = getService(); 696 if (service != null && isEnabled() 697 && isValidDevice(device)) { 698 return service.isA2dpPlaying(device, mAttributionSource); 699 } 700 if (service == null) Log.w(TAG, "Proxy not attached to service"); 701 return false; 702 } catch (RemoteException e) { 703 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 704 return false; 705 } 706 } 707 708 /** 709 * This function checks if the remote device is an AVCRP 710 * target and thus whether we should send volume keys 711 * changes or not. 712 * 713 * @hide 714 */ 715 @RequiresBluetoothConnectPermission 716 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) shouldSendVolumeKeys(BluetoothDevice device)717 public boolean shouldSendVolumeKeys(BluetoothDevice device) { 718 if (isEnabled() && isValidDevice(device)) { 719 ParcelUuid[] uuids = device.getUuids(); 720 if (uuids == null) return false; 721 722 for (ParcelUuid uuid : uuids) { 723 if (uuid.equals(BluetoothUuid.AVRCP_TARGET)) { 724 return true; 725 } 726 } 727 } 728 return false; 729 } 730 731 /** 732 * Gets the current codec status (configuration and capability). 733 * 734 * @param device the remote Bluetooth device. If null, use the current 735 * active A2DP Bluetooth device. 736 * @return the current codec status 737 * @hide 738 */ 739 @UnsupportedAppUsage(trackingBug = 181103983) 740 @Nullable 741 @RequiresLegacyBluetoothPermission 742 @RequiresBluetoothConnectPermission 743 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getCodecStatus(@onNull BluetoothDevice device)744 public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { 745 if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); 746 verifyDeviceNotNull(device, "getCodecStatus"); 747 try { 748 final IBluetoothA2dp service = getService(); 749 if (service != null && isEnabled()) { 750 return service.getCodecStatus(device, mAttributionSource); 751 } 752 if (service == null) { 753 Log.w(TAG, "Proxy not attached to service"); 754 } 755 return null; 756 } catch (RemoteException e) { 757 Log.e(TAG, "Error talking to BT service in getCodecStatus()", e); 758 return null; 759 } 760 } 761 762 /** 763 * Sets the codec configuration preference. 764 * 765 * @param device the remote Bluetooth device. If null, use the current 766 * active A2DP Bluetooth device. 767 * @param codecConfig the codec configuration preference 768 * @hide 769 */ 770 @UnsupportedAppUsage(trackingBug = 181103983) 771 @RequiresLegacyBluetoothPermission 772 @RequiresBluetoothConnectPermission 773 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setCodecConfigPreference(@onNull BluetoothDevice device, @NonNull BluetoothCodecConfig codecConfig)774 public void setCodecConfigPreference(@NonNull BluetoothDevice device, 775 @NonNull BluetoothCodecConfig codecConfig) { 776 if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")"); 777 verifyDeviceNotNull(device, "setCodecConfigPreference"); 778 if (codecConfig == null) { 779 Log.e(TAG, "setCodecConfigPreference: Codec config can't be null"); 780 throw new IllegalArgumentException("codecConfig cannot be null"); 781 } 782 try { 783 final IBluetoothA2dp service = getService(); 784 if (service != null && isEnabled()) { 785 service.setCodecConfigPreference(device, codecConfig, mAttributionSource); 786 } 787 if (service == null) Log.w(TAG, "Proxy not attached to service"); 788 return; 789 } catch (RemoteException e) { 790 Log.e(TAG, "Error talking to BT service in setCodecConfigPreference()", e); 791 return; 792 } 793 } 794 795 /** 796 * Enables the optional codecs. 797 * 798 * @param device the remote Bluetooth device. If null, use the currect 799 * active A2DP Bluetooth device. 800 * @hide 801 */ 802 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 803 @RequiresLegacyBluetoothPermission 804 @RequiresBluetoothConnectPermission 805 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) enableOptionalCodecs(@onNull BluetoothDevice device)806 public void enableOptionalCodecs(@NonNull BluetoothDevice device) { 807 if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")"); 808 verifyDeviceNotNull(device, "enableOptionalCodecs"); 809 enableDisableOptionalCodecs(device, true); 810 } 811 812 /** 813 * Disables the optional codecs. 814 * 815 * @param device the remote Bluetooth device. If null, use the currect 816 * active A2DP Bluetooth device. 817 * @hide 818 */ 819 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 820 @RequiresLegacyBluetoothPermission 821 @RequiresBluetoothConnectPermission 822 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disableOptionalCodecs(@onNull BluetoothDevice device)823 public void disableOptionalCodecs(@NonNull BluetoothDevice device) { 824 if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")"); 825 verifyDeviceNotNull(device, "disableOptionalCodecs"); 826 enableDisableOptionalCodecs(device, false); 827 } 828 829 /** 830 * Enables or disables the optional codecs. 831 * 832 * @param device the remote Bluetooth device. If null, use the currect 833 * active A2DP Bluetooth device. 834 * @param enable if true, enable the optional codecs, other disable them 835 */ 836 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) enableDisableOptionalCodecs(BluetoothDevice device, boolean enable)837 private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) { 838 try { 839 final IBluetoothA2dp service = getService(); 840 if (service != null && isEnabled()) { 841 if (enable) { 842 service.enableOptionalCodecs(device, mAttributionSource); 843 } else { 844 service.disableOptionalCodecs(device, mAttributionSource); 845 } 846 } 847 if (service == null) Log.w(TAG, "Proxy not attached to service"); 848 return; 849 } catch (RemoteException e) { 850 Log.e(TAG, "Error talking to BT service in enableDisableOptionalCodecs()", e); 851 return; 852 } 853 } 854 855 /** 856 * Returns whether this device supports optional codecs. 857 * 858 * @param device The device to check 859 * @return one of OPTIONAL_CODECS_SUPPORT_UNKNOWN, OPTIONAL_CODECS_NOT_SUPPORTED, or 860 * OPTIONAL_CODECS_SUPPORTED. 861 * @hide 862 */ 863 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 864 @RequiresLegacyBluetoothAdminPermission 865 @RequiresBluetoothConnectPermission 866 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 867 @OptionalCodecsSupportStatus isOptionalCodecsSupported(@onNull BluetoothDevice device)868 public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) { 869 verifyDeviceNotNull(device, "isOptionalCodecsSupported"); 870 try { 871 final IBluetoothA2dp service = getService(); 872 if (service != null && isEnabled() && isValidDevice(device)) { 873 return service.supportsOptionalCodecs(device, mAttributionSource); 874 } 875 if (service == null) Log.w(TAG, "Proxy not attached to service"); 876 return OPTIONAL_CODECS_SUPPORT_UNKNOWN; 877 } catch (RemoteException e) { 878 Log.e(TAG, "Error talking to BT service in supportsOptionalCodecs()", e); 879 return OPTIONAL_CODECS_SUPPORT_UNKNOWN; 880 } 881 } 882 883 /** 884 * Returns whether this device should have optional codecs enabled. 885 * 886 * @param device The device in question. 887 * @return one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or 888 * OPTIONAL_CODECS_PREF_DISABLED. 889 * @hide 890 */ 891 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 892 @RequiresLegacyBluetoothAdminPermission 893 @RequiresBluetoothConnectPermission 894 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 895 @OptionalCodecsPreferenceStatus isOptionalCodecsEnabled(@onNull BluetoothDevice device)896 public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) { 897 verifyDeviceNotNull(device, "isOptionalCodecsEnabled"); 898 try { 899 final IBluetoothA2dp service = getService(); 900 if (service != null && isEnabled() && isValidDevice(device)) { 901 return service.getOptionalCodecsEnabled(device, mAttributionSource); 902 } 903 if (service == null) Log.w(TAG, "Proxy not attached to service"); 904 return OPTIONAL_CODECS_PREF_UNKNOWN; 905 } catch (RemoteException e) { 906 Log.e(TAG, "Error talking to BT service in getOptionalCodecsEnabled()", e); 907 return OPTIONAL_CODECS_PREF_UNKNOWN; 908 } 909 } 910 911 /** 912 * Sets a persistent preference for whether a given device should have optional codecs enabled. 913 * 914 * @param device The device to set this preference for. 915 * @param value Whether the optional codecs should be enabled for this device. This should be 916 * one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or 917 * OPTIONAL_CODECS_PREF_DISABLED. 918 * @hide 919 */ 920 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 921 @RequiresLegacyBluetoothAdminPermission 922 @RequiresBluetoothConnectPermission 923 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setOptionalCodecsEnabled(@onNull BluetoothDevice device, @OptionalCodecsPreferenceStatus int value)924 public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device, 925 @OptionalCodecsPreferenceStatus int value) { 926 verifyDeviceNotNull(device, "setOptionalCodecsEnabled"); 927 try { 928 if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN 929 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED 930 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { 931 Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value); 932 return; 933 } 934 final IBluetoothA2dp service = getService(); 935 if (service != null && isEnabled() 936 && isValidDevice(device)) { 937 service.setOptionalCodecsEnabled(device, value, mAttributionSource); 938 } 939 if (service == null) Log.w(TAG, "Proxy not attached to service"); 940 return; 941 } catch (RemoteException e) { 942 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 943 return; 944 } 945 } 946 947 /** 948 * Get the supported type of the Dynamic Audio Buffer. 949 * <p>Possible return values are 950 * {@link #DYNAMIC_BUFFER_SUPPORT_NONE}, 951 * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD}, 952 * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING}. 953 * 954 * @return supported type of Dynamic Audio Buffer feature 955 * 956 * @hide 957 */ 958 @SystemApi 959 @RequiresBluetoothConnectPermission 960 @RequiresPermission(allOf = { 961 android.Manifest.permission.BLUETOOTH_CONNECT, 962 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 963 }) getDynamicBufferSupport()964 public @Type int getDynamicBufferSupport() { 965 if (VDBG) log("getDynamicBufferSupport()"); 966 try { 967 final IBluetoothA2dp service = getService(); 968 if (service != null && isEnabled()) { 969 return service.getDynamicBufferSupport(mAttributionSource); 970 } 971 if (service == null) Log.w(TAG, "Proxy not attached to service"); 972 return DYNAMIC_BUFFER_SUPPORT_NONE; 973 } catch (RemoteException e) { 974 Log.e(TAG, "failed to get getDynamicBufferSupport, error: ", e); 975 return DYNAMIC_BUFFER_SUPPORT_NONE; 976 } 977 } 978 979 /** 980 * Return the record of {@link BufferConstraints} object that 981 * has the default/maximum/minimum audio buffer. This can be used to inform what the controller 982 * has support for the audio buffer. 983 * 984 * @return a record with {@link BufferConstraints} or null if report is unavailable 985 * or unsupported 986 * 987 * @hide 988 */ 989 @SystemApi 990 @RequiresBluetoothConnectPermission 991 @RequiresPermission(allOf = { 992 android.Manifest.permission.BLUETOOTH_CONNECT, 993 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 994 }) getBufferConstraints()995 public @Nullable BufferConstraints getBufferConstraints() { 996 if (VDBG) log("getBufferConstraints()"); 997 try { 998 final IBluetoothA2dp service = getService(); 999 if (service != null && isEnabled()) { 1000 return service.getBufferConstraints(mAttributionSource); 1001 } 1002 if (service == null) Log.w(TAG, "Proxy not attached to service"); 1003 return null; 1004 } catch (RemoteException e) { 1005 Log.e(TAG, "", e); 1006 return null; 1007 } 1008 } 1009 1010 /** 1011 * Set Dynamic Audio Buffer Size. 1012 * 1013 * @param codec audio codec 1014 * @param value buffer millis 1015 * @return true to indicate success, or false on immediate error 1016 * 1017 * @hide 1018 */ 1019 @SystemApi 1020 @RequiresBluetoothConnectPermission 1021 @RequiresPermission(allOf = { 1022 android.Manifest.permission.BLUETOOTH_CONNECT, 1023 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1024 }) setBufferLengthMillis(@luetoothCodecConfig.SourceCodecType int codec, int value)1025 public boolean setBufferLengthMillis(@BluetoothCodecConfig.SourceCodecType int codec, 1026 int value) { 1027 if (VDBG) log("setBufferLengthMillis(" + codec + ", " + value + ")"); 1028 if (value < 0) { 1029 Log.e(TAG, "Trying to set audio buffer length to a negative value: " + value); 1030 return false; 1031 } 1032 try { 1033 final IBluetoothA2dp service = getService(); 1034 if (service != null && isEnabled()) { 1035 return service.setBufferLengthMillis(codec, value, mAttributionSource); 1036 } 1037 if (service == null) Log.w(TAG, "Proxy not attached to service"); 1038 return false; 1039 } catch (RemoteException e) { 1040 Log.e(TAG, "", e); 1041 return false; 1042 } 1043 } 1044 1045 /** 1046 * Helper for converting a state to a string. 1047 * 1048 * For debug use only - strings are not internationalized. 1049 * 1050 * @hide 1051 */ 1052 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) stateToString(int state)1053 public static String stateToString(int state) { 1054 switch (state) { 1055 case STATE_DISCONNECTED: 1056 return "disconnected"; 1057 case STATE_CONNECTING: 1058 return "connecting"; 1059 case STATE_CONNECTED: 1060 return "connected"; 1061 case STATE_DISCONNECTING: 1062 return "disconnecting"; 1063 case STATE_PLAYING: 1064 return "playing"; 1065 case STATE_NOT_PLAYING: 1066 return "not playing"; 1067 default: 1068 return "<unknown state " + state + ">"; 1069 } 1070 } 1071 isEnabled()1072 private boolean isEnabled() { 1073 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 1074 return false; 1075 } 1076 verifyDeviceNotNull(BluetoothDevice device, String methodName)1077 private void verifyDeviceNotNull(BluetoothDevice device, String methodName) { 1078 if (device == null) { 1079 Log.e(TAG, methodName + ": device param is null"); 1080 throw new IllegalArgumentException("Device cannot be null"); 1081 } 1082 } 1083 isValidDevice(BluetoothDevice device)1084 private boolean isValidDevice(BluetoothDevice device) { 1085 if (device == null) return false; 1086 1087 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 1088 return false; 1089 } 1090 log(String msg)1091 private static void log(String msg) { 1092 Log.d(TAG, msg); 1093 } 1094 } 1095