BluetoothHeadset.java revision 4e25533945ebefd888b48f4256e8735591b4f94b
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.RemoteException; 26import android.os.IBinder; 27import android.util.Log; 28 29/** 30 * The Android Bluetooth API is not finalized, and *will* change. Use at your 31 * own risk. 32 * 33 * Public API for controlling the Bluetooth Headset Service. This includes both 34 * Bluetooth Headset and Handsfree (v1.5) profiles. The Headset service will 35 * attempt a handsfree connection first, and fall back to headset. 36 * 37 * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset 38 * Service via IPC. 39 * 40 * Creating a BluetoothHeadset object will create a binding with the 41 * BluetoothHeadset service. Users of this object should call close() when they 42 * are finished with the BluetoothHeadset, so that this proxy object can unbind 43 * from the service. 44 * 45 * This BluetoothHeadset object is not immediately bound to the 46 * BluetoothHeadset service. Use the ServiceListener interface to obtain a 47 * notification when it is bound, this is especially important if you wish to 48 * immediately call methods on BluetoothHeadset after construction. 49 * 50 * Android only supports one connected Bluetooth Headset at a time. 51 * 52 * @hide 53 */ 54public final class BluetoothHeadset { 55 56 private static final String TAG = "BluetoothHeadset"; 57 private static final boolean DBG = false; 58 59 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 60 public static final String ACTION_STATE_CHANGED = 61 "android.bluetooth.headset.action.STATE_CHANGED"; 62 /** 63 * TODO(API release): Consider incorporating as new state in 64 * HEADSET_STATE_CHANGED 65 */ 66 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 67 public static final String ACTION_AUDIO_STATE_CHANGED = 68 "android.bluetooth.headset.action.AUDIO_STATE_CHANGED"; 69 public static final String EXTRA_STATE = 70 "android.bluetooth.headset.extra.STATE"; 71 public static final String EXTRA_PREVIOUS_STATE = 72 "android.bluetooth.headset.extra.PREVIOUS_STATE"; 73 public static final String EXTRA_AUDIO_STATE = 74 "android.bluetooth.headset.extra.AUDIO_STATE"; 75 76 /** Extra to be used with the Headset State change intent. 77 * This will be used only when Headset state changes to 78 * {@link #STATE_DISCONNECTED} from any previous state. 79 * This extra field is optional and will be used when 80 * we have deterministic information regarding whether 81 * the disconnect was initiated by the remote device or 82 * by the local adapter. 83 */ 84 public static final String EXTRA_DISCONNECT_INITIATOR = 85 "android.bluetooth.headset.extra.DISCONNECT_INITIATOR"; 86 87 /** 88 * Broadcast Action: Indicates a headset has posted a vendor-specific event. 89 * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, 90 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD}, and 91 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS}. 92 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. 93 * @hide 94 */ 95 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 96 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = 97 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT"; 98 99 /** 100 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 101 * intents that contains the name of the vendor-specific command. 102 * @hide 103 */ 104 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = 105 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD"; 106 107 /** 108 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 109 * intents that contains the Company ID of the vendor defining the vendor-specific 110 * command. 111 * @see <a href="https://www.bluetooth.org/Technical/AssignedNumbers/identifiers.htm"> 112 * Bluetooth SIG Assigned Numbers - Company Identifiers</a> 113 * @hide 114 */ 115 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID = 116 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID"; 117 118 /** 119 * A Parcelable String array extra field in 120 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains 121 * the arguments to the vendor-specific command. 122 * @hide 123 */ 124 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = 125 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; 126 127 128 /** 129 * TODO(API release): Consider incorporating as new state in 130 * HEADSET_STATE_CHANGED 131 */ 132 private IBluetoothHeadset mService; 133 private final Context mContext; 134 private final ServiceListener mServiceListener; 135 136 /** There was an error trying to obtain the state */ 137 public static final int STATE_ERROR = -1; 138 /** No headset currently connected */ 139 public static final int STATE_DISCONNECTED = 0; 140 /** Connection attempt in progress */ 141 public static final int STATE_CONNECTING = 1; 142 /** A headset is currently connected */ 143 public static final int STATE_CONNECTED = 2; 144 145 /** A SCO audio channel is not established */ 146 public static final int AUDIO_STATE_DISCONNECTED = 0; 147 /** A SCO audio channel is established */ 148 public static final int AUDIO_STATE_CONNECTED = 1; 149 150 public static final int RESULT_FAILURE = 0; 151 public static final int RESULT_SUCCESS = 1; 152 /** Connection canceled before completion. */ 153 public static final int RESULT_CANCELED = 2; 154 155 /** Values for {@link #EXTRA_DISCONNECT_INITIATOR} */ 156 public static final int REMOTE_DISCONNECT = 0; 157 public static final int LOCAL_DISCONNECT = 1; 158 159 160 /** Default priority for headsets that for which we will accept 161 * inconing connections and auto-connect */ 162 public static final int PRIORITY_AUTO_CONNECT = 1000; 163 /** Default priority for headsets that for which we will accept 164 * inconing connections but not auto-connect */ 165 public static final int PRIORITY_ON = 100; 166 /** Default priority for headsets that should not be auto-connected 167 * and not allow incoming connections. */ 168 public static final int PRIORITY_OFF = 0; 169 /** Default priority when not set or when the device is unpaired */ 170 public static final int PRIORITY_UNDEFINED = -1; 171 172 /** 173 * An interface for notifying BluetoothHeadset IPC clients when they have 174 * been connected to the BluetoothHeadset service. 175 */ 176 public interface ServiceListener { 177 /** 178 * Called to notify the client when this proxy object has been 179 * connected to the BluetoothHeadset service. Clients must wait for 180 * this callback before making IPC calls on the BluetoothHeadset 181 * service. 182 */ 183 public void onServiceConnected(); 184 185 /** 186 * Called to notify the client that this proxy object has been 187 * disconnected from the BluetoothHeadset service. Clients must not 188 * make IPC calls on the BluetoothHeadset service after this callback. 189 * This callback will currently only occur if the application hosting 190 * the BluetoothHeadset service, but may be called more often in future. 191 */ 192 public void onServiceDisconnected(); 193 } 194 195 /** 196 * Create a BluetoothHeadset proxy object. 197 */ 198 public BluetoothHeadset(Context context, ServiceListener l) { 199 mContext = context; 200 mServiceListener = l; 201 if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) { 202 Log.e(TAG, "Could not bind to Bluetooth Headset Service"); 203 } 204 } 205 206 protected void finalize() throws Throwable { 207 try { 208 close(); 209 } finally { 210 super.finalize(); 211 } 212 } 213 214 /** 215 * Close the connection to the backing service. 216 * Other public functions of BluetoothHeadset will return default error 217 * results once close() has been called. Multiple invocations of close() 218 * are ok. 219 */ 220 public synchronized void close() { 221 if (DBG) log("close()"); 222 if (mConnection != null) { 223 mContext.unbindService(mConnection); 224 mConnection = null; 225 } 226 } 227 228 /** 229 * Get the current state of the Bluetooth Headset service. 230 * @return One of the STATE_ return codes, or STATE_ERROR if this proxy 231 * object is currently not connected to the Headset service. 232 */ 233 public int getState(BluetoothDevice device) { 234 if (DBG) log("getState()"); 235 if (mService != null) { 236 try { 237 return mService.getState(device); 238 } catch (RemoteException e) {Log.e(TAG, e.toString());} 239 } else { 240 Log.w(TAG, "Proxy not attached to service"); 241 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 242 } 243 return BluetoothHeadset.STATE_ERROR; 244 } 245 246 /** 247 * Get the BluetoothDevice for the current headset. 248 * @return current headset, or null if not in connected or connecting 249 * state, or if this proxy object is not connected to the Headset 250 * service. 251 */ 252 public BluetoothDevice getCurrentHeadset() { 253 if (DBG) log("getCurrentHeadset()"); 254 if (mService != null) { 255 try { 256 return mService.getCurrentHeadset(); 257 } catch (RemoteException e) {Log.e(TAG, e.toString());} 258 } else { 259 Log.w(TAG, "Proxy not attached to service"); 260 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 261 } 262 return null; 263 } 264 265 /** 266 * Request to initiate a connection to a headset. 267 * This call does not block. Fails if a headset is already connecting 268 * or connected. 269 * Initiates auto-connection if device is null. Tries to connect to all 270 * devices with priority greater than PRIORITY_AUTO in descending order. 271 * @param device device to connect to, or null to auto-connect last connected 272 * headset 273 * @return false if there was a problem initiating the connection 274 * procedure, and no further HEADSET_STATE_CHANGED intents 275 * will be expected. 276 */ 277 public boolean connectHeadset(BluetoothDevice device) { 278 if (DBG) log("connectHeadset(" + device + ")"); 279 if (mService != null) { 280 try { 281 if (mService.connectHeadset(device)) { 282 return true; 283 } 284 } catch (RemoteException e) {Log.e(TAG, e.toString());} 285 } else { 286 Log.w(TAG, "Proxy not attached to service"); 287 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 288 } 289 return false; 290 } 291 292 /** 293 * Returns true if the specified headset is connected (does not include 294 * connecting). Returns false if not connected, or if this proxy object 295 * if not currently connected to the headset service. 296 */ 297 public boolean isConnected(BluetoothDevice device) { 298 if (DBG) log("isConnected(" + device + ")"); 299 if (mService != null) { 300 try { 301 return mService.isConnected(device); 302 } catch (RemoteException e) {Log.e(TAG, e.toString());} 303 } else { 304 Log.w(TAG, "Proxy not attached to service"); 305 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 306 } 307 return false; 308 } 309 310 /** 311 * Disconnects the current headset. Currently this call blocks, it may soon 312 * be made asynchornous. Returns false if this proxy object is 313 * not currently connected to the Headset service. 314 */ 315 public boolean disconnectHeadset(BluetoothDevice device) { 316 if (DBG) log("disconnectHeadset()"); 317 if (mService != null) { 318 try { 319 mService.disconnectHeadset(device); 320 return true; 321 } catch (RemoteException e) {Log.e(TAG, e.toString());} 322 } else { 323 Log.w(TAG, "Proxy not attached to service"); 324 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 325 } 326 return false; 327 } 328 329 /** 330 * Start BT Voice Recognition mode, and set up Bluetooth audio path. 331 * Returns false if there is no headset connected, or if the 332 * connected headset does not support voice recognition, or on 333 * error. 334 */ 335 public boolean startVoiceRecognition() { 336 if (DBG) log("startVoiceRecognition()"); 337 if (mService != null) { 338 try { 339 return mService.startVoiceRecognition(); 340 } catch (RemoteException e) {Log.e(TAG, e.toString());} 341 } else { 342 Log.w(TAG, "Proxy not attached to service"); 343 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 344 } 345 return false; 346 } 347 348 /** 349 * Stop BT Voice Recognition mode, and shut down Bluetooth audio path. 350 * Returns false if there is no headset connected, or the connected 351 * headset is not in voice recognition mode, or on error. 352 */ 353 public boolean stopVoiceRecognition() { 354 if (DBG) log("stopVoiceRecognition()"); 355 if (mService != null) { 356 try { 357 return mService.stopVoiceRecognition(); 358 } catch (RemoteException e) {Log.e(TAG, e.toString());} 359 } else { 360 Log.w(TAG, "Proxy not attached to service"); 361 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 362 } 363 return false; 364 } 365 366 /** 367 * Set priority of headset. 368 * Priority is a non-negative integer. By default paired headsets will have 369 * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0). 370 * Headsets with priority greater than zero will be auto-connected, and 371 * incoming connections will be accepted (if no other headset is 372 * connected). 373 * Auto-connection occurs at the following events: boot, incoming phone 374 * call, outgoing phone call. 375 * Headsets with priority equal to zero, or that are unpaired, are not 376 * auto-connected. 377 * Incoming connections are ignored regardless of priority if there is 378 * already a headset connected. 379 * @param device paired headset 380 * @param priority Integer priority, for example PRIORITY_AUTO or 381 * PRIORITY_NONE 382 * @return true if successful, false if there was some error 383 */ 384 public boolean setPriority(BluetoothDevice device, int priority) { 385 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 386 if (mService != null) { 387 try { 388 return mService.setPriority(device, priority); 389 } catch (RemoteException e) {Log.e(TAG, e.toString());} 390 } else { 391 Log.w(TAG, "Proxy not attached to service"); 392 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 393 } 394 return false; 395 } 396 397 /** 398 * Get priority of headset. 399 * @param device headset 400 * @return non-negative priority, or negative error code on error 401 */ 402 public int getPriority(BluetoothDevice device) { 403 if (DBG) log("getPriority(" + device + ")"); 404 if (mService != null) { 405 try { 406 return mService.getPriority(device); 407 } catch (RemoteException e) {Log.e(TAG, e.toString());} 408 } else { 409 Log.w(TAG, "Proxy not attached to service"); 410 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 411 } 412 return -1; 413 } 414 415 /** 416 * Get battery usage hint for Bluetooth Headset service. 417 * This is a monotonically increasing integer. Wraps to 0 at 418 * Integer.MAX_INT, and at boot. 419 * Current implementation returns the number of AT commands handled since 420 * boot. This is a good indicator for spammy headset/handsfree units that 421 * can keep the device awake by polling for cellular status updates. As a 422 * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms 423 * @return monotonically increasing battery usage hint, or a negative error 424 * code on error 425 * @hide 426 */ 427 public int getBatteryUsageHint() { 428 if (DBG) log("getBatteryUsageHint()"); 429 if (mService != null) { 430 try { 431 return mService.getBatteryUsageHint(); 432 } catch (RemoteException e) {Log.e(TAG, e.toString());} 433 } else { 434 Log.w(TAG, "Proxy not attached to service"); 435 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 436 } 437 return -1; 438 } 439 /** 440 * Indicates if current platform supports voice dialing over bluetooth SCO. 441 * @return true if voice dialing over bluetooth is supported, false otherwise. 442 * @hide 443 */ 444 public static boolean isBluetoothVoiceDialingEnabled(Context context) { 445 return context.getResources().getBoolean( 446 com.android.internal.R.bool.config_bluetooth_sco_off_call); 447 } 448 449 /** 450 * Cancel the outgoing connection. 451 * @hide 452 */ 453 public boolean cancelConnectThread() { 454 if (DBG) log("cancelConnectThread"); 455 if (mService != null) { 456 try { 457 return mService.cancelConnectThread(); 458 } catch (RemoteException e) {Log.e(TAG, e.toString());} 459 } else { 460 Log.w(TAG, "Proxy not attached to service"); 461 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 462 } 463 return false; 464 } 465 466 /** 467 * Accept the incoming connection. 468 * @hide 469 */ 470 public boolean acceptIncomingConnect(BluetoothDevice device) { 471 if (DBG) log("acceptIncomingConnect"); 472 if (mService != null) { 473 try { 474 return mService.acceptIncomingConnect(device); 475 } catch (RemoteException e) {Log.e(TAG, e.toString());} 476 } else { 477 Log.w(TAG, "Proxy not attached to service"); 478 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 479 } 480 return false; 481 } 482 483 /** 484 * Create the connect thread the incoming connection. 485 * @hide 486 */ 487 public boolean createIncomingConnect(BluetoothDevice device) { 488 if (DBG) log("createIncomingConnect"); 489 if (mService != null) { 490 try { 491 return mService.createIncomingConnect(device); 492 } catch (RemoteException e) {Log.e(TAG, e.toString());} 493 } else { 494 Log.w(TAG, "Proxy not attached to service"); 495 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 496 } 497 return false; 498 } 499 500 /** 501 * Connect to a Bluetooth Headset. 502 * Note: This is an internal function and shouldn't be exposed 503 * @hide 504 */ 505 public boolean connectHeadsetInternal(BluetoothDevice device) { 506 if (DBG) log("connectHeadsetInternal"); 507 if (mService != null) { 508 try { 509 return mService.connectHeadsetInternal(device); 510 } catch (RemoteException e) {Log.e(TAG, e.toString());} 511 } else { 512 Log.w(TAG, "Proxy not attached to service"); 513 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 514 } 515 return false; 516 } 517 518 /** 519 * Disconnect a Bluetooth Headset. 520 * Note: This is an internal function and shouldn't be exposed 521 * @hide 522 */ 523 public boolean disconnectHeadsetInternal(BluetoothDevice device) { 524 if (DBG) log("disconnectHeadsetInternal"); 525 if (mService != null) { 526 try { 527 return mService.disconnectHeadsetInternal(device); 528 } catch (RemoteException e) {Log.e(TAG, e.toString());} 529 } else { 530 Log.w(TAG, "Proxy not attached to service"); 531 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 532 } 533 return false; 534 } 535 private ServiceConnection mConnection = new ServiceConnection() { 536 public void onServiceConnected(ComponentName className, IBinder service) { 537 if (DBG) Log.d(TAG, "Proxy object connected"); 538 mService = IBluetoothHeadset.Stub.asInterface(service); 539 if (mServiceListener != null) { 540 mServiceListener.onServiceConnected(); 541 } 542 } 543 public void onServiceDisconnected(ComponentName className) { 544 if (DBG) Log.d(TAG, "Proxy object disconnected"); 545 mService = null; 546 if (mServiceListener != null) { 547 mServiceListener.onServiceDisconnected(); 548 } 549 } 550 }; 551 552 private static void log(String msg) { 553 Log.d(TAG, msg); 554 } 555} 556