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