1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.bluetooth; 18 19import android.Manifest; 20import android.annotation.Nullable; 21import android.annotation.RequiresPermission; 22import android.annotation.SdkConstant; 23import android.annotation.SdkConstant.SdkConstantType; 24import android.annotation.SystemApi; 25import android.content.ComponentName; 26import android.content.Context; 27import android.os.Binder; 28import android.os.Handler; 29import android.os.IBinder; 30import android.os.Looper; 31import android.os.Message; 32import android.os.RemoteException; 33import android.util.Log; 34 35import java.util.ArrayList; 36import 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 */ 52public 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 */ 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 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 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 */ 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) 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) 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 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 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 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) 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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) 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) 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 */ 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 */ 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 */ 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) 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) 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) 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 */ 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 1166 private boolean isEnabled() { 1167 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 1168 } 1169 1170 private boolean isDisabled() { 1171 return mAdapter.getState() == BluetoothAdapter.STATE_OFF; 1172 } 1173 1174 private static boolean isValidDevice(BluetoothDevice device) { 1175 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 1176 } 1177 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