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