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