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.Context; 22 import android.os.IBinder; 23 import android.os.ParcelUuid; 24 import android.os.RemoteException; 25 import android.os.ServiceManager; 26 import android.server.BluetoothA2dpService; 27 import android.util.Log; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 33 /** 34 * This class provides the public APIs to control the Bluetooth A2DP 35 * profile. 36 * 37 *<p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP 38 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get 39 * the BluetoothA2dp proxy object. 40 * 41 * <p> Android only supports one connected Bluetooth A2dp device at a time. 42 * Each method is protected with its appropriate permission. 43 */ 44 public final class BluetoothA2dp implements BluetoothProfile { 45 private static final String TAG = "BluetoothA2dp"; 46 private static final boolean DBG = false; 47 48 /** 49 * Intent used to broadcast the change in connection state of the A2DP 50 * profile. 51 * 52 * <p>This intent will have 3 extras: 53 * <ul> 54 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 55 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> 56 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 57 * </ul> 58 * 59 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 60 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 61 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 62 * 63 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 64 * receive. 65 */ 66 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 67 public static final String ACTION_CONNECTION_STATE_CHANGED = 68 "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"; 69 70 /** 71 * Intent used to broadcast the change in the Playing state of the A2DP 72 * profile. 73 * 74 * <p>This intent will have 3 extras: 75 * <ul> 76 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 77 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 78 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 79 * </ul> 80 * 81 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 82 * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, 83 * 84 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 85 * receive. 86 */ 87 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 88 public static final String ACTION_PLAYING_STATE_CHANGED = 89 "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"; 90 91 /** 92 * A2DP sink device is streaming music. This state can be one of 93 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 94 * {@link #ACTION_PLAYING_STATE_CHANGED} intent. 95 */ 96 public static final int STATE_PLAYING = 10; 97 98 /** 99 * A2DP sink device is NOT 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_NOT_PLAYING = 11; 104 105 private ServiceListener mServiceListener; 106 private IBluetoothA2dp mService; 107 private BluetoothAdapter mAdapter; 108 109 /** 110 * Create a BluetoothA2dp proxy object for interacting with the local 111 * Bluetooth A2DP service. 112 * 113 */ 114 /*package*/ BluetoothA2dp(Context mContext, ServiceListener l) { 115 IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE); 116 mServiceListener = l; 117 mAdapter = BluetoothAdapter.getDefaultAdapter(); 118 if (b != null) { 119 mService = IBluetoothA2dp.Stub.asInterface(b); 120 if (mServiceListener != null) { 121 mServiceListener.onServiceConnected(BluetoothProfile.A2DP, this); 122 } 123 } else { 124 Log.w(TAG, "Bluetooth A2DP service not available!"); 125 126 // Instead of throwing an exception which prevents people from going 127 // into Wireless settings in the emulator. Let it crash later when it is actually used. 128 mService = null; 129 } 130 } 131 132 /*package*/ void close() { 133 mServiceListener = null; 134 } 135 136 /** 137 * Initiate connection to a profile of the remote bluetooth device. 138 * 139 * <p> Currently, the system supports only 1 connection to the 140 * A2DP profile. The API will automatically disconnect connected 141 * devices before connecting. 142 * 143 * <p> This API returns false in scenarios like the profile on the 144 * device is already connected or Bluetooth is not turned on. 145 * When this API returns true, it is guaranteed that 146 * connection state intent for the profile will be broadcasted with 147 * the state. Users can get the connection state of the profile 148 * from this intent. 149 * 150 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 151 * permission. 152 * 153 * @param device Remote Bluetooth Device 154 * @return false on immediate error, 155 * true otherwise 156 * @hide 157 */ 158 public boolean connect(BluetoothDevice device) { 159 if (DBG) log("connect(" + device + ")"); 160 if (mService != null && isEnabled() && 161 isValidDevice(device)) { 162 try { 163 return mService.connect(device); 164 } catch (RemoteException e) { 165 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 166 return false; 167 } 168 } 169 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 170 return false; 171 } 172 173 /** 174 * Initiate disconnection from a profile 175 * 176 * <p> This API will return false in scenarios like the profile on the 177 * Bluetooth device is not in connected state etc. When this API returns, 178 * true, it is guaranteed that the connection state change 179 * intent will be broadcasted with the state. Users can get the 180 * disconnection state of the profile from this intent. 181 * 182 * <p> If the disconnection is initiated by a remote device, the state 183 * will transition from {@link #STATE_CONNECTED} to 184 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 185 * host (local) device the state will transition from 186 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 187 * state {@link #STATE_DISCONNECTED}. The transition to 188 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 189 * two scenarios. 190 * 191 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 192 * permission. 193 * 194 * @param device Remote Bluetooth Device 195 * @return false on immediate error, 196 * true otherwise 197 * @hide 198 */ 199 public boolean disconnect(BluetoothDevice device) { 200 if (DBG) log("disconnect(" + device + ")"); 201 if (mService != null && isEnabled() && 202 isValidDevice(device)) { 203 try { 204 return mService.disconnect(device); 205 } catch (RemoteException e) { 206 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 207 return false; 208 } 209 } 210 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 211 return false; 212 } 213 214 /** 215 * {@inheritDoc} 216 */ 217 public List<BluetoothDevice> getConnectedDevices() { 218 if (DBG) log("getConnectedDevices()"); 219 if (mService != null && isEnabled()) { 220 try { 221 return mService.getConnectedDevices(); 222 } catch (RemoteException e) { 223 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 224 return new ArrayList<BluetoothDevice>(); 225 } 226 } 227 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 228 return new ArrayList<BluetoothDevice>(); 229 } 230 231 /** 232 * {@inheritDoc} 233 */ 234 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 235 if (DBG) log("getDevicesMatchingStates()"); 236 if (mService != null && isEnabled()) { 237 try { 238 return mService.getDevicesMatchingConnectionStates(states); 239 } catch (RemoteException e) { 240 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 241 return new ArrayList<BluetoothDevice>(); 242 } 243 } 244 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 245 return new ArrayList<BluetoothDevice>(); 246 } 247 248 /** 249 * {@inheritDoc} 250 */ 251 public int getConnectionState(BluetoothDevice device) { 252 if (DBG) log("getState(" + device + ")"); 253 if (mService != null && isEnabled() 254 && isValidDevice(device)) { 255 try { 256 return mService.getConnectionState(device); 257 } catch (RemoteException e) { 258 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 259 return BluetoothProfile.STATE_DISCONNECTED; 260 } 261 } 262 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 263 return BluetoothProfile.STATE_DISCONNECTED; 264 } 265 266 /** 267 * Set priority of the profile 268 * 269 * <p> The device should already be paired. 270 * Priority can be one of {@link #PRIORITY_ON} or 271 * {@link #PRIORITY_OFF}, 272 * 273 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 274 * permission. 275 * 276 * @param device Paired bluetooth device 277 * @param priority 278 * @return true if priority is set, false on error 279 * @hide 280 */ 281 public boolean setPriority(BluetoothDevice device, int priority) { 282 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 283 if (mService != null && isEnabled() 284 && isValidDevice(device)) { 285 if (priority != BluetoothProfile.PRIORITY_OFF && 286 priority != BluetoothProfile.PRIORITY_ON) { 287 return false; 288 } 289 try { 290 return mService.setPriority(device, priority); 291 } catch (RemoteException e) { 292 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 293 return false; 294 } 295 } 296 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 297 return false; 298 } 299 300 /** 301 * Get the priority of the profile. 302 * 303 * <p> The priority can be any of: 304 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, 305 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 306 * 307 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 308 * 309 * @param device Bluetooth device 310 * @return priority of the device 311 * @hide 312 */ 313 public int getPriority(BluetoothDevice device) { 314 if (DBG) log("getPriority(" + device + ")"); 315 if (mService != null && isEnabled() 316 && isValidDevice(device)) { 317 try { 318 return mService.getPriority(device); 319 } catch (RemoteException e) { 320 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 321 return BluetoothProfile.PRIORITY_OFF; 322 } 323 } 324 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 325 return BluetoothProfile.PRIORITY_OFF; 326 } 327 328 /** 329 * Check if A2DP profile is streaming music. 330 * 331 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 332 * 333 * @param device BluetoothDevice device 334 */ 335 public boolean isA2dpPlaying(BluetoothDevice device) { 336 if (mService != null && isEnabled() 337 && isValidDevice(device)) { 338 try { 339 return mService.isA2dpPlaying(device); 340 } catch (RemoteException e) { 341 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 342 return false; 343 } 344 } 345 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 346 return false; 347 } 348 349 /** 350 * Initiate suspend from an A2DP sink. 351 * 352 * <p> This API will return false in scenarios like the A2DP 353 * device is not in connected state etc. When this API returns, 354 * true, it is guaranteed that {@link #ACTION_CONNECTION_STATE_CHANGED} 355 * intent will be broadcasted with the state. Users can get the 356 * state of the A2DP device from this intent. 357 * 358 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 359 * permission. 360 * 361 * @param device Remote A2DP sink 362 * @return false on immediate error, 363 * true otherwise 364 * @hide 365 */ 366 public boolean suspendSink(BluetoothDevice device) { 367 if (mService != null && isEnabled() 368 && isValidDevice(device)) { 369 try { 370 return mService.suspendSink(device); 371 } catch (RemoteException e) { 372 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 373 return false; 374 } 375 } 376 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 377 return false; 378 } 379 380 /** 381 * Initiate resume from a suspended A2DP sink. 382 * 383 * <p> This API will return false in scenarios like the A2DP 384 * device is not in suspended state etc. When this API returns, 385 * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED} 386 * intent will be broadcasted with the state. Users can get the 387 * state of the A2DP device from this intent. 388 * 389 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 390 * 391 * @param device Remote A2DP sink 392 * @return false on immediate error, 393 * true otherwise 394 * @hide 395 */ 396 public boolean resumeSink(BluetoothDevice device) { 397 if (mService != null && isEnabled() 398 && isValidDevice(device)) { 399 try { 400 return mService.resumeSink(device); 401 } catch (RemoteException e) { 402 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 403 return false; 404 } 405 } 406 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 407 return false; 408 } 409 410 /** 411 * This function checks if the remote device is an AVCRP 412 * target and thus whether we should send volume keys 413 * changes or not. 414 * @hide 415 */ 416 public boolean shouldSendVolumeKeys(BluetoothDevice device) { 417 if (isEnabled() && isValidDevice(device)) { 418 ParcelUuid[] uuids = device.getUuids(); 419 if (uuids == null) return false; 420 421 for (ParcelUuid uuid: uuids) { 422 if (BluetoothUuid.isAvrcpTarget(uuid)) { 423 return true; 424 } 425 } 426 } 427 return false; 428 } 429 430 /** 431 * Allow or disallow incoming connection 432 * @param device Sink 433 * @param value True / False 434 * @return Success or Failure of the binder call. 435 * @hide 436 */ 437 public boolean allowIncomingConnect(BluetoothDevice device, boolean value) { 438 if (DBG) log("allowIncomingConnect(" + device + ":" + value + ")"); 439 try { 440 return mService.allowIncomingConnect(device, value); 441 } catch (RemoteException e) { 442 Log.e(TAG, "", e); 443 return false; 444 } 445 } 446 447 /** 448 * Helper for converting a state to a string. 449 * 450 * For debug use only - strings are not internationalized. 451 * @hide 452 */ 453 public static String stateToString(int state) { 454 switch (state) { 455 case STATE_DISCONNECTED: 456 return "disconnected"; 457 case STATE_CONNECTING: 458 return "connecting"; 459 case STATE_CONNECTED: 460 return "connected"; 461 case STATE_DISCONNECTING: 462 return "disconnecting"; 463 case STATE_PLAYING: 464 return "playing"; 465 case STATE_NOT_PLAYING: 466 return "not playing"; 467 default: 468 return "<unknown state " + state + ">"; 469 } 470 } 471 472 private boolean isEnabled() { 473 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 474 return false; 475 } 476 477 private boolean isValidDevice(BluetoothDevice device) { 478 if (device == null) return false; 479 480 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 481 return false; 482 } 483 484 private static void log(String msg) { 485 Log.d(TAG, msg); 486 } 487 } 488