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