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 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any 689 * audio to the HF unless explicitly told to. 690 * This method should be used in cases where the SCO channel is shared between multiple profiles 691 * and must be delegated by a source knowledgeable 692 * Note: This is an internal function and shouldn't be exposed 693 * 694 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise. 695 * 696 * @hide 697 */ 698 public void setAudioRouteAllowed(boolean allowed) { 699 if (VDBG) log("setAudioRouteAllowed"); 700 if (mService != null && isEnabled()) { 701 try { 702 mService.setAudioRouteAllowed(allowed); 703 } catch (RemoteException e) {Log.e(TAG, e.toString());} 704 } else { 705 Log.w(TAG, "Proxy not attached to service"); 706 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 707 } 708 } 709 710 /** 711 * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}. 712 * Note: This is an internal function and shouldn't be exposed 713 * 714 * @hide 715 */ 716 public boolean getAudioRouteAllowed() { 717 if (VDBG) log("getAudioRouteAllowed"); 718 if (mService != null && isEnabled()) { 719 try { 720 return mService.getAudioRouteAllowed(); 721 } catch (RemoteException e) {Log.e(TAG, e.toString());} 722 } else { 723 Log.w(TAG, "Proxy not attached to service"); 724 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 725 } 726 return false; 727 } 728 729 /** 730 * Check if Bluetooth SCO audio is connected. 731 * 732 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 733 * 734 * @return true if SCO is connected, 735 * false otherwise or on error 736 * @hide 737 */ 738 public boolean isAudioOn() { 739 if (VDBG) log("isAudioOn()"); 740 if (mService != null && isEnabled()) { 741 try { 742 return mService.isAudioOn(); 743 } catch (RemoteException e) { 744 Log.e(TAG, Log.getStackTraceString(new Throwable())); 745 } 746 } 747 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 748 return false; 749 750 } 751 752 /** 753 * Initiates a connection of headset audio. 754 * It setup SCO channel with remote connected headset device. 755 * 756 * @return true if successful 757 * false if there was some error such as 758 * there is no connected headset 759 * @hide 760 */ 761 public boolean connectAudio() { 762 if (mService != null && isEnabled()) { 763 try { 764 return mService.connectAudio(); 765 } catch (RemoteException e) { 766 Log.e(TAG, e.toString()); 767 } 768 } else { 769 Log.w(TAG, "Proxy not attached to service"); 770 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 771 } 772 return false; 773 } 774 775 /** 776 * Initiates a disconnection of headset audio. 777 * It tears down the SCO channel from remote headset device. 778 * 779 * @return true if successful 780 * false if there was some error such as 781 * there is no connected SCO channel 782 * @hide 783 */ 784 public boolean disconnectAudio() { 785 if (mService != null && isEnabled()) { 786 try { 787 return mService.disconnectAudio(); 788 } catch (RemoteException e) { 789 Log.e(TAG, e.toString()); 790 } 791 } else { 792 Log.w(TAG, "Proxy not attached to service"); 793 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 794 } 795 return false; 796 } 797 798 /** 799 * Initiates a SCO channel connection with the headset (if connected). 800 * Also initiates a virtual voice call for Handsfree devices as many devices 801 * do not accept SCO audio without a call. 802 * This API allows the handsfree device to be used for routing non-cellular 803 * call audio. 804 * 805 * @param device Remote Bluetooth Device 806 * @return true if successful, false if there was some error. 807 * @hide 808 */ 809 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 810 if (DBG) log("startScoUsingVirtualVoiceCall()"); 811 if (mService != null && isEnabled() && isValidDevice(device)) { 812 try { 813 return mService.startScoUsingVirtualVoiceCall(device); 814 } catch (RemoteException e) { 815 Log.e(TAG, e.toString()); 816 } 817 } else { 818 Log.w(TAG, "Proxy not attached to service"); 819 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 820 } 821 return false; 822 } 823 824 /** 825 * Terminates an ongoing SCO connection and the associated virtual 826 * call. 827 * 828 * @param device Remote Bluetooth Device 829 * @return true if successful, false if there was some error. 830 * @hide 831 */ 832 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 833 if (DBG) log("stopScoUsingVirtualVoiceCall()"); 834 if (mService != null && isEnabled() && isValidDevice(device)) { 835 try { 836 return mService.stopScoUsingVirtualVoiceCall(device); 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 return false; 845 } 846 847 /** 848 * Notify Headset of phone state change. 849 * This is a backdoor for phone app to call BluetoothHeadset since 850 * there is currently not a good way to get precise call state change outside 851 * of phone app. 852 * 853 * @hide 854 */ 855 public void phoneStateChanged(int numActive, int numHeld, int callState, String number, 856 int type) { 857 if (mService != null && isEnabled()) { 858 try { 859 mService.phoneStateChanged(numActive, numHeld, callState, number, type); 860 } catch (RemoteException e) { 861 Log.e(TAG, e.toString()); 862 } 863 } else { 864 Log.w(TAG, "Proxy not attached to service"); 865 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 866 } 867 } 868 869 /** 870 * Send Headset of CLCC response 871 * 872 * @hide 873 */ 874 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 875 String number, int type) { 876 if (mService != null && isEnabled()) { 877 try { 878 mService.clccResponse(index, direction, status, mode, mpty, number, type); 879 } catch (RemoteException e) { 880 Log.e(TAG, e.toString()); 881 } 882 } else { 883 Log.w(TAG, "Proxy not attached to service"); 884 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 885 } 886 } 887 888 /** 889 * Sends a vendor-specific unsolicited result code to the headset. 890 * 891 * <p>The actual string to be sent is <code>command + ": " + arg</code>. 892 * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} 893 * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent. 894 * 895 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. 896 * 897 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 898 * 899 * @param device Bluetooth headset. 900 * @param command A vendor-specific command. 901 * @param arg The argument that will be attached to the command. 902 * @return {@code false} if there is no headset connected, or if the command is not an allowed 903 * vendor-specific unsolicited result code, or on error. {@code true} otherwise. 904 * @throws IllegalArgumentException if {@code command} is {@code null}. 905 */ 906 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, 907 String arg) { 908 if (DBG) { 909 log("sendVendorSpecificResultCode()"); 910 } 911 if (command == null) { 912 throw new IllegalArgumentException("command is null"); 913 } 914 if (mService != null && isEnabled() && 915 isValidDevice(device)) { 916 try { 917 return mService.sendVendorSpecificResultCode(device, command, arg); 918 } catch (RemoteException e) { 919 Log.e(TAG, Log.getStackTraceString(new Throwable())); 920 } 921 } 922 if (mService == null) { 923 Log.w(TAG, "Proxy not attached to service"); 924 } 925 return false; 926 } 927 928 /** 929 * enable WBS codec setting. 930 * 931 * @return true if successful 932 * false if there was some error such as 933 * there is no connected headset 934 * @hide 935 */ 936 public boolean enableWBS() { 937 if (mService != null && isEnabled()) { 938 try { 939 return mService.enableWBS(); 940 } catch (RemoteException e) { 941 Log.e(TAG, e.toString()); 942 } 943 } else { 944 Log.w(TAG, "Proxy not attached to service"); 945 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 946 } 947 return false; 948 } 949 950 /** 951 * disable WBS codec settting. It set NBS codec. 952 * 953 * @return true if successful 954 * false if there was some error such as 955 * there is no connected headset 956 * @hide 957 */ 958 public boolean disableWBS() { 959 if (mService != null && isEnabled()) { 960 try { 961 return mService.disableWBS(); 962 } catch (RemoteException e) { 963 Log.e(TAG, e.toString()); 964 } 965 } else { 966 Log.w(TAG, "Proxy not attached to service"); 967 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 968 } 969 return false; 970 } 971 972 private final IBluetoothProfileServiceConnection mConnection 973 = new IBluetoothProfileServiceConnection.Stub() { 974 @Override 975 public void onServiceConnected(ComponentName className, IBinder service) { 976 if (DBG) Log.d(TAG, "Proxy object connected"); 977 mService = IBluetoothHeadset.Stub.asInterface(service); 978 mHandler.sendMessage(mHandler.obtainMessage( 979 MESSAGE_HEADSET_SERVICE_CONNECTED)); 980 } 981 @Override 982 public void onServiceDisconnected(ComponentName className) { 983 if (DBG) Log.d(TAG, "Proxy object disconnected"); 984 mService = null; 985 mHandler.sendMessage(mHandler.obtainMessage( 986 MESSAGE_HEADSET_SERVICE_DISCONNECTED)); 987 } 988 }; 989 990 private boolean isEnabled() { 991 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 992 return false; 993 } 994 995 private boolean isDisabled() { 996 if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true; 997 return false; 998 } 999 1000 private boolean isValidDevice(BluetoothDevice device) { 1001 if (device == null) return false; 1002 1003 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 1004 return false; 1005 } 1006 1007 private static void log(String msg) { 1008 Log.d(TAG, msg); 1009 } 1010 1011 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 1012 @Override 1013 public void handleMessage(Message msg) { 1014 switch (msg.what) { 1015 case MESSAGE_HEADSET_SERVICE_CONNECTED: { 1016 if (mServiceListener != null) { 1017 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, 1018 BluetoothHeadset.this); 1019 } 1020 break; 1021 } 1022 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: { 1023 if (mServiceListener != null) { 1024 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); 1025 } 1026 break; 1027 } 1028 } 1029 } 1030 }; 1031} 1032