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