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