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 17package android.bluetooth; 18 19import android.Manifest; 20import android.annotation.RequiresPermission; 21import android.annotation.SdkConstant; 22import android.annotation.SdkConstant.SdkConstantType; 23import android.content.ComponentName; 24import android.content.Context; 25import android.content.Intent; 26import android.content.ServiceConnection; 27import android.media.AudioManager; 28import android.os.IBinder; 29import android.os.ParcelUuid; 30import android.os.RemoteException; 31import android.util.Log; 32 33import com.android.internal.annotations.GuardedBy; 34 35import java.util.ArrayList; 36import java.util.List; 37import java.util.concurrent.locks.ReentrantReadWriteLock; 38 39 40/** 41 * This class provides the public APIs to control the Bluetooth A2DP 42 * profile. 43 * 44 *<p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP 45 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get 46 * the BluetoothA2dp proxy object. 47 * 48 * <p> Android only supports one connected Bluetooth A2dp device at a time. 49 * Each method is protected with its appropriate permission. 50 */ 51public final class BluetoothA2dp implements BluetoothProfile { 52 private static final String TAG = "BluetoothA2dp"; 53 private static final boolean DBG = true; 54 private static final boolean VDBG = false; 55 56 /** 57 * Intent used to broadcast the change in connection state of the A2DP 58 * profile. 59 * 60 * <p>This intent will have 3 extras: 61 * <ul> 62 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 63 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> 64 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 65 * </ul> 66 * 67 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 68 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 69 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 70 * 71 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 72 * receive. 73 */ 74 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 75 public static final String ACTION_CONNECTION_STATE_CHANGED = 76 "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"; 77 78 /** 79 * Intent used to broadcast the change in the Playing state of the A2DP 80 * profile. 81 * 82 * <p>This intent will have 3 extras: 83 * <ul> 84 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 85 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 86 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 87 * </ul> 88 * 89 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 90 * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, 91 * 92 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 93 * receive. 94 */ 95 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 96 public static final String ACTION_PLAYING_STATE_CHANGED = 97 "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"; 98 99 /** @hide */ 100 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 101 public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED = 102 "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED"; 103 104 /** 105 * A2DP sink device is streaming music. This state can be one of 106 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 107 * {@link #ACTION_PLAYING_STATE_CHANGED} intent. 108 */ 109 public static final int STATE_PLAYING = 10; 110 111 /** 112 * A2DP sink device is NOT streaming music. This state can be one of 113 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 114 * {@link #ACTION_PLAYING_STATE_CHANGED} intent. 115 */ 116 public static final int STATE_NOT_PLAYING = 11; 117 118 private Context mContext; 119 private ServiceListener mServiceListener; 120 private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock(); 121 @GuardedBy("mServiceLock") private IBluetoothA2dp mService; 122 private BluetoothAdapter mAdapter; 123 124 final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = 125 new IBluetoothStateChangeCallback.Stub() { 126 public void onBluetoothStateChange(boolean up) { 127 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); 128 if (!up) { 129 if (VDBG) Log.d(TAG, "Unbinding service..."); 130 try { 131 mServiceLock.writeLock().lock(); 132 mService = null; 133 mContext.unbindService(mConnection); 134 } catch (Exception re) { 135 Log.e(TAG, "", re); 136 } finally { 137 mServiceLock.writeLock().unlock(); 138 } 139 } else { 140 try { 141 mServiceLock.readLock().lock(); 142 if (mService == null) { 143 if (VDBG) Log.d(TAG,"Binding service..."); 144 doBind(); 145 } 146 } catch (Exception re) { 147 Log.e(TAG,"",re); 148 } finally { 149 mServiceLock.readLock().unlock(); 150 } 151 } 152 } 153 }; 154 /** 155 * Create a BluetoothA2dp proxy object for interacting with the local 156 * Bluetooth A2DP service. 157 * 158 */ 159 /*package*/ BluetoothA2dp(Context context, ServiceListener l) { 160 mContext = context; 161 mServiceListener = l; 162 mAdapter = BluetoothAdapter.getDefaultAdapter(); 163 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 164 if (mgr != null) { 165 try { 166 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 167 } catch (RemoteException e) { 168 Log.e(TAG,"",e); 169 } 170 } 171 172 doBind(); 173 } 174 175 boolean doBind() { 176 Intent intent = new Intent(IBluetoothA2dp.class.getName()); 177 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); 178 intent.setComponent(comp); 179 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, 180 android.os.Process.myUserHandle())) { 181 Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent); 182 return false; 183 } 184 return true; 185 } 186 187 /*package*/ void close() { 188 mServiceListener = null; 189 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 190 if (mgr != null) { 191 try { 192 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 193 } catch (Exception e) { 194 Log.e(TAG,"",e); 195 } 196 } 197 198 try { 199 mServiceLock.writeLock().lock(); 200 if (mService != null) { 201 mService = null; 202 mContext.unbindService(mConnection); 203 } 204 } catch (Exception re) { 205 Log.e(TAG, "", re); 206 } finally { 207 mServiceLock.writeLock().unlock(); 208 } 209 } 210 211 public void finalize() { 212 // The empty finalize needs to be kept or the 213 // cts signature tests would fail. 214 } 215 /** 216 * Initiate connection to a profile of the remote bluetooth device. 217 * 218 * <p> Currently, the system supports only 1 connection to the 219 * A2DP profile. The API will automatically disconnect connected 220 * devices before connecting. 221 * 222 * <p> This API returns false in scenarios like the profile on the 223 * device is already connected or Bluetooth is not turned on. 224 * When this API returns true, it is guaranteed that 225 * connection state intent for the profile will be broadcasted with 226 * the state. Users can get the connection state of the profile 227 * from this intent. 228 * 229 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 230 * permission. 231 * 232 * @param device Remote Bluetooth Device 233 * @return false on immediate error, 234 * true otherwise 235 * @hide 236 */ 237 public boolean connect(BluetoothDevice device) { 238 if (DBG) log("connect(" + device + ")"); 239 try { 240 mServiceLock.readLock().lock(); 241 if (mService != null && isEnabled() && 242 isValidDevice(device)) { 243 return mService.connect(device); 244 } 245 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 246 return false; 247 } catch (RemoteException e) { 248 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 249 return false; 250 } finally { 251 mServiceLock.readLock().unlock(); 252 } 253 } 254 255 /** 256 * Initiate disconnection from a profile 257 * 258 * <p> This API will return false in scenarios like the profile on the 259 * Bluetooth device is not in connected state etc. When this API returns, 260 * true, it is guaranteed that the connection state change 261 * intent will be broadcasted with the state. Users can get the 262 * disconnection state of the profile from this intent. 263 * 264 * <p> If the disconnection is initiated by a remote device, the state 265 * will transition from {@link #STATE_CONNECTED} to 266 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 267 * host (local) device the state will transition from 268 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 269 * state {@link #STATE_DISCONNECTED}. The transition to 270 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 271 * two scenarios. 272 * 273 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 274 * permission. 275 * 276 * @param device Remote Bluetooth Device 277 * @return false on immediate error, 278 * true otherwise 279 * @hide 280 */ 281 public boolean disconnect(BluetoothDevice device) { 282 if (DBG) log("disconnect(" + device + ")"); 283 try { 284 mServiceLock.readLock().lock(); 285 if (mService != null && isEnabled() && 286 isValidDevice(device)) { 287 return mService.disconnect(device); 288 } 289 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 290 return false; 291 } catch (RemoteException e) { 292 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 293 return false; 294 } finally { 295 mServiceLock.readLock().unlock(); 296 } 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 public List<BluetoothDevice> getConnectedDevices() { 303 if (VDBG) log("getConnectedDevices()"); 304 try { 305 mServiceLock.readLock().lock(); 306 if (mService != null && isEnabled()) { 307 return mService.getConnectedDevices(); 308 } 309 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 310 return new ArrayList<BluetoothDevice>(); 311 } catch (RemoteException e) { 312 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 313 return new ArrayList<BluetoothDevice>(); 314 } finally { 315 mServiceLock.readLock().unlock(); 316 } 317 } 318 319 /** 320 * {@inheritDoc} 321 */ 322 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 323 if (VDBG) log("getDevicesMatchingStates()"); 324 try { 325 mServiceLock.readLock().lock(); 326 if (mService != null && isEnabled()) { 327 return mService.getDevicesMatchingConnectionStates(states); 328 } 329 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 330 return new ArrayList<BluetoothDevice>(); 331 } catch (RemoteException e) { 332 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 333 return new ArrayList<BluetoothDevice>(); 334 } finally { 335 mServiceLock.readLock().unlock(); 336 } 337 } 338 339 /** 340 * {@inheritDoc} 341 */ 342 public int getConnectionState(BluetoothDevice device) { 343 if (VDBG) log("getState(" + device + ")"); 344 try { 345 mServiceLock.readLock().lock(); 346 if (mService != null && isEnabled() 347 && isValidDevice(device)) { 348 return mService.getConnectionState(device); 349 } 350 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 351 return BluetoothProfile.STATE_DISCONNECTED; 352 } catch (RemoteException e) { 353 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 354 return BluetoothProfile.STATE_DISCONNECTED; 355 } finally { 356 mServiceLock.readLock().unlock(); 357 } 358 } 359 360 /** 361 * Set priority of the profile 362 * 363 * <p> The device should already be paired. 364 * Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager 365 * {@link #PRIORITY_OFF}, 366 * 367 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 368 * permission. 369 * 370 * @param device Paired bluetooth device 371 * @param priority 372 * @return true if priority is set, false on error 373 * @hide 374 */ 375 public boolean setPriority(BluetoothDevice device, int priority) { 376 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 377 try { 378 mServiceLock.readLock().lock(); 379 if (mService != null && isEnabled() 380 && isValidDevice(device)) { 381 if (priority != BluetoothProfile.PRIORITY_OFF && 382 priority != BluetoothProfile.PRIORITY_ON) { 383 return false; 384 } 385 return mService.setPriority(device, priority); 386 } 387 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 388 return false; 389 } catch (RemoteException e) { 390 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 391 return false; 392 } finally { 393 mServiceLock.readLock().unlock(); 394 } 395 } 396 397 /** 398 * Get the priority of the profile. 399 * 400 * <p> The priority can be any of: 401 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, 402 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 403 * 404 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 405 * 406 * @param device Bluetooth device 407 * @return priority of the device 408 * @hide 409 */ 410 @RequiresPermission(Manifest.permission.BLUETOOTH) 411 public int getPriority(BluetoothDevice device) { 412 if (VDBG) log("getPriority(" + device + ")"); 413 try { 414 mServiceLock.readLock().lock(); 415 if (mService != null && isEnabled() 416 && isValidDevice(device)) { 417 return mService.getPriority(device); 418 } 419 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 420 return BluetoothProfile.PRIORITY_OFF; 421 } catch (RemoteException e) { 422 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 423 return BluetoothProfile.PRIORITY_OFF; 424 } finally { 425 mServiceLock.readLock().unlock(); 426 } 427 } 428 429 /** 430 * Checks if Avrcp device supports the absolute volume feature. 431 * 432 * @return true if device supports absolute volume 433 * @hide 434 */ 435 public boolean isAvrcpAbsoluteVolumeSupported() { 436 if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported"); 437 try { 438 mServiceLock.readLock().lock(); 439 if (mService != null && isEnabled()) { 440 return mService.isAvrcpAbsoluteVolumeSupported(); 441 } 442 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 443 return false; 444 } catch (RemoteException e) { 445 Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e); 446 return false; 447 } finally { 448 mServiceLock.readLock().unlock(); 449 } 450 } 451 452 /** 453 * Tells remote device to adjust volume. Only if absolute volume is 454 * supported. Uses the following values: 455 * <ul> 456 * <li>{@link AudioManager#ADJUST_LOWER}</li> 457 * <li>{@link AudioManager#ADJUST_RAISE}</li> 458 * <li>{@link AudioManager#ADJUST_MUTE}</li> 459 * <li>{@link AudioManager#ADJUST_UNMUTE}</li> 460 * </ul> 461 * 462 * @param direction One of the supported adjust values. 463 * @hide 464 */ 465 public void adjustAvrcpAbsoluteVolume(int direction) { 466 if (DBG) Log.d(TAG, "adjustAvrcpAbsoluteVolume"); 467 try { 468 mServiceLock.readLock().lock(); 469 if (mService != null && isEnabled()) { 470 mService.adjustAvrcpAbsoluteVolume(direction); 471 } 472 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 473 } catch (RemoteException e) { 474 Log.e(TAG, "Error talking to BT service in adjustAvrcpAbsoluteVolume()", e); 475 } finally { 476 mServiceLock.readLock().unlock(); 477 } 478 } 479 480 /** 481 * Tells remote device to set an absolute volume. Only if absolute volume is supported 482 * 483 * @param volume Absolute volume to be set on AVRCP side 484 * @hide 485 */ 486 public void setAvrcpAbsoluteVolume(int volume) { 487 if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume"); 488 try { 489 mServiceLock.readLock().lock(); 490 if (mService != null && isEnabled()) { 491 mService.setAvrcpAbsoluteVolume(volume); 492 } 493 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 494 } catch (RemoteException e) { 495 Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e); 496 } finally { 497 mServiceLock.readLock().unlock(); 498 } 499 } 500 501 /** 502 * Check if A2DP profile is streaming music. 503 * 504 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 505 * 506 * @param device BluetoothDevice device 507 */ 508 public boolean isA2dpPlaying(BluetoothDevice device) { 509 try { 510 mServiceLock.readLock().lock(); 511 if (mService != null && isEnabled() 512 && isValidDevice(device)) { 513 return mService.isA2dpPlaying(device); 514 } 515 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 516 return false; 517 } catch (RemoteException e) { 518 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 519 return false; 520 } finally { 521 mServiceLock.readLock().unlock(); 522 } 523 } 524 525 /** 526 * This function checks if the remote device is an AVCRP 527 * target and thus whether we should send volume keys 528 * changes or not. 529 * @hide 530 */ 531 public boolean shouldSendVolumeKeys(BluetoothDevice device) { 532 if (isEnabled() && isValidDevice(device)) { 533 ParcelUuid[] uuids = device.getUuids(); 534 if (uuids == null) return false; 535 536 for (ParcelUuid uuid: uuids) { 537 if (BluetoothUuid.isAvrcpTarget(uuid)) { 538 return true; 539 } 540 } 541 } 542 return false; 543 } 544 545 /** 546 * Helper for converting a state to a string. 547 * 548 * For debug use only - strings are not internationalized. 549 * @hide 550 */ 551 public static String stateToString(int state) { 552 switch (state) { 553 case STATE_DISCONNECTED: 554 return "disconnected"; 555 case STATE_CONNECTING: 556 return "connecting"; 557 case STATE_CONNECTED: 558 return "connected"; 559 case STATE_DISCONNECTING: 560 return "disconnecting"; 561 case STATE_PLAYING: 562 return "playing"; 563 case STATE_NOT_PLAYING: 564 return "not playing"; 565 default: 566 return "<unknown state " + state + ">"; 567 } 568 } 569 570 private final ServiceConnection mConnection = new ServiceConnection() { 571 public void onServiceConnected(ComponentName className, IBinder service) { 572 if (DBG) Log.d(TAG, "Proxy object connected"); 573 try { 574 mServiceLock.writeLock().lock(); 575 mService = IBluetoothA2dp.Stub.asInterface(service); 576 } finally { 577 mServiceLock.writeLock().unlock(); 578 } 579 580 if (mServiceListener != null) { 581 mServiceListener.onServiceConnected(BluetoothProfile.A2DP, BluetoothA2dp.this); 582 } 583 } 584 public void onServiceDisconnected(ComponentName className) { 585 if (DBG) Log.d(TAG, "Proxy object disconnected"); 586 try { 587 mServiceLock.writeLock().lock(); 588 mService = null; 589 } finally { 590 mServiceLock.writeLock().unlock(); 591 } 592 if (mServiceListener != null) { 593 mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP); 594 } 595 } 596 }; 597 598 private boolean isEnabled() { 599 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 600 return false; 601 } 602 603 private boolean isValidDevice(BluetoothDevice device) { 604 if (device == null) return false; 605 606 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 607 return false; 608 } 609 610 private static void log(String msg) { 611 Log.d(TAG, msg); 612 } 613} 614