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