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