BluetoothA2dp.java revision c8fa4ff838a0c3d2c67db65540fa751e5abe27ed
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.Context; 22import android.os.IBinder; 23import android.os.ParcelUuid; 24import android.os.RemoteException; 25import android.os.ServiceManager; 26import android.server.BluetoothA2dpService; 27import android.util.Log; 28 29import java.util.ArrayList; 30import 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 */ 44public 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 * {@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 * {@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 /** 133 * {@inheritDoc} 134 * @hide 135 */ 136 public boolean connect(BluetoothDevice device) { 137 if (DBG) log("connect(" + device + ")"); 138 if (mService != null && isEnabled() && 139 isValidDevice(device)) { 140 try { 141 return mService.connect(device); 142 } catch (RemoteException e) { 143 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 144 return false; 145 } 146 } 147 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 148 return false; 149 } 150 151 /** 152 * {@inheritDoc} 153 * @hide 154 */ 155 public boolean disconnect(BluetoothDevice device) { 156 if (DBG) log("disconnect(" + device + ")"); 157 if (mService != null && isEnabled() && 158 isValidDevice(device)) { 159 try { 160 return mService.disconnect(device); 161 } catch (RemoteException e) { 162 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 163 return false; 164 } 165 } 166 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 167 return false; 168 } 169 170 /** 171 * {@inheritDoc} 172 */ 173 public List<BluetoothDevice> getConnectedDevices() { 174 if (DBG) log("getConnectedDevices()"); 175 if (mService != null && isEnabled()) { 176 try { 177 return mService.getConnectedDevices(); 178 } catch (RemoteException e) { 179 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 180 return new ArrayList<BluetoothDevice>(); 181 } 182 } 183 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 184 return new ArrayList<BluetoothDevice>(); 185 } 186 187 /** 188 * {@inheritDoc} 189 */ 190 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 191 if (DBG) log("getDevicesMatchingStates()"); 192 if (mService != null && isEnabled()) { 193 try { 194 return mService.getDevicesMatchingConnectionStates(states); 195 } catch (RemoteException e) { 196 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 197 return new ArrayList<BluetoothDevice>(); 198 } 199 } 200 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 201 return new ArrayList<BluetoothDevice>(); 202 } 203 204 /** 205 * {@inheritDoc} 206 */ 207 public int getConnectionState(BluetoothDevice device) { 208 if (DBG) log("getState(" + device + ")"); 209 if (mService != null && isEnabled() 210 && isValidDevice(device)) { 211 try { 212 return mService.getConnectionState(device); 213 } catch (RemoteException e) { 214 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 215 return BluetoothProfile.STATE_DISCONNECTED; 216 } 217 } 218 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 219 return BluetoothProfile.STATE_DISCONNECTED; 220 } 221 222 /** 223 * {@inheritDoc} 224 * @hide 225 */ 226 public boolean setPriority(BluetoothDevice device, int priority) { 227 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 228 if (mService != null && isEnabled() 229 && isValidDevice(device)) { 230 if (priority != BluetoothProfile.PRIORITY_OFF && 231 priority != BluetoothProfile.PRIORITY_ON) { 232 return false; 233 } 234 try { 235 return mService.setPriority(device, priority); 236 } catch (RemoteException e) { 237 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 238 return false; 239 } 240 } 241 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 242 return false; 243 } 244 245 /** 246 * {@inheritDoc} 247 * @hide 248 */ 249 public int getPriority(BluetoothDevice device) { 250 if (DBG) log("getPriority(" + device + ")"); 251 if (mService != null && isEnabled() 252 && isValidDevice(device)) { 253 try { 254 return mService.getPriority(device); 255 } catch (RemoteException e) { 256 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 257 return BluetoothProfile.PRIORITY_OFF; 258 } 259 } 260 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 261 return BluetoothProfile.PRIORITY_OFF; 262 } 263 264 /** 265 * Check if A2DP profile is streaming music. 266 * 267 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 268 * 269 * @param device BluetoothDevice device 270 */ 271 public boolean isA2dpPlaying(BluetoothDevice device) { 272 if (mService != null && isEnabled() 273 && isValidDevice(device)) { 274 try { 275 return mService.isA2dpPlaying(device); 276 } catch (RemoteException e) { 277 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 278 return false; 279 } 280 } 281 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 282 return false; 283 } 284 285 /** 286 * Initiate suspend from an A2DP sink. 287 * 288 * <p> This API will return false in scenarios like the A2DP 289 * device is not in connected state etc. When this API returns, 290 * true, it is guaranteed that {@link #ACTION_CONNECTION_STATE_CHANGED} 291 * intent will be broadcasted with the state. Users can get the 292 * state of the A2DP device from this intent. 293 * 294 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 295 * permission. 296 * 297 * @param device Remote A2DP sink 298 * @return false on immediate error, 299 * true otherwise 300 * @hide 301 */ 302 public boolean suspendSink(BluetoothDevice device) { 303 if (mService != null && isEnabled() 304 && isValidDevice(device)) { 305 try { 306 return mService.suspendSink(device); 307 } catch (RemoteException e) { 308 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 309 return false; 310 } 311 } 312 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 313 return false; 314 } 315 316 /** 317 * Initiate resume from a suspended A2DP sink. 318 * 319 * <p> This API will return false in scenarios like the A2DP 320 * device is not in suspended state etc. When this API returns, 321 * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED} 322 * intent will be broadcasted with the state. Users can get the 323 * state of the A2DP device from this intent. 324 * 325 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 326 * 327 * @param device Remote A2DP sink 328 * @return false on immediate error, 329 * true otherwise 330 * @hide 331 */ 332 public boolean resumeSink(BluetoothDevice device) { 333 if (mService != null && isEnabled() 334 && isValidDevice(device)) { 335 try { 336 return mService.resumeSink(device); 337 } catch (RemoteException e) { 338 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 339 return false; 340 } 341 } 342 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 343 return false; 344 } 345 346 /** 347 * This function checks if the remote device is an AVCRP 348 * target and thus whether we should send volume keys 349 * changes or not. 350 * @hide 351 */ 352 public boolean shouldSendVolumeKeys(BluetoothDevice device) { 353 if (isEnabled() && isValidDevice(device)) { 354 ParcelUuid[] uuids = device.getUuids(); 355 if (uuids == null) return false; 356 357 for (ParcelUuid uuid: uuids) { 358 if (BluetoothUuid.isAvrcpTarget(uuid)) { 359 return true; 360 } 361 } 362 } 363 return false; 364 } 365 366 /** 367 * Helper for converting a state to a string. 368 * 369 * For debug use only - strings are not internationalized. 370 * @hide 371 */ 372 public static String stateToString(int state) { 373 switch (state) { 374 case STATE_DISCONNECTED: 375 return "disconnected"; 376 case STATE_CONNECTING: 377 return "connecting"; 378 case STATE_CONNECTED: 379 return "connected"; 380 case STATE_DISCONNECTING: 381 return "disconnecting"; 382 case STATE_PLAYING: 383 return "playing"; 384 case STATE_NOT_PLAYING: 385 return "not playing"; 386 default: 387 return "<unknown state " + state + ">"; 388 } 389 } 390 391 private boolean isEnabled() { 392 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 393 return false; 394 } 395 396 private boolean isValidDevice(BluetoothDevice device) { 397 if (device == null) return false; 398 399 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 400 return false; 401 } 402 403 private static void log(String msg) { 404 Log.d(TAG, msg); 405 } 406} 407