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.os.Binder; 24import android.os.Handler; 25import android.os.IBinder; 26import android.os.Looper; 27import android.os.Message; 28import android.os.RemoteException; 29import android.util.Log; 30 31import java.util.ArrayList; 32import java.util.List; 33 34/** 35 * Public API for controlling the Bluetooth Headset Service. This includes both 36 * Bluetooth Headset and Handsfree (v1.5) profiles. 37 * 38 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset 39 * Service via IPC. 40 * 41 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get 42 * the BluetoothHeadset proxy object. Use 43 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. 44 * 45 * <p> Android only supports one connected Bluetooth Headset at a time. 46 * Each method is protected with its appropriate permission. 47 */ 48public final class BluetoothHeadset implements BluetoothProfile { 49 private static final String TAG = "BluetoothHeadset"; 50 private static final boolean DBG = true; 51 private static final boolean VDBG = false; 52 53 /** 54 * Intent used to broadcast the change in connection state of the Headset 55 * profile. 56 * 57 * <p>This intent will have 3 extras: 58 * <ul> 59 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 60 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 61 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 62 * </ul> 63 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 64 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 65 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 66 * 67 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 68 * receive. 69 */ 70 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 71 public static final String ACTION_CONNECTION_STATE_CHANGED = 72 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"; 73 74 /** 75 * Intent used to broadcast the change in the Audio Connection state of the 76 * A2DP profile. 77 * 78 * <p>This intent will have 3 extras: 79 * <ul> 80 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 81 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 82 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 83 * </ul> 84 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 85 * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED}, 86 * 87 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission 88 * to receive. 89 */ 90 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 91 public static final String ACTION_AUDIO_STATE_CHANGED = 92 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; 93 94 95 /** 96 * Intent used to broadcast that the headset has posted a 97 * vendor-specific event. 98 * 99 * <p>This intent will have 4 extras and 1 category. 100 * <ul> 101 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device 102 * </li> 103 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor 104 * specific command </li> 105 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT 106 * command type which can be one of {@link #AT_CMD_TYPE_READ}, 107 * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET}, 108 * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li> 109 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command 110 * arguments. </li> 111 * </ul> 112 * 113 *<p> The category is the Company ID of the vendor defining the 114 * vendor-specific command. {@link BluetoothAssignedNumbers} 115 * 116 * For example, for Plantronics specific events 117 * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55 118 * 119 * <p> For example, an AT+XEVENT=foo,3 will get translated into 120 * <ul> 121 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li> 122 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li> 123 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li> 124 * </ul> 125 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission 126 * to receive. 127 */ 128 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 129 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = 130 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT"; 131 132 /** 133 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 134 * intents that contains the name of the vendor-specific command. 135 */ 136 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = 137 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD"; 138 139 /** 140 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 141 * intents that contains the AT command type of the vendor-specific command. 142 */ 143 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = 144 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE"; 145 146 /** 147 * AT command type READ used with 148 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 149 * For example, AT+VGM?. There are no arguments for this command type. 150 */ 151 public static final int AT_CMD_TYPE_READ = 0; 152 153 /** 154 * AT command type TEST used with 155 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 156 * For example, AT+VGM=?. There are no arguments for this command type. 157 */ 158 public static final int AT_CMD_TYPE_TEST = 1; 159 160 /** 161 * AT command type SET used with 162 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 163 * For example, AT+VGM=<args>. 164 */ 165 public static final int AT_CMD_TYPE_SET = 2; 166 167 /** 168 * AT command type BASIC used with 169 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 170 * For example, ATD. Single character commands and everything following the 171 * character are arguments. 172 */ 173 public static final int AT_CMD_TYPE_BASIC = 3; 174 175 /** 176 * AT command type ACTION used with 177 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 178 * For example, AT+CHUP. There are no arguments for action commands. 179 */ 180 public static final int AT_CMD_TYPE_ACTION = 4; 181 182 /** 183 * A Parcelable String array extra field in 184 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains 185 * the arguments to the vendor-specific command. 186 */ 187 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = 188 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; 189 190 /** 191 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 192 * for the companyId 193 */ 194 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = 195 "android.bluetooth.headset.intent.category.companyid"; 196 197 /** 198 * A vendor-specific command for unsolicited result code. 199 */ 200 public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID"; 201 202 /** 203 * Headset state when SCO audio is not connected. 204 * This state can be one of 205 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 206 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 207 */ 208 public static final int STATE_AUDIO_DISCONNECTED = 10; 209 210 /** 211 * Headset state when SCO audio is connecting. 212 * This state can be one of 213 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 214 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 215 */ 216 public static final int STATE_AUDIO_CONNECTING = 11; 217 218 /** 219 * Headset state when SCO audio is connected. 220 * This state can be one of 221 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 222 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 223 */ 224 225 /** 226 * Intent used to broadcast the headset's indicator status 227 * 228 * <p>This intent will have 3 extras: 229 * <ul> 230 * <li> {@link #EXTRA_IND_ID} - The Assigned number of headset Indicator which is supported by 231 the headset ( as indicated by AT+BIND 232 command in the SLC sequence).or whose value 233 is changed (indicated by AT+BIEV command)</li> 234 * <li> {@link #EXTRA_IND_VALUE}- The updated value of headset indicator. </li> 235 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 236 * </ul> 237 * <p>{@link #EXTRA_IND_ID} is defined by Bluetooth SIG and each of the indicators are 238 * given an assigned number. Below shows the assigned number of Indicator added so far 239 * - Enhanced Safety - 1 240 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 241 * receive. 242 * @hide 243 */ 244 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED = 245 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED"; 246 247 /** 248 * A String extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} 249 * intents that contains the UUID of the headset indicator (as defined by Bluetooth SIG) 250 * that is being sent. 251 * @hide 252 */ 253 public static final String EXTRA_HF_INDICATORS_IND_ID = 254 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID"; 255 256 /** 257 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} 258 * intents that contains the value of the Headset indicator that is being sent. 259 * @hide 260 */ 261 public static final String EXTRA_HF_INDICATORS_IND_VALUE = 262 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE"; 263 264 public static final int STATE_AUDIO_CONNECTED = 12; 265 266 private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100; 267 private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101; 268 269 private Context mContext; 270 private ServiceListener mServiceListener; 271 private IBluetoothHeadset mService; 272 private BluetoothAdapter mAdapter; 273 274 final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = 275 new IBluetoothStateChangeCallback.Stub() { 276 public void onBluetoothStateChange(boolean up) { 277 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); 278 if (!up) { 279 if (VDBG) Log.d(TAG,"Unbinding service..."); 280 doUnbind(); 281 } else { 282 synchronized (mConnection) { 283 try { 284 if (mService == null) { 285 if (VDBG) Log.d(TAG,"Binding service..."); 286 doBind(); 287 } 288 } catch (Exception re) { 289 Log.e(TAG,"",re); 290 } 291 } 292 } 293 } 294 }; 295 296 /** 297 * Create a BluetoothHeadset proxy object. 298 */ 299 /*package*/ BluetoothHeadset(Context context, ServiceListener l) { 300 mContext = context; 301 mServiceListener = l; 302 mAdapter = BluetoothAdapter.getDefaultAdapter(); 303 304 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 305 if (mgr != null) { 306 try { 307 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 308 } catch (RemoteException e) { 309 Log.e(TAG,"",e); 310 } 311 } 312 313 doBind(); 314 } 315 316 boolean doBind() { 317 try { 318 return mAdapter.getBluetoothManager().bindBluetoothProfileService( 319 BluetoothProfile.HEADSET, mConnection); 320 } catch (RemoteException e) { 321 Log.e(TAG, "Unable to bind HeadsetService", e); 322 } 323 return false; 324 } 325 326 void doUnbind() { 327 synchronized (mConnection) { 328 if (mService != null) { 329 try { 330 mAdapter.getBluetoothManager().unbindBluetoothProfileService( 331 BluetoothProfile.HEADSET, mConnection); 332 } catch (RemoteException e) { 333 Log.e(TAG,"Unable to unbind HeadsetService", e); 334 } 335 } 336 } 337 } 338 339 /** 340 * Close the connection to the backing service. 341 * Other public functions of BluetoothHeadset will return default error 342 * results once close() has been called. Multiple invocations of close() 343 * are ok. 344 */ 345 /*package*/ void close() { 346 if (VDBG) log("close()"); 347 348 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 349 if (mgr != null) { 350 try { 351 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 352 } catch (Exception e) { 353 Log.e(TAG,"",e); 354 } 355 } 356 mServiceListener = null; 357 doUnbind(); 358 } 359 360 /** 361 * Initiate connection to a profile of the remote bluetooth device. 362 * 363 * <p> Currently, the system supports only 1 connection to the 364 * headset/handsfree profile. The API will automatically disconnect connected 365 * devices before connecting. 366 * 367 * <p> This API returns false in scenarios like the profile on the 368 * device is already connected or Bluetooth is not turned on. 369 * When this API returns true, it is guaranteed that 370 * connection state intent for the profile will be broadcasted with 371 * the state. Users can get the connection state of the profile 372 * from this intent. 373 * 374 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 375 * permission. 376 * 377 * @param device Remote Bluetooth Device 378 * @return false on immediate error, 379 * true otherwise 380 * @hide 381 */ 382 public boolean connect(BluetoothDevice device) { 383 if (DBG) log("connect(" + device + ")"); 384 if (mService != null && isEnabled() && 385 isValidDevice(device)) { 386 try { 387 return mService.connect(device); 388 } catch (RemoteException e) { 389 Log.e(TAG, Log.getStackTraceString(new Throwable())); 390 return false; 391 } 392 } 393 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 394 return false; 395 } 396 397 /** 398 * Initiate disconnection from a profile 399 * 400 * <p> This API will return false in scenarios like the profile on the 401 * Bluetooth device is not in connected state etc. When this API returns, 402 * true, it is guaranteed that the connection state change 403 * intent will be broadcasted with the state. Users can get the 404 * disconnection state of the profile from this intent. 405 * 406 * <p> If the disconnection is initiated by a remote device, the state 407 * will transition from {@link #STATE_CONNECTED} to 408 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 409 * host (local) device the state will transition from 410 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 411 * state {@link #STATE_DISCONNECTED}. The transition to 412 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 413 * two scenarios. 414 * 415 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 416 * permission. 417 * 418 * @param device Remote Bluetooth Device 419 * @return false on immediate error, 420 * true otherwise 421 * @hide 422 */ 423 public boolean disconnect(BluetoothDevice device) { 424 if (DBG) log("disconnect(" + device + ")"); 425 if (mService != null && isEnabled() && 426 isValidDevice(device)) { 427 try { 428 return mService.disconnect(device); 429 } catch (RemoteException e) { 430 Log.e(TAG, Log.getStackTraceString(new Throwable())); 431 return false; 432 } 433 } 434 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 435 return false; 436 } 437 438 /** 439 * {@inheritDoc} 440 */ 441 public List<BluetoothDevice> getConnectedDevices() { 442 if (VDBG) log("getConnectedDevices()"); 443 if (mService != null && isEnabled()) { 444 try { 445 return mService.getConnectedDevices(); 446 } catch (RemoteException e) { 447 Log.e(TAG, Log.getStackTraceString(new Throwable())); 448 return new ArrayList<BluetoothDevice>(); 449 } 450 } 451 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 452 return new ArrayList<BluetoothDevice>(); 453 } 454 455 /** 456 * {@inheritDoc} 457 */ 458 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 459 if (VDBG) log("getDevicesMatchingStates()"); 460 if (mService != null && isEnabled()) { 461 try { 462 return mService.getDevicesMatchingConnectionStates(states); 463 } catch (RemoteException e) { 464 Log.e(TAG, Log.getStackTraceString(new Throwable())); 465 return new ArrayList<BluetoothDevice>(); 466 } 467 } 468 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 469 return new ArrayList<BluetoothDevice>(); 470 } 471 472 /** 473 * {@inheritDoc} 474 */ 475 public int getConnectionState(BluetoothDevice device) { 476 if (VDBG) log("getConnectionState(" + device + ")"); 477 if (mService != null && isEnabled() && 478 isValidDevice(device)) { 479 try { 480 return mService.getConnectionState(device); 481 } catch (RemoteException e) { 482 Log.e(TAG, Log.getStackTraceString(new Throwable())); 483 return BluetoothProfile.STATE_DISCONNECTED; 484 } 485 } 486 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 487 return BluetoothProfile.STATE_DISCONNECTED; 488 } 489 490 /** 491 * Set priority of the profile 492 * 493 * <p> The device should already be paired. 494 * Priority can be one of {@link #PRIORITY_ON} or 495 * {@link #PRIORITY_OFF}, 496 * 497 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 498 * permission. 499 * 500 * @param device Paired bluetooth device 501 * @param priority 502 * @return true if priority is set, false on error 503 * @hide 504 */ 505 public boolean setPriority(BluetoothDevice device, int priority) { 506 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 507 if (mService != null && isEnabled() && 508 isValidDevice(device)) { 509 if (priority != BluetoothProfile.PRIORITY_OFF && 510 priority != BluetoothProfile.PRIORITY_ON) { 511 return false; 512 } 513 try { 514 return mService.setPriority(device, priority); 515 } catch (RemoteException e) { 516 Log.e(TAG, Log.getStackTraceString(new Throwable())); 517 return false; 518 } 519 } 520 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 521 return false; 522 } 523 524 /** 525 * Get the priority of the profile. 526 * 527 * <p> The priority can be any of: 528 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, 529 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 530 * 531 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 532 * 533 * @param device Bluetooth device 534 * @return priority of the device 535 * @hide 536 */ 537 public int getPriority(BluetoothDevice device) { 538 if (VDBG) log("getPriority(" + device + ")"); 539 if (mService != null && isEnabled() && 540 isValidDevice(device)) { 541 try { 542 return mService.getPriority(device); 543 } catch (RemoteException e) { 544 Log.e(TAG, Log.getStackTraceString(new Throwable())); 545 return PRIORITY_OFF; 546 } 547 } 548 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 549 return PRIORITY_OFF; 550 } 551 552 /** 553 * Start Bluetooth voice recognition. This methods sends the voice 554 * recognition AT command to the headset and establishes the 555 * audio connection. 556 * 557 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 558 * If this function returns true, this intent will be broadcasted with 559 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. 560 * 561 * <p> {@link #EXTRA_STATE} will transition from 562 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when 563 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} 564 * in case of failure to establish the audio connection. 565 * 566 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 567 * 568 * @param device Bluetooth headset 569 * @return false if there is no headset connected of if the 570 * connected headset doesn't support voice recognition 571 * or on error, true otherwise 572 */ 573 public boolean startVoiceRecognition(BluetoothDevice device) { 574 if (DBG) log("startVoiceRecognition()"); 575 if (mService != null && isEnabled() && 576 isValidDevice(device)) { 577 try { 578 return mService.startVoiceRecognition(device); 579 } catch (RemoteException e) { 580 Log.e(TAG, Log.getStackTraceString(new Throwable())); 581 } 582 } 583 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 584 return false; 585 } 586 587 /** 588 * Stop Bluetooth Voice Recognition mode, and shut down the 589 * Bluetooth audio path. 590 * 591 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 592 * 593 * @param device Bluetooth headset 594 * @return false if there is no headset connected 595 * or on error, true otherwise 596 */ 597 public boolean stopVoiceRecognition(BluetoothDevice device) { 598 if (DBG) log("stopVoiceRecognition()"); 599 if (mService != null && isEnabled() && 600 isValidDevice(device)) { 601 try { 602 return mService.stopVoiceRecognition(device); 603 } catch (RemoteException e) { 604 Log.e(TAG, Log.getStackTraceString(new Throwable())); 605 } 606 } 607 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 608 return false; 609 } 610 611 /** 612 * Check if Bluetooth SCO audio is connected. 613 * 614 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 615 * 616 * @param device Bluetooth headset 617 * @return true if SCO is connected, 618 * false otherwise or on error 619 */ 620 public boolean isAudioConnected(BluetoothDevice device) { 621 if (VDBG) log("isAudioConnected()"); 622 if (mService != null && isEnabled() && 623 isValidDevice(device)) { 624 try { 625 return mService.isAudioConnected(device); 626 } catch (RemoteException e) { 627 Log.e(TAG, Log.getStackTraceString(new Throwable())); 628 } 629 } 630 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 631 return false; 632 } 633 634 /** 635 * Get battery usage hint for Bluetooth Headset service. 636 * This is a monotonically increasing integer. Wraps to 0 at 637 * Integer.MAX_INT, and at boot. 638 * Current implementation returns the number of AT commands handled since 639 * boot. This is a good indicator for spammy headset/handsfree units that 640 * can keep the device awake by polling for cellular status updates. As a 641 * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms 642 * 643 * @param device the bluetooth headset. 644 * @return monotonically increasing battery usage hint, or a negative error 645 * code on error 646 * @hide 647 */ 648 public int getBatteryUsageHint(BluetoothDevice device) { 649 if (VDBG) log("getBatteryUsageHint()"); 650 if (mService != null && isEnabled() && 651 isValidDevice(device)) { 652 try { 653 return mService.getBatteryUsageHint(device); 654 } catch (RemoteException e) { 655 Log.e(TAG, Log.getStackTraceString(new Throwable())); 656 } 657 } 658 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 659 return -1; 660 } 661 662 /** 663 * Indicates if current platform supports voice dialing over bluetooth SCO. 664 * 665 * @return true if voice dialing over bluetooth is supported, false otherwise. 666 * @hide 667 */ 668 public static boolean isBluetoothVoiceDialingEnabled(Context context) { 669 return context.getResources().getBoolean( 670 com.android.internal.R.bool.config_bluetooth_sco_off_call); 671 } 672 673 /** 674 * Accept the incoming connection. 675 * Note: This is an internal function and shouldn't be exposed 676 * 677 * @hide 678 */ 679 public boolean acceptIncomingConnect(BluetoothDevice device) { 680 if (DBG) log("acceptIncomingConnect"); 681 if (mService != null && isEnabled()) { 682 try { 683 return mService.acceptIncomingConnect(device); 684 } catch (RemoteException e) {Log.e(TAG, e.toString());} 685 } else { 686 Log.w(TAG, "Proxy not attached to service"); 687 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 688 } 689 return false; 690 } 691 692 /** 693 * Reject the incoming connection. 694 * @hide 695 */ 696 public boolean rejectIncomingConnect(BluetoothDevice device) { 697 if (DBG) log("rejectIncomingConnect"); 698 if (mService != null) { 699 try { 700 return mService.rejectIncomingConnect(device); 701 } catch (RemoteException e) {Log.e(TAG, e.toString());} 702 } else { 703 Log.w(TAG, "Proxy not attached to service"); 704 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 705 } 706 return false; 707 } 708 709 /** 710 * Get the current audio state of the Headset. 711 * Note: This is an internal function and shouldn't be exposed 712 * 713 * @hide 714 */ 715 public int getAudioState(BluetoothDevice device) { 716 if (VDBG) log("getAudioState"); 717 if (mService != null && !isDisabled()) { 718 try { 719 return mService.getAudioState(device); 720 } catch (RemoteException e) {Log.e(TAG, e.toString());} 721 } else { 722 Log.w(TAG, "Proxy not attached to service"); 723 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 724 } 725 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 726 } 727 728 /** 729 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any 730 * audio to the HF unless explicitly told to. 731 * This method should be used in cases where the SCO channel is shared between multiple profiles 732 * and must be delegated by a source knowledgeable 733 * Note: This is an internal function and shouldn't be exposed 734 * 735 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise. 736 * 737 * @hide 738 */ 739 public void setAudioRouteAllowed(boolean allowed) { 740 if (VDBG) log("setAudioRouteAllowed"); 741 if (mService != null && isEnabled()) { 742 try { 743 mService.setAudioRouteAllowed(allowed); 744 } catch (RemoteException e) {Log.e(TAG, e.toString());} 745 } else { 746 Log.w(TAG, "Proxy not attached to service"); 747 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 748 } 749 } 750 751 /** 752 * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}. 753 * Note: This is an internal function and shouldn't be exposed 754 * 755 * @hide 756 */ 757 public boolean getAudioRouteAllowed() { 758 if (VDBG) log("getAudioRouteAllowed"); 759 if (mService != null && isEnabled()) { 760 try { 761 return mService.getAudioRouteAllowed(); 762 } catch (RemoteException e) {Log.e(TAG, e.toString());} 763 } else { 764 Log.w(TAG, "Proxy not attached to service"); 765 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 766 } 767 return false; 768 } 769 770 /** 771 * Check if Bluetooth SCO audio is connected. 772 * 773 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 774 * 775 * @return true if SCO is connected, 776 * false otherwise or on error 777 * @hide 778 */ 779 public boolean isAudioOn() { 780 if (VDBG) log("isAudioOn()"); 781 if (mService != null && isEnabled()) { 782 try { 783 return mService.isAudioOn(); 784 } catch (RemoteException e) { 785 Log.e(TAG, Log.getStackTraceString(new Throwable())); 786 } 787 } 788 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 789 return false; 790 791 } 792 793 /** 794 * Initiates a connection of headset audio. 795 * It setup SCO channel with remote connected headset device. 796 * 797 * @return true if successful 798 * false if there was some error such as 799 * there is no connected headset 800 * @hide 801 */ 802 public boolean connectAudio() { 803 if (mService != null && isEnabled()) { 804 try { 805 return mService.connectAudio(); 806 } catch (RemoteException e) { 807 Log.e(TAG, e.toString()); 808 } 809 } else { 810 Log.w(TAG, "Proxy not attached to service"); 811 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 812 } 813 return false; 814 } 815 816 /** 817 * Initiates a disconnection of headset audio. 818 * It tears down the SCO channel from remote headset device. 819 * 820 * @return true if successful 821 * false if there was some error such as 822 * there is no connected SCO channel 823 * @hide 824 */ 825 public boolean disconnectAudio() { 826 if (mService != null && isEnabled()) { 827 try { 828 return mService.disconnectAudio(); 829 } catch (RemoteException e) { 830 Log.e(TAG, e.toString()); 831 } 832 } else { 833 Log.w(TAG, "Proxy not attached to service"); 834 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 835 } 836 return false; 837 } 838 839 /** 840 * Initiates a SCO channel connection with the headset (if connected). 841 * Also initiates a virtual voice call for Handsfree devices as many devices 842 * do not accept SCO audio without a call. 843 * This API allows the handsfree device to be used for routing non-cellular 844 * call audio. 845 * 846 * @param device Remote Bluetooth Device 847 * @return true if successful, false if there was some error. 848 * @hide 849 */ 850 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 851 if (DBG) log("startScoUsingVirtualVoiceCall()"); 852 if (mService != null && isEnabled() && isValidDevice(device)) { 853 try { 854 return mService.startScoUsingVirtualVoiceCall(device); 855 } catch (RemoteException e) { 856 Log.e(TAG, e.toString()); 857 } 858 } else { 859 Log.w(TAG, "Proxy not attached to service"); 860 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 861 } 862 return false; 863 } 864 865 /** 866 * Terminates an ongoing SCO connection and the associated virtual 867 * call. 868 * 869 * @param device Remote Bluetooth Device 870 * @return true if successful, false if there was some error. 871 * @hide 872 */ 873 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 874 if (DBG) log("stopScoUsingVirtualVoiceCall()"); 875 if (mService != null && isEnabled() && isValidDevice(device)) { 876 try { 877 return mService.stopScoUsingVirtualVoiceCall(device); 878 } catch (RemoteException e) { 879 Log.e(TAG, e.toString()); 880 } 881 } else { 882 Log.w(TAG, "Proxy not attached to service"); 883 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 884 } 885 return false; 886 } 887 888 /** 889 * Notify Headset of phone state change. 890 * This is a backdoor for phone app to call BluetoothHeadset since 891 * there is currently not a good way to get precise call state change outside 892 * of phone app. 893 * 894 * @hide 895 */ 896 public void phoneStateChanged(int numActive, int numHeld, int callState, String number, 897 int type) { 898 if (mService != null && isEnabled()) { 899 try { 900 mService.phoneStateChanged(numActive, numHeld, callState, number, type); 901 } catch (RemoteException e) { 902 Log.e(TAG, e.toString()); 903 } 904 } else { 905 Log.w(TAG, "Proxy not attached to service"); 906 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 907 } 908 } 909 910 /** 911 * Send Headset of CLCC response 912 * 913 * @hide 914 */ 915 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 916 String number, int type) { 917 if (mService != null && isEnabled()) { 918 try { 919 mService.clccResponse(index, direction, status, mode, mpty, number, type); 920 } catch (RemoteException e) { 921 Log.e(TAG, e.toString()); 922 } 923 } else { 924 Log.w(TAG, "Proxy not attached to service"); 925 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 926 } 927 } 928 929 /** 930 * Sends a vendor-specific unsolicited result code to the headset. 931 * 932 * <p>The actual string to be sent is <code>command + ": " + arg</code>. 933 * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} 934 * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent. 935 * 936 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. 937 * 938 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 939 * 940 * @param device Bluetooth headset. 941 * @param command A vendor-specific command. 942 * @param arg The argument that will be attached to the command. 943 * @return {@code false} if there is no headset connected, or if the command is not an allowed 944 * vendor-specific unsolicited result code, or on error. {@code true} otherwise. 945 * @throws IllegalArgumentException if {@code command} is {@code null}. 946 */ 947 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, 948 String arg) { 949 if (DBG) { 950 log("sendVendorSpecificResultCode()"); 951 } 952 if (command == null) { 953 throw new IllegalArgumentException("command is null"); 954 } 955 if (mService != null && isEnabled() && 956 isValidDevice(device)) { 957 try { 958 return mService.sendVendorSpecificResultCode(device, command, arg); 959 } catch (RemoteException e) { 960 Log.e(TAG, Log.getStackTraceString(new Throwable())); 961 } 962 } 963 if (mService == null) { 964 Log.w(TAG, "Proxy not attached to service"); 965 } 966 return false; 967 } 968 969 /** 970 * enable WBS codec setting. 971 * 972 * @return true if successful 973 * false if there was some error such as 974 * there is no connected headset 975 * @hide 976 */ 977 public boolean enableWBS() { 978 if (mService != null && isEnabled()) { 979 try { 980 return mService.enableWBS(); 981 } catch (RemoteException e) { 982 Log.e(TAG, e.toString()); 983 } 984 } else { 985 Log.w(TAG, "Proxy not attached to service"); 986 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 987 } 988 return false; 989 } 990 991 /** 992 * disable WBS codec settting. It set NBS codec. 993 * 994 * @return true if successful 995 * false if there was some error such as 996 * there is no connected headset 997 * @hide 998 */ 999 public boolean disableWBS() { 1000 if (mService != null && isEnabled()) { 1001 try { 1002 return mService.disableWBS(); 1003 } catch (RemoteException e) { 1004 Log.e(TAG, e.toString()); 1005 } 1006 } else { 1007 Log.w(TAG, "Proxy not attached to service"); 1008 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1009 } 1010 return false; 1011 } 1012 1013 /** 1014 * check if in-band ringing is supported for this platform. 1015 * 1016 * @return true if in-band ringing is supported 1017 * false if in-band ringing is not supported 1018 * @hide 1019 */ 1020 public static boolean isInbandRingingSupported(Context context) { 1021 return context.getResources().getBoolean( 1022 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support); 1023 } 1024 1025 /** 1026 * Send Headset the BIND response from AG to report change in the status of the 1027 * HF indicators to the headset 1028 * 1029 * @param ind_id Assigned Number of the indicator (defined by SIG) 1030 * @param ind_status 1031 * possible values- false-Indicator is disabled, no value changes shall be sent for this indicator 1032 * true-Indicator is enabled, value changes may be sent for this indicator 1033 * @hide 1034 */ 1035 public void bindResponse(int ind_id, boolean ind_status) { 1036 if (mService != null && isEnabled()) { 1037 try { 1038 mService.bindResponse(ind_id, ind_status); 1039 } catch (RemoteException e) { 1040 Log.e(TAG, e.toString()); 1041 } 1042 } else { 1043 Log.w(TAG, "Proxy not attached to service"); 1044 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1045 } 1046 } 1047 1048 private final IBluetoothProfileServiceConnection mConnection 1049 = new IBluetoothProfileServiceConnection.Stub() { 1050 @Override 1051 public void onServiceConnected(ComponentName className, IBinder service) { 1052 if (DBG) Log.d(TAG, "Proxy object connected"); 1053 mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service)); 1054 mHandler.sendMessage(mHandler.obtainMessage( 1055 MESSAGE_HEADSET_SERVICE_CONNECTED)); 1056 } 1057 @Override 1058 public void onServiceDisconnected(ComponentName className) { 1059 if (DBG) Log.d(TAG, "Proxy object disconnected"); 1060 mService = null; 1061 mHandler.sendMessage(mHandler.obtainMessage( 1062 MESSAGE_HEADSET_SERVICE_DISCONNECTED)); 1063 } 1064 }; 1065 1066 private boolean isEnabled() { 1067 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 1068 return false; 1069 } 1070 1071 private boolean isDisabled() { 1072 if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true; 1073 return false; 1074 } 1075 1076 private boolean isValidDevice(BluetoothDevice device) { 1077 if (device == null) return false; 1078 1079 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 1080 return false; 1081 } 1082 1083 private static void log(String msg) { 1084 Log.d(TAG, msg); 1085 } 1086 1087 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 1088 @Override 1089 public void handleMessage(Message msg) { 1090 switch (msg.what) { 1091 case MESSAGE_HEADSET_SERVICE_CONNECTED: { 1092 if (mServiceListener != null) { 1093 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, 1094 BluetoothHeadset.this); 1095 } 1096 break; 1097 } 1098 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: { 1099 if (mServiceListener != null) { 1100 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); 1101 } 1102 break; 1103 } 1104 } 1105 } 1106 }; 1107} 1108