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