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