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 */ BluetoothHeadset(Context context, ServiceListener l)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 doBind()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 doUnbind()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 close()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) connect(BluetoothDevice device)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) disconnect(BluetoothDevice device)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 getConnectedDevices()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 getDevicesMatchingConnectionStates(int[] states)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 getConnectionState(BluetoothDevice device)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) setPriority(BluetoothDevice device, int priority)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 getPriority(BluetoothDevice device)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 */ startVoiceRecognition(BluetoothDevice device)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 */ stopVoiceRecognition(BluetoothDevice device)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 */ isAudioConnected(BluetoothDevice device)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 */ isBluetoothVoiceDialingEnabled(Context context)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 getAudioState(BluetoothDevice device)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 */ setAudioRouteAllowed(boolean allowed)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 */ getAudioRouteAllowed()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 */ setForceScoAudio(boolean forced)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 */ isAudioOn()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 connectAudio()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 disconnectAudio()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 startScoUsingVirtualVoiceCall()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 stopScoUsingVirtualVoiceCall()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 phoneStateChanged(int numActive, int numHeld, int callState, String number, int type, String name)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 */ clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type)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 */ sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg)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 setActiveDevice(@ullable BluetoothDevice device)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 getActiveDevice()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) isInbandRingingEnabled()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 */ isInbandRingingSupported(Context context)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 isEnabled()1177 private boolean isEnabled() { 1178 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 1179 } 1180 isDisabled()1181 private boolean isDisabled() { 1182 return mAdapter.getState() == BluetoothAdapter.STATE_OFF; 1183 } 1184 isValidDevice(BluetoothDevice device)1185 private static boolean isValidDevice(BluetoothDevice device) { 1186 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 1187 } 1188 log(String msg)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