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