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