1 /* 2 * Copyright (C) 2014 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 package android.bluetooth; 17 18 import static android.Manifest.permission.BLUETOOTH_CONNECT; 19 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; 20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; 21 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 22 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; 23 import static android.bluetooth.BluetoothUtils.isValidDevice; 24 25 import static java.util.Objects.requireNonNull; 26 27 import android.annotation.IntRange; 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.annotation.RequiresNoPermission; 31 import android.annotation.RequiresPermission; 32 import android.annotation.SdkConstant; 33 import android.annotation.SdkConstant.SdkConstantType; 34 import android.annotation.SuppressLint; 35 import android.annotation.SystemApi; 36 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 37 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 38 import android.compat.annotation.UnsupportedAppUsage; 39 import android.content.AttributionSource; 40 import android.content.Context; 41 import android.os.Build; 42 import android.os.Bundle; 43 import android.os.IBinder; 44 import android.os.Parcel; 45 import android.os.Parcelable; 46 import android.os.RemoteException; 47 import android.util.CloseGuard; 48 import android.util.Log; 49 50 import com.android.internal.annotations.VisibleForTesting; 51 52 import java.util.Collections; 53 import java.util.List; 54 55 /** 56 * This class provides the System APIs to interact with the Hands-Free Client profile. 57 * 58 * <p>BluetoothHeadsetClient is a proxy object for controlling the Bluetooth HFP Client Service via 59 * IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHeadsetClient proxy object. 60 * 61 * @hide 62 */ 63 @SystemApi 64 public final class BluetoothHeadsetClient implements BluetoothProfile, AutoCloseable { 65 private static final String TAG = BluetoothHeadsetClient.class.getSimpleName(); 66 67 private static final boolean DBG = true; 68 private static final boolean VDBG = false; 69 private final CloseGuard mCloseGuard; 70 71 /** 72 * Intent used to broadcast the change in connection state of the HFP Client profile. 73 * 74 * <p>This intent will have 3 extras: 75 * 76 * <ul> 77 * <li>{@link #EXTRA_STATE} - The current state of the profile. 78 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. 79 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 80 * </ul> 81 * 82 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link 83 * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link 84 * #STATE_DISCONNECTING}. 85 * 86 * @hide 87 */ 88 @SuppressLint("ActionValue") 89 @SystemApi 90 @RequiresBluetoothConnectPermission 91 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) 92 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 93 public static final String ACTION_CONNECTION_STATE_CHANGED = 94 "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED"; 95 96 /** 97 * Intent sent whenever audio state changes. 98 * 99 * <p>It includes two mandatory extras: {@link BluetoothProfile#EXTRA_STATE}, {@link 100 * BluetoothProfile#EXTRA_PREVIOUS_STATE}, with possible values: {@link 101 * #STATE_AUDIO_CONNECTING}, {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED} 102 * 103 * <p>When <code>EXTRA_STATE</code> is set to </code>STATE_AUDIO_CONNECTED</code>, it also 104 * includes {@link #EXTRA_AUDIO_WBS} indicating wide band speech support. 105 * 106 * @hide 107 */ 108 @RequiresBluetoothConnectPermission 109 @RequiresPermission(BLUETOOTH_CONNECT) 110 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 111 @SuppressLint("ActionValue") 112 public static final String ACTION_AUDIO_STATE_CHANGED = 113 "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED"; 114 115 /** 116 * Intent sending updates of the Audio Gateway state. Each extra is being sent only when value 117 * it represents has been changed recently on AG. 118 * 119 * <p>It can contain one or more of the following extras: {@link #EXTRA_NETWORK_STATUS}, {@link 120 * #EXTRA_NETWORK_SIGNAL_STRENGTH}, {@link #EXTRA_NETWORK_ROAMING}, {@link 121 * #EXTRA_BATTERY_LEVEL}, {@link #EXTRA_OPERATOR_NAME}, {@link #EXTRA_VOICE_RECOGNITION}, {@link 122 * #EXTRA_IN_BAND_RING} 123 * 124 * @hide 125 */ 126 @RequiresBluetoothConnectPermission 127 @RequiresPermission(BLUETOOTH_CONNECT) 128 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 129 public static final String ACTION_AG_EVENT = 130 "android.bluetooth.headsetclient.profile.action.AG_EVENT"; 131 132 /** 133 * Intent sent whenever state of a call changes. 134 * 135 * <p>It includes: {@link #EXTRA_CALL}, with value of {@link BluetoothHeadsetClientCall} 136 * instance, representing actual call state. 137 * 138 * @hide 139 */ 140 @RequiresBluetoothConnectPermission 141 @RequiresPermission(BLUETOOTH_CONNECT) 142 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 143 public static final String ACTION_CALL_CHANGED = 144 "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED"; 145 146 /** 147 * Intent that notifies about the result of the last issued action. Please note that not every 148 * action results in explicit action result code being sent. Instead other notifications about 149 * new Audio Gateway state might be sent, like <code>ACTION_AG_EVENT</code> with <code> 150 * EXTRA_VOICE_RECOGNITION</code> value when for example user started voice recognition from HF 151 * unit. 152 * 153 * @hide 154 */ 155 @RequiresBluetoothConnectPermission 156 @RequiresPermission(BLUETOOTH_CONNECT) 157 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 158 public static final String ACTION_RESULT = 159 "android.bluetooth.headsetclient.profile.action.RESULT"; 160 161 /** 162 * Intent that notifies about vendor specific event arrival. Events not defined in HFP spec will 163 * be matched with supported vendor event list and this intent will be broadcasted upon a match. 164 * Supported vendor events are of format of "+eventCode" or "+eventCode=xxxx" or 165 * "+eventCode:=xxxx". Vendor event can be a response to a vendor specific command or 166 * unsolicited. 167 * 168 * @hide 169 */ 170 @RequiresBluetoothConnectPermission 171 @RequiresPermission(BLUETOOTH_CONNECT) 172 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 173 public static final String ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT = 174 "android.bluetooth.headsetclient.profile.action.VENDOR_SPECIFIC_EVENT"; 175 176 /** 177 * Intent that notifies about the number attached to the last voice tag recorded on AG. 178 * 179 * <p>It contains: {@link #EXTRA_NUMBER}, with a <code>String</code> value representing phone 180 * number. 181 * 182 * @hide 183 */ 184 @RequiresBluetoothConnectPermission 185 @RequiresPermission(BLUETOOTH_CONNECT) 186 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 187 public static final String ACTION_LAST_VTAG = 188 "android.bluetooth.headsetclient.profile.action.LAST_VTAG"; 189 190 /** @hide */ 191 public static final int STATE_AUDIO_DISCONNECTED = 0; 192 193 /** @hide */ 194 public static final int STATE_AUDIO_CONNECTING = 1; 195 196 /** @hide */ 197 public static final int STATE_AUDIO_CONNECTED = 2; 198 199 /** 200 * Extra with information if connected audio is WBS. 201 * 202 * <p>Possible values: <code>true</code>, <code>false</code>. 203 * 204 * @hide 205 */ 206 public static final String EXTRA_AUDIO_WBS = "android.bluetooth.headsetclient.extra.AUDIO_WBS"; 207 208 /** 209 * Extra for AG_EVENT indicates network status. 210 * 211 * <p>Value: 0 - network unavailable, 1 - network available 212 * 213 * @hide 214 */ 215 public static final String EXTRA_NETWORK_STATUS = 216 "android.bluetooth.headsetclient.extra.NETWORK_STATUS"; 217 218 /** 219 * Extra for AG_EVENT intent indicates network signal strength. 220 * 221 * <p>Value: <code>Integer</code> representing signal strength. 222 * 223 * @hide 224 */ 225 public static final String EXTRA_NETWORK_SIGNAL_STRENGTH = 226 "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH"; 227 228 /** 229 * Extra for AG_EVENT intent indicates roaming state. 230 * 231 * <p>Value: 0 - no roaming 1 - active roaming 232 * 233 * @hide 234 */ 235 public static final String EXTRA_NETWORK_ROAMING = 236 "android.bluetooth.headsetclient.extra.NETWORK_ROAMING"; 237 238 /** 239 * Extra for AG_EVENT intent indicates the battery level. 240 * 241 * <p>Value: <code>Integer</code> representing signal strength. 242 * 243 * @hide 244 */ 245 public static final String EXTRA_BATTERY_LEVEL = 246 "android.bluetooth.headsetclient.extra.BATTERY_LEVEL"; 247 248 /** 249 * Extra for AG_EVENT intent indicates operator name. 250 * 251 * <p>Value: <code>String</code> representing operator name. 252 * 253 * @hide 254 */ 255 public static final String EXTRA_OPERATOR_NAME = 256 "android.bluetooth.headsetclient.extra.OPERATOR_NAME"; 257 258 /** 259 * Extra for AG_EVENT intent indicates voice recognition state. 260 * 261 * <p>Value: 0 - voice recognition stopped, 1 - voice recognition started. 262 * 263 * @hide 264 */ 265 public static final String EXTRA_VOICE_RECOGNITION = 266 "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION"; 267 268 /** 269 * Extra for AG_EVENT intent indicates in band ring state. 270 * 271 * <p>Value: 0 - in band ring tone not supported, or 1 - in band ring tone supported. 272 * 273 * @hide 274 */ 275 public static final String EXTRA_IN_BAND_RING = 276 "android.bluetooth.headsetclient.extra.IN_BAND_RING"; 277 278 /** 279 * Extra for AG_EVENT intent indicates subscriber info. 280 * 281 * <p>Value: <code>String</code> containing subscriber information. 282 * 283 * @hide 284 */ 285 public static final String EXTRA_SUBSCRIBER_INFO = 286 "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO"; 287 288 /** 289 * Extra for AG_CALL_CHANGED intent indicates the {@link BluetoothHeadsetClientCall} object that 290 * has changed. 291 * 292 * @hide 293 */ 294 public static final String EXTRA_CALL = "android.bluetooth.headsetclient.extra.CALL"; 295 296 /** 297 * Extra for ACTION_LAST_VTAG intent. 298 * 299 * <p>Value: <code>String</code> representing phone number corresponding to last voice tag 300 * recorded on AG 301 * 302 * @hide 303 */ 304 public static final String EXTRA_NUMBER = "android.bluetooth.headsetclient.extra.NUMBER"; 305 306 /** 307 * Extra for ACTION_RESULT intent that shows the result code of last issued action. 308 * 309 * <p>Possible results: {@link #ACTION_RESULT_OK}, {@link #ACTION_RESULT_ERROR}, {@link 310 * #ACTION_RESULT_ERROR_NO_CARRIER}, {@link #ACTION_RESULT_ERROR_BUSY}, {@link 311 * #ACTION_RESULT_ERROR_NO_ANSWER}, {@link #ACTION_RESULT_ERROR_DELAYED}, {@link 312 * #ACTION_RESULT_ERROR_BLACKLISTED}, {@link #ACTION_RESULT_ERROR_CME} 313 * 314 * @hide 315 */ 316 public static final String EXTRA_RESULT_CODE = 317 "android.bluetooth.headsetclient.extra.RESULT_CODE"; 318 319 /** 320 * Extra for ACTION_RESULT intent that shows the extended result code of last issued action. 321 * 322 * <p>Value: <code>Integer</code> - error code. 323 * 324 * @hide 325 */ 326 public static final String EXTRA_CME_CODE = "android.bluetooth.headsetclient.extra.CME_CODE"; 327 328 /** 329 * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that indicates vendor ID. 330 * 331 * @hide 332 */ 333 public static final String EXTRA_VENDOR_ID = "android.bluetooth.headsetclient.extra.VENDOR_ID"; 334 335 /** 336 * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that indicates vendor event code. 337 * 338 * @hide 339 */ 340 public static final String EXTRA_VENDOR_EVENT_CODE = 341 "android.bluetooth.headsetclient.extra.VENDOR_EVENT_CODE"; 342 343 /** 344 * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that contains full vendor event 345 * including event code and full arguments. 346 * 347 * @hide 348 */ 349 public static final String EXTRA_VENDOR_EVENT_FULL_ARGS = 350 "android.bluetooth.headsetclient.extra.VENDOR_EVENT_FULL_ARGS"; 351 352 /* Extras for AG_FEATURES, extras type is boolean */ 353 // TODO verify if all of those are actually useful 354 /** 355 * AG feature: three way calling. 356 * 357 * @hide 358 */ 359 public static final String EXTRA_AG_FEATURE_3WAY_CALLING = 360 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING"; 361 362 /** 363 * AG feature: voice recognition. 364 * 365 * @hide 366 */ 367 public static final String EXTRA_AG_FEATURE_VOICE_RECOGNITION = 368 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION"; 369 370 /** 371 * AG feature: fetching phone number for voice tagging procedure. 372 * 373 * @hide 374 */ 375 public static final String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT = 376 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT"; 377 378 /** 379 * AG feature: ability to reject incoming call. 380 * 381 * @hide 382 */ 383 public static final String EXTRA_AG_FEATURE_REJECT_CALL = 384 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL"; 385 386 /** 387 * AG feature: enhanced call handling (terminate specific call, private consultation). 388 * 389 * @hide 390 */ 391 public static final String EXTRA_AG_FEATURE_ECC = 392 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC"; 393 394 /** 395 * AG feature: response and hold. 396 * 397 * @hide 398 */ 399 public static final String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD = 400 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD"; 401 402 /** 403 * AG call handling feature: accept held or waiting call in three way calling scenarios. 404 * 405 * @hide 406 */ 407 public static final String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL = 408 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL"; 409 410 /** 411 * AG call handling feature: release held or waiting call in three way calling scenarios. 412 * 413 * @hide 414 */ 415 public static final String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL = 416 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL"; 417 418 /** 419 * AG call handling feature: release active call and accept held or waiting call in three way 420 * calling scenarios. 421 * 422 * @hide 423 */ 424 public static final String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT = 425 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT"; 426 427 /** 428 * AG call handling feature: merge two calls, held and active - multi party conference mode. 429 * 430 * @hide 431 */ 432 public static final String EXTRA_AG_FEATURE_MERGE = 433 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE"; 434 435 /** 436 * AG call handling feature: merge calls and disconnect from multi party conversation leaving 437 * peers connected to each other. Note that this feature needs to be supported by mobile network 438 * operator as it requires connection and billing transfer. 439 * 440 * @hide 441 */ 442 public static final String EXTRA_AG_FEATURE_MERGE_AND_DETACH = 443 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH"; 444 445 /* Action result codes */ 446 /** @hide */ 447 public static final int ACTION_RESULT_OK = 0; 448 449 /** @hide */ 450 public static final int ACTION_RESULT_ERROR = 1; 451 452 /** @hide */ 453 public static final int ACTION_RESULT_ERROR_NO_CARRIER = 2; 454 455 /** @hide */ 456 public static final int ACTION_RESULT_ERROR_BUSY = 3; 457 458 /** @hide */ 459 public static final int ACTION_RESULT_ERROR_NO_ANSWER = 4; 460 461 /** @hide */ 462 public static final int ACTION_RESULT_ERROR_DELAYED = 5; 463 464 /** @hide */ 465 public static final int ACTION_RESULT_ERROR_BLACKLISTED = 6; 466 467 /** @hide */ 468 public static final int ACTION_RESULT_ERROR_CME = 7; 469 470 /* Detailed CME error codes */ 471 /** @hide */ 472 public static final int CME_PHONE_FAILURE = 0; 473 474 /** @hide */ 475 public static final int CME_NO_CONNECTION_TO_PHONE = 1; 476 477 /** @hide */ 478 public static final int CME_OPERATION_NOT_ALLOWED = 3; 479 480 /** @hide */ 481 public static final int CME_OPERATION_NOT_SUPPORTED = 4; 482 483 /** @hide */ 484 public static final int CME_PHSIM_PIN_REQUIRED = 5; 485 486 /** @hide */ 487 public static final int CME_PHFSIM_PIN_REQUIRED = 6; 488 489 /** @hide */ 490 public static final int CME_PHFSIM_PUK_REQUIRED = 7; 491 492 /** @hide */ 493 public static final int CME_SIM_NOT_INSERTED = 10; 494 495 /** @hide */ 496 public static final int CME_SIM_PIN_REQUIRED = 11; 497 498 /** @hide */ 499 public static final int CME_SIM_PUK_REQUIRED = 12; 500 501 /** @hide */ 502 public static final int CME_SIM_FAILURE = 13; 503 504 /** @hide */ 505 public static final int CME_SIM_BUSY = 14; 506 507 /** @hide */ 508 public static final int CME_SIM_WRONG = 15; 509 510 /** @hide */ 511 public static final int CME_INCORRECT_PASSWORD = 16; 512 513 /** @hide */ 514 public static final int CME_SIM_PIN2_REQUIRED = 17; 515 516 /** @hide */ 517 public static final int CME_SIM_PUK2_REQUIRED = 18; 518 519 /** @hide */ 520 public static final int CME_MEMORY_FULL = 20; 521 522 /** @hide */ 523 public static final int CME_INVALID_INDEX = 21; 524 525 /** @hide */ 526 public static final int CME_NOT_FOUND = 22; 527 528 /** @hide */ 529 public static final int CME_MEMORY_FAILURE = 23; 530 531 /** @hide */ 532 public static final int CME_TEXT_STRING_TOO_LONG = 24; 533 534 /** @hide */ 535 public static final int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25; 536 537 /** @hide */ 538 public static final int CME_DIAL_STRING_TOO_LONG = 26; 539 540 /** @hide */ 541 public static final int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27; 542 543 /** @hide */ 544 public static final int CME_NO_NETWORK_SERVICE = 30; 545 546 /** @hide */ 547 public static final int CME_NETWORK_TIMEOUT = 31; 548 549 /** @hide */ 550 public static final int CME_EMERGENCY_SERVICE_ONLY = 32; 551 552 /** @hide */ 553 public static final int CME_NO_SIMULTANEOUS_VOIP_CS_CALLS = 33; 554 555 /** @hide */ 556 public static final int CME_NOT_SUPPORTED_FOR_VOIP = 34; 557 558 /** @hide */ 559 public static final int CME_SIP_RESPONSE_CODE = 35; 560 561 /** @hide */ 562 public static final int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40; 563 564 /** @hide */ 565 public static final int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41; 566 567 /** @hide */ 568 public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42; 569 570 /** @hide */ 571 public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43; 572 573 /** @hide */ 574 public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44; 575 576 /** @hide */ 577 public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45; 578 579 /** @hide */ 580 public static final int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46; 581 582 /** @hide */ 583 public static final int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47; 584 585 /** @hide */ 586 public static final int CME_HIDDEN_KEY_REQUIRED = 48; 587 588 /** @hide */ 589 public static final int CME_EAP_NOT_SUPPORTED = 49; 590 591 /** @hide */ 592 public static final int CME_INCORRECT_PARAMETERS = 50; 593 594 /* Action policy for other calls when accepting call */ 595 /** @hide */ 596 public static final int CALL_ACCEPT_NONE = 0; 597 598 /** @hide */ 599 public static final int CALL_ACCEPT_HOLD = 1; 600 601 /** @hide */ 602 public static final int CALL_ACCEPT_TERMINATE = 2; 603 604 private final BluetoothAdapter mAdapter; 605 private final AttributionSource mAttributionSource; 606 607 private IBluetoothHeadsetClient mService; 608 609 /** Create a BluetoothHeadsetClient proxy object. */ BluetoothHeadsetClient(Context context, BluetoothAdapter adapter)610 BluetoothHeadsetClient(Context context, BluetoothAdapter adapter) { 611 mAdapter = adapter; 612 mAttributionSource = adapter.getAttributionSource(); 613 mService = null; 614 mCloseGuard = new CloseGuard(); 615 mCloseGuard.open("close"); 616 } 617 618 /** 619 * Close the connection to the backing service. Other public functions of BluetoothHeadsetClient 620 * will return default error results once close() has been called. Multiple invocations of 621 * close() are ok. 622 * 623 * @hide 624 */ 625 @Override close()626 public void close() { 627 if (VDBG) log("close()"); 628 mAdapter.closeProfileProxy(this); 629 if (mCloseGuard != null) { 630 mCloseGuard.close(); 631 } 632 } 633 634 /** @hide */ 635 @Override 636 @RequiresNoPermission onServiceConnected(IBinder service)637 public void onServiceConnected(IBinder service) { 638 mService = IBluetoothHeadsetClient.Stub.asInterface(service); 639 } 640 641 /** @hide */ 642 @Override 643 @RequiresNoPermission onServiceDisconnected()644 public void onServiceDisconnected() { 645 mService = null; 646 } 647 getService()648 private IBluetoothHeadsetClient getService() { 649 return mService; 650 } 651 652 /** @hide */ 653 @Override 654 @RequiresNoPermission getAdapter()655 public BluetoothAdapter getAdapter() { 656 return mAdapter; 657 } 658 659 /** @hide */ 660 @SuppressWarnings("Finalize") // TODO(b/314811467) finalize()661 protected void finalize() { 662 if (mCloseGuard != null) { 663 mCloseGuard.warnIfOpen(); 664 } 665 close(); 666 } 667 668 /** 669 * Connects to remote device. 670 * 671 * <p>Currently, the system supports only 1 connection. So, in case of the second connection, 672 * this implementation will disconnect already connected device automatically and will process 673 * the new one. 674 * 675 * @param device a remote device we want connect to 676 * @return <code>true</code> if command has been issued successfully; <code>false</code> 677 * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. 678 * @hide 679 */ 680 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 681 @RequiresBluetoothConnectPermission 682 @RequiresPermission(BLUETOOTH_CONNECT) connect(BluetoothDevice device)683 public boolean connect(BluetoothDevice device) { 684 if (DBG) log("connect(" + device + ")"); 685 final IBluetoothHeadsetClient service = getService(); 686 if (service == null) { 687 Log.w(TAG, "Proxy not attached to service"); 688 if (DBG) log(Log.getStackTraceString(new Throwable())); 689 } else if (isEnabled() && isValidDevice(device)) { 690 try { 691 return service.connect(device, mAttributionSource); 692 } catch (RemoteException e) { 693 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 694 } 695 } 696 return false; 697 } 698 699 /** 700 * Disconnects remote device 701 * 702 * @param device a remote device we want disconnect 703 * @return <code>true</code> if command has been issued successfully; <code>false</code> 704 * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. 705 * @hide 706 */ 707 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 708 @RequiresBluetoothConnectPermission 709 @RequiresPermission(BLUETOOTH_CONNECT) disconnect(BluetoothDevice device)710 public boolean disconnect(BluetoothDevice device) { 711 if (DBG) log("disconnect(" + device + ")"); 712 final IBluetoothHeadsetClient service = getService(); 713 if (service == null) { 714 Log.w(TAG, "Proxy not attached to service"); 715 if (DBG) log(Log.getStackTraceString(new Throwable())); 716 } else if (isEnabled() && isValidDevice(device)) { 717 try { 718 return service.disconnect(device, mAttributionSource); 719 } catch (RemoteException e) { 720 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 721 } 722 } 723 return false; 724 } 725 726 /** 727 * {@inheritDoc} 728 * 729 * @hide 730 */ 731 @SystemApi 732 @Override 733 @RequiresBluetoothConnectPermission 734 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) getConnectedDevices()735 public @NonNull List<BluetoothDevice> getConnectedDevices() { 736 if (VDBG) log("getConnectedDevices()"); 737 final IBluetoothHeadsetClient service = getService(); 738 if (service == null) { 739 Log.w(TAG, "Proxy not attached to service"); 740 if (DBG) log(Log.getStackTraceString(new Throwable())); 741 } else if (isEnabled()) { 742 try { 743 return Attributable.setAttributionSource( 744 service.getConnectedDevices(mAttributionSource), mAttributionSource); 745 } catch (RemoteException e) { 746 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 747 } 748 } 749 return Collections.emptyList(); 750 } 751 752 /** 753 * {@inheritDoc} 754 * 755 * @hide 756 */ 757 @SystemApi 758 @Override 759 @RequiresBluetoothConnectPermission 760 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) 761 @NonNull getDevicesMatchingConnectionStates(@onNull int[] states)762 public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) { 763 if (VDBG) log("getDevicesMatchingStates()"); 764 final IBluetoothHeadsetClient service = getService(); 765 if (service == null) { 766 Log.w(TAG, "Proxy not attached to service"); 767 if (DBG) log(Log.getStackTraceString(new Throwable())); 768 } else if (isEnabled()) { 769 try { 770 return Attributable.setAttributionSource( 771 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 772 mAttributionSource); 773 } catch (RemoteException e) { 774 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 775 } 776 } 777 return Collections.emptyList(); 778 } 779 780 /** 781 * {@inheritDoc} 782 * 783 * @hide 784 */ 785 @SystemApi 786 @Override 787 @RequiresBluetoothConnectPermission 788 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) getConnectionState(@onNull BluetoothDevice device)789 public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { 790 if (VDBG) log("getConnectionState(" + device + ")"); 791 final IBluetoothHeadsetClient service = getService(); 792 if (service == null) { 793 Log.w(TAG, "Proxy not attached to service"); 794 if (DBG) log(Log.getStackTraceString(new Throwable())); 795 } else if (isEnabled() && isValidDevice(device)) { 796 try { 797 return service.getConnectionState(device, mAttributionSource); 798 } catch (RemoteException e) { 799 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 800 } 801 } 802 return STATE_DISCONNECTED; 803 } 804 805 /** 806 * Set connection policy of the profile 807 * 808 * <p>The device should already be paired. Connection policy can be one of {@link 809 * #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, {@link 810 * #CONNECTION_POLICY_UNKNOWN} 811 * 812 * @param device Paired bluetooth device 813 * @param connectionPolicy is the connection policy to set to for this profile 814 * @return true if connectionPolicy is set, false on error 815 * @hide 816 */ 817 @SystemApi 818 @RequiresBluetoothConnectPermission 819 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) setConnectionPolicy( @onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)820 public boolean setConnectionPolicy( 821 @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { 822 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 823 final IBluetoothHeadsetClient service = getService(); 824 if (service == null) { 825 Log.w(TAG, "Proxy not attached to service"); 826 if (DBG) log(Log.getStackTraceString(new Throwable())); 827 } else if (isEnabled() 828 && isValidDevice(device) 829 && (connectionPolicy == CONNECTION_POLICY_FORBIDDEN 830 || connectionPolicy == CONNECTION_POLICY_ALLOWED)) { 831 try { 832 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 833 } catch (RemoteException e) { 834 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 835 } 836 } 837 return false; 838 } 839 840 /** 841 * Get the connection policy of the profile. 842 * 843 * <p>The connection policy can be any of: {@link #CONNECTION_POLICY_ALLOWED}, {@link 844 * #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 845 * 846 * @param device Bluetooth device 847 * @return connection policy of the device 848 * @hide 849 */ 850 @SystemApi 851 @RequiresLegacyBluetoothPermission 852 @RequiresBluetoothConnectPermission 853 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) getConnectionPolicy(@onNull BluetoothDevice device)854 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 855 if (VDBG) log("getConnectionPolicy(" + device + ")"); 856 final IBluetoothHeadsetClient service = getService(); 857 if (service == null) { 858 Log.w(TAG, "Proxy not attached to service"); 859 if (DBG) log(Log.getStackTraceString(new Throwable())); 860 } else if (isEnabled() && isValidDevice(device)) { 861 try { 862 return service.getConnectionPolicy(device, mAttributionSource); 863 } catch (RemoteException e) { 864 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 865 } 866 } 867 return CONNECTION_POLICY_FORBIDDEN; 868 } 869 870 /** 871 * Starts voice recognition. 872 * 873 * @param device remote device 874 * @return <code>true</code> if command has been issued successfully; <code>false</code> 875 * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. 876 * <p>Feature required for successful execution is being reported by: {@link 877 * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when 878 * feature is not supported. 879 * @hide 880 */ 881 @RequiresBluetoothConnectPermission 882 @RequiresPermission(BLUETOOTH_CONNECT) startVoiceRecognition(BluetoothDevice device)883 public boolean startVoiceRecognition(BluetoothDevice device) { 884 if (DBG) log("startVoiceRecognition()"); 885 final IBluetoothHeadsetClient service = getService(); 886 if (service == null) { 887 Log.w(TAG, "Proxy not attached to service"); 888 if (DBG) log(Log.getStackTraceString(new Throwable())); 889 } else if (isEnabled() && isValidDevice(device)) { 890 try { 891 return service.startVoiceRecognition(device, mAttributionSource); 892 } catch (RemoteException e) { 893 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 894 } 895 } 896 return false; 897 } 898 899 /** 900 * Send vendor specific AT command. 901 * 902 * @param device remote device 903 * @param vendorId vendor number by Bluetooth SIG 904 * @param atCommand command to be sent. It start with + prefix and only one command at one time. 905 * @return <code>true</code> if command has been issued successfully; <code>false</code> 906 * otherwise. 907 * @hide 908 */ 909 @RequiresBluetoothConnectPermission 910 @RequiresPermission(BLUETOOTH_CONNECT) sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand)911 public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) { 912 if (DBG) log("sendVendorSpecificCommand()"); 913 final IBluetoothHeadsetClient service = getService(); 914 if (service == null) { 915 Log.w(TAG, "Proxy not attached to service"); 916 if (DBG) log(Log.getStackTraceString(new Throwable())); 917 } else if (isEnabled() && isValidDevice(device)) { 918 try { 919 return service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource); 920 } catch (RemoteException e) { 921 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 922 } 923 } 924 return false; 925 } 926 927 /** 928 * Stops voice recognition. 929 * 930 * @param device remote device 931 * @return <code>true</code> if command has been issued successfully; <code>false</code> 932 * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. 933 * <p>Feature required for successful execution is being reported by: {@link 934 * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when 935 * feature is not supported. 936 * @hide 937 */ 938 @RequiresBluetoothConnectPermission 939 @RequiresPermission(BLUETOOTH_CONNECT) stopVoiceRecognition(BluetoothDevice device)940 public boolean stopVoiceRecognition(BluetoothDevice device) { 941 if (DBG) log("stopVoiceRecognition()"); 942 final IBluetoothHeadsetClient service = getService(); 943 if (service == null) { 944 Log.w(TAG, "Proxy not attached to service"); 945 if (DBG) log(Log.getStackTraceString(new Throwable())); 946 } else if (isEnabled() && isValidDevice(device)) { 947 try { 948 return service.stopVoiceRecognition(device, mAttributionSource); 949 } catch (RemoteException e) { 950 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 951 } 952 } 953 return false; 954 } 955 956 /** 957 * Returns list of all calls in any state. 958 * 959 * @param device remote device 960 * @return list of calls; empty list if none call exists 961 * @hide 962 */ 963 @VisibleForTesting 964 @RequiresBluetoothConnectPermission 965 @RequiresPermission(BLUETOOTH_CONNECT) getCurrentCalls(BluetoothDevice device)966 public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) { 967 if (DBG) log("getCurrentCalls()"); 968 final IBluetoothHeadsetClient service = getService(); 969 if (service == null) { 970 Log.w(TAG, "Proxy not attached to service"); 971 if (DBG) log(Log.getStackTraceString(new Throwable())); 972 } else if (isEnabled() && isValidDevice(device)) { 973 try { 974 return Attributable.setAttributionSource( 975 service.getCurrentCalls(device, mAttributionSource), mAttributionSource); 976 } catch (RemoteException e) { 977 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 978 } 979 } 980 return null; 981 } 982 983 /** 984 * Returns list of current values of AG indicators. 985 * 986 * @param device remote device 987 * @return bundle of AG indicators; null if device is not in CONNECTED state 988 * @hide 989 */ 990 @RequiresBluetoothConnectPermission 991 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) getCurrentAgEvents(BluetoothDevice device)992 public Bundle getCurrentAgEvents(BluetoothDevice device) { 993 if (DBG) log("getCurrentAgEvents()"); 994 final IBluetoothHeadsetClient service = getService(); 995 if (service == null) { 996 Log.w(TAG, "Proxy not attached to service"); 997 if (DBG) log(Log.getStackTraceString(new Throwable())); 998 } else if (isEnabled() && isValidDevice(device)) { 999 try { 1000 return service.getCurrentAgEvents(device, mAttributionSource); 1001 } catch (RemoteException e) { 1002 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1003 } 1004 } 1005 return null; 1006 } 1007 1008 /** 1009 * Accepts a call 1010 * 1011 * @param device remote device 1012 * @param flag action policy while accepting a call. Possible values {@link #CALL_ACCEPT_NONE}, 1013 * {@link #CALL_ACCEPT_HOLD}, {@link #CALL_ACCEPT_TERMINATE} 1014 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1015 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 1016 * @hide 1017 */ 1018 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1019 @RequiresBluetoothConnectPermission 1020 @RequiresPermission(BLUETOOTH_CONNECT) acceptCall(BluetoothDevice device, int flag)1021 public boolean acceptCall(BluetoothDevice device, int flag) { 1022 if (DBG) log("acceptCall()"); 1023 final IBluetoothHeadsetClient service = getService(); 1024 if (service == null) { 1025 Log.w(TAG, "Proxy not attached to service"); 1026 if (DBG) log(Log.getStackTraceString(new Throwable())); 1027 } else if (isEnabled() && isValidDevice(device)) { 1028 try { 1029 return service.acceptCall(device, flag, mAttributionSource); 1030 } catch (RemoteException e) { 1031 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1032 } 1033 } 1034 return false; 1035 } 1036 1037 /** 1038 * Holds a call. 1039 * 1040 * @param device remote device 1041 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1042 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 1043 * @hide 1044 */ 1045 @RequiresBluetoothConnectPermission 1046 @RequiresPermission(BLUETOOTH_CONNECT) holdCall(BluetoothDevice device)1047 public boolean holdCall(BluetoothDevice device) { 1048 if (DBG) log("holdCall()"); 1049 final IBluetoothHeadsetClient service = getService(); 1050 if (service == null) { 1051 Log.w(TAG, "Proxy not attached to service"); 1052 if (DBG) log(Log.getStackTraceString(new Throwable())); 1053 } else if (isEnabled() && isValidDevice(device)) { 1054 try { 1055 return service.holdCall(device, mAttributionSource); 1056 } catch (RemoteException e) { 1057 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1058 } 1059 } 1060 return false; 1061 } 1062 1063 /** 1064 * Rejects a call. 1065 * 1066 * @param device remote device 1067 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1068 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 1069 * <p>Feature required for successful execution is being reported by: {@link 1070 * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is 1071 * not supported. 1072 * @hide 1073 */ 1074 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1075 @RequiresBluetoothConnectPermission 1076 @RequiresPermission(BLUETOOTH_CONNECT) rejectCall(BluetoothDevice device)1077 public boolean rejectCall(BluetoothDevice device) { 1078 if (DBG) log("rejectCall()"); 1079 final IBluetoothHeadsetClient service = getService(); 1080 if (service == null) { 1081 Log.w(TAG, "Proxy not attached to service"); 1082 if (DBG) log(Log.getStackTraceString(new Throwable())); 1083 } else if (isEnabled() && isValidDevice(device)) { 1084 try { 1085 return service.rejectCall(device, mAttributionSource); 1086 } catch (RemoteException e) { 1087 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1088 } 1089 } 1090 return false; 1091 } 1092 1093 /** 1094 * Terminates a specified call. 1095 * 1096 * <p>Works only when Extended Call Control is supported by Audio Gateway. 1097 * 1098 * @param device remote device 1099 * @param call Handle of call obtained in {@link #dial(BluetoothDevice, String)} or obtained via 1100 * {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which case we will hangup all 1101 * active calls. 1102 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1103 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 1104 * <p>Feature required for successful execution is being reported by: {@link 1105 * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not 1106 * supported. 1107 * @hide 1108 */ 1109 @RequiresBluetoothConnectPermission 1110 @RequiresPermission(BLUETOOTH_CONNECT) terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call)1111 public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) { 1112 if (DBG) log("terminateCall()"); 1113 final IBluetoothHeadsetClient service = getService(); 1114 if (service == null) { 1115 Log.w(TAG, "Proxy not attached to service"); 1116 if (DBG) log(Log.getStackTraceString(new Throwable())); 1117 } else if (isEnabled() && isValidDevice(device)) { 1118 try { 1119 return service.terminateCall(device, call, mAttributionSource); 1120 } catch (RemoteException e) { 1121 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1122 } 1123 } 1124 return false; 1125 } 1126 1127 /** 1128 * Enters private mode with a specified call. 1129 * 1130 * <p>Works only when Extended Call Control is supported by Audio Gateway. 1131 * 1132 * @param device remote device 1133 * @param index index of the call to connect in private mode 1134 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1135 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 1136 * <p>Feature required for successful execution is being reported by: {@link 1137 * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not 1138 * supported. 1139 * @hide 1140 */ 1141 @RequiresBluetoothConnectPermission 1142 @RequiresPermission(BLUETOOTH_CONNECT) enterPrivateMode(BluetoothDevice device, int index)1143 public boolean enterPrivateMode(BluetoothDevice device, int index) { 1144 if (DBG) log("enterPrivateMode()"); 1145 final IBluetoothHeadsetClient service = getService(); 1146 if (service == null) { 1147 Log.w(TAG, "Proxy not attached to service"); 1148 if (DBG) log(Log.getStackTraceString(new Throwable())); 1149 } else if (isEnabled() && isValidDevice(device)) { 1150 try { 1151 return service.enterPrivateMode(device, index, mAttributionSource); 1152 } catch (RemoteException e) { 1153 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1154 } 1155 } 1156 return false; 1157 } 1158 1159 /** 1160 * Performs explicit call transfer. 1161 * 1162 * <p>That means connect other calls and disconnect. 1163 * 1164 * @param device remote device 1165 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1166 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 1167 * <p>Feature required for successful execution is being reported by: {@link 1168 * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when 1169 * feature is not supported. 1170 * @hide 1171 */ 1172 @RequiresBluetoothConnectPermission 1173 @RequiresPermission(BLUETOOTH_CONNECT) explicitCallTransfer(BluetoothDevice device)1174 public boolean explicitCallTransfer(BluetoothDevice device) { 1175 if (DBG) log("explicitCallTransfer()"); 1176 final IBluetoothHeadsetClient service = getService(); 1177 if (service == null) { 1178 Log.w(TAG, "Proxy not attached to service"); 1179 if (DBG) log(Log.getStackTraceString(new Throwable())); 1180 } else if (isEnabled() && isValidDevice(device)) { 1181 try { 1182 return service.explicitCallTransfer(device, mAttributionSource); 1183 } catch (RemoteException e) { 1184 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1185 } 1186 } 1187 return false; 1188 } 1189 1190 /** 1191 * Places a call with specified number. 1192 * 1193 * @param device remote device 1194 * @param number valid phone number 1195 * @return <code>{@link BluetoothHeadsetClientCall} call</code> if command has been issued 1196 * successfully; <code>{@code null}</code> otherwise; upon completion HFP sends {@link 1197 * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent 1198 * otherwise; 1199 * @hide 1200 */ 1201 @RequiresBluetoothConnectPermission 1202 @RequiresPermission(BLUETOOTH_CONNECT) dial(BluetoothDevice device, String number)1203 public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) { 1204 if (DBG) log("dial()"); 1205 final IBluetoothHeadsetClient service = getService(); 1206 if (service == null) { 1207 Log.w(TAG, "Proxy not attached to service"); 1208 if (DBG) log(Log.getStackTraceString(new Throwable())); 1209 } else if (isEnabled() && isValidDevice(device)) { 1210 try { 1211 return Attributable.setAttributionSource( 1212 service.dial(device, number, mAttributionSource), mAttributionSource); 1213 } catch (RemoteException e) { 1214 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1215 } 1216 } 1217 return null; 1218 } 1219 1220 /** 1221 * Sends DTMF code. 1222 * 1223 * <p>Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,# 1224 * 1225 * @param device remote device 1226 * @param code ASCII code 1227 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1228 * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent; 1229 * @hide 1230 */ 1231 @RequiresBluetoothConnectPermission 1232 @RequiresPermission(BLUETOOTH_CONNECT) sendDTMF(BluetoothDevice device, byte code)1233 public boolean sendDTMF(BluetoothDevice device, byte code) { 1234 if (DBG) log("sendDTMF()"); 1235 final IBluetoothHeadsetClient service = getService(); 1236 if (service == null) { 1237 Log.w(TAG, "Proxy not attached to service"); 1238 if (DBG) log(Log.getStackTraceString(new Throwable())); 1239 } else if (isEnabled() && isValidDevice(device)) { 1240 try { 1241 return service.sendDTMF(device, code, mAttributionSource); 1242 } catch (RemoteException e) { 1243 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1244 } 1245 } 1246 return false; 1247 } 1248 1249 /** 1250 * Get a number corresponding to last voice tag recorded on AG. 1251 * 1252 * @param device remote device 1253 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1254 * otherwise; upon completion HFP sends {@link #ACTION_LAST_VTAG} or {@link #ACTION_RESULT} 1255 * intent; 1256 * <p>Feature required for successful execution is being reported by: {@link 1257 * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when 1258 * feature is not supported. 1259 * @hide 1260 */ 1261 @RequiresBluetoothConnectPermission 1262 @RequiresPermission(BLUETOOTH_CONNECT) getLastVoiceTagNumber(BluetoothDevice device)1263 public boolean getLastVoiceTagNumber(BluetoothDevice device) { 1264 if (DBG) log("getLastVoiceTagNumber()"); 1265 final IBluetoothHeadsetClient service = getService(); 1266 if (service == null) { 1267 Log.w(TAG, "Proxy not attached to service"); 1268 if (DBG) log(Log.getStackTraceString(new Throwable())); 1269 } else if (isEnabled() && isValidDevice(device)) { 1270 try { 1271 return service.getLastVoiceTagNumber(device, mAttributionSource); 1272 } catch (RemoteException e) { 1273 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1274 } 1275 } 1276 return false; 1277 } 1278 1279 /** 1280 * Returns current audio state of Audio Gateway. 1281 * 1282 * <p>Note: This is an internal function and shouldn't be exposed 1283 * 1284 * @hide 1285 */ 1286 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1287 @RequiresBluetoothConnectPermission 1288 @RequiresPermission(BLUETOOTH_CONNECT) getAudioState(BluetoothDevice device)1289 public int getAudioState(BluetoothDevice device) { 1290 if (VDBG) log("getAudioState"); 1291 final IBluetoothHeadsetClient service = getService(); 1292 if (service == null) { 1293 Log.w(TAG, "Proxy not attached to service"); 1294 if (DBG) log(Log.getStackTraceString(new Throwable())); 1295 } else if (isEnabled()) { 1296 try { 1297 return service.getAudioState(device, mAttributionSource); 1298 } catch (RemoteException e) { 1299 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1300 } 1301 } 1302 return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; 1303 } 1304 1305 /** 1306 * Sets whether audio routing is allowed. 1307 * 1308 * @param device remote device 1309 * @param allowed if routing is allowed to the device Note: This is an internal function and 1310 * shouldn't be exposed 1311 * @hide 1312 */ 1313 @RequiresBluetoothConnectPermission 1314 @RequiresPermission(BLUETOOTH_CONNECT) setAudioRouteAllowed(BluetoothDevice device, boolean allowed)1315 public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) { 1316 if (VDBG) log("setAudioRouteAllowed"); 1317 final IBluetoothHeadsetClient service = getService(); 1318 if (service == null) { 1319 Log.w(TAG, "Proxy not attached to service"); 1320 if (DBG) log(Log.getStackTraceString(new Throwable())); 1321 } else if (isEnabled()) { 1322 try { 1323 service.setAudioRouteAllowed(device, allowed, mAttributionSource); 1324 } catch (RemoteException e) { 1325 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1326 } 1327 } 1328 } 1329 1330 /** 1331 * Returns whether audio routing is allowed. 1332 * 1333 * @param device remote device 1334 * @return whether the command succeeded Note: This is an internal function and shouldn't be 1335 * exposed 1336 * @hide 1337 */ 1338 @RequiresBluetoothConnectPermission 1339 @RequiresPermission(BLUETOOTH_CONNECT) getAudioRouteAllowed(BluetoothDevice device)1340 public boolean getAudioRouteAllowed(BluetoothDevice device) { 1341 if (VDBG) log("getAudioRouteAllowed"); 1342 final IBluetoothHeadsetClient service = getService(); 1343 if (service == null) { 1344 Log.w(TAG, "Proxy not attached to service"); 1345 if (DBG) log(Log.getStackTraceString(new Throwable())); 1346 } else if (isEnabled()) { 1347 try { 1348 return service.getAudioRouteAllowed(device, mAttributionSource); 1349 } catch (RemoteException e) { 1350 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1351 } 1352 } 1353 return false; 1354 } 1355 1356 /** 1357 * Initiates a connection of audio channel. 1358 * 1359 * <p>It setup SCO channel with remote connected Handsfree AG device. 1360 * 1361 * @param device remote device 1362 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1363 * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; 1364 * @hide 1365 */ 1366 @RequiresBluetoothConnectPermission 1367 @RequiresPermission(BLUETOOTH_CONNECT) connectAudio(BluetoothDevice device)1368 public boolean connectAudio(BluetoothDevice device) { 1369 if (VDBG) log("connectAudio"); 1370 final IBluetoothHeadsetClient service = getService(); 1371 if (service == null) { 1372 Log.w(TAG, "Proxy not attached to service"); 1373 if (DBG) log(Log.getStackTraceString(new Throwable())); 1374 } else if (isEnabled()) { 1375 try { 1376 return service.connectAudio(device, mAttributionSource); 1377 } catch (RemoteException e) { 1378 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1379 } 1380 } 1381 return false; 1382 } 1383 1384 /** 1385 * Disconnects audio channel. 1386 * 1387 * <p>It tears down the SCO channel from remote AG device. 1388 * 1389 * @param device remote device 1390 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1391 * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; 1392 * @hide 1393 */ 1394 @RequiresBluetoothConnectPermission 1395 @RequiresPermission(BLUETOOTH_CONNECT) disconnectAudio(BluetoothDevice device)1396 public boolean disconnectAudio(BluetoothDevice device) { 1397 if (VDBG) log("disconnectAudio"); 1398 final IBluetoothHeadsetClient service = getService(); 1399 if (service == null) { 1400 Log.w(TAG, "Proxy not attached to service"); 1401 if (DBG) log(Log.getStackTraceString(new Throwable())); 1402 } else if (isEnabled()) { 1403 try { 1404 return service.disconnectAudio(device, mAttributionSource); 1405 } catch (RemoteException e) { 1406 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1407 } 1408 } 1409 return false; 1410 } 1411 1412 /** 1413 * Get Audio Gateway features 1414 * 1415 * @param device remote device 1416 * @return bundle of AG features; null if no service or AG not connected 1417 * @hide 1418 */ 1419 @RequiresBluetoothConnectPermission 1420 @RequiresPermission(BLUETOOTH_CONNECT) getCurrentAgFeatures(BluetoothDevice device)1421 public Bundle getCurrentAgFeatures(BluetoothDevice device) { 1422 if (VDBG) log("getCurrentAgFeatures"); 1423 final IBluetoothHeadsetClient service = getService(); 1424 if (service == null) { 1425 Log.w(TAG, "Proxy not attached to service"); 1426 if (DBG) log(Log.getStackTraceString(new Throwable())); 1427 } else if (isEnabled()) { 1428 try { 1429 return service.getCurrentAgFeatures(device, mAttributionSource); 1430 } catch (RemoteException e) { 1431 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1432 } 1433 } 1434 return null; 1435 } 1436 1437 /** 1438 * A class that contains the network service info provided by the HFP Client profile 1439 * 1440 * @hide 1441 */ 1442 @SystemApi 1443 public static final class NetworkServiceState implements Parcelable { 1444 /** The device associated with this service state */ 1445 private final BluetoothDevice mDevice; 1446 1447 /** True if there is service available, False otherwise */ 1448 private final boolean mIsServiceAvailable; 1449 1450 /** The name of the operator associated with the remote device's current network */ 1451 private final String mOperatorName; 1452 1453 /** The general signal strength, from 0 to 5 */ 1454 private final int mSignalStrength; 1455 1456 /** True if we are network roaming, False otherwise */ 1457 private final boolean mIsRoaming; 1458 1459 /** 1460 * Create a NetworkServiceState Object 1461 * 1462 * @param device The device associated with this network signal state 1463 * @param isServiceAvailable True if there is service available, False otherwise 1464 * @param operatorName The name of the operator associated with the remote device's current 1465 * network. Use Null if the value is unknown 1466 * @param signalStrength The general signal strength 1467 * @param isRoaming True if we are network roaming, False otherwise 1468 * @hide 1469 */ NetworkServiceState( BluetoothDevice device, boolean isServiceAvailable, String operatorName, int signalStrength, boolean isRoaming)1470 public NetworkServiceState( 1471 BluetoothDevice device, 1472 boolean isServiceAvailable, 1473 String operatorName, 1474 int signalStrength, 1475 boolean isRoaming) { 1476 mDevice = requireNonNull(device); 1477 mIsServiceAvailable = isServiceAvailable; 1478 mOperatorName = operatorName; 1479 mSignalStrength = signalStrength; 1480 mIsRoaming = isRoaming; 1481 } 1482 1483 /** 1484 * Get the device associated with this network service state 1485 * 1486 * @return a BluetoothDevice associated with this state 1487 * @hide 1488 */ 1489 @SystemApi getDevice()1490 public @NonNull BluetoothDevice getDevice() { 1491 return mDevice; 1492 } 1493 1494 /** 1495 * Get the network service availability state 1496 * 1497 * @return True if there is service available, False otherwise 1498 * @hide 1499 */ 1500 @SystemApi isServiceAvailable()1501 public boolean isServiceAvailable() { 1502 return mIsServiceAvailable; 1503 } 1504 1505 /** 1506 * Get the network operator name 1507 * 1508 * @return A string representing the name of the operator the remote device is on, or null 1509 * if unknown. 1510 * @hide 1511 */ 1512 @SystemApi getNetworkOperatorName()1513 public @Nullable String getNetworkOperatorName() { 1514 return mOperatorName; 1515 } 1516 1517 /** 1518 * The HFP Client defined signal strength, from 0 to 5. 1519 * 1520 * <p>Bluetooth HFP v1.8 specifies that the signal strength of a device can be [0, 5]. It 1521 * does not place any requirements on how a device derives those values. While they're 1522 * typically derived from signal quality/RSSI buckets, there's no way to be certain on the 1523 * exact meaning. Derivation methods can even change between wireless cellular technologies. 1524 * 1525 * <p>That said, you can "generally" interpret the values relative to each other as follows: 1526 * - Level 0: None/Unknown - Level 1: Very Poor - Level 2: Poor - Level 3: Fair - Level 4: 1527 * Good - Level 5: Great 1528 * 1529 * @return the HFP Client defined signal strength, range [0, 5] 1530 * @hide 1531 */ 1532 @SystemApi getSignalStrength()1533 public @IntRange(from = 0, to = 5) int getSignalStrength() { 1534 return mSignalStrength; 1535 } 1536 1537 /** 1538 * Get the network service roaming status 1539 * 1540 * <p>* @return True if we are network roaming, False otherwise 1541 * 1542 * @hide 1543 */ 1544 @SystemApi isRoaming()1545 public boolean isRoaming() { 1546 return mIsRoaming; 1547 } 1548 1549 /** {@link Parcelable.Creator} interface implementation. */ 1550 public static final @NonNull Parcelable.Creator<NetworkServiceState> CREATOR = 1551 new Parcelable.Creator<NetworkServiceState>() { 1552 public NetworkServiceState createFromParcel(Parcel in) { 1553 return new NetworkServiceState( 1554 BluetoothDevice.CREATOR.createFromParcel(in), 1555 in.readInt() == 1, 1556 in.readString(), 1557 in.readInt(), 1558 in.readInt() == 1); 1559 } 1560 1561 public @NonNull NetworkServiceState[] newArray(int size) { 1562 return new NetworkServiceState[size]; 1563 } 1564 }; 1565 1566 /** @hide */ 1567 @Override writeToParcel(@onNull Parcel out, int flags)1568 public void writeToParcel(@NonNull Parcel out, int flags) { 1569 mDevice.writeToParcel(out, flags); 1570 out.writeInt(mIsServiceAvailable ? 1 : 0); 1571 BluetoothUtils.writeStringToParcel(out, mOperatorName); 1572 out.writeInt(mSignalStrength); 1573 out.writeInt(mIsRoaming ? 1 : 0); 1574 } 1575 1576 /** @hide */ 1577 @Override describeContents()1578 public int describeContents() { 1579 return 0; 1580 } 1581 } 1582 1583 /** 1584 * Intent used to broadcast the change in network service state of an HFP Client device 1585 * 1586 * <p>This intent will have 2 extras: 1587 * 1588 * <ul> 1589 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 1590 * <li>{@link EXTRA_NETWORK_SERVICE_STATE} - A {@link NetworkServiceState} object. 1591 * </ul> 1592 * 1593 * @hide 1594 */ 1595 @SuppressLint("ActionValue") 1596 @SystemApi 1597 @RequiresBluetoothConnectPermission 1598 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) 1599 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 1600 public static final String ACTION_NETWORK_SERVICE_STATE_CHANGED = 1601 "android.bluetooth.headsetclient.profile.action.NETWORK_SERVICE_STATE_CHANGED"; 1602 1603 /** 1604 * Extra for the network service state changed intent. 1605 * 1606 * <p>This extra represents the current network service state of a connected Bluetooth device. 1607 * 1608 * @hide 1609 */ 1610 @SuppressLint("ActionValue") 1611 @SystemApi 1612 public static final String EXTRA_NETWORK_SERVICE_STATE = 1613 "android.bluetooth.headsetclient.extra.EXTRA_NETWORK_SERVICE_STATE"; 1614 1615 /** 1616 * Get the network service state for a device 1617 * 1618 * @param device The {@link BluetoothDevice} you want the network service state for 1619 * @return A {@link NetworkServiceState} representing the network service state of the device, 1620 * or null if the device is not connected 1621 * @hide 1622 */ 1623 @SystemApi 1624 @RequiresBluetoothConnectPermission 1625 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) getNetworkServiceState(@onNull BluetoothDevice device)1626 public @Nullable NetworkServiceState getNetworkServiceState(@NonNull BluetoothDevice device) { 1627 if (device == null) { 1628 return null; 1629 } 1630 1631 Bundle agEvents = getCurrentAgEvents(device); 1632 if (agEvents == null) { 1633 return null; 1634 } 1635 1636 boolean isServiceAvailable = (agEvents.getInt(EXTRA_NETWORK_STATUS, 0) == 1); 1637 int signalStrength = agEvents.getInt(EXTRA_NETWORK_SIGNAL_STRENGTH, 0); 1638 String operatorName = agEvents.getString(EXTRA_OPERATOR_NAME, null); 1639 boolean isRoaming = (agEvents.getInt(EXTRA_NETWORK_ROAMING, 0) == 1); 1640 1641 return new NetworkServiceState( 1642 device, isServiceAvailable, operatorName, signalStrength, isRoaming); 1643 } 1644 isEnabled()1645 private boolean isEnabled() { 1646 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 1647 } 1648 log(String msg)1649 private static void log(String msg) { 1650 Log.d(TAG, msg); 1651 } 1652 } 1653