BluetoothHeadset.java revision 03cd78cf5e51c3adb78d2e3d314838dcf3e36b26
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.RemoteException; 27import android.util.Log; 28 29import java.util.ArrayList; 30import java.util.List; 31 32/** 33 * Public API for controlling the Bluetooth Headset Service. This includes both 34 * Bluetooth Headset and Handsfree (v1.5) profiles. 35 * 36 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset 37 * Service via IPC. 38 * 39 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get 40 * the BluetoothHeadset proxy object. Use 41 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. 42 * 43 * <p> Android only supports one connected Bluetooth Headset at a time. 44 * Each method is protected with its appropriate permission. 45 */ 46public final class BluetoothHeadset implements BluetoothProfile { 47 private static final String TAG = "BluetoothHeadset"; 48 private static final boolean DBG = false; 49 50 /** 51 * Intent used to broadcast the change in connection state of the Headset 52 * profile. 53 * 54 * <p>This intent will have 3 extras: 55 * {@link #EXTRA_STATE} - The current state of the profile. 56 * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile 57 * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 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} to receive. 64 */ 65 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 66 public static final String ACTION_CONNECTION_STATE_CHANGED = 67 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"; 68 69 /** 70 * Intent used to broadcast the change in the Audio Connection state of the 71 * A2DP profile. 72 * 73 * <p>This intent will have 3 extras: 74 * {@link #EXTRA_STATE} - The current state of the profile. 75 * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile 76 * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 77 * 78 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 79 * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED}, 80 * 81 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. 82 */ 83 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 84 public static final String ACTION_AUDIO_STATE_CHANGED = 85 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; 86 87 88 /** 89 * Broadcast Action: Indicates a headset has posted a vendor-specific event. 90 * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, 91 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD}, and 92 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS}. 93 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. 94 * @hide 95 */ 96 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 97 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = 98 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT"; 99 100 /** 101 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 102 * intents that contains the name of the vendor-specific command. 103 * @hide 104 */ 105 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = 106 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD"; 107 108 /** 109 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 110 * intents that contains the Company ID of the vendor defining the vendor-specific 111 * command. 112 * @see <a href="https://www.bluetooth.org/Technical/AssignedNumbers/identifiers.htm"> 113 * Bluetooth SIG Assigned Numbers - Company Identifiers</a> 114 * @hide 115 */ 116 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID = 117 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID"; 118 119 /** 120 * A Parcelable String array extra field in 121 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains 122 * the arguments to the vendor-specific command. 123 * @hide 124 */ 125 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = 126 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; 127 128 /* 129 * Headset state when SCO audio is connected 130 * This state can be one of 131 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 132 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 133 */ 134 public static final int STATE_AUDIO_CONNECTED = 10; 135 136 /** 137 * Headset state when SCO audio is NOT connected 138 * This state can be one of 139 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 140 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 141 */ 142 public static final int STATE_AUDIO_DISCONNECTED = 11; 143 144 145 private Context mContext; 146 private ServiceListener mServiceListener; 147 private IBluetoothHeadset mService; 148 BluetoothAdapter mAdapter; 149 150 /** 151 * Create a BluetoothHeadset proxy object. 152 */ 153 /*package*/ BluetoothHeadset(Context context, ServiceListener l) { 154 mContext = context; 155 mServiceListener = l; 156 mAdapter = BluetoothAdapter.getDefaultAdapter(); 157 if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) { 158 Log.e(TAG, "Could not bind to Bluetooth Headset Service"); 159 } 160 } 161 162 /** 163 * Close the connection to the backing service. 164 * Other public functions of BluetoothHeadset will return default error 165 * results once close() has been called. Multiple invocations of close() 166 * are ok. 167 */ 168 /*package*/ synchronized void close() { 169 if (DBG) log("close()"); 170 if (mConnection != null) { 171 mContext.unbindService(mConnection); 172 mConnection = null; 173 } 174 } 175 176 /** 177 * {@inheritDoc} 178 * @hide 179 */ 180 public boolean connect(BluetoothDevice device) { 181 if (DBG) log("connect(" + device + ")"); 182 if (mService != null && isEnabled() && 183 isValidDevice(device)) { 184 try { 185 return mService.connect(device); 186 } catch (RemoteException e) { 187 Log.e(TAG, Log.getStackTraceString(new Throwable())); 188 return false; 189 } 190 } 191 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 192 return false; 193 } 194 195 /** 196 * {@inheritDoc} 197 * @hide 198 */ 199 public boolean disconnect(BluetoothDevice device) { 200 if (DBG) log("disconnect(" + device + ")"); 201 if (mService != null && isEnabled() && 202 isValidDevice(device)) { 203 try { 204 return mService.disconnect(device); 205 } catch (RemoteException e) { 206 Log.e(TAG, Log.getStackTraceString(new Throwable())); 207 return false; 208 } 209 } 210 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 211 return false; 212 } 213 214 /** 215 * {@inheritDoc} 216 */ 217 public List<BluetoothDevice> getConnectedDevices() { 218 if (DBG) log("getConnectedDevices()"); 219 if (mService != null && isEnabled()) { 220 try { 221 return mService.getConnectedDevices(); 222 } catch (RemoteException e) { 223 Log.e(TAG, Log.getStackTraceString(new Throwable())); 224 return new ArrayList<BluetoothDevice>(); 225 } 226 } 227 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 228 return new ArrayList<BluetoothDevice>(); 229 } 230 231 /** 232 * {@inheritDoc} 233 */ 234 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 235 if (DBG) log("getDevicesMatchingStates()"); 236 if (mService != null && isEnabled()) { 237 try { 238 return mService.getDevicesMatchingConnectionStates(states); 239 } catch (RemoteException e) { 240 Log.e(TAG, Log.getStackTraceString(new Throwable())); 241 return new ArrayList<BluetoothDevice>(); 242 } 243 } 244 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 245 return new ArrayList<BluetoothDevice>(); 246 } 247 248 /** 249 * {@inheritDoc} 250 */ 251 public int getConnectionState(BluetoothDevice device) { 252 if (DBG) log("getConnectionState(" + device + ")"); 253 if (mService != null && isEnabled() && 254 isValidDevice(device)) { 255 try { 256 return mService.getConnectionState(device); 257 } catch (RemoteException e) { 258 Log.e(TAG, Log.getStackTraceString(new Throwable())); 259 return BluetoothProfile.STATE_DISCONNECTED; 260 } 261 } 262 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 263 return BluetoothProfile.STATE_DISCONNECTED; 264 } 265 266 /** 267 * {@inheritDoc} 268 * @hide 269 */ 270 public boolean setPriority(BluetoothDevice device, int priority) { 271 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 272 if (mService != null && isEnabled() && 273 isValidDevice(device)) { 274 if (priority != BluetoothProfile.PRIORITY_OFF && 275 priority != BluetoothProfile.PRIORITY_ON) { 276 return false; 277 } 278 try { 279 return mService.setPriority(device, priority); 280 } catch (RemoteException e) { 281 Log.e(TAG, Log.getStackTraceString(new Throwable())); 282 return false; 283 } 284 } 285 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 286 return false; 287 } 288 289 /** 290 * {@inheritDoc} 291 * @hide 292 */ 293 public int getPriority(BluetoothDevice device) { 294 if (DBG) log("getPriority(" + device + ")"); 295 if (mService != null && isEnabled() && 296 isValidDevice(device)) { 297 try { 298 return mService.getPriority(device); 299 } catch (RemoteException e) { 300 Log.e(TAG, Log.getStackTraceString(new Throwable())); 301 return PRIORITY_OFF; 302 } 303 } 304 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 305 return PRIORITY_OFF; 306 } 307 308 /** 309 * Start Bluetooth voice recognition. This methods sends the voice 310 * recognition AT command to the headset and establishes the 311 * audio connection. 312 * 313 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 314 * {@link #EXTRA_STATE} will be set to {@link #STATE_AUDIO_CONNECTED} 315 * when the audio connection is established. 316 * 317 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} 318 * 319 * @param device Bluetooth headset 320 * @return false if there is no headset connected of if the 321 * connected headset doesn't support voice recognition 322 * or on error, true otherwise 323 */ 324 public boolean startVoiceRecognition(BluetoothDevice device) { 325 if (DBG) log("startVoiceRecognition()"); 326 if (mService != null && isEnabled() && 327 isValidDevice(device)) { 328 try { 329 return mService.startVoiceRecognition(device); 330 } catch (RemoteException e) { 331 Log.e(TAG, Log.getStackTraceString(new Throwable())); 332 } 333 } 334 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 335 return false; 336 } 337 338 /** 339 * Stop Bluetooth Voice Recognition mode, and shut down the 340 * Bluetooth audio path. 341 * 342 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} 343 * 344 * @param device Bluetooth headset 345 * @return false if there is no headset connected 346 * or on error, true otherwise 347 */ 348 public boolean stopVoiceRecognition(BluetoothDevice device) { 349 if (DBG) log("stopVoiceRecognition()"); 350 if (mService != null && isEnabled() && 351 isValidDevice(device)) { 352 try { 353 return mService.stopVoiceRecognition(device); 354 } catch (RemoteException e) { 355 Log.e(TAG, Log.getStackTraceString(new Throwable())); 356 } 357 } 358 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 359 return false; 360 } 361 362 /** 363 * Check if Bluetooth SCO audio is connected. 364 * 365 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} 366 * 367 * @param device Bluetooth headset 368 * @return true if SCO is connected, 369 * false otherwise or on error 370 */ 371 public boolean isAudioConnected(BluetoothDevice device) { 372 if (DBG) log("isAudioConnected()"); 373 if (mService != null && isEnabled() && 374 isValidDevice(device)) { 375 try { 376 return mService.isAudioConnected(device); 377 } catch (RemoteException e) { 378 Log.e(TAG, Log.getStackTraceString(new Throwable())); 379 } 380 } 381 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 382 return false; 383 } 384 385 /** 386 * Get battery usage hint for Bluetooth Headset service. 387 * This is a monotonically increasing integer. Wraps to 0 at 388 * Integer.MAX_INT, and at boot. 389 * Current implementation returns the number of AT commands handled since 390 * boot. This is a good indicator for spammy headset/handsfree units that 391 * can keep the device awake by polling for cellular status updates. As a 392 * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms 393 * 394 * @param device the bluetooth headset. 395 * @return monotonically increasing battery usage hint, or a negative error 396 * code on error 397 * @hide 398 */ 399 public int getBatteryUsageHint(BluetoothDevice device) { 400 if (DBG) log("getBatteryUsageHint()"); 401 if (mService != null && isEnabled() && 402 isValidDevice(device)) { 403 try { 404 return mService.getBatteryUsageHint(device); 405 } catch (RemoteException e) { 406 Log.e(TAG, Log.getStackTraceString(new Throwable())); 407 } 408 } 409 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 410 return -1; 411 } 412 413 /** 414 * Indicates if current platform supports voice dialing over bluetooth SCO. 415 * 416 * @return true if voice dialing over bluetooth is supported, false otherwise. 417 * @hide 418 */ 419 public static boolean isBluetoothVoiceDialingEnabled(Context context) { 420 return context.getResources().getBoolean( 421 com.android.internal.R.bool.config_bluetooth_sco_off_call); 422 } 423 424 /** 425 * Cancel the outgoing connection. 426 * Note: This is an internal function and shouldn't be exposed 427 * 428 * @hide 429 */ 430 public boolean cancelConnectThread() { 431 if (DBG) log("cancelConnectThread"); 432 if (mService != null && isEnabled()) { 433 try { 434 return mService.cancelConnectThread(); 435 } catch (RemoteException e) {Log.e(TAG, e.toString());} 436 } else { 437 Log.w(TAG, "Proxy not attached to service"); 438 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 439 } 440 return false; 441 } 442 443 /** 444 * Accept the incoming connection. 445 * Note: This is an internal function and shouldn't be exposed 446 * 447 * @hide 448 */ 449 public boolean acceptIncomingConnect(BluetoothDevice device) { 450 if (DBG) log("acceptIncomingConnect"); 451 if (mService != null && isEnabled()) { 452 try { 453 return mService.acceptIncomingConnect(device); 454 } catch (RemoteException e) {Log.e(TAG, e.toString());} 455 } else { 456 Log.w(TAG, "Proxy not attached to service"); 457 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 458 } 459 return false; 460 } 461 462 /** 463 * Create the connect thread for the incoming connection. 464 * Note: This is an internal function and shouldn't be exposed 465 * 466 * @hide 467 */ 468 public boolean createIncomingConnect(BluetoothDevice device) { 469 if (DBG) log("createIncomingConnect"); 470 if (mService != null && isEnabled()) { 471 try { 472 return mService.createIncomingConnect(device); 473 } catch (RemoteException e) {Log.e(TAG, e.toString());} 474 } else { 475 Log.w(TAG, "Proxy not attached to service"); 476 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 477 } 478 return false; 479 } 480 481 /** 482 * Connect to a Bluetooth Headset. 483 * Note: This is an internal function and shouldn't be exposed 484 * 485 * @hide 486 */ 487 public boolean connectHeadsetInternal(BluetoothDevice device) { 488 if (DBG) log("connectHeadsetInternal"); 489 if (mService != null && isEnabled()) { 490 try { 491 return mService.connectHeadsetInternal(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 * Disconnect a Bluetooth Headset. 502 * Note: This is an internal function and shouldn't be exposed 503 * 504 * @hide 505 */ 506 public boolean disconnectHeadsetInternal(BluetoothDevice device) { 507 if (DBG) log("disconnectHeadsetInternal"); 508 if (mService != null && isEnabled()) { 509 try { 510 return mService.disconnectHeadsetInternal(device); 511 } catch (RemoteException e) {Log.e(TAG, e.toString());} 512 } else { 513 Log.w(TAG, "Proxy not attached to service"); 514 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 515 } 516 return false; 517 } 518 519 /** 520 * Set the audio state of the Headset. 521 * Note: This is an internal function and shouldn't be exposed 522 * 523 * @hide 524 */ 525 public boolean setAudioState(BluetoothDevice device, int state) { 526 if (DBG) log("setAudioState"); 527 if (mService != null && isEnabled()) { 528 try { 529 return mService.setAudioState(device, state); 530 } catch (RemoteException e) {Log.e(TAG, e.toString());} 531 } else { 532 Log.w(TAG, "Proxy not attached to service"); 533 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 534 } 535 return false; 536 } 537 538 private ServiceConnection mConnection = new ServiceConnection() { 539 public void onServiceConnected(ComponentName className, IBinder service) { 540 if (DBG) Log.d(TAG, "Proxy object connected"); 541 mService = IBluetoothHeadset.Stub.asInterface(service); 542 543 if (mServiceListener != null) { 544 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this); 545 } 546 } 547 public void onServiceDisconnected(ComponentName className) { 548 if (DBG) Log.d(TAG, "Proxy object disconnected"); 549 mService = null; 550 if (mServiceListener != null) { 551 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); 552 } 553 } 554 }; 555 556 private boolean isEnabled() { 557 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 558 return false; 559 } 560 561 private boolean isValidDevice(BluetoothDevice device) { 562 if (device == null) return false; 563 564 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 565 return false; 566 } 567 568 private static void log(String msg) { 569 Log.d(TAG, msg); 570 } 571} 572