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