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