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