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 * TODO(API release): Consider incorporating as new state in 89 * HEADSET_STATE_CHANGED 90 */ 91 private IBluetoothHeadset mService; 92 private final Context mContext; 93 private final ServiceListener mServiceListener; 94 95 /** There was an error trying to obtain the state */ 96 public static final int STATE_ERROR = -1; 97 /** No headset currently connected */ 98 public static final int STATE_DISCONNECTED = 0; 99 /** Connection attempt in progress */ 100 public static final int STATE_CONNECTING = 1; 101 /** A headset is currently connected */ 102 public static final int STATE_CONNECTED = 2; 103 104 /** A SCO audio channel is not established */ 105 public static final int AUDIO_STATE_DISCONNECTED = 0; 106 /** A SCO audio channel is established */ 107 public static final int AUDIO_STATE_CONNECTED = 1; 108 109 public static final int RESULT_FAILURE = 0; 110 public static final int RESULT_SUCCESS = 1; 111 /** Connection canceled before completion. */ 112 public static final int RESULT_CANCELED = 2; 113 114 /** Values for {@link #EXTRA_DISCONNECT_INITIATOR} */ 115 public static final int REMOTE_DISCONNECT = 0; 116 public static final int LOCAL_DISCONNECT = 1; 117 118 119 /** Default priority for headsets for which we will accept 120 * incoming connections and auto-connect. */ 121 public static final int PRIORITY_AUTO_CONNECT = 1000; 122 /** Default priority for headsets for which we will accept 123 * incoming connections but not auto-connect. */ 124 public static final int PRIORITY_ON = 100; 125 /** Default priority for headsets that should not be auto-connected 126 * and not allow incoming connections. */ 127 public static final int PRIORITY_OFF = 0; 128 /** Default priority when not set or when the device is unpaired */ 129 public static final int PRIORITY_UNDEFINED = -1; 130 131 /** 132 * An interface for notifying BluetoothHeadset IPC clients when they have 133 * been connected to the BluetoothHeadset service. 134 */ 135 public interface ServiceListener { 136 /** 137 * Called to notify the client when this proxy object has been 138 * connected to the BluetoothHeadset service. Clients must wait for 139 * this callback before making IPC calls on the BluetoothHeadset 140 * service. 141 */ 142 public void onServiceConnected(); 143 144 /** 145 * Called to notify the client that this proxy object has been 146 * disconnected from the BluetoothHeadset service. Clients must not 147 * make IPC calls on the BluetoothHeadset service after this callback. 148 * This callback will currently only occur if the application hosting 149 * the BluetoothHeadset service, but may be called more often in future. 150 */ 151 public void onServiceDisconnected(); 152 } 153 154 /** 155 * Create a BluetoothHeadset proxy object. 156 */ 157 public BluetoothHeadset(Context context, ServiceListener l) { 158 mContext = context; 159 mServiceListener = l; 160 if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) { 161 Log.e(TAG, "Could not bind to Bluetooth Headset Service"); 162 } 163 } 164 165 protected void finalize() throws Throwable { 166 try { 167 close(); 168 } finally { 169 super.finalize(); 170 } 171 } 172 173 /** 174 * Close the connection to the backing service. 175 * Other public functions of BluetoothHeadset will return default error 176 * results once close() has been called. Multiple invocations of close() 177 * are ok. 178 */ 179 public synchronized void close() { 180 if (DBG) log("close()"); 181 if (mConnection != null) { 182 mContext.unbindService(mConnection); 183 mConnection = null; 184 } 185 } 186 187 /** 188 * Get the current state of the Bluetooth Headset service. 189 * @return One of the STATE_ return codes, or STATE_ERROR if this proxy 190 * object is currently not connected to the Headset service. 191 */ 192 public int getState(BluetoothDevice device) { 193 if (DBG) log("getState()"); 194 if (mService != null) { 195 try { 196 return mService.getState(device); 197 } catch (RemoteException e) {Log.e(TAG, e.toString());} 198 } else { 199 Log.w(TAG, "Proxy not attached to service"); 200 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 201 } 202 return BluetoothHeadset.STATE_ERROR; 203 } 204 205 /** 206 * Get the BluetoothDevice for the current headset. 207 * @return current headset, or null if not in connected or connecting 208 * state, or if this proxy object is not connected to the Headset 209 * service. 210 */ 211 public BluetoothDevice getCurrentHeadset() { 212 if (DBG) log("getCurrentHeadset()"); 213 if (mService != null) { 214 try { 215 return mService.getCurrentHeadset(); 216 } catch (RemoteException e) {Log.e(TAG, e.toString());} 217 } else { 218 Log.w(TAG, "Proxy not attached to service"); 219 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 220 } 221 return null; 222 } 223 224 /** 225 * Request to initiate a connection to a headset. 226 * This call does not block. Fails if a headset is already connecting 227 * or connected. 228 * Initiates auto-connection if device is null. Tries to connect to all 229 * devices with priority greater than PRIORITY_AUTO in descending order. 230 * @param device device to connect to, or null to auto-connect last connected 231 * headset 232 * @return false if there was a problem initiating the connection 233 * procedure, and no further HEADSET_STATE_CHANGED intents 234 * will be expected. 235 */ 236 public boolean connectHeadset(BluetoothDevice device) { 237 if (DBG) log("connectHeadset(" + device + ")"); 238 if (mService != null) { 239 try { 240 if (mService.connectHeadset(device)) { 241 return true; 242 } 243 } catch (RemoteException e) {Log.e(TAG, e.toString());} 244 } else { 245 Log.w(TAG, "Proxy not attached to service"); 246 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 247 } 248 return false; 249 } 250 251 /** 252 * Returns true if the specified headset is connected (does not include 253 * connecting). Returns false if not connected, or if this proxy object 254 * if not currently connected to the headset service. 255 */ 256 public boolean isConnected(BluetoothDevice device) { 257 if (DBG) log("isConnected(" + device + ")"); 258 if (mService != null) { 259 try { 260 return mService.isConnected(device); 261 } catch (RemoteException e) {Log.e(TAG, e.toString());} 262 } else { 263 Log.w(TAG, "Proxy not attached to service"); 264 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 265 } 266 return false; 267 } 268 269 /** 270 * Disconnects the current headset. Currently this call blocks, it may soon 271 * be made asynchronous. Returns false if this proxy object is 272 * not currently connected to the Headset service. 273 */ 274 public boolean disconnectHeadset(BluetoothDevice device) { 275 if (DBG) log("disconnectHeadset()"); 276 if (mService != null) { 277 try { 278 mService.disconnectHeadset(device); 279 return true; 280 } catch (RemoteException e) {Log.e(TAG, e.toString());} 281 } else { 282 Log.w(TAG, "Proxy not attached to service"); 283 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 284 } 285 return false; 286 } 287 288 /** 289 * Start BT Voice Recognition mode, and set up Bluetooth audio path. 290 * Returns false if there is no headset connected, or if the 291 * connected headset does not support voice recognition, or on 292 * error. 293 */ 294 public boolean startVoiceRecognition() { 295 if (DBG) log("startVoiceRecognition()"); 296 if (mService != null) { 297 try { 298 return mService.startVoiceRecognition(); 299 } catch (RemoteException e) {Log.e(TAG, e.toString());} 300 } else { 301 Log.w(TAG, "Proxy not attached to service"); 302 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 303 } 304 return false; 305 } 306 307 /** 308 * Stop BT Voice Recognition mode, and shut down Bluetooth audio path. 309 * Returns false if there is no headset connected, or the connected 310 * headset is not in voice recognition mode, or on error. 311 */ 312 public boolean stopVoiceRecognition() { 313 if (DBG) log("stopVoiceRecognition()"); 314 if (mService != null) { 315 try { 316 return mService.stopVoiceRecognition(); 317 } catch (RemoteException e) {Log.e(TAG, e.toString());} 318 } else { 319 Log.w(TAG, "Proxy not attached to service"); 320 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 321 } 322 return false; 323 } 324 325 /** 326 * Set priority of headset. 327 * Priority is a non-negative integer. By default paired headsets will have 328 * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0). 329 * Headsets with priority greater than zero will be auto-connected, and 330 * incoming connections will be accepted (if no other headset is 331 * connected). 332 * Auto-connection occurs at the following events: boot, incoming phone 333 * call, outgoing phone call. 334 * Headsets with priority equal to zero, or that are unpaired, are not 335 * auto-connected. 336 * Incoming connections are ignored regardless of priority if there is 337 * already a headset connected. 338 * @param device paired headset 339 * @param priority Integer priority, for example PRIORITY_AUTO or 340 * PRIORITY_NONE 341 * @return true if successful, false if there was some error 342 */ 343 public boolean setPriority(BluetoothDevice device, int priority) { 344 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 345 if (mService != null) { 346 try { 347 return mService.setPriority(device, priority); 348 } catch (RemoteException e) {Log.e(TAG, e.toString());} 349 } else { 350 Log.w(TAG, "Proxy not attached to service"); 351 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 352 } 353 return false; 354 } 355 356 /** 357 * Get priority of headset. 358 * @param device headset 359 * @return non-negative priority, or negative error code on error 360 */ 361 public int getPriority(BluetoothDevice device) { 362 if (DBG) log("getPriority(" + device + ")"); 363 if (mService != null) { 364 try { 365 return mService.getPriority(device); 366 } catch (RemoteException e) {Log.e(TAG, e.toString());} 367 } else { 368 Log.w(TAG, "Proxy not attached to service"); 369 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 370 } 371 return -1; 372 } 373 374 /** 375 * Get battery usage hint for Bluetooth Headset service. 376 * This is a monotonically increasing integer. Wraps to 0 at 377 * Integer.MAX_INT, and at boot. 378 * Current implementation returns the number of AT commands handled since 379 * boot. This is a good indicator for spammy headset/handsfree units that 380 * can keep the device awake by polling for cellular status updates. As a 381 * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms 382 * @return monotonically increasing battery usage hint, or a negative error 383 * code on error 384 * @hide 385 */ 386 public int getBatteryUsageHint() { 387 if (DBG) log("getBatteryUsageHint()"); 388 if (mService != null) { 389 try { 390 return mService.getBatteryUsageHint(); 391 } catch (RemoteException e) {Log.e(TAG, e.toString());} 392 } else { 393 Log.w(TAG, "Proxy not attached to service"); 394 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 395 } 396 return -1; 397 } 398 /** 399 * Indicates if current platform supports voice dialing over bluetooth SCO. 400 * @return true if voice dialing over bluetooth is supported, false otherwise. 401 * @hide 402 */ 403 public static boolean isBluetoothVoiceDialingEnabled(Context context) { 404 return context.getResources().getBoolean( 405 com.android.internal.R.bool.config_bluetooth_sco_off_call); 406 } 407 408 /** 409 * Cancel the outgoing connection. 410 * @hide 411 */ 412 public boolean cancelConnectThread() { 413 if (DBG) log("cancelConnectThread"); 414 if (mService != null) { 415 try { 416 return mService.cancelConnectThread(); 417 } catch (RemoteException e) {Log.e(TAG, e.toString());} 418 } else { 419 Log.w(TAG, "Proxy not attached to service"); 420 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 421 } 422 return false; 423 } 424 425 /** 426 * Accept the incoming connection. 427 * @hide 428 */ 429 public boolean acceptIncomingConnect(BluetoothDevice device) { 430 if (DBG) log("acceptIncomingConnect"); 431 if (mService != null) { 432 try { 433 return mService.acceptIncomingConnect(device); 434 } catch (RemoteException e) {Log.e(TAG, e.toString());} 435 } else { 436 Log.w(TAG, "Proxy not attached to service"); 437 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 438 } 439 return false; 440 } 441 442 /** 443 * Create the connect thread the incoming connection. 444 * @hide 445 */ 446 public boolean createIncomingConnect(BluetoothDevice device) { 447 if (DBG) log("createIncomingConnect"); 448 if (mService != null) { 449 try { 450 return mService.createIncomingConnect(device); 451 } catch (RemoteException e) {Log.e(TAG, e.toString());} 452 } else { 453 Log.w(TAG, "Proxy not attached to service"); 454 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 455 } 456 return false; 457 } 458 459 /** 460 * Reject the incoming connection. 461 * @hide 462 */ 463 public boolean rejectIncomingConnect(BluetoothDevice device) { 464 if (DBG) log("rejectIncomingConnect"); 465 if (mService != null) { 466 try { 467 return mService.rejectIncomingConnect(device); 468 } catch (RemoteException e) {Log.e(TAG, e.toString());} 469 } else { 470 Log.w(TAG, "Proxy not attached to service"); 471 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 472 } 473 return false; 474 } 475 476 /** 477 * Connect to a Bluetooth Headset. 478 * Note: This is an internal function and shouldn't be exposed 479 * @hide 480 */ 481 public boolean connectHeadsetInternal(BluetoothDevice device) { 482 if (DBG) log("connectHeadsetInternal"); 483 if (mService != null) { 484 try { 485 return mService.connectHeadsetInternal(device); 486 } catch (RemoteException e) {Log.e(TAG, e.toString());} 487 } else { 488 Log.w(TAG, "Proxy not attached to service"); 489 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 490 } 491 return false; 492 } 493 494 /** 495 * Disconnect a Bluetooth Headset. 496 * Note: This is an internal function and shouldn't be exposed 497 * @hide 498 */ 499 public boolean disconnectHeadsetInternal(BluetoothDevice device) { 500 if (DBG) log("disconnectHeadsetInternal"); 501 if (mService != null) { 502 try { 503 return mService.disconnectHeadsetInternal(device); 504 } catch (RemoteException e) {Log.e(TAG, e.toString());} 505 } else { 506 Log.w(TAG, "Proxy not attached to service"); 507 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 508 } 509 return false; 510 } 511 private ServiceConnection mConnection = new ServiceConnection() { 512 public void onServiceConnected(ComponentName className, IBinder service) { 513 if (DBG) Log.d(TAG, "Proxy object connected"); 514 mService = IBluetoothHeadset.Stub.asInterface(service); 515 if (mServiceListener != null) { 516 mServiceListener.onServiceConnected(); 517 } 518 } 519 public void onServiceDisconnected(ComponentName className) { 520 if (DBG) Log.d(TAG, "Proxy object disconnected"); 521 mService = null; 522 if (mServiceListener != null) { 523 mServiceListener.onServiceDisconnected(); 524 } 525 } 526 }; 527 528 private static void log(String msg) { 529 Log.d(TAG, msg); 530 } 531} 532