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