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