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