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