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 */ BluetoothA2dp(Context mContext, ServiceListener l)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 /** 133 * Initiate connection to a profile of the remote bluetooth device. 134 * 135 * <p> Currently, the system supports only 1 connection to the 136 * A2DP profile. The API will automatically disconnect connected 137 * devices before connecting. 138 * 139 * <p> This API returns false in scenarios like the profile on the 140 * device is already connected or Bluetooth is not turned on. 141 * When this API returns true, it is guaranteed that 142 * connection state intent for the profile will be broadcasted with 143 * the state. Users can get the connection state of the profile 144 * from this intent. 145 * 146 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 147 * permission. 148 * 149 * @param device Remote Bluetooth Device 150 * @return false on immediate error, 151 * true otherwise 152 * @hide 153 */ connect(BluetoothDevice device)154 public boolean connect(BluetoothDevice device) { 155 if (DBG) log("connect(" + device + ")"); 156 if (mService != null && isEnabled() && 157 isValidDevice(device)) { 158 try { 159 return mService.connect(device); 160 } catch (RemoteException e) { 161 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 162 return false; 163 } 164 } 165 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 166 return false; 167 } 168 169 /** 170 * Initiate disconnection from a profile 171 * 172 * <p> This API will return false in scenarios like the profile on the 173 * Bluetooth device is not in connected state etc. When this API returns, 174 * true, it is guaranteed that the connection state change 175 * intent will be broadcasted with the state. Users can get the 176 * disconnection state of the profile from this intent. 177 * 178 * <p> If the disconnection is initiated by a remote device, the state 179 * will transition from {@link #STATE_CONNECTED} to 180 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 181 * host (local) device the state will transition from 182 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 183 * state {@link #STATE_DISCONNECTED}. The transition to 184 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 185 * two scenarios. 186 * 187 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 188 * permission. 189 * 190 * @param device Remote Bluetooth Device 191 * @return false on immediate error, 192 * true otherwise 193 * @hide 194 */ disconnect(BluetoothDevice device)195 public boolean disconnect(BluetoothDevice device) { 196 if (DBG) log("disconnect(" + device + ")"); 197 if (mService != null && isEnabled() && 198 isValidDevice(device)) { 199 try { 200 return mService.disconnect(device); 201 } catch (RemoteException e) { 202 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 203 return false; 204 } 205 } 206 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 207 return false; 208 } 209 210 /** 211 * {@inheritDoc} 212 */ getConnectedDevices()213 public List<BluetoothDevice> getConnectedDevices() { 214 if (DBG) log("getConnectedDevices()"); 215 if (mService != null && isEnabled()) { 216 try { 217 return mService.getConnectedDevices(); 218 } catch (RemoteException e) { 219 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 220 return new ArrayList<BluetoothDevice>(); 221 } 222 } 223 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 224 return new ArrayList<BluetoothDevice>(); 225 } 226 227 /** 228 * {@inheritDoc} 229 */ getDevicesMatchingConnectionStates(int[] states)230 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 231 if (DBG) log("getDevicesMatchingStates()"); 232 if (mService != null && isEnabled()) { 233 try { 234 return mService.getDevicesMatchingConnectionStates(states); 235 } catch (RemoteException e) { 236 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 237 return new ArrayList<BluetoothDevice>(); 238 } 239 } 240 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 241 return new ArrayList<BluetoothDevice>(); 242 } 243 244 /** 245 * {@inheritDoc} 246 */ getConnectionState(BluetoothDevice device)247 public int getConnectionState(BluetoothDevice device) { 248 if (DBG) log("getState(" + device + ")"); 249 if (mService != null && isEnabled() 250 && isValidDevice(device)) { 251 try { 252 return mService.getConnectionState(device); 253 } catch (RemoteException e) { 254 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 255 return BluetoothProfile.STATE_DISCONNECTED; 256 } 257 } 258 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 259 return BluetoothProfile.STATE_DISCONNECTED; 260 } 261 262 /** 263 * Set priority of the profile 264 * 265 * <p> The device should already be paired. 266 * Priority can be one of {@link #PRIORITY_ON} or 267 * {@link #PRIORITY_OFF}, 268 * 269 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 270 * permission. 271 * 272 * @param device Paired bluetooth device 273 * @param priority 274 * @return true if priority is set, false on error 275 * @hide 276 */ setPriority(BluetoothDevice device, int priority)277 public boolean setPriority(BluetoothDevice device, int priority) { 278 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 279 if (mService != null && isEnabled() 280 && isValidDevice(device)) { 281 if (priority != BluetoothProfile.PRIORITY_OFF && 282 priority != BluetoothProfile.PRIORITY_ON) { 283 return false; 284 } 285 try { 286 return mService.setPriority(device, priority); 287 } catch (RemoteException e) { 288 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 289 return false; 290 } 291 } 292 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 293 return false; 294 } 295 296 /** 297 * Get the priority of the profile. 298 * 299 * <p> The priority can be any of: 300 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, 301 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 302 * 303 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 304 * 305 * @param device Bluetooth device 306 * @return priority of the device 307 * @hide 308 */ getPriority(BluetoothDevice device)309 public int getPriority(BluetoothDevice device) { 310 if (DBG) log("getPriority(" + device + ")"); 311 if (mService != null && isEnabled() 312 && isValidDevice(device)) { 313 try { 314 return mService.getPriority(device); 315 } catch (RemoteException e) { 316 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 317 return BluetoothProfile.PRIORITY_OFF; 318 } 319 } 320 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 321 return BluetoothProfile.PRIORITY_OFF; 322 } 323 324 /** 325 * Check if A2DP profile is streaming music. 326 * 327 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 328 * 329 * @param device BluetoothDevice device 330 */ isA2dpPlaying(BluetoothDevice device)331 public boolean isA2dpPlaying(BluetoothDevice device) { 332 if (mService != null && isEnabled() 333 && isValidDevice(device)) { 334 try { 335 return mService.isA2dpPlaying(device); 336 } catch (RemoteException e) { 337 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 338 return false; 339 } 340 } 341 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 342 return false; 343 } 344 345 /** 346 * Initiate suspend from an A2DP sink. 347 * 348 * <p> This API will return false in scenarios like the A2DP 349 * device is not in connected state etc. When this API returns, 350 * true, it is guaranteed that {@link #ACTION_CONNECTION_STATE_CHANGED} 351 * intent will be broadcasted with the state. Users can get the 352 * state of the A2DP device from this intent. 353 * 354 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 355 * permission. 356 * 357 * @param device Remote A2DP sink 358 * @return false on immediate error, 359 * true otherwise 360 * @hide 361 */ suspendSink(BluetoothDevice device)362 public boolean suspendSink(BluetoothDevice device) { 363 if (mService != null && isEnabled() 364 && isValidDevice(device)) { 365 try { 366 return mService.suspendSink(device); 367 } catch (RemoteException e) { 368 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 369 return false; 370 } 371 } 372 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 373 return false; 374 } 375 376 /** 377 * Initiate resume from a suspended A2DP sink. 378 * 379 * <p> This API will return false in scenarios like the A2DP 380 * device is not in suspended state etc. When this API returns, 381 * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED} 382 * intent will be broadcasted with the state. Users can get the 383 * state of the A2DP device from this intent. 384 * 385 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 386 * 387 * @param device Remote A2DP sink 388 * @return false on immediate error, 389 * true otherwise 390 * @hide 391 */ resumeSink(BluetoothDevice device)392 public boolean resumeSink(BluetoothDevice device) { 393 if (mService != null && isEnabled() 394 && isValidDevice(device)) { 395 try { 396 return mService.resumeSink(device); 397 } catch (RemoteException e) { 398 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 399 return false; 400 } 401 } 402 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 403 return false; 404 } 405 406 /** 407 * This function checks if the remote device is an AVCRP 408 * target and thus whether we should send volume keys 409 * changes or not. 410 * @hide 411 */ shouldSendVolumeKeys(BluetoothDevice device)412 public boolean shouldSendVolumeKeys(BluetoothDevice device) { 413 if (isEnabled() && isValidDevice(device)) { 414 ParcelUuid[] uuids = device.getUuids(); 415 if (uuids == null) return false; 416 417 for (ParcelUuid uuid: uuids) { 418 if (BluetoothUuid.isAvrcpTarget(uuid)) { 419 return true; 420 } 421 } 422 } 423 return false; 424 } 425 426 /** 427 * Allow or disallow incoming connection 428 * @param device Sink 429 * @param value True / False 430 * @return Success or Failure of the binder call. 431 * @hide 432 */ allowIncomingConnect(BluetoothDevice device, boolean value)433 public boolean allowIncomingConnect(BluetoothDevice device, boolean value) { 434 if (DBG) log("allowIncomingConnect(" + device + ":" + value + ")"); 435 try { 436 return mService.allowIncomingConnect(device, value); 437 } catch (RemoteException e) { 438 Log.e(TAG, "", e); 439 return false; 440 } 441 } 442 443 /** 444 * Helper for converting a state to a string. 445 * 446 * For debug use only - strings are not internationalized. 447 * @hide 448 */ stateToString(int state)449 public static String stateToString(int state) { 450 switch (state) { 451 case STATE_DISCONNECTED: 452 return "disconnected"; 453 case STATE_CONNECTING: 454 return "connecting"; 455 case STATE_CONNECTED: 456 return "connected"; 457 case STATE_DISCONNECTING: 458 return "disconnecting"; 459 case STATE_PLAYING: 460 return "playing"; 461 case STATE_NOT_PLAYING: 462 return "not playing"; 463 default: 464 return "<unknown state " + state + ">"; 465 } 466 } 467 isEnabled()468 private boolean isEnabled() { 469 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 470 return false; 471 } 472 isValidDevice(BluetoothDevice device)473 private boolean isValidDevice(BluetoothDevice device) { 474 if (device == null) return false; 475 476 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 477 return false; 478 } 479 log(String msg)480 private static void log(String msg) { 481 Log.d(TAG, msg); 482 } 483 } 484