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