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