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.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SdkConstant; 24 import android.annotation.SdkConstant.SdkConstantType; 25 import android.annotation.SystemApi; 26 import android.compat.annotation.UnsupportedAppUsage; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.os.Binder; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.Looper; 33 import android.os.Message; 34 import android.os.RemoteException; 35 import android.util.Log; 36 37 import java.util.ArrayList; 38 import java.util.List; 39 40 /** 41 * Public API for controlling the Bluetooth Headset Service. This includes both 42 * Bluetooth Headset and Handsfree (v1.5) profiles. 43 * 44 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset 45 * Service via IPC. 46 * 47 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get 48 * the BluetoothHeadset proxy object. Use 49 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. 50 * 51 * <p> Android only supports one connected Bluetooth Headset at a time. 52 * Each method is protected with its appropriate permission. 53 */ 54 public final class BluetoothHeadset implements BluetoothProfile { 55 private static final String TAG = "BluetoothHeadset"; 56 private static final boolean DBG = true; 57 private static final boolean VDBG = false; 58 59 /** 60 * Intent used to broadcast the change in connection state of the Headset 61 * profile. 62 * 63 * <p>This intent will have 3 extras: 64 * <ul> 65 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 66 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 67 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 68 * </ul> 69 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 70 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 71 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 72 * 73 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 74 * receive. 75 */ 76 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 77 public static final String ACTION_CONNECTION_STATE_CHANGED = 78 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"; 79 80 /** 81 * Intent used to broadcast the change in the Audio Connection state of the 82 * A2DP profile. 83 * 84 * <p>This intent will have 3 extras: 85 * <ul> 86 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 87 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 88 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 89 * </ul> 90 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 91 * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED}, 92 * 93 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission 94 * to receive. 95 */ 96 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 97 public static final String ACTION_AUDIO_STATE_CHANGED = 98 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; 99 100 /** 101 * Intent used to broadcast the selection of a connected device as active. 102 * 103 * <p>This intent will have one extra: 104 * <ul> 105 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can 106 * be null if no device is active. </li> 107 * </ul> 108 * 109 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 110 * receive. 111 * 112 * @hide 113 */ 114 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 115 @UnsupportedAppUsage 116 public static final String ACTION_ACTIVE_DEVICE_CHANGED = 117 "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED"; 118 119 /** 120 * Intent used to broadcast that the headset has posted a 121 * vendor-specific event. 122 * 123 * <p>This intent will have 4 extras and 1 category. 124 * <ul> 125 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device 126 * </li> 127 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor 128 * specific command </li> 129 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT 130 * command type which can be one of {@link #AT_CMD_TYPE_READ}, 131 * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET}, 132 * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li> 133 * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command 134 * arguments. </li> 135 * </ul> 136 * 137 * <p> The category is the Company ID of the vendor defining the 138 * vendor-specific command. {@link BluetoothAssignedNumbers} 139 * 140 * For example, for Plantronics specific events 141 * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55 142 * 143 * <p> For example, an AT+XEVENT=foo,3 will get translated into 144 * <ul> 145 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li> 146 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li> 147 * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li> 148 * </ul> 149 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission 150 * to receive. 151 */ 152 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 153 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = 154 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT"; 155 156 /** 157 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 158 * intents that contains the name of the vendor-specific command. 159 */ 160 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = 161 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD"; 162 163 /** 164 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 165 * intents that contains the AT command type of the vendor-specific command. 166 */ 167 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = 168 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE"; 169 170 /** 171 * AT command type READ used with 172 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 173 * For example, AT+VGM?. There are no arguments for this command type. 174 */ 175 public static final int AT_CMD_TYPE_READ = 0; 176 177 /** 178 * AT command type TEST used with 179 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 180 * For example, AT+VGM=?. There are no arguments for this command type. 181 */ 182 public static final int AT_CMD_TYPE_TEST = 1; 183 184 /** 185 * AT command type SET used with 186 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 187 * For example, AT+VGM=<args>. 188 */ 189 public static final int AT_CMD_TYPE_SET = 2; 190 191 /** 192 * AT command type BASIC used with 193 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 194 * For example, ATD. Single character commands and everything following the 195 * character are arguments. 196 */ 197 public static final int AT_CMD_TYPE_BASIC = 3; 198 199 /** 200 * AT command type ACTION used with 201 * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} 202 * For example, AT+CHUP. There are no arguments for action commands. 203 */ 204 public static final int AT_CMD_TYPE_ACTION = 4; 205 206 /** 207 * A Parcelable String array extra field in 208 * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains 209 * the arguments to the vendor-specific command. 210 */ 211 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = 212 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; 213 214 /** 215 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 216 * for the companyId 217 */ 218 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = 219 "android.bluetooth.headset.intent.category.companyid"; 220 221 /** 222 * A vendor-specific command for unsolicited result code. 223 */ 224 public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID"; 225 226 /** 227 * A vendor-specific AT command 228 * 229 * @hide 230 */ 231 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL"; 232 233 /** 234 * A vendor-specific AT command 235 * 236 * @hide 237 */ 238 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV"; 239 240 /** 241 * Battery level indicator associated with 242 * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV} 243 * 244 * @hide 245 */ 246 public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1; 247 248 /** 249 * A vendor-specific AT command 250 * 251 * @hide 252 */ 253 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT"; 254 255 /** 256 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT} 257 * 258 * @hide 259 */ 260 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY"; 261 262 /** 263 * Headset state when SCO audio is not connected. 264 * This state can be one of 265 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 266 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 267 */ 268 public static final int STATE_AUDIO_DISCONNECTED = 10; 269 270 /** 271 * Headset state when SCO audio is connecting. 272 * This state can be one of 273 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 274 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 275 */ 276 public static final int STATE_AUDIO_CONNECTING = 11; 277 278 /** 279 * Headset state when SCO audio is connected. 280 * This state can be one of 281 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of 282 * {@link #ACTION_AUDIO_STATE_CHANGED} intent. 283 */ 284 public static final int STATE_AUDIO_CONNECTED = 12; 285 286 /** 287 * Intent used to broadcast the headset's indicator status 288 * 289 * <p>This intent will have 3 extras: 290 * <ul> 291 * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which 292 * is supported by the headset ( as indicated by AT+BIND command in the SLC 293 * sequence) or whose value is changed (indicated by AT+BIEV command) </li> 294 * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li> 295 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li> 296 * </ul> 297 * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators 298 * are given an assigned number. Below shows the assigned number of Indicator added so far 299 * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled 300 * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery 301 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive. 302 * 303 * @hide 304 */ 305 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED = 306 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED"; 307 308 /** 309 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} 310 * intents that contains the assigned number of the headset indicator as defined by 311 * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7 312 * 313 * @hide 314 */ 315 public static final String EXTRA_HF_INDICATORS_IND_ID = 316 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID"; 317 318 /** 319 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} 320 * intents that contains the value of the Headset indicator that is being sent. 321 * 322 * @hide 323 */ 324 public static final String EXTRA_HF_INDICATORS_IND_VALUE = 325 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE"; 326 327 private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100; 328 private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101; 329 330 private Context mContext; 331 private ServiceListener mServiceListener; 332 private volatile IBluetoothHeadset mService; 333 private BluetoothAdapter mAdapter; 334 335 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = 336 new IBluetoothStateChangeCallback.Stub() { 337 public void onBluetoothStateChange(boolean up) { 338 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); 339 if (!up) { 340 doUnbind(); 341 } else { 342 doBind(); 343 } 344 } 345 }; 346 347 /** 348 * Create a BluetoothHeadset proxy object. 349 */ BluetoothHeadset(Context context, ServiceListener l)350 /*package*/ BluetoothHeadset(Context context, ServiceListener l) { 351 mContext = context; 352 mServiceListener = l; 353 mAdapter = BluetoothAdapter.getDefaultAdapter(); 354 355 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 356 if (mgr != null) { 357 try { 358 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 359 } catch (RemoteException e) { 360 Log.e(TAG, "", e); 361 } 362 } 363 364 doBind(); 365 } 366 doBind()367 private boolean doBind() { 368 synchronized (mConnection) { 369 if (mService == null) { 370 if (VDBG) Log.d(TAG, "Binding service..."); 371 try { 372 return mAdapter.getBluetoothManager().bindBluetoothProfileService( 373 BluetoothProfile.HEADSET, mConnection); 374 } catch (RemoteException e) { 375 Log.e(TAG, "Unable to bind HeadsetService", e); 376 } 377 } 378 } 379 return false; 380 } 381 doUnbind()382 private void doUnbind() { 383 synchronized (mConnection) { 384 if (mService != null) { 385 if (VDBG) Log.d(TAG, "Unbinding service..."); 386 try { 387 mAdapter.getBluetoothManager().unbindBluetoothProfileService( 388 BluetoothProfile.HEADSET, mConnection); 389 } catch (RemoteException e) { 390 Log.e(TAG, "Unable to unbind HeadsetService", e); 391 } finally { 392 mService = null; 393 } 394 } 395 } 396 } 397 398 /** 399 * Close the connection to the backing service. 400 * Other public functions of BluetoothHeadset will return default error 401 * results once close() has been called. Multiple invocations of close() 402 * are ok. 403 */ 404 @UnsupportedAppUsage close()405 /*package*/ void close() { 406 if (VDBG) log("close()"); 407 408 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 409 if (mgr != null) { 410 try { 411 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 412 } catch (RemoteException re) { 413 Log.e(TAG, "", re); 414 } 415 } 416 mServiceListener = null; 417 doUnbind(); 418 } 419 420 /** 421 * Initiate connection to a profile of the remote bluetooth device. 422 * 423 * <p> Currently, the system supports only 1 connection to the 424 * headset/handsfree profile. The API will automatically disconnect connected 425 * devices before connecting. 426 * 427 * <p> This API returns false in scenarios like the profile on the 428 * device is already connected or Bluetooth is not turned on. 429 * When this API returns true, it is guaranteed that 430 * connection state intent for the profile will be broadcasted with 431 * the state. Users can get the connection state of the profile 432 * from this intent. 433 * 434 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 435 * permission. 436 * 437 * @param device Remote Bluetooth Device 438 * @return false on immediate error, true otherwise 439 * @hide 440 */ 441 @SystemApi 442 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) connect(BluetoothDevice device)443 public boolean connect(BluetoothDevice device) { 444 if (DBG) log("connect(" + device + ")"); 445 final IBluetoothHeadset service = mService; 446 if (service != null && isEnabled() && isValidDevice(device)) { 447 try { 448 return service.connect(device); 449 } catch (RemoteException e) { 450 Log.e(TAG, Log.getStackTraceString(new Throwable())); 451 return false; 452 } 453 } 454 if (service == null) Log.w(TAG, "Proxy not attached to service"); 455 return false; 456 } 457 458 /** 459 * Initiate disconnection from a profile 460 * 461 * <p> This API will return false in scenarios like the profile on the 462 * Bluetooth device is not in connected state etc. When this API returns, 463 * true, it is guaranteed that the connection state change 464 * intent will be broadcasted with the state. Users can get the 465 * disconnection state of the profile from this intent. 466 * 467 * <p> If the disconnection is initiated by a remote device, the state 468 * will transition from {@link #STATE_CONNECTED} to 469 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 470 * host (local) device the state will transition from 471 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 472 * state {@link #STATE_DISCONNECTED}. The transition to 473 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 474 * two scenarios. 475 * 476 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 477 * permission. 478 * 479 * @param device Remote Bluetooth Device 480 * @return false on immediate error, true otherwise 481 * @hide 482 */ 483 @SystemApi 484 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) disconnect(BluetoothDevice device)485 public boolean disconnect(BluetoothDevice device) { 486 if (DBG) log("disconnect(" + device + ")"); 487 final IBluetoothHeadset service = mService; 488 if (service != null && isEnabled() && isValidDevice(device)) { 489 try { 490 return service.disconnect(device); 491 } catch (RemoteException e) { 492 Log.e(TAG, Log.getStackTraceString(new Throwable())); 493 return false; 494 } 495 } 496 if (service == null) Log.w(TAG, "Proxy not attached to service"); 497 return false; 498 } 499 500 /** 501 * {@inheritDoc} 502 */ 503 @Override getConnectedDevices()504 public List<BluetoothDevice> getConnectedDevices() { 505 if (VDBG) log("getConnectedDevices()"); 506 final IBluetoothHeadset service = mService; 507 if (service != null && isEnabled()) { 508 try { 509 return service.getConnectedDevices(); 510 } catch (RemoteException e) { 511 Log.e(TAG, Log.getStackTraceString(new Throwable())); 512 return new ArrayList<BluetoothDevice>(); 513 } 514 } 515 if (service == null) Log.w(TAG, "Proxy not attached to service"); 516 return new ArrayList<BluetoothDevice>(); 517 } 518 519 /** 520 * {@inheritDoc} 521 */ 522 @Override getDevicesMatchingConnectionStates(int[] states)523 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 524 if (VDBG) log("getDevicesMatchingStates()"); 525 final IBluetoothHeadset service = mService; 526 if (service != null && isEnabled()) { 527 try { 528 return service.getDevicesMatchingConnectionStates(states); 529 } catch (RemoteException e) { 530 Log.e(TAG, Log.getStackTraceString(new Throwable())); 531 return new ArrayList<BluetoothDevice>(); 532 } 533 } 534 if (service == null) Log.w(TAG, "Proxy not attached to service"); 535 return new ArrayList<BluetoothDevice>(); 536 } 537 538 /** 539 * {@inheritDoc} 540 */ 541 @Override getConnectionState(BluetoothDevice device)542 public int getConnectionState(BluetoothDevice device) { 543 if (VDBG) log("getConnectionState(" + device + ")"); 544 final IBluetoothHeadset service = mService; 545 if (service != null && isEnabled() && isValidDevice(device)) { 546 try { 547 return service.getConnectionState(device); 548 } catch (RemoteException e) { 549 Log.e(TAG, Log.getStackTraceString(new Throwable())); 550 return BluetoothProfile.STATE_DISCONNECTED; 551 } 552 } 553 if (service == null) Log.w(TAG, "Proxy not attached to service"); 554 return BluetoothProfile.STATE_DISCONNECTED; 555 } 556 557 /** 558 * Set priority of the profile 559 * 560 * <p> The device should already be paired. 561 * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or 562 * {@link BluetoothProfile#PRIORITY_OFF} 563 * 564 * @param device Paired bluetooth device 565 * @param priority 566 * @return true if priority is set, false on error 567 * @hide 568 * @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)} 569 */ 570 @Deprecated 571 @SystemApi 572 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) setPriority(BluetoothDevice device, int priority)573 public boolean setPriority(BluetoothDevice device, int priority) { 574 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 575 final IBluetoothHeadset service = mService; 576 if (service != null && isEnabled() && isValidDevice(device)) { 577 if (priority != BluetoothProfile.PRIORITY_OFF 578 && priority != BluetoothProfile.PRIORITY_ON) { 579 return false; 580 } 581 try { 582 return service.setPriority( 583 device, BluetoothAdapter.priorityToConnectionPolicy(priority)); 584 } catch (RemoteException e) { 585 Log.e(TAG, Log.getStackTraceString(new Throwable())); 586 return false; 587 } 588 } 589 if (service == null) Log.w(TAG, "Proxy not attached to service"); 590 return false; 591 } 592 593 /** 594 * Set connection policy of the profile 595 * 596 * <p> The device should already be paired. 597 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 598 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 599 * 600 * @param device Paired bluetooth device 601 * @param connectionPolicy is the connection policy to set to for this profile 602 * @return true if connectionPolicy is set, false on error 603 * @hide 604 */ 605 @SystemApi 606 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)607 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 608 @ConnectionPolicy int connectionPolicy) { 609 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 610 final IBluetoothHeadset service = mService; 611 if (service != null && isEnabled() && isValidDevice(device)) { 612 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 613 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 614 return false; 615 } 616 try { 617 return service.setConnectionPolicy(device, connectionPolicy); 618 } catch (RemoteException e) { 619 Log.e(TAG, Log.getStackTraceString(new Throwable())); 620 return false; 621 } 622 } 623 if (service == null) Log.w(TAG, "Proxy not attached to service"); 624 return false; 625 } 626 627 /** 628 * Get the priority of the profile. 629 * 630 * <p> The priority can be any of: 631 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, 632 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 633 * 634 * @param device Bluetooth device 635 * @return priority of the device 636 * @hide 637 */ 638 @UnsupportedAppUsage 639 @RequiresPermission(Manifest.permission.BLUETOOTH) getPriority(BluetoothDevice device)640 public int getPriority(BluetoothDevice device) { 641 if (VDBG) log("getPriority(" + device + ")"); 642 final IBluetoothHeadset service = mService; 643 if (service != null && isEnabled() && isValidDevice(device)) { 644 try { 645 return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device)); 646 } catch (RemoteException e) { 647 Log.e(TAG, Log.getStackTraceString(new Throwable())); 648 return BluetoothProfile.PRIORITY_OFF; 649 } 650 } 651 if (service == null) Log.w(TAG, "Proxy not attached to service"); 652 return BluetoothProfile.PRIORITY_OFF; 653 } 654 655 /** 656 * Get the connection policy of the profile. 657 * 658 * <p> The connection policy can be any of: 659 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, 660 * {@link #CONNECTION_POLICY_UNKNOWN} 661 * 662 * @param device Bluetooth device 663 * @return connection policy of the device 664 * @hide 665 */ 666 @SystemApi 667 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectionPolicy(@onNull BluetoothDevice device)668 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 669 if (VDBG) log("getConnectionPolicy(" + device + ")"); 670 final IBluetoothHeadset service = mService; 671 if (service != null && isEnabled() && isValidDevice(device)) { 672 try { 673 return service.getConnectionPolicy(device); 674 } catch (RemoteException e) { 675 Log.e(TAG, Log.getStackTraceString(new Throwable())); 676 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 677 } 678 } 679 if (service == null) Log.w(TAG, "Proxy not attached to service"); 680 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 681 } 682 683 /** 684 * Start Bluetooth voice recognition. This methods sends the voice 685 * recognition AT command to the headset and establishes the 686 * audio connection. 687 * 688 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 689 * If this function returns true, this intent will be broadcasted with 690 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. 691 * 692 * <p> {@link #EXTRA_STATE} will transition from 693 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when 694 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} 695 * in case of failure to establish the audio connection. 696 * 697 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 698 * 699 * @param device Bluetooth headset 700 * @return false if there is no headset connected, or the connected headset doesn't support 701 * voice recognition, or voice recognition is already started, or audio channel is occupied, 702 * or on error, true otherwise 703 */ startVoiceRecognition(BluetoothDevice device)704 public boolean startVoiceRecognition(BluetoothDevice device) { 705 if (DBG) log("startVoiceRecognition()"); 706 final IBluetoothHeadset service = mService; 707 if (service != null && isEnabled() && isValidDevice(device)) { 708 try { 709 return service.startVoiceRecognition(device); 710 } catch (RemoteException e) { 711 Log.e(TAG, Log.getStackTraceString(new Throwable())); 712 } 713 } 714 if (service == null) Log.w(TAG, "Proxy not attached to service"); 715 return false; 716 } 717 718 /** 719 * Stop Bluetooth Voice Recognition mode, and shut down the 720 * Bluetooth audio path. 721 * 722 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 723 * If this function returns true, this intent will be broadcasted with 724 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. 725 * 726 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 727 * 728 * @param device Bluetooth headset 729 * @return false if there is no headset connected, or voice recognition has not started, 730 * or voice recognition has ended on this headset, or on error, true otherwise 731 */ stopVoiceRecognition(BluetoothDevice device)732 public boolean stopVoiceRecognition(BluetoothDevice device) { 733 if (DBG) log("stopVoiceRecognition()"); 734 final IBluetoothHeadset service = mService; 735 if (service != null && isEnabled() && isValidDevice(device)) { 736 try { 737 return service.stopVoiceRecognition(device); 738 } catch (RemoteException e) { 739 Log.e(TAG, Log.getStackTraceString(new Throwable())); 740 } 741 } 742 if (service == null) Log.w(TAG, "Proxy not attached to service"); 743 return false; 744 } 745 746 /** 747 * Check if Bluetooth SCO audio is connected. 748 * 749 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 750 * 751 * @param device Bluetooth headset 752 * @return true if SCO is connected, false otherwise or on error 753 */ isAudioConnected(BluetoothDevice device)754 public boolean isAudioConnected(BluetoothDevice device) { 755 if (VDBG) log("isAudioConnected()"); 756 final IBluetoothHeadset service = mService; 757 if (service != null && isEnabled() && isValidDevice(device)) { 758 try { 759 return service.isAudioConnected(device); 760 } catch (RemoteException e) { 761 Log.e(TAG, Log.getStackTraceString(new Throwable())); 762 } 763 } 764 if (service == null) Log.w(TAG, "Proxy not attached to service"); 765 return false; 766 } 767 768 /** 769 * Indicates if current platform supports voice dialing over bluetooth SCO. 770 * 771 * @return true if voice dialing over bluetooth is supported, false otherwise. 772 * @hide 773 */ isBluetoothVoiceDialingEnabled(Context context)774 public static boolean isBluetoothVoiceDialingEnabled(Context context) { 775 return context.getResources().getBoolean( 776 com.android.internal.R.bool.config_bluetooth_sco_off_call); 777 } 778 779 /** 780 * Get the current audio state of the Headset. 781 * Note: This is an internal function and shouldn't be exposed 782 * 783 * @hide 784 */ 785 @UnsupportedAppUsage getAudioState(BluetoothDevice device)786 public int getAudioState(BluetoothDevice device) { 787 if (VDBG) log("getAudioState"); 788 final IBluetoothHeadset service = mService; 789 if (service != null && !isDisabled()) { 790 try { 791 return service.getAudioState(device); 792 } catch (RemoteException e) { 793 Log.e(TAG, e.toString()); 794 } 795 } else { 796 Log.w(TAG, "Proxy not attached to service"); 797 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 798 } 799 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 800 } 801 802 /** 803 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any 804 * audio to the HF unless explicitly told to. 805 * This method should be used in cases where the SCO channel is shared between multiple profiles 806 * and must be delegated by a source knowledgeable 807 * Note: This is an internal function and shouldn't be exposed 808 * 809 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise. 810 * @hide 811 */ setAudioRouteAllowed(boolean allowed)812 public void setAudioRouteAllowed(boolean allowed) { 813 if (VDBG) log("setAudioRouteAllowed"); 814 final IBluetoothHeadset service = mService; 815 if (service != null && isEnabled()) { 816 try { 817 service.setAudioRouteAllowed(allowed); 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 * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}. 829 * Note: This is an internal function and shouldn't be exposed 830 * 831 * @hide 832 */ getAudioRouteAllowed()833 public boolean getAudioRouteAllowed() { 834 if (VDBG) log("getAudioRouteAllowed"); 835 final IBluetoothHeadset service = mService; 836 if (service != null && isEnabled()) { 837 try { 838 return service.getAudioRouteAllowed(); 839 } catch (RemoteException e) { 840 Log.e(TAG, e.toString()); 841 } 842 } else { 843 Log.w(TAG, "Proxy not attached to service"); 844 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 845 } 846 return false; 847 } 848 849 /** 850 * Force SCO audio to be opened regardless any other restrictions 851 * 852 * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio 853 * False to use SCO audio in normal manner 854 * @hide 855 */ setForceScoAudio(boolean forced)856 public void setForceScoAudio(boolean forced) { 857 if (VDBG) log("setForceScoAudio " + String.valueOf(forced)); 858 final IBluetoothHeadset service = mService; 859 if (service != null && isEnabled()) { 860 try { 861 service.setForceScoAudio(forced); 862 } catch (RemoteException e) { 863 Log.e(TAG, e.toString()); 864 } 865 } else { 866 Log.w(TAG, "Proxy not attached to service"); 867 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 868 } 869 } 870 871 /** 872 * Check if at least one headset's SCO audio is connected or connecting 873 * 874 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 875 * 876 * @return true if at least one device's SCO audio is connected or connecting, false otherwise 877 * or on error 878 * @hide 879 */ isAudioOn()880 public boolean isAudioOn() { 881 if (VDBG) log("isAudioOn()"); 882 final IBluetoothHeadset service = mService; 883 if (service != null && isEnabled()) { 884 try { 885 return service.isAudioOn(); 886 } catch (RemoteException e) { 887 Log.e(TAG, Log.getStackTraceString(new Throwable())); 888 } 889 } 890 if (service == null) Log.w(TAG, "Proxy not attached to service"); 891 return false; 892 893 } 894 895 /** 896 * Initiates a connection of headset audio to the current active device 897 * 898 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 899 * If this function returns true, this intent will be broadcasted with 900 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. 901 * 902 * <p> {@link #EXTRA_STATE} will transition from 903 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when 904 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} 905 * in case of failure to establish the audio connection. 906 * 907 * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true 908 * before calling this method 909 * 910 * @return false if there was some error such as there is no active headset 911 * @hide 912 */ 913 @UnsupportedAppUsage connectAudio()914 public boolean connectAudio() { 915 final IBluetoothHeadset service = mService; 916 if (service != null && isEnabled()) { 917 try { 918 return service.connectAudio(); 919 } catch (RemoteException e) { 920 Log.e(TAG, e.toString()); 921 } 922 } else { 923 Log.w(TAG, "Proxy not attached to service"); 924 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 925 } 926 return false; 927 } 928 929 /** 930 * Initiates a disconnection of HFP SCO audio. 931 * Tear down voice recognition or virtual voice call if any. 932 * 933 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 934 * If this function returns true, this intent will be broadcasted with 935 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. 936 * 937 * @return false if audio is not connected, or on error, true otherwise 938 * @hide 939 */ 940 @UnsupportedAppUsage disconnectAudio()941 public boolean disconnectAudio() { 942 final IBluetoothHeadset service = mService; 943 if (service != null && isEnabled()) { 944 try { 945 return service.disconnectAudio(); 946 } catch (RemoteException e) { 947 Log.e(TAG, e.toString()); 948 } 949 } else { 950 Log.w(TAG, "Proxy not attached to service"); 951 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 952 } 953 return false; 954 } 955 956 /** 957 * Initiates a SCO channel connection as a virtual voice call to the current active device 958 * Active handsfree device will be notified of incoming call and connected call. 959 * 960 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 961 * If this function returns true, this intent will be broadcasted with 962 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. 963 * 964 * <p> {@link #EXTRA_STATE} will transition from 965 * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when 966 * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} 967 * in case of failure to establish the audio connection. 968 * 969 * @return true if successful, false if one of the following case applies 970 * - SCO audio is not idle (connecting or connected) 971 * - virtual call has already started 972 * - there is no active device 973 * - a Telecom managed call is going on 974 * - binder is dead or Bluetooth is disabled or other error 975 * @hide 976 */ 977 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) 978 @UnsupportedAppUsage startScoUsingVirtualVoiceCall()979 public boolean startScoUsingVirtualVoiceCall() { 980 if (DBG) log("startScoUsingVirtualVoiceCall()"); 981 final IBluetoothHeadset service = mService; 982 if (service != null && isEnabled()) { 983 try { 984 return service.startScoUsingVirtualVoiceCall(); 985 } catch (RemoteException e) { 986 Log.e(TAG, e.toString()); 987 } 988 } else { 989 Log.w(TAG, "Proxy not attached to service"); 990 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 991 } 992 return false; 993 } 994 995 /** 996 * Terminates an ongoing SCO connection and the associated virtual call. 997 * 998 * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. 999 * If this function returns true, this intent will be broadcasted with 1000 * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. 1001 * 1002 * @return true if successful, false if one of the following case applies 1003 * - virtual voice call is not started or has ended 1004 * - binder is dead or Bluetooth is disabled or other error 1005 * @hide 1006 */ 1007 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) 1008 @UnsupportedAppUsage stopScoUsingVirtualVoiceCall()1009 public boolean stopScoUsingVirtualVoiceCall() { 1010 if (DBG) log("stopScoUsingVirtualVoiceCall()"); 1011 final IBluetoothHeadset service = mService; 1012 if (service != null && isEnabled()) { 1013 try { 1014 return service.stopScoUsingVirtualVoiceCall(); 1015 } catch (RemoteException e) { 1016 Log.e(TAG, e.toString()); 1017 } 1018 } else { 1019 Log.w(TAG, "Proxy not attached to service"); 1020 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1021 } 1022 return false; 1023 } 1024 1025 /** 1026 * Notify Headset of phone state change. 1027 * This is a backdoor for phone app to call BluetoothHeadset since 1028 * there is currently not a good way to get precise call state change outside 1029 * of phone app. 1030 * 1031 * @hide 1032 */ 1033 @UnsupportedAppUsage phoneStateChanged(int numActive, int numHeld, int callState, String number, int type, String name)1034 public void phoneStateChanged(int numActive, int numHeld, int callState, String number, 1035 int type, String name) { 1036 final IBluetoothHeadset service = mService; 1037 if (service != null && isEnabled()) { 1038 try { 1039 service.phoneStateChanged(numActive, numHeld, callState, number, type, name); 1040 } catch (RemoteException e) { 1041 Log.e(TAG, e.toString()); 1042 } 1043 } else { 1044 Log.w(TAG, "Proxy not attached to service"); 1045 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1046 } 1047 } 1048 1049 /** 1050 * Send Headset of CLCC response 1051 * 1052 * @hide 1053 */ clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type)1054 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 1055 String number, int type) { 1056 final IBluetoothHeadset service = mService; 1057 if (service != null && isEnabled()) { 1058 try { 1059 service.clccResponse(index, direction, status, mode, mpty, number, type); 1060 } catch (RemoteException e) { 1061 Log.e(TAG, e.toString()); 1062 } 1063 } else { 1064 Log.w(TAG, "Proxy not attached to service"); 1065 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1066 } 1067 } 1068 1069 /** 1070 * Sends a vendor-specific unsolicited result code to the headset. 1071 * 1072 * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code 1073 * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the 1074 * string <code>"+ANDROID: 0"</code> will be sent. 1075 * 1076 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. 1077 * 1078 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 1079 * 1080 * @param device Bluetooth headset. 1081 * @param command A vendor-specific command. 1082 * @param arg The argument that will be attached to the command. 1083 * @return {@code false} if there is no headset connected, or if the command is not an allowed 1084 * vendor-specific unsolicited result code, or on error. {@code true} otherwise. 1085 * @throws IllegalArgumentException if {@code command} is {@code null}. 1086 */ sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg)1087 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, 1088 String arg) { 1089 if (DBG) { 1090 log("sendVendorSpecificResultCode()"); 1091 } 1092 if (command == null) { 1093 throw new IllegalArgumentException("command is null"); 1094 } 1095 final IBluetoothHeadset service = mService; 1096 if (service != null && isEnabled() && isValidDevice(device)) { 1097 try { 1098 return service.sendVendorSpecificResultCode(device, command, arg); 1099 } catch (RemoteException e) { 1100 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1101 } 1102 } 1103 if (service == null) { 1104 Log.w(TAG, "Proxy not attached to service"); 1105 } 1106 return false; 1107 } 1108 1109 /** 1110 * Select a connected device as active. 1111 * 1112 * The active device selection is per profile. An active device's 1113 * purpose is profile-specific. For example, in HFP and HSP profiles, 1114 * it is the device used for phone call audio. If a remote device is not 1115 * connected, it cannot be selected as active. 1116 * 1117 * <p> This API returns false in scenarios like the profile on the 1118 * device is not connected or Bluetooth is not turned on. 1119 * When this API returns true, it is guaranteed that the 1120 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted 1121 * with the active device. 1122 * 1123 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 1124 * permission. 1125 * 1126 * @param device Remote Bluetooth Device, could be null if phone call audio should not be 1127 * streamed to a headset 1128 * @return false on immediate error, true otherwise 1129 * @hide 1130 */ 1131 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) 1132 @UnsupportedAppUsage setActiveDevice(@ullable BluetoothDevice device)1133 public boolean setActiveDevice(@Nullable BluetoothDevice device) { 1134 if (DBG) { 1135 Log.d(TAG, "setActiveDevice: " + device); 1136 } 1137 final IBluetoothHeadset service = mService; 1138 if (service != null && isEnabled() && (device == null || isValidDevice(device))) { 1139 try { 1140 return service.setActiveDevice(device); 1141 } catch (RemoteException e) { 1142 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1143 } 1144 } 1145 if (service == null) { 1146 Log.w(TAG, "Proxy not attached to service"); 1147 } 1148 return false; 1149 } 1150 1151 /** 1152 * Get the connected device that is active. 1153 * 1154 * @return the connected device that is active or null if no device 1155 * is active. 1156 * @hide 1157 */ 1158 @UnsupportedAppUsage 1159 @Nullable 1160 @RequiresPermission(Manifest.permission.BLUETOOTH) getActiveDevice()1161 public BluetoothDevice getActiveDevice() { 1162 if (VDBG) { 1163 Log.d(TAG, "getActiveDevice"); 1164 } 1165 final IBluetoothHeadset service = mService; 1166 if (service != null && isEnabled()) { 1167 try { 1168 return service.getActiveDevice(); 1169 } catch (RemoteException e) { 1170 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1171 } 1172 } 1173 if (service == null) { 1174 Log.w(TAG, "Proxy not attached to service"); 1175 } 1176 return null; 1177 } 1178 1179 /** 1180 * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an 1181 * active connection. 1182 * 1183 * @return true if in-band ringing is enabled, false if in-band ringing is disabled 1184 * @hide 1185 */ 1186 @RequiresPermission(android.Manifest.permission.BLUETOOTH) isInbandRingingEnabled()1187 public boolean isInbandRingingEnabled() { 1188 if (DBG) { 1189 log("isInbandRingingEnabled()"); 1190 } 1191 final IBluetoothHeadset service = mService; 1192 if (service != null && isEnabled()) { 1193 try { 1194 return service.isInbandRingingEnabled(); 1195 } catch (RemoteException e) { 1196 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1197 } 1198 } 1199 if (service == null) { 1200 Log.w(TAG, "Proxy not attached to service"); 1201 } 1202 return false; 1203 } 1204 1205 /** 1206 * Check if in-band ringing is supported for this platform. 1207 * 1208 * @return true if in-band ringing is supported, false if in-band ringing is not supported 1209 * @hide 1210 */ isInbandRingingSupported(Context context)1211 public static boolean isInbandRingingSupported(Context context) { 1212 return context.getResources().getBoolean( 1213 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support); 1214 } 1215 1216 private final IBluetoothProfileServiceConnection mConnection = 1217 new IBluetoothProfileServiceConnection.Stub() { 1218 @Override 1219 public void onServiceConnected(ComponentName className, IBinder service) { 1220 if (DBG) Log.d(TAG, "Proxy object connected"); 1221 mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service)); 1222 mHandler.sendMessage(mHandler.obtainMessage( 1223 MESSAGE_HEADSET_SERVICE_CONNECTED)); 1224 } 1225 1226 @Override 1227 public void onServiceDisconnected(ComponentName className) { 1228 if (DBG) Log.d(TAG, "Proxy object disconnected"); 1229 doUnbind(); 1230 mHandler.sendMessage(mHandler.obtainMessage( 1231 MESSAGE_HEADSET_SERVICE_DISCONNECTED)); 1232 } 1233 }; 1234 1235 @UnsupportedAppUsage isEnabled()1236 private boolean isEnabled() { 1237 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 1238 } 1239 isDisabled()1240 private boolean isDisabled() { 1241 return mAdapter.getState() == BluetoothAdapter.STATE_OFF; 1242 } 1243 isValidDevice(BluetoothDevice device)1244 private static boolean isValidDevice(BluetoothDevice device) { 1245 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 1246 } 1247 log(String msg)1248 private static void log(String msg) { 1249 Log.d(TAG, msg); 1250 } 1251 1252 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 1253 @Override 1254 public void handleMessage(Message msg) { 1255 switch (msg.what) { 1256 case MESSAGE_HEADSET_SERVICE_CONNECTED: { 1257 if (mServiceListener != null) { 1258 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, 1259 BluetoothHeadset.this); 1260 } 1261 break; 1262 } 1263 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: { 1264 if (mServiceListener != null) { 1265 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); 1266 } 1267 break; 1268 } 1269 } 1270 } 1271 }; 1272 } 1273