1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.bluetooth; 18 19import android.annotation.SdkConstant; 20import android.annotation.SdkConstant.SdkConstantType; 21import android.content.ComponentName; 22import android.content.Context; 23import android.os.Handler; 24import android.os.IBinder; 25import android.os.Looper; 26import android.os.Message; 27import android.os.RemoteException; 28import android.util.Log; 29 30import java.util.ArrayList; 31import 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 */ 47public 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 public static final int STATE_AUDIO_CONNECTED = 12; 224 225 private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100; 226 private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101; 227 228 private Context mContext; 229 private ServiceListener mServiceListener; 230 private IBluetoothHeadset mService; 231 private BluetoothAdapter mAdapter; 232 233 final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = 234 new IBluetoothStateChangeCallback.Stub() { 235 public void onBluetoothStateChange(boolean up) { 236 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); 237 if (!up) { 238 if (VDBG) Log.d(TAG,"Unbinding service..."); 239 doUnbind(); 240 } else { 241 synchronized (mConnection) { 242 try { 243 if (mService == null) { 244 if (VDBG) Log.d(TAG,"Binding service..."); 245 doBind(); 246 } 247 } catch (Exception re) { 248 Log.e(TAG,"",re); 249 } 250 } 251 } 252 } 253 }; 254 255 /** 256 * Create a BluetoothHeadset proxy object. 257 */ 258 /*package*/ BluetoothHeadset(Context context, ServiceListener l) { 259 mContext = context; 260 mServiceListener = l; 261 mAdapter = BluetoothAdapter.getDefaultAdapter(); 262 263 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 264 if (mgr != null) { 265 try { 266 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 267 } catch (RemoteException e) { 268 Log.e(TAG,"",e); 269 } 270 } 271 272 doBind(); 273 } 274 275 boolean doBind() { 276 try { 277 return mAdapter.getBluetoothManager().bindBluetoothProfileService( 278 BluetoothProfile.HEADSET, mConnection); 279 } catch (RemoteException e) { 280 Log.e(TAG, "Unable to bind HeadsetService", e); 281 } 282 return false; 283 } 284 285 void doUnbind() { 286 synchronized (mConnection) { 287 if (mService != null) { 288 try { 289 mAdapter.getBluetoothManager().unbindBluetoothProfileService( 290 BluetoothProfile.HEADSET, mConnection); 291 } catch (RemoteException e) { 292 Log.e(TAG,"Unable to unbind HeadsetService", e); 293 } 294 } 295 } 296 } 297 298 /** 299 * Close the connection to the backing service. 300 * Other public functions of BluetoothHeadset will return default error 301 * results once close() has been called. Multiple invocations of close() 302 * are ok. 303 */ 304 /*package*/ void close() { 305 if (VDBG) log("close()"); 306 307 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 308 if (mgr != null) { 309 try { 310 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 311 } catch (Exception e) { 312 Log.e(TAG,"",e); 313 } 314 } 315 mServiceListener = null; 316 doUnbind(); 317 } 318 319 /** 320 * Initiate connection to a profile of the remote bluetooth device. 321 * 322 * <p> Currently, the system supports only 1 connection to the 323 * headset/handsfree profile. The API will automatically disconnect connected 324 * devices before connecting. 325 * 326 * <p> This API returns false in scenarios like the profile on the 327 * device is already connected or Bluetooth is not turned on. 328 * When this API returns true, it is guaranteed that 329 * connection state intent for the profile will be broadcasted with 330 * the state. Users can get the connection state of the profile 331 * from this intent. 332 * 333 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 334 * permission. 335 * 336 * @param device Remote Bluetooth Device 337 * @return false on immediate error, 338 * true otherwise 339 * @hide 340 */ 341 public boolean connect(BluetoothDevice device) { 342 if (DBG) log("connect(" + device + ")"); 343 if (mService != null && isEnabled() && 344 isValidDevice(device)) { 345 try { 346 return mService.connect(device); 347 } catch (RemoteException e) { 348 Log.e(TAG, Log.getStackTraceString(new Throwable())); 349 return false; 350 } 351 } 352 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 353 return false; 354 } 355 356 /** 357 * Initiate disconnection from a profile 358 * 359 * <p> This API will return false in scenarios like the profile on the 360 * Bluetooth device is not in connected state etc. When this API returns, 361 * true, it is guaranteed that the connection state change 362 * intent will be broadcasted with the state. Users can get the 363 * disconnection state of the profile from this intent. 364 * 365 * <p> If the disconnection is initiated by a remote device, the state 366 * will transition from {@link #STATE_CONNECTED} to 367 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 368 * host (local) device the state will transition from 369 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 370 * state {@link #STATE_DISCONNECTED}. The transition to 371 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 372 * two scenarios. 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 */ 382 public boolean disconnect(BluetoothDevice device) { 383 if (DBG) log("disconnect(" + device + ")"); 384 if (mService != null && isEnabled() && 385 isValidDevice(device)) { 386 try { 387 return mService.disconnect(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 * {@inheritDoc} 399 */ 400 public List<BluetoothDevice> getConnectedDevices() { 401 if (VDBG) log("getConnectedDevices()"); 402 if (mService != null && isEnabled()) { 403 try { 404 return mService.getConnectedDevices(); 405 } catch (RemoteException e) { 406 Log.e(TAG, Log.getStackTraceString(new Throwable())); 407 return new ArrayList<BluetoothDevice>(); 408 } 409 } 410 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 411 return new ArrayList<BluetoothDevice>(); 412 } 413 414 /** 415 * {@inheritDoc} 416 */ 417 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 418 if (VDBG) log("getDevicesMatchingStates()"); 419 if (mService != null && isEnabled()) { 420 try { 421 return mService.getDevicesMatchingConnectionStates(states); 422 } catch (RemoteException e) { 423 Log.e(TAG, Log.getStackTraceString(new Throwable())); 424 return new ArrayList<BluetoothDevice>(); 425 } 426 } 427 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 428 return new ArrayList<BluetoothDevice>(); 429 } 430 431 /** 432 * {@inheritDoc} 433 */ 434 public int getConnectionState(BluetoothDevice device) { 435 if (VDBG) log("getConnectionState(" + device + ")"); 436 if (mService != null && isEnabled() && 437 isValidDevice(device)) { 438 try { 439 return mService.getConnectionState(device); 440 } catch (RemoteException e) { 441 Log.e(TAG, Log.getStackTraceString(new Throwable())); 442 return BluetoothProfile.STATE_DISCONNECTED; 443 } 444 } 445 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 446 return BluetoothProfile.STATE_DISCONNECTED; 447 } 448 449 /** 450 * Set priority of the profile 451 * 452 * <p> The device should already be paired. 453 * Priority can be one of {@link #PRIORITY_ON} or 454 * {@link #PRIORITY_OFF}, 455 * 456 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 457 * permission. 458 * 459 * @param device Paired bluetooth device 460 * @param priority 461 * @return true if priority is set, false on error 462 * @hide 463 */ 464 public boolean setPriority(BluetoothDevice device, int priority) { 465 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 466 if (mService != null && isEnabled() && 467 isValidDevice(device)) { 468 if (priority != BluetoothProfile.PRIORITY_OFF && 469 priority != BluetoothProfile.PRIORITY_ON) { 470 return false; 471 } 472 try { 473 return mService.setPriority(device, priority); 474 } catch (RemoteException e) { 475 Log.e(TAG, Log.getStackTraceString(new Throwable())); 476 return false; 477 } 478 } 479 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 480 return false; 481 } 482 483 /** 484 * Get the priority of the profile. 485 * 486 * <p> The priority can be any of: 487 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, 488 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 489 * 490 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 491 * 492 * @param device Bluetooth device 493 * @return priority of the device 494 * @hide 495 */ 496 public int getPriority(BluetoothDevice device) { 497 if (VDBG) log("getPriority(" + device + ")"); 498 if (mService != null && isEnabled() && 499 isValidDevice(device)) { 500 try { 501 return mService.getPriority(device); 502 } catch (RemoteException e) { 503 Log.e(TAG, Log.getStackTraceString(new Throwable())); 504 return PRIORITY_OFF; 505 } 506 } 507 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 508 return PRIORITY_OFF; 509 } 510 511 /** 512 * Start Bluetooth voice recognition. This methods sends the voice 513 * recognition AT command to the headset and establishes the 514 * audio connection. 515 * 516 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 517 * If this function returns true, this intent will be broadcasted with 518 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. 519 * 520 * <p> {@link #EXTRA_STATE} will transition from 521 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when 522 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} 523 * in case of failure to establish the audio connection. 524 * 525 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 526 * 527 * @param device Bluetooth headset 528 * @return false if there is no headset connected of if the 529 * connected headset doesn't support voice recognition 530 * or on error, true otherwise 531 */ 532 public boolean startVoiceRecognition(BluetoothDevice device) { 533 if (DBG) log("startVoiceRecognition()"); 534 if (mService != null && isEnabled() && 535 isValidDevice(device)) { 536 try { 537 return mService.startVoiceRecognition(device); 538 } catch (RemoteException e) { 539 Log.e(TAG, Log.getStackTraceString(new Throwable())); 540 } 541 } 542 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 543 return false; 544 } 545 546 /** 547 * Stop Bluetooth Voice Recognition mode, and shut down the 548 * Bluetooth audio path. 549 * 550 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 551 * 552 * @param device Bluetooth headset 553 * @return false if there is no headset connected 554 * or on error, true otherwise 555 */ 556 public boolean stopVoiceRecognition(BluetoothDevice device) { 557 if (DBG) log("stopVoiceRecognition()"); 558 if (mService != null && isEnabled() && 559 isValidDevice(device)) { 560 try { 561 return mService.stopVoiceRecognition(device); 562 } catch (RemoteException e) { 563 Log.e(TAG, Log.getStackTraceString(new Throwable())); 564 } 565 } 566 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 567 return false; 568 } 569 570 /** 571 * Check if Bluetooth SCO audio is connected. 572 * 573 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 574 * 575 * @param device Bluetooth headset 576 * @return true if SCO is connected, 577 * false otherwise or on error 578 */ 579 public boolean isAudioConnected(BluetoothDevice device) { 580 if (VDBG) log("isAudioConnected()"); 581 if (mService != null && isEnabled() && 582 isValidDevice(device)) { 583 try { 584 return mService.isAudioConnected(device); 585 } catch (RemoteException e) { 586 Log.e(TAG, Log.getStackTraceString(new Throwable())); 587 } 588 } 589 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 590 return false; 591 } 592 593 /** 594 * Get battery usage hint for Bluetooth Headset service. 595 * This is a monotonically increasing integer. Wraps to 0 at 596 * Integer.MAX_INT, and at boot. 597 * Current implementation returns the number of AT commands handled since 598 * boot. This is a good indicator for spammy headset/handsfree units that 599 * can keep the device awake by polling for cellular status updates. As a 600 * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms 601 * 602 * @param device the bluetooth headset. 603 * @return monotonically increasing battery usage hint, or a negative error 604 * code on error 605 * @hide 606 */ 607 public int getBatteryUsageHint(BluetoothDevice device) { 608 if (VDBG) log("getBatteryUsageHint()"); 609 if (mService != null && isEnabled() && 610 isValidDevice(device)) { 611 try { 612 return mService.getBatteryUsageHint(device); 613 } catch (RemoteException e) { 614 Log.e(TAG, Log.getStackTraceString(new Throwable())); 615 } 616 } 617 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 618 return -1; 619 } 620 621 /** 622 * Indicates if current platform supports voice dialing over bluetooth SCO. 623 * 624 * @return true if voice dialing over bluetooth is supported, false otherwise. 625 * @hide 626 */ 627 public static boolean isBluetoothVoiceDialingEnabled(Context context) { 628 return context.getResources().getBoolean( 629 com.android.internal.R.bool.config_bluetooth_sco_off_call); 630 } 631 632 /** 633 * Accept the incoming connection. 634 * Note: This is an internal function and shouldn't be exposed 635 * 636 * @hide 637 */ 638 public boolean acceptIncomingConnect(BluetoothDevice device) { 639 if (DBG) log("acceptIncomingConnect"); 640 if (mService != null && isEnabled()) { 641 try { 642 return mService.acceptIncomingConnect(device); 643 } catch (RemoteException e) {Log.e(TAG, e.toString());} 644 } else { 645 Log.w(TAG, "Proxy not attached to service"); 646 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 647 } 648 return false; 649 } 650 651 /** 652 * Reject the incoming connection. 653 * @hide 654 */ 655 public boolean rejectIncomingConnect(BluetoothDevice device) { 656 if (DBG) log("rejectIncomingConnect"); 657 if (mService != null) { 658 try { 659 return mService.rejectIncomingConnect(device); 660 } catch (RemoteException e) {Log.e(TAG, e.toString());} 661 } else { 662 Log.w(TAG, "Proxy not attached to service"); 663 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 664 } 665 return false; 666 } 667 668 /** 669 * Get the current audio state of the Headset. 670 * Note: This is an internal function and shouldn't be exposed 671 * 672 * @hide 673 */ 674 public int getAudioState(BluetoothDevice device) { 675 if (VDBG) log("getAudioState"); 676 if (mService != null && !isDisabled()) { 677 try { 678 return mService.getAudioState(device); 679 } catch (RemoteException e) {Log.e(TAG, e.toString());} 680 } else { 681 Log.w(TAG, "Proxy not attached to service"); 682 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 683 } 684 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 685 } 686 687 /** 688 * Check if Bluetooth SCO audio is connected. 689 * 690 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 691 * 692 * @return true if SCO is connected, 693 * false otherwise or on error 694 * @hide 695 */ 696 public boolean isAudioOn() { 697 if (VDBG) log("isAudioOn()"); 698 if (mService != null && isEnabled()) { 699 try { 700 return mService.isAudioOn(); 701 } catch (RemoteException e) { 702 Log.e(TAG, Log.getStackTraceString(new Throwable())); 703 } 704 } 705 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 706 return false; 707 708 } 709 710 /** 711 * Initiates a connection of headset audio. 712 * It setup SCO channel with remote connected headset device. 713 * 714 * @return true if successful 715 * false if there was some error such as 716 * there is no connected headset 717 * @hide 718 */ 719 public boolean connectAudio() { 720 if (mService != null && isEnabled()) { 721 try { 722 return mService.connectAudio(); 723 } catch (RemoteException e) { 724 Log.e(TAG, e.toString()); 725 } 726 } else { 727 Log.w(TAG, "Proxy not attached to service"); 728 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 729 } 730 return false; 731 } 732 733 /** 734 * Initiates a disconnection of headset audio. 735 * It tears down the SCO channel from remote headset device. 736 * 737 * @return true if successful 738 * false if there was some error such as 739 * there is no connected SCO channel 740 * @hide 741 */ 742 public boolean disconnectAudio() { 743 if (mService != null && isEnabled()) { 744 try { 745 return mService.disconnectAudio(); 746 } catch (RemoteException e) { 747 Log.e(TAG, e.toString()); 748 } 749 } else { 750 Log.w(TAG, "Proxy not attached to service"); 751 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 752 } 753 return false; 754 } 755 756 /** 757 * Initiates a SCO channel connection with the headset (if connected). 758 * Also initiates a virtual voice call for Handsfree devices as many devices 759 * do not accept SCO audio without a call. 760 * This API allows the handsfree device to be used for routing non-cellular 761 * call audio. 762 * 763 * @param device Remote Bluetooth Device 764 * @return true if successful, false if there was some error. 765 * @hide 766 */ 767 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 768 if (DBG) log("startScoUsingVirtualVoiceCall()"); 769 if (mService != null && isEnabled() && isValidDevice(device)) { 770 try { 771 return mService.startScoUsingVirtualVoiceCall(device); 772 } catch (RemoteException e) { 773 Log.e(TAG, e.toString()); 774 } 775 } else { 776 Log.w(TAG, "Proxy not attached to service"); 777 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 778 } 779 return false; 780 } 781 782 /** 783 * Terminates an ongoing SCO connection and the associated virtual 784 * call. 785 * 786 * @param device Remote Bluetooth Device 787 * @return true if successful, false if there was some error. 788 * @hide 789 */ 790 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 791 if (DBG) log("stopScoUsingVirtualVoiceCall()"); 792 if (mService != null && isEnabled() && isValidDevice(device)) { 793 try { 794 return mService.stopScoUsingVirtualVoiceCall(device); 795 } catch (RemoteException e) { 796 Log.e(TAG, e.toString()); 797 } 798 } else { 799 Log.w(TAG, "Proxy not attached to service"); 800 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 801 } 802 return false; 803 } 804 805 /** 806 * Notify Headset of phone state change. 807 * This is a backdoor for phone app to call BluetoothHeadset since 808 * there is currently not a good way to get precise call state change outside 809 * of phone app. 810 * 811 * @hide 812 */ 813 public void phoneStateChanged(int numActive, int numHeld, int callState, String number, 814 int type) { 815 if (mService != null && isEnabled()) { 816 try { 817 mService.phoneStateChanged(numActive, numHeld, callState, number, type); 818 } catch (RemoteException e) { 819 Log.e(TAG, e.toString()); 820 } 821 } else { 822 Log.w(TAG, "Proxy not attached to service"); 823 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 824 } 825 } 826 827 /** 828 * Send Headset of CLCC response 829 * 830 * @hide 831 */ 832 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 833 String number, int type) { 834 if (mService != null && isEnabled()) { 835 try { 836 mService.clccResponse(index, direction, status, mode, mpty, number, type); 837 } catch (RemoteException e) { 838 Log.e(TAG, e.toString()); 839 } 840 } else { 841 Log.w(TAG, "Proxy not attached to service"); 842 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 843 } 844 } 845 846 /** 847 * Sends a vendor-specific unsolicited result code to the headset. 848 * 849 * <p>The actual string to be sent is <code>command + ": " + arg</code>. 850 * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} 851 * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent. 852 * 853 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. 854 * 855 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 856 * 857 * @param device Bluetooth headset. 858 * @param command A vendor-specific command. 859 * @param arg The argument that will be attached to the command. 860 * @return {@code false} if there is no headset connected, or if the command is not an allowed 861 * vendor-specific unsolicited result code, or on error. {@code true} otherwise. 862 * @throws IllegalArgumentException if {@code command} is {@code null}. 863 */ 864 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, 865 String arg) { 866 if (DBG) { 867 log("sendVendorSpecificResultCode()"); 868 } 869 if (command == null) { 870 throw new IllegalArgumentException("command is null"); 871 } 872 if (mService != null && isEnabled() && 873 isValidDevice(device)) { 874 try { 875 return mService.sendVendorSpecificResultCode(device, command, arg); 876 } catch (RemoteException e) { 877 Log.e(TAG, Log.getStackTraceString(new Throwable())); 878 } 879 } 880 if (mService == null) { 881 Log.w(TAG, "Proxy not attached to service"); 882 } 883 return false; 884 } 885 886 /** 887 * enable WBS codec setting. 888 * 889 * @return true if successful 890 * false if there was some error such as 891 * there is no connected headset 892 * @hide 893 */ 894 public boolean enableWBS() { 895 if (mService != null && isEnabled()) { 896 try { 897 return mService.enableWBS(); 898 } catch (RemoteException e) { 899 Log.e(TAG, e.toString()); 900 } 901 } else { 902 Log.w(TAG, "Proxy not attached to service"); 903 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 904 } 905 return false; 906 } 907 908 /** 909 * disable WBS codec settting. It set NBS codec. 910 * 911 * @return true if successful 912 * false if there was some error such as 913 * there is no connected headset 914 * @hide 915 */ 916 public boolean disableWBS() { 917 if (mService != null && isEnabled()) { 918 try { 919 return mService.disableWBS(); 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 return false; 928 } 929 930 private final IBluetoothProfileServiceConnection mConnection 931 = new IBluetoothProfileServiceConnection.Stub() { 932 @Override 933 public void onServiceConnected(ComponentName className, IBinder service) { 934 if (DBG) Log.d(TAG, "Proxy object connected"); 935 mService = IBluetoothHeadset.Stub.asInterface(service); 936 mHandler.sendMessage(mHandler.obtainMessage( 937 MESSAGE_HEADSET_SERVICE_CONNECTED)); 938 } 939 @Override 940 public void onServiceDisconnected(ComponentName className) { 941 if (DBG) Log.d(TAG, "Proxy object disconnected"); 942 mService = null; 943 mHandler.sendMessage(mHandler.obtainMessage( 944 MESSAGE_HEADSET_SERVICE_DISCONNECTED)); 945 } 946 }; 947 948 private boolean isEnabled() { 949 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 950 return false; 951 } 952 953 private boolean isDisabled() { 954 if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true; 955 return false; 956 } 957 958 private boolean isValidDevice(BluetoothDevice device) { 959 if (device == null) return false; 960 961 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 962 return false; 963 } 964 965 private static void log(String msg) { 966 Log.d(TAG, msg); 967 } 968 969 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 970 @Override 971 public void handleMessage(Message msg) { 972 switch (msg.what) { 973 case MESSAGE_HEADSET_SERVICE_CONNECTED: { 974 if (mServiceListener != null) { 975 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, 976 BluetoothHeadset.this); 977 } 978 break; 979 } 980 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: { 981 if (mServiceListener != null) { 982 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); 983 } 984 break; 985 } 986 } 987 } 988 }; 989} 990