BluetoothHeadset.java revision 0706fed52075f7f2b25101a40287519ac18d3184
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.content.Intent; 24import android.content.ServiceConnection; 25import android.os.IBinder; 26import android.os.RemoteException; 27import android.util.Log; 28 29import java.util.ArrayList; 30import 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 */ 46public final class BluetoothHeadset implements BluetoothProfile { 47 private static final String TAG = "BluetoothHeadset"; 48 private static final boolean DBG = false; 49 50 /** 51 * Intent used to broadcast the change in connection state of the Headset 52 * profile. 53 * 54 * <p>This intent will have 3 extras: 55 * <ul> 56 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 57 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 58 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 59 * </ul> 60 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 61 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 62 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 63 * 64 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 65 * receive. 66 */ 67 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 68 public static final String ACTION_CONNECTION_STATE_CHANGED = 69 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"; 70 71 /** 72 * Intent used to broadcast the change in the Audio Connection state of the 73 * A2DP profile. 74 * 75 * <p>This intent will have 3 extras: 76 * <ul> 77 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 78 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 79 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 80 * </ul> 81 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 82 * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED}, 83 * 84 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission 85 * to receive. 86 */ 87 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 88 public static final String ACTION_AUDIO_STATE_CHANGED = 89 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; 90 91 92 /** 93 * Intent used to broadcast that the headset has posted a 94 * vendor-specific event. 95 * 96 * <p>This intent will have 4 extras and 1 category. 97 * <ul> 98 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device 99 * </li> 100 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor 101 * specific command </li> 102 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT 103 * command type which can be one of {@link #AT_CMD_TYPE_READ}, 104 * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET}, 105 * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li> 106 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command 107 * arguments. </li> 108 * </ul> 109 * 110 *<p> The category is the Company ID of the vendor defining the 111 * vendor-specific command. {@link BluetoothAssignedNumbers} 112 * 113 * For example, for Plantronics specific events 114 * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55 115 * 116 * <p> For example, an AT+XEVENT=foo,3 will get translated into 117 * <ul> 118 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li> 119 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li> 120 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li> 121 * </ul> 122 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission 123 * to receive. 124 */ 125 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 126 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = 127 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT"; 128 129 /** 130 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 131 * intents that contains the name of the vendor-specific command. 132 */ 133 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = 134 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD"; 135 136 /** 137 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 138 * intents that contains the AT command type of the vendor-specific command. 139 */ 140 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = 141 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE"; 142 143 /** 144 * AT command type READ used with 145 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 146 * For example, AT+VGM?. There are no arguments for this command type. 147 */ 148 public static final int AT_CMD_TYPE_READ = 0; 149 150 /** 151 * AT command type TEST used with 152 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 153 * For example, AT+VGM=?. There are no arguments for this command type. 154 */ 155 public static final int AT_CMD_TYPE_TEST = 1; 156 157 /** 158 * AT command type SET used with 159 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 160 * For example, AT+VGM=<args>. 161 */ 162 public static final int AT_CMD_TYPE_SET = 2; 163 164 /** 165 * AT command type BASIC used with 166 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 167 * For example, ATD. Single character commands and everything following the 168 * character are arguments. 169 */ 170 public static final int AT_CMD_TYPE_BASIC = 3; 171 172 /** 173 * AT command type ACTION used with 174 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 175 * For example, AT+CHUP. There are no arguments for action commands. 176 */ 177 public static final int AT_CMD_TYPE_ACTION = 4; 178 179 /** 180 * A Parcelable String array extra field in 181 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains 182 * the arguments to the vendor-specific command. 183 */ 184 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = 185 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; 186 187 /** 188 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 189 * for the companyId 190 */ 191 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = 192 "android.bluetooth.headset.intent.category.companyid"; 193 194 /** 195 * Headset state when SCO audio is not connected. 196 * This state can be one of 197 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 198 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 199 */ 200 public static final int STATE_AUDIO_DISCONNECTED = 10; 201 202 /** 203 * Headset state when SCO audio is connecting. 204 * This state can be one of 205 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 206 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 207 */ 208 public static final int STATE_AUDIO_CONNECTING = 11; 209 210 /** 211 * Headset state when SCO audio is connected. 212 * This state can be one of 213 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 214 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 215 */ 216 public static final int STATE_AUDIO_CONNECTED = 12; 217 218 219 private Context mContext; 220 private ServiceListener mServiceListener; 221 private IBluetoothHeadset mService; 222 BluetoothAdapter mAdapter; 223 224 /** 225 * Create a BluetoothHeadset proxy object. 226 */ 227 /*package*/ BluetoothHeadset(Context context, ServiceListener l) { 228 mContext = context; 229 mServiceListener = l; 230 mAdapter = BluetoothAdapter.getDefaultAdapter(); 231 if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) { 232 Log.e(TAG, "Could not bind to Bluetooth Headset Service"); 233 } 234 } 235 236 /** 237 * Close the connection to the backing service. 238 * Other public functions of BluetoothHeadset will return default error 239 * results once close() has been called. Multiple invocations of close() 240 * are ok. 241 */ 242 /*package*/ synchronized void close() { 243 if (DBG) log("close()"); 244 if (mConnection != null) { 245 mContext.unbindService(mConnection); 246 mConnection = null; 247 } 248 } 249 250 /** 251 * {@inheritDoc} 252 * @hide 253 */ 254 public boolean connect(BluetoothDevice device) { 255 if (DBG) log("connect(" + device + ")"); 256 if (mService != null && isEnabled() && 257 isValidDevice(device)) { 258 try { 259 return mService.connect(device); 260 } catch (RemoteException e) { 261 Log.e(TAG, Log.getStackTraceString(new Throwable())); 262 return false; 263 } 264 } 265 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 266 return false; 267 } 268 269 /** 270 * {@inheritDoc} 271 * @hide 272 */ 273 public boolean disconnect(BluetoothDevice device) { 274 if (DBG) log("disconnect(" + device + ")"); 275 if (mService != null && isEnabled() && 276 isValidDevice(device)) { 277 try { 278 return mService.disconnect(device); 279 } catch (RemoteException e) { 280 Log.e(TAG, Log.getStackTraceString(new Throwable())); 281 return false; 282 } 283 } 284 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 285 return false; 286 } 287 288 /** 289 * {@inheritDoc} 290 */ 291 public List<BluetoothDevice> getConnectedDevices() { 292 if (DBG) log("getConnectedDevices()"); 293 if (mService != null && isEnabled()) { 294 try { 295 return mService.getConnectedDevices(); 296 } catch (RemoteException e) { 297 Log.e(TAG, Log.getStackTraceString(new Throwable())); 298 return new ArrayList<BluetoothDevice>(); 299 } 300 } 301 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 302 return new ArrayList<BluetoothDevice>(); 303 } 304 305 /** 306 * {@inheritDoc} 307 */ 308 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 309 if (DBG) log("getDevicesMatchingStates()"); 310 if (mService != null && isEnabled()) { 311 try { 312 return mService.getDevicesMatchingConnectionStates(states); 313 } catch (RemoteException e) { 314 Log.e(TAG, Log.getStackTraceString(new Throwable())); 315 return new ArrayList<BluetoothDevice>(); 316 } 317 } 318 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 319 return new ArrayList<BluetoothDevice>(); 320 } 321 322 /** 323 * {@inheritDoc} 324 */ 325 public int getConnectionState(BluetoothDevice device) { 326 if (DBG) log("getConnectionState(" + device + ")"); 327 if (mService != null && isEnabled() && 328 isValidDevice(device)) { 329 try { 330 return mService.getConnectionState(device); 331 } catch (RemoteException e) { 332 Log.e(TAG, Log.getStackTraceString(new Throwable())); 333 return BluetoothProfile.STATE_DISCONNECTED; 334 } 335 } 336 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 337 return BluetoothProfile.STATE_DISCONNECTED; 338 } 339 340 /** 341 * {@inheritDoc} 342 * @hide 343 */ 344 public boolean setPriority(BluetoothDevice device, int priority) { 345 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 346 if (mService != null && isEnabled() && 347 isValidDevice(device)) { 348 if (priority != BluetoothProfile.PRIORITY_OFF && 349 priority != BluetoothProfile.PRIORITY_ON) { 350 return false; 351 } 352 try { 353 return mService.setPriority(device, priority); 354 } catch (RemoteException e) { 355 Log.e(TAG, Log.getStackTraceString(new Throwable())); 356 return false; 357 } 358 } 359 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 360 return false; 361 } 362 363 /** 364 * {@inheritDoc} 365 * @hide 366 */ 367 public int getPriority(BluetoothDevice device) { 368 if (DBG) log("getPriority(" + device + ")"); 369 if (mService != null && isEnabled() && 370 isValidDevice(device)) { 371 try { 372 return mService.getPriority(device); 373 } catch (RemoteException e) { 374 Log.e(TAG, Log.getStackTraceString(new Throwable())); 375 return PRIORITY_OFF; 376 } 377 } 378 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 379 return PRIORITY_OFF; 380 } 381 382 /** 383 * Start Bluetooth voice recognition. This methods sends the voice 384 * recognition AT command to the headset and establishes the 385 * audio connection. 386 * 387 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 388 * If this function returns true, this intent will be broadcasted with 389 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. 390 * 391 * <p> {@link #EXTRA_STATE} will transition from 392 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when 393 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} 394 * in case of failure to establish the audio connection. 395 * 396 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 397 * 398 * @param device Bluetooth headset 399 * @return false if there is no headset connected of if the 400 * connected headset doesn't support voice recognition 401 * or on error, true otherwise 402 */ 403 public boolean startVoiceRecognition(BluetoothDevice device) { 404 if (DBG) log("startVoiceRecognition()"); 405 if (mService != null && isEnabled() && 406 isValidDevice(device)) { 407 try { 408 return mService.startVoiceRecognition(device); 409 } catch (RemoteException e) { 410 Log.e(TAG, Log.getStackTraceString(new Throwable())); 411 } 412 } 413 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 414 return false; 415 } 416 417 /** 418 * Stop Bluetooth Voice Recognition mode, and shut down the 419 * Bluetooth audio path. 420 * 421 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 422 * 423 * @param device Bluetooth headset 424 * @return false if there is no headset connected 425 * or on error, true otherwise 426 */ 427 public boolean stopVoiceRecognition(BluetoothDevice device) { 428 if (DBG) log("stopVoiceRecognition()"); 429 if (mService != null && isEnabled() && 430 isValidDevice(device)) { 431 try { 432 return mService.stopVoiceRecognition(device); 433 } catch (RemoteException e) { 434 Log.e(TAG, Log.getStackTraceString(new Throwable())); 435 } 436 } 437 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 438 return false; 439 } 440 441 /** 442 * Check if Bluetooth SCO audio is connected. 443 * 444 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 445 * 446 * @param device Bluetooth headset 447 * @return true if SCO is connected, 448 * false otherwise or on error 449 */ 450 public boolean isAudioConnected(BluetoothDevice device) { 451 if (DBG) log("isAudioConnected()"); 452 if (mService != null && isEnabled() && 453 isValidDevice(device)) { 454 try { 455 return mService.isAudioConnected(device); 456 } catch (RemoteException e) { 457 Log.e(TAG, Log.getStackTraceString(new Throwable())); 458 } 459 } 460 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 461 return false; 462 } 463 464 /** 465 * Get battery usage hint for Bluetooth Headset service. 466 * This is a monotonically increasing integer. Wraps to 0 at 467 * Integer.MAX_INT, and at boot. 468 * Current implementation returns the number of AT commands handled since 469 * boot. This is a good indicator for spammy headset/handsfree units that 470 * can keep the device awake by polling for cellular status updates. As a 471 * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms 472 * 473 * @param device the bluetooth headset. 474 * @return monotonically increasing battery usage hint, or a negative error 475 * code on error 476 * @hide 477 */ 478 public int getBatteryUsageHint(BluetoothDevice device) { 479 if (DBG) log("getBatteryUsageHint()"); 480 if (mService != null && isEnabled() && 481 isValidDevice(device)) { 482 try { 483 return mService.getBatteryUsageHint(device); 484 } catch (RemoteException e) { 485 Log.e(TAG, Log.getStackTraceString(new Throwable())); 486 } 487 } 488 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 489 return -1; 490 } 491 492 /** 493 * Indicates if current platform supports voice dialing over bluetooth SCO. 494 * 495 * @return true if voice dialing over bluetooth is supported, false otherwise. 496 * @hide 497 */ 498 public static boolean isBluetoothVoiceDialingEnabled(Context context) { 499 return context.getResources().getBoolean( 500 com.android.internal.R.bool.config_bluetooth_sco_off_call); 501 } 502 503 /** 504 * Cancel the outgoing connection. 505 * Note: This is an internal function and shouldn't be exposed 506 * 507 * @hide 508 */ 509 public boolean cancelConnectThread() { 510 if (DBG) log("cancelConnectThread"); 511 if (mService != null && isEnabled()) { 512 try { 513 return mService.cancelConnectThread(); 514 } catch (RemoteException e) {Log.e(TAG, e.toString());} 515 } else { 516 Log.w(TAG, "Proxy not attached to service"); 517 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 518 } 519 return false; 520 } 521 522 /** 523 * Accept the incoming connection. 524 * Note: This is an internal function and shouldn't be exposed 525 * 526 * @hide 527 */ 528 public boolean acceptIncomingConnect(BluetoothDevice device) { 529 if (DBG) log("acceptIncomingConnect"); 530 if (mService != null && isEnabled()) { 531 try { 532 return mService.acceptIncomingConnect(device); 533 } catch (RemoteException e) {Log.e(TAG, e.toString());} 534 } else { 535 Log.w(TAG, "Proxy not attached to service"); 536 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 537 } 538 return false; 539 } 540 541 /** 542 * Create the connect thread for the incoming connection. 543 * Note: This is an internal function and shouldn't be exposed 544 * 545 * @hide 546 */ 547 public boolean createIncomingConnect(BluetoothDevice device) { 548 if (DBG) log("createIncomingConnect"); 549 if (mService != null && isEnabled()) { 550 try { 551 return mService.createIncomingConnect(device); 552 } catch (RemoteException e) {Log.e(TAG, e.toString());} 553 } else { 554 Log.w(TAG, "Proxy not attached to service"); 555 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 556 } 557 return false; 558 } 559 560 /** 561 * Connect to a Bluetooth Headset. 562 * Note: This is an internal function and shouldn't be exposed 563 * 564 * @hide 565 */ 566 public boolean connectHeadsetInternal(BluetoothDevice device) { 567 if (DBG) log("connectHeadsetInternal"); 568 if (mService != null && isEnabled()) { 569 try { 570 return mService.connectHeadsetInternal(device); 571 } catch (RemoteException e) {Log.e(TAG, e.toString());} 572 } else { 573 Log.w(TAG, "Proxy not attached to service"); 574 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 575 } 576 return false; 577 } 578 579 /** 580 * Disconnect a Bluetooth Headset. 581 * Note: This is an internal function and shouldn't be exposed 582 * 583 * @hide 584 */ 585 public boolean disconnectHeadsetInternal(BluetoothDevice device) { 586 if (DBG) log("disconnectHeadsetInternal"); 587 if (mService != null && isEnabled()) { 588 try { 589 return mService.disconnectHeadsetInternal(device); 590 } catch (RemoteException e) {Log.e(TAG, e.toString());} 591 } else { 592 Log.w(TAG, "Proxy not attached to service"); 593 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 594 } 595 return false; 596 } 597 598 /** 599 * Set the audio state of the Headset. 600 * Note: This is an internal function and shouldn't be exposed 601 * 602 * @hide 603 */ 604 public boolean setAudioState(BluetoothDevice device, int state) { 605 if (DBG) log("setAudioState"); 606 if (mService != null && isEnabled()) { 607 try { 608 return mService.setAudioState(device, state); 609 } catch (RemoteException e) {Log.e(TAG, e.toString());} 610 } else { 611 Log.w(TAG, "Proxy not attached to service"); 612 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 613 } 614 return false; 615 } 616 617 /** 618 * Get the current audio state of the Headset. 619 * Note: This is an internal function and shouldn't be exposed 620 * 621 * @hide 622 */ 623 public int getAudioState(BluetoothDevice device) { 624 if (DBG) log("getAudioState"); 625 if (mService != null && isEnabled()) { 626 try { 627 return mService.getAudioState(device); 628 } catch (RemoteException e) {Log.e(TAG, e.toString());} 629 } else { 630 Log.w(TAG, "Proxy not attached to service"); 631 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 632 } 633 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 634 } 635 636 /** 637 * Initiates a SCO channel connection with the headset (if connected). 638 * Also initiates a virtual voice call for Handsfree devices as many devices 639 * do not accept SCO audio without a call. 640 * This API allows the handsfree device to be used for routing non-cellular 641 * call audio. 642 * 643 * @param device Remote Bluetooth Device 644 * @return true if successful, false if there was some error. 645 * @hide 646 */ 647 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 648 if (DBG) log("startScoUsingVirtualVoiceCall()"); 649 if (mService != null && isEnabled() && isValidDevice(device)) { 650 try { 651 return mService.startScoUsingVirtualVoiceCall(device); 652 } catch (RemoteException e) { 653 Log.e(TAG, e.toString()); 654 } 655 } else { 656 Log.w(TAG, "Proxy not attached to service"); 657 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 658 } 659 return false; 660 } 661 662 /** 663 * Terminates an ongoing SCO connection and the associated virtual 664 * call. 665 * 666 * @param device Remote Bluetooth Device 667 * @return true if successful, false if there was some error. 668 * @hide 669 */ 670 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 671 if (DBG) log("stopScoUsingVirtualVoiceCall()"); 672 if (mService != null && isEnabled() && isValidDevice(device)) { 673 try { 674 return mService.stopScoUsingVirtualVoiceCall(device); 675 } catch (RemoteException e) { 676 Log.e(TAG, e.toString()); 677 } 678 } else { 679 Log.w(TAG, "Proxy not attached to service"); 680 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 681 } 682 return false; 683 } 684 685 private ServiceConnection mConnection = new ServiceConnection() { 686 public void onServiceConnected(ComponentName className, IBinder service) { 687 if (DBG) Log.d(TAG, "Proxy object connected"); 688 mService = IBluetoothHeadset.Stub.asInterface(service); 689 690 if (mServiceListener != null) { 691 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this); 692 } 693 } 694 public void onServiceDisconnected(ComponentName className) { 695 if (DBG) Log.d(TAG, "Proxy object disconnected"); 696 mService = null; 697 if (mServiceListener != null) { 698 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); 699 } 700 } 701 }; 702 703 private boolean isEnabled() { 704 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 705 return false; 706 } 707 708 private boolean isValidDevice(BluetoothDevice device) { 709 if (device == null) return false; 710 711 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 712 return false; 713 } 714 715 private static void log(String msg) { 716 Log.d(TAG, msg); 717 } 718} 719