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 17 package android.bluetooth; 18 19 import android.annotation.SdkConstant; 20 import android.annotation.SdkConstant.SdkConstantType; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.os.Binder; 24 import android.os.Handler; 25 import android.os.IBinder; 26 import android.os.Looper; 27 import android.os.Message; 28 import android.os.RemoteException; 29 import android.util.Log; 30 31 import java.util.ArrayList; 32 import 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 */ 48 public 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 * A vendor-specific AT command 204 * @hide 205 */ 206 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL"; 207 208 /** 209 * A vendor-specific AT command 210 * @hide 211 */ 212 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV"; 213 214 /** 215 * Battery level indicator associated with 216 * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV} 217 * @hide 218 */ 219 public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1; 220 221 /** 222 * A vendor-specific AT command 223 * @hide 224 */ 225 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT"; 226 227 /** 228 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT} 229 * @hide 230 */ 231 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY"; 232 233 /** 234 * Headset state when SCO audio is not connected. 235 * This state can be one of 236 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 237 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 238 */ 239 public static final int STATE_AUDIO_DISCONNECTED = 10; 240 241 /** 242 * Headset state when SCO audio is connecting. 243 * This state can be one of 244 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 245 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 246 */ 247 public static final int STATE_AUDIO_CONNECTING = 11; 248 249 /** 250 * Headset state when SCO audio is connected. 251 * This state can be one of 252 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 253 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 254 */ 255 256 /** 257 * Intent used to broadcast the headset's indicator status 258 * 259 * <p>This intent will have 3 extras: 260 * <ul> 261 * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which 262 * is supported by the headset ( as indicated by AT+BIND command in the SLC 263 * sequence) or whose value is changed (indicated by AT+BIEV command) </li> 264 * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li> 265 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li> 266 * </ul> 267 * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators 268 * are given an assigned number. Below shows the assigned number of Indicator added so far 269 * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled 270 * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery 271 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive. 272 * @hide 273 */ 274 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED = 275 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED"; 276 277 /** 278 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} 279 * intents that contains the assigned number of the headset indicator as defined by 280 * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7 281 * @hide 282 */ 283 public static final String EXTRA_HF_INDICATORS_IND_ID = 284 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID"; 285 286 /** 287 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} 288 * intents that contains the value of the Headset indicator that is being sent. 289 * @hide 290 */ 291 public static final String EXTRA_HF_INDICATORS_IND_VALUE = 292 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE"; 293 294 public static final int STATE_AUDIO_CONNECTED = 12; 295 296 private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100; 297 private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101; 298 299 private Context mContext; 300 private ServiceListener mServiceListener; 301 private volatile IBluetoothHeadset mService; 302 private BluetoothAdapter mAdapter; 303 304 final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = 305 new IBluetoothStateChangeCallback.Stub() { 306 public void onBluetoothStateChange(boolean up) { 307 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); 308 if (!up) { 309 if (VDBG) Log.d(TAG,"Unbinding service..."); 310 doUnbind(); 311 } else { 312 synchronized (mConnection) { 313 try { 314 if (mService == null) { 315 if (VDBG) Log.d(TAG,"Binding service..."); 316 doBind(); 317 } 318 } catch (Exception re) { 319 Log.e(TAG,"",re); 320 } 321 } 322 } 323 } 324 }; 325 326 /** 327 * Create a BluetoothHeadset proxy object. 328 */ 329 /*package*/ BluetoothHeadset(Context context, ServiceListener l) { 330 mContext = context; 331 mServiceListener = l; 332 mAdapter = BluetoothAdapter.getDefaultAdapter(); 333 334 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 335 if (mgr != null) { 336 try { 337 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 338 } catch (RemoteException e) { 339 Log.e(TAG,"",e); 340 } 341 } 342 343 doBind(); 344 } 345 346 boolean doBind() { 347 try { 348 return mAdapter.getBluetoothManager().bindBluetoothProfileService( 349 BluetoothProfile.HEADSET, mConnection); 350 } catch (RemoteException e) { 351 Log.e(TAG, "Unable to bind HeadsetService", e); 352 } 353 return false; 354 } 355 356 void doUnbind() { 357 synchronized (mConnection) { 358 if (mService != null) { 359 try { 360 mAdapter.getBluetoothManager().unbindBluetoothProfileService( 361 BluetoothProfile.HEADSET, mConnection); 362 } catch (RemoteException e) { 363 Log.e(TAG,"Unable to unbind HeadsetService", e); 364 } 365 } 366 } 367 } 368 369 /** 370 * Close the connection to the backing service. 371 * Other public functions of BluetoothHeadset will return default error 372 * results once close() has been called. Multiple invocations of close() 373 * are ok. 374 */ 375 /*package*/ void close() { 376 if (VDBG) log("close()"); 377 378 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 379 if (mgr != null) { 380 try { 381 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 382 } catch (Exception e) { 383 Log.e(TAG,"",e); 384 } 385 } 386 mServiceListener = null; 387 doUnbind(); 388 } 389 390 /** 391 * Initiate connection to a profile of the remote bluetooth device. 392 * 393 * <p> Currently, the system supports only 1 connection to the 394 * headset/handsfree profile. The API will automatically disconnect connected 395 * devices before connecting. 396 * 397 * <p> This API returns false in scenarios like the profile on the 398 * device is already connected or Bluetooth is not turned on. 399 * When this API returns true, it is guaranteed that 400 * connection state intent for the profile will be broadcasted with 401 * the state. Users can get the connection state of the profile 402 * from this intent. 403 * 404 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 405 * permission. 406 * 407 * @param device Remote Bluetooth Device 408 * @return false on immediate error, 409 * true otherwise 410 * @hide 411 */ 412 public boolean connect(BluetoothDevice device) { 413 if (DBG) log("connect(" + device + ")"); 414 final IBluetoothHeadset service = mService; 415 if (service != null && isEnabled() && isValidDevice(device)) { 416 try { 417 return service.connect(device); 418 } catch (RemoteException e) { 419 Log.e(TAG, Log.getStackTraceString(new Throwable())); 420 return false; 421 } 422 } 423 if (service == null) Log.w(TAG, "Proxy not attached to service"); 424 return false; 425 } 426 427 /** 428 * Initiate disconnection from a profile 429 * 430 * <p> This API will return false in scenarios like the profile on the 431 * Bluetooth device is not in connected state etc. When this API returns, 432 * true, it is guaranteed that the connection state change 433 * intent will be broadcasted with the state. Users can get the 434 * disconnection state of the profile from this intent. 435 * 436 * <p> If the disconnection is initiated by a remote device, the state 437 * will transition from {@link #STATE_CONNECTED} to 438 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 439 * host (local) device the state will transition from 440 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 441 * state {@link #STATE_DISCONNECTED}. The transition to 442 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 443 * two scenarios. 444 * 445 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 446 * permission. 447 * 448 * @param device Remote Bluetooth Device 449 * @return false on immediate error, 450 * true otherwise 451 * @hide 452 */ 453 public boolean disconnect(BluetoothDevice device) { 454 if (DBG) log("disconnect(" + device + ")"); 455 final IBluetoothHeadset service = mService; 456 if (service != null && isEnabled() && isValidDevice(device)) { 457 try { 458 return service.disconnect(device); 459 } catch (RemoteException e) { 460 Log.e(TAG, Log.getStackTraceString(new Throwable())); 461 return false; 462 } 463 } 464 if (service == null) Log.w(TAG, "Proxy not attached to service"); 465 return false; 466 } 467 468 /** 469 * {@inheritDoc} 470 */ 471 public List<BluetoothDevice> getConnectedDevices() { 472 if (VDBG) log("getConnectedDevices()"); 473 final IBluetoothHeadset service = mService; 474 if (service != null && isEnabled()) { 475 try { 476 return service.getConnectedDevices(); 477 } catch (RemoteException e) { 478 Log.e(TAG, Log.getStackTraceString(new Throwable())); 479 return new ArrayList<BluetoothDevice>(); 480 } 481 } 482 if (service == null) Log.w(TAG, "Proxy not attached to service"); 483 return new ArrayList<BluetoothDevice>(); 484 } 485 486 /** 487 * {@inheritDoc} 488 */ 489 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 490 if (VDBG) log("getDevicesMatchingStates()"); 491 final IBluetoothHeadset service = mService; 492 if (service != null && isEnabled()) { 493 try { 494 return service.getDevicesMatchingConnectionStates(states); 495 } catch (RemoteException e) { 496 Log.e(TAG, Log.getStackTraceString(new Throwable())); 497 return new ArrayList<BluetoothDevice>(); 498 } 499 } 500 if (service == null) Log.w(TAG, "Proxy not attached to service"); 501 return new ArrayList<BluetoothDevice>(); 502 } 503 504 /** 505 * {@inheritDoc} 506 */ 507 public int getConnectionState(BluetoothDevice device) { 508 if (VDBG) log("getConnectionState(" + device + ")"); 509 final IBluetoothHeadset service = mService; 510 if (service != null && isEnabled() && isValidDevice(device)) { 511 try { 512 return service.getConnectionState(device); 513 } catch (RemoteException e) { 514 Log.e(TAG, Log.getStackTraceString(new Throwable())); 515 return BluetoothProfile.STATE_DISCONNECTED; 516 } 517 } 518 if (service == null) Log.w(TAG, "Proxy not attached to service"); 519 return BluetoothProfile.STATE_DISCONNECTED; 520 } 521 522 /** 523 * Set priority of the profile 524 * 525 * <p> The device should already be paired. 526 * Priority can be one of {@link #PRIORITY_ON} or 527 * {@link #PRIORITY_OFF}, 528 * 529 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 530 * permission. 531 * 532 * @param device Paired bluetooth device 533 * @param priority 534 * @return true if priority is set, false on error 535 * @hide 536 */ 537 public boolean setPriority(BluetoothDevice device, int priority) { 538 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 539 final IBluetoothHeadset service = mService; 540 if (service != null && isEnabled() && isValidDevice(device)) { 541 if (priority != BluetoothProfile.PRIORITY_OFF 542 && priority != BluetoothProfile.PRIORITY_ON) { 543 return false; 544 } 545 try { 546 return service.setPriority(device, priority); 547 } catch (RemoteException e) { 548 Log.e(TAG, Log.getStackTraceString(new Throwable())); 549 return false; 550 } 551 } 552 if (service == null) Log.w(TAG, "Proxy not attached to service"); 553 return false; 554 } 555 556 /** 557 * Get the priority of the profile. 558 * 559 * <p> The priority can be any of: 560 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, 561 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 562 * 563 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 564 * 565 * @param device Bluetooth device 566 * @return priority of the device 567 * @hide 568 */ 569 public int getPriority(BluetoothDevice device) { 570 if (VDBG) log("getPriority(" + device + ")"); 571 final IBluetoothHeadset service = mService; 572 if (service != null && isEnabled() && isValidDevice(device)) { 573 try { 574 return service.getPriority(device); 575 } catch (RemoteException e) { 576 Log.e(TAG, Log.getStackTraceString(new Throwable())); 577 return PRIORITY_OFF; 578 } 579 } 580 if (service == null) Log.w(TAG, "Proxy not attached to service"); 581 return PRIORITY_OFF; 582 } 583 584 /** 585 * Start Bluetooth voice recognition. This methods sends the voice 586 * recognition AT command to the headset and establishes the 587 * audio connection. 588 * 589 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 590 * If this function returns true, this intent will be broadcasted with 591 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. 592 * 593 * <p> {@link #EXTRA_STATE} will transition from 594 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when 595 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} 596 * in case of failure to establish the audio connection. 597 * 598 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 599 * 600 * @param device Bluetooth headset 601 * @return false if there is no headset connected of if the 602 * connected headset doesn't support voice recognition 603 * or on error, true otherwise 604 */ 605 public boolean startVoiceRecognition(BluetoothDevice device) { 606 if (DBG) log("startVoiceRecognition()"); 607 final IBluetoothHeadset service = mService; 608 if (service != null && isEnabled() && isValidDevice(device)) { 609 try { 610 return service.startVoiceRecognition(device); 611 } catch (RemoteException e) { 612 Log.e(TAG, Log.getStackTraceString(new Throwable())); 613 } 614 } 615 if (service == null) Log.w(TAG, "Proxy not attached to service"); 616 return false; 617 } 618 619 /** 620 * Stop Bluetooth Voice Recognition mode, and shut down the 621 * Bluetooth audio path. 622 * 623 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 624 * 625 * @param device Bluetooth headset 626 * @return false if there is no headset connected 627 * or on error, true otherwise 628 */ 629 public boolean stopVoiceRecognition(BluetoothDevice device) { 630 if (DBG) log("stopVoiceRecognition()"); 631 final IBluetoothHeadset service = mService; 632 if (service != null && isEnabled() && isValidDevice(device)) { 633 try { 634 return service.stopVoiceRecognition(device); 635 } catch (RemoteException e) { 636 Log.e(TAG, Log.getStackTraceString(new Throwable())); 637 } 638 } 639 if (service == null) Log.w(TAG, "Proxy not attached to service"); 640 return false; 641 } 642 643 /** 644 * Check if Bluetooth SCO audio is connected. 645 * 646 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 647 * 648 * @param device Bluetooth headset 649 * @return true if SCO is connected, 650 * false otherwise or on error 651 */ 652 public boolean isAudioConnected(BluetoothDevice device) { 653 if (VDBG) log("isAudioConnected()"); 654 final IBluetoothHeadset service = mService; 655 if (service != null && isEnabled() && isValidDevice(device)) { 656 try { 657 return service.isAudioConnected(device); 658 } catch (RemoteException e) { 659 Log.e(TAG, Log.getStackTraceString(new Throwable())); 660 } 661 } 662 if (service == null) Log.w(TAG, "Proxy not attached to service"); 663 return false; 664 } 665 666 /** 667 * Get battery usage hint for Bluetooth Headset service. 668 * This is a monotonically increasing integer. Wraps to 0 at 669 * Integer.MAX_INT, and at boot. 670 * Current implementation returns the number of AT commands handled since 671 * boot. This is a good indicator for spammy headset/handsfree units that 672 * can keep the device awake by polling for cellular status updates. As a 673 * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms 674 * 675 * @param device the bluetooth headset. 676 * @return monotonically increasing battery usage hint, or a negative error 677 * code on error 678 * @hide 679 */ 680 public int getBatteryUsageHint(BluetoothDevice device) { 681 if (VDBG) log("getBatteryUsageHint()"); 682 final IBluetoothHeadset service = mService; 683 if (service != null && isEnabled() && isValidDevice(device)) { 684 try { 685 return service.getBatteryUsageHint(device); 686 } catch (RemoteException e) { 687 Log.e(TAG, Log.getStackTraceString(new Throwable())); 688 } 689 } 690 if (service == null) Log.w(TAG, "Proxy not attached to service"); 691 return -1; 692 } 693 694 /** 695 * Indicates if current platform supports voice dialing over bluetooth SCO. 696 * 697 * @return true if voice dialing over bluetooth is supported, false otherwise. 698 * @hide 699 */ 700 public static boolean isBluetoothVoiceDialingEnabled(Context context) { 701 return context.getResources().getBoolean( 702 com.android.internal.R.bool.config_bluetooth_sco_off_call); 703 } 704 705 /** 706 * Accept the incoming connection. 707 * Note: This is an internal function and shouldn't be exposed 708 * 709 * @hide 710 */ 711 public boolean acceptIncomingConnect(BluetoothDevice device) { 712 if (DBG) log("acceptIncomingConnect"); 713 final IBluetoothHeadset service = mService; 714 if (service != null && isEnabled()) { 715 try { 716 return service.acceptIncomingConnect(device); 717 } catch (RemoteException e) { 718 Log.e(TAG, e.toString()); 719 } 720 } else { 721 Log.w(TAG, "Proxy not attached to service"); 722 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 723 } 724 return false; 725 } 726 727 /** 728 * Reject the incoming connection. 729 * @hide 730 */ 731 public boolean rejectIncomingConnect(BluetoothDevice device) { 732 if (DBG) log("rejectIncomingConnect"); 733 final IBluetoothHeadset service = mService; 734 if (service != null) { 735 try { 736 return service.rejectIncomingConnect(device); 737 } catch (RemoteException e) { 738 Log.e(TAG, e.toString()); 739 } 740 } else { 741 Log.w(TAG, "Proxy not attached to service"); 742 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 743 } 744 return false; 745 } 746 747 /** 748 * Get the current audio state of the Headset. 749 * Note: This is an internal function and shouldn't be exposed 750 * 751 * @hide 752 */ 753 public int getAudioState(BluetoothDevice device) { 754 if (VDBG) log("getAudioState"); 755 final IBluetoothHeadset service = mService; 756 if (service != null && !isDisabled()) { 757 try { 758 return service.getAudioState(device); 759 } catch (RemoteException e) { 760 Log.e(TAG, e.toString()); 761 } 762 } else { 763 Log.w(TAG, "Proxy not attached to service"); 764 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 765 } 766 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 767 } 768 769 /** 770 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any 771 * audio to the HF unless explicitly told to. 772 * This method should be used in cases where the SCO channel is shared between multiple profiles 773 * and must be delegated by a source knowledgeable 774 * Note: This is an internal function and shouldn't be exposed 775 * 776 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise. 777 * 778 * @hide 779 */ 780 public void setAudioRouteAllowed(boolean allowed) { 781 if (VDBG) log("setAudioRouteAllowed"); 782 final IBluetoothHeadset service = mService; 783 if (service != null && isEnabled()) { 784 try { 785 service.setAudioRouteAllowed(allowed); 786 } catch (RemoteException e) { 787 Log.e(TAG, e.toString()); 788 } 789 } else { 790 Log.w(TAG, "Proxy not attached to service"); 791 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 792 } 793 } 794 795 /** 796 * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}. 797 * Note: This is an internal function and shouldn't be exposed 798 * 799 * @hide 800 */ 801 public boolean getAudioRouteAllowed() { 802 if (VDBG) log("getAudioRouteAllowed"); 803 final IBluetoothHeadset service = mService; 804 if (service != null && isEnabled()) { 805 try { 806 return service.getAudioRouteAllowed(); 807 } catch (RemoteException e) { 808 Log.e(TAG, e.toString()); 809 } 810 } else { 811 Log.w(TAG, "Proxy not attached to service"); 812 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 813 } 814 return false; 815 } 816 817 /** 818 * Force SCO audio to be opened regardless any other restrictions 819 * 820 * @param forced Whether or not SCO audio connection should be forced: 821 * True to force SCO audio 822 * False to use SCO audio in normal manner 823 * @hide 824 */ 825 public void setForceScoAudio(boolean forced) { 826 if (VDBG) log("setForceScoAudio " + String.valueOf(forced)); 827 final IBluetoothHeadset service = mService; 828 if (service != null && isEnabled()) { 829 try { 830 service.setForceScoAudio(forced); 831 } catch (RemoteException e) { 832 Log.e(TAG, e.toString()); 833 } 834 } else { 835 Log.w(TAG, "Proxy not attached to service"); 836 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 837 } 838 } 839 840 /** 841 * Check if Bluetooth SCO audio is connected. 842 * 843 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 844 * 845 * @return true if SCO is connected, 846 * false otherwise or on error 847 * @hide 848 */ 849 public boolean isAudioOn() { 850 if (VDBG) log("isAudioOn()"); 851 final IBluetoothHeadset service = mService; 852 if (service != null && isEnabled()) { 853 try { 854 return service.isAudioOn(); 855 } catch (RemoteException e) { 856 Log.e(TAG, Log.getStackTraceString(new Throwable())); 857 } 858 } 859 if (service == null) Log.w(TAG, "Proxy not attached to service"); 860 return false; 861 862 } 863 864 /** 865 * Initiates a connection of headset audio. 866 * It setup SCO channel with remote connected headset device. 867 * 868 * @return true if successful 869 * false if there was some error such as 870 * there is no connected headset 871 * @hide 872 */ 873 public boolean connectAudio() { 874 final IBluetoothHeadset service = mService; 875 if (service != null && isEnabled()) { 876 try { 877 return service.connectAudio(); 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 * Initiates a disconnection of headset audio. 890 * It tears down the SCO channel from remote headset device. 891 * 892 * @return true if successful 893 * false if there was some error such as 894 * there is no connected SCO channel 895 * @hide 896 */ 897 public boolean disconnectAudio() { 898 final IBluetoothHeadset service = mService; 899 if (service != null && isEnabled()) { 900 try { 901 return service.disconnectAudio(); 902 } catch (RemoteException e) { 903 Log.e(TAG, e.toString()); 904 } 905 } else { 906 Log.w(TAG, "Proxy not attached to service"); 907 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 908 } 909 return false; 910 } 911 912 /** 913 * Initiates a SCO channel connection with the headset (if connected). 914 * Also initiates a virtual voice call for Handsfree devices as many devices 915 * do not accept SCO audio without a call. 916 * This API allows the handsfree device to be used for routing non-cellular 917 * call audio. 918 * 919 * @param device Remote Bluetooth Device 920 * @return true if successful, false if there was some error. 921 * @hide 922 */ 923 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 924 if (DBG) log("startScoUsingVirtualVoiceCall()"); 925 final IBluetoothHeadset service = mService; 926 if (service != null && isEnabled() && isValidDevice(device)) { 927 try { 928 return service.startScoUsingVirtualVoiceCall(device); 929 } catch (RemoteException e) { 930 Log.e(TAG, e.toString()); 931 } 932 } else { 933 Log.w(TAG, "Proxy not attached to service"); 934 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 935 } 936 return false; 937 } 938 939 /** 940 * Terminates an ongoing SCO connection and the associated virtual 941 * call. 942 * 943 * @param device Remote Bluetooth Device 944 * @return true if successful, false if there was some error. 945 * @hide 946 */ 947 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 948 if (DBG) log("stopScoUsingVirtualVoiceCall()"); 949 final IBluetoothHeadset service = mService; 950 if (service != null && isEnabled() && isValidDevice(device)) { 951 try { 952 return service.stopScoUsingVirtualVoiceCall(device); 953 } catch (RemoteException e) { 954 Log.e(TAG, e.toString()); 955 } 956 } else { 957 Log.w(TAG, "Proxy not attached to service"); 958 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 959 } 960 return false; 961 } 962 963 /** 964 * Notify Headset of phone state change. 965 * This is a backdoor for phone app to call BluetoothHeadset since 966 * there is currently not a good way to get precise call state change outside 967 * of phone app. 968 * 969 * @hide 970 */ 971 public void phoneStateChanged(int numActive, int numHeld, int callState, String number, 972 int type) { 973 final IBluetoothHeadset service = mService; 974 if (service != null && isEnabled()) { 975 try { 976 service.phoneStateChanged(numActive, numHeld, callState, number, type); 977 } catch (RemoteException e) { 978 Log.e(TAG, e.toString()); 979 } 980 } else { 981 Log.w(TAG, "Proxy not attached to service"); 982 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 983 } 984 } 985 986 /** 987 * Send Headset of CLCC response 988 * 989 * @hide 990 */ 991 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 992 String number, int type) { 993 final IBluetoothHeadset service = mService; 994 if (service != null && isEnabled()) { 995 try { 996 service.clccResponse(index, direction, status, mode, mpty, number, type); 997 } catch (RemoteException e) { 998 Log.e(TAG, e.toString()); 999 } 1000 } else { 1001 Log.w(TAG, "Proxy not attached to service"); 1002 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1003 } 1004 } 1005 1006 /** 1007 * Sends a vendor-specific unsolicited result code to the headset. 1008 * 1009 * <p>The actual string to be sent is <code>command + ": " + arg</code>. 1010 * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} 1011 * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent. 1012 * 1013 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. 1014 * 1015 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 1016 * 1017 * @param device Bluetooth headset. 1018 * @param command A vendor-specific command. 1019 * @param arg The argument that will be attached to the command. 1020 * @return {@code false} if there is no headset connected, or if the command is not an allowed 1021 * vendor-specific unsolicited result code, or on error. {@code true} otherwise. 1022 * @throws IllegalArgumentException if {@code command} is {@code null}. 1023 */ 1024 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, 1025 String arg) { 1026 if (DBG) { 1027 log("sendVendorSpecificResultCode()"); 1028 } 1029 if (command == null) { 1030 throw new IllegalArgumentException("command is null"); 1031 } 1032 final IBluetoothHeadset service = mService; 1033 if (service != null && isEnabled() && isValidDevice(device)) { 1034 try { 1035 return service.sendVendorSpecificResultCode(device, command, arg); 1036 } catch (RemoteException e) { 1037 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1038 } 1039 } 1040 if (service == null) { 1041 Log.w(TAG, "Proxy not attached to service"); 1042 } 1043 return false; 1044 } 1045 1046 /** 1047 * enable WBS codec setting. 1048 * 1049 * @return true if successful 1050 * false if there was some error such as 1051 * there is no connected headset 1052 * @hide 1053 */ 1054 public boolean enableWBS() { 1055 final IBluetoothHeadset service = mService; 1056 if (service != null && isEnabled()) { 1057 try { 1058 return service.enableWBS(); 1059 } catch (RemoteException e) { 1060 Log.e(TAG, e.toString()); 1061 } 1062 } else { 1063 Log.w(TAG, "Proxy not attached to service"); 1064 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1065 } 1066 return false; 1067 } 1068 1069 /** 1070 * disable WBS codec settting. It set NBS codec. 1071 * 1072 * @return true if successful 1073 * false if there was some error such as 1074 * there is no connected headset 1075 * @hide 1076 */ 1077 public boolean disableWBS() { 1078 final IBluetoothHeadset service = mService; 1079 if (service != null && isEnabled()) { 1080 try { 1081 return service.disableWBS(); 1082 } catch (RemoteException e) { 1083 Log.e(TAG, e.toString()); 1084 } 1085 } else { 1086 Log.w(TAG, "Proxy not attached to service"); 1087 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1088 } 1089 return false; 1090 } 1091 1092 /** 1093 * check if in-band ringing is supported for this platform. 1094 * 1095 * @return true if in-band ringing is supported 1096 * false if in-band ringing is not supported 1097 * @hide 1098 */ 1099 public static boolean isInbandRingingSupported(Context context) { 1100 return context.getResources().getBoolean( 1101 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support); 1102 } 1103 1104 /** 1105 * Send Headset the BIND response from AG to report change in the status of the 1106 * HF indicators to the headset 1107 * 1108 * @param indId Assigned Number of the indicator (defined by SIG) 1109 * @param indStatus 1110 * possible values- false-Indicator is disabled, no value changes shall be sent for this indicator 1111 * true-Indicator is enabled, value changes may be sent for this indicator 1112 * @hide 1113 */ 1114 public void bindResponse(int indId, boolean indStatus) { 1115 final IBluetoothHeadset service = mService; 1116 if (service != null && isEnabled()) { 1117 try { 1118 service.bindResponse(indId, indStatus); 1119 } catch (RemoteException e) { 1120 Log.e(TAG, e.toString()); 1121 } 1122 } else { 1123 Log.w(TAG, "Proxy not attached to service"); 1124 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1125 } 1126 } 1127 1128 private final IBluetoothProfileServiceConnection mConnection 1129 = new IBluetoothProfileServiceConnection.Stub() { 1130 @Override 1131 public void onServiceConnected(ComponentName className, IBinder service) { 1132 if (DBG) Log.d(TAG, "Proxy object connected"); 1133 mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service)); 1134 mHandler.sendMessage(mHandler.obtainMessage( 1135 MESSAGE_HEADSET_SERVICE_CONNECTED)); 1136 } 1137 @Override 1138 public void onServiceDisconnected(ComponentName className) { 1139 if (DBG) Log.d(TAG, "Proxy object disconnected"); 1140 mService = null; 1141 mHandler.sendMessage(mHandler.obtainMessage( 1142 MESSAGE_HEADSET_SERVICE_DISCONNECTED)); 1143 } 1144 }; 1145 1146 private boolean isEnabled() { 1147 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 1148 } 1149 1150 private boolean isDisabled() { 1151 return mAdapter.getState() == BluetoothAdapter.STATE_OFF; 1152 } 1153 1154 private static boolean isValidDevice(BluetoothDevice device) { 1155 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 1156 } 1157 1158 private static void log(String msg) { 1159 Log.d(TAG, msg); 1160 } 1161 1162 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 1163 @Override 1164 public void handleMessage(Message msg) { 1165 switch (msg.what) { 1166 case MESSAGE_HEADSET_SERVICE_CONNECTED: { 1167 if (mServiceListener != null) { 1168 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, 1169 BluetoothHeadset.this); 1170 } 1171 break; 1172 } 1173 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: { 1174 if (mServiceListener != null) { 1175 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); 1176 } 1177 break; 1178 } 1179 } 1180 } 1181 }; 1182 } 1183