1 /* 2 * Copyright 2019 HIMSA II K/S - www.himsa.com. 3 * Represented by EHIMA - www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package android.bluetooth; 19 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SuppressLint; 25 import android.content.AttributionSource; 26 import android.content.Context; 27 import android.os.Binder; 28 import android.os.IBinder; 29 import android.os.ParcelUuid; 30 import android.os.RemoteException; 31 import android.util.Log; 32 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.UUID; 38 import java.util.concurrent.Executor; 39 40 /** 41 * This class provides the APIs to control the Call Control profile. 42 * 43 * <p> 44 * This class provides Bluetooth Telephone Bearer Service functionality, 45 * allowing applications to expose a GATT Service based interface to control the 46 * state of the calls by remote devices such as LE audio devices. 47 * 48 * <p> 49 * BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer 50 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the 51 * BluetoothLeCallControl proxy object. 52 * 53 * @hide 54 */ 55 public final class BluetoothLeCallControl implements BluetoothProfile { 56 private static final String TAG = "BluetoothLeCallControl"; 57 private static final boolean DBG = true; 58 private static final boolean VDBG = false; 59 60 /** @hide */ 61 @IntDef(prefix = "RESULT_", value = { 62 RESULT_SUCCESS, 63 RESULT_ERROR_UNKNOWN_CALL_ID, 64 RESULT_ERROR_INVALID_URI, 65 RESULT_ERROR_APPLICATION 66 }) 67 @Retention(RetentionPolicy.SOURCE) 68 public @interface Result { 69 } 70 71 /** 72 * Opcode write was successful. 73 * 74 * @hide 75 */ 76 public static final int RESULT_SUCCESS = 0; 77 78 /** 79 * Unknown call Id has been used in the operation. 80 * 81 * @hide 82 */ 83 public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1; 84 85 /** 86 * The URI provided in {@link Callback#onPlaceCallRequest} is invalid. 87 * 88 * @hide 89 */ 90 public static final int RESULT_ERROR_INVALID_URI = 2; 91 92 /** 93 * Application internal error. 94 * 95 * @hide 96 */ 97 public static final int RESULT_ERROR_APPLICATION = 3; 98 99 /** @hide */ 100 @IntDef(prefix = "TERMINATION_REASON_", value = { 101 TERMINATION_REASON_INVALID_URI, 102 TERMINATION_REASON_FAIL, 103 TERMINATION_REASON_REMOTE_HANGUP, 104 TERMINATION_REASON_SERVER_HANGUP, 105 TERMINATION_REASON_LINE_BUSY, 106 TERMINATION_REASON_NETWORK_CONGESTION, 107 TERMINATION_REASON_CLIENT_HANGUP, 108 TERMINATION_REASON_NO_SERVICE, 109 TERMINATION_REASON_NO_ANSWER 110 }) 111 @Retention(RetentionPolicy.SOURCE) 112 public @interface TerminationReason { 113 } 114 115 /** 116 * Remote Caller ID value used to place a call was formed improperly. 117 * 118 * @hide 119 */ 120 public static final int TERMINATION_REASON_INVALID_URI = 0x00; 121 122 /** 123 * Call fail. 124 * 125 * @hide 126 */ 127 public static final int TERMINATION_REASON_FAIL = 0x01; 128 129 /** 130 * Remote party ended call. 131 * 132 * @hide 133 */ 134 public static final int TERMINATION_REASON_REMOTE_HANGUP = 0x02; 135 136 /** 137 * Call ended from the server. 138 * 139 * @hide 140 */ 141 public static final int TERMINATION_REASON_SERVER_HANGUP = 0x03; 142 143 /** 144 * Line busy. 145 * 146 * @hide 147 */ 148 public static final int TERMINATION_REASON_LINE_BUSY = 0x04; 149 150 /** 151 * Network congestion. 152 * 153 * @hide 154 */ 155 public static final int TERMINATION_REASON_NETWORK_CONGESTION = 0x05; 156 157 /** 158 * Client terminated. 159 * 160 * @hide 161 */ 162 public static final int TERMINATION_REASON_CLIENT_HANGUP = 0x06; 163 164 /** 165 * No service. 166 * 167 * @hide 168 */ 169 public static final int TERMINATION_REASON_NO_SERVICE = 0x07; 170 171 /** 172 * No answer. 173 * 174 * @hide 175 */ 176 public static final int TERMINATION_REASON_NO_ANSWER = 0x08; 177 178 /* 179 * Flag indicating support for hold/unhold call feature. 180 * 181 * @hide 182 */ 183 public static final int CAPABILITY_HOLD_CALL = 0x00000001; 184 185 /** 186 * Flag indicating support for joining calls feature. 187 * 188 * @hide 189 */ 190 public static final int CAPABILITY_JOIN_CALLS = 0x00000002; 191 192 private static final int REG_TIMEOUT = 10000; 193 194 /** 195 * The template class is used to call callback functions on events from the TBS 196 * server. Callback functions are wrapped in this class and registered to the 197 * Android system during app registration. 198 * 199 * @hide 200 */ 201 public abstract static class Callback { 202 203 private static final String TAG = "BluetoothLeCallControl.Callback"; 204 205 /** 206 * Called when a remote client requested to accept the call. 207 * 208 * <p> 209 * An application must call {@link BluetoothLeCallControl#requestResult} to complete the 210 * request. 211 * 212 * @param requestId The Id of the request 213 * @param callId The call Id requested to be accepted 214 * @hide 215 */ onAcceptCall(int requestId, @NonNull UUID callId)216 public abstract void onAcceptCall(int requestId, @NonNull UUID callId); 217 218 /** 219 * A remote client has requested to terminate the call. 220 * 221 * <p> 222 * An application must call {@link BluetoothLeCallControl#requestResult} to complete the 223 * request. 224 * 225 * @param requestId The Id of the request 226 * @param callId The call Id requested to terminate 227 * @hide 228 */ onTerminateCall(int requestId, @NonNull UUID callId)229 public abstract void onTerminateCall(int requestId, @NonNull UUID callId); 230 231 /** 232 * A remote client has requested to hold the call. 233 * 234 * <p> 235 * An application must call {@link BluetoothLeCallControl#requestResult} to complete the 236 * request. 237 * 238 * @param requestId The Id of the request 239 * @param callId The call Id requested to be put on hold 240 * @hide 241 */ onHoldCall(int requestId, @NonNull UUID callId)242 public void onHoldCall(int requestId, @NonNull UUID callId) { 243 Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); 244 } 245 246 /** 247 * A remote client has requested to unhold the call. 248 * 249 * <p> 250 * An application must call {@link BluetoothLeCallControl#requestResult} to complete the 251 * request. 252 * 253 * @param requestId The Id of the request 254 * @param callId The call Id requested to unhold 255 * @hide 256 */ onUnholdCall(int requestId, @NonNull UUID callId)257 public void onUnholdCall(int requestId, @NonNull UUID callId) { 258 Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); 259 } 260 261 /** 262 * A remote client has requested to place a call. 263 * 264 * <p> 265 * An application must call {@link BluetoothLeCallControl#requestResult} to complete the 266 * request. 267 * 268 * @param requestId The Id of the request 269 * @param callId The Id to be assigned for the new call 270 * @param uri The caller URI requested 271 * @hide 272 */ onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri)273 public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri); 274 275 /** 276 * A remote client has requested to join the calls. 277 * 278 * <p> 279 * An application must call {@link BluetoothLeCallControl#requestResult} to complete the 280 * request. 281 * 282 * @param requestId The Id of the request 283 * @param callIds The call Id list requested to join 284 * @hide 285 */ onJoinCalls(int requestId, @NonNull List<UUID> callIds)286 public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) { 287 Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!"); 288 } 289 } 290 291 private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub { 292 293 private final Executor mExecutor; 294 private final Callback mCallback; 295 CallbackWrapper(Executor executor, Callback callback)296 CallbackWrapper(Executor executor, Callback callback) { 297 mExecutor = executor; 298 mCallback = callback; 299 } 300 301 @Override onBearerRegistered(int ccid)302 public void onBearerRegistered(int ccid) { 303 if (mCallback != null) { 304 mCcid = ccid; 305 } else { 306 // registration timeout 307 Log.e(TAG, "onBearerRegistered: mCallback is null"); 308 } 309 } 310 311 @Override onAcceptCall(int requestId, ParcelUuid uuid)312 public void onAcceptCall(int requestId, ParcelUuid uuid) { 313 final long identityToken = Binder.clearCallingIdentity(); 314 try { 315 mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid())); 316 } finally { 317 Binder.restoreCallingIdentity(identityToken); 318 } 319 } 320 321 @Override onTerminateCall(int requestId, ParcelUuid uuid)322 public void onTerminateCall(int requestId, ParcelUuid uuid) { 323 final long identityToken = Binder.clearCallingIdentity(); 324 try { 325 mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid())); 326 } finally { 327 Binder.restoreCallingIdentity(identityToken); 328 } 329 } 330 331 @Override onHoldCall(int requestId, ParcelUuid uuid)332 public void onHoldCall(int requestId, ParcelUuid uuid) { 333 final long identityToken = Binder.clearCallingIdentity(); 334 try { 335 mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid())); 336 } finally { 337 Binder.restoreCallingIdentity(identityToken); 338 } 339 } 340 341 @Override onUnholdCall(int requestId, ParcelUuid uuid)342 public void onUnholdCall(int requestId, ParcelUuid uuid) { 343 final long identityToken = Binder.clearCallingIdentity(); 344 try { 345 mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid())); 346 } finally { 347 Binder.restoreCallingIdentity(identityToken); 348 } 349 } 350 351 @Override onPlaceCall(int requestId, ParcelUuid uuid, String uri)352 public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) { 353 final long identityToken = Binder.clearCallingIdentity(); 354 try { 355 mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri)); 356 } finally { 357 Binder.restoreCallingIdentity(identityToken); 358 } 359 } 360 361 @Override onJoinCalls(int requestId, List<ParcelUuid> parcelUuids)362 public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) { 363 List<UUID> uuids = new ArrayList<>(); 364 for (ParcelUuid parcelUuid : parcelUuids) { 365 uuids.add(parcelUuid.getUuid()); 366 } 367 368 final long identityToken = Binder.clearCallingIdentity(); 369 try { 370 mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids)); 371 } finally { 372 Binder.restoreCallingIdentity(identityToken); 373 } 374 } 375 }; 376 377 private BluetoothAdapter mAdapter; 378 private final AttributionSource mAttributionSource; 379 private int mCcid = 0; 380 private String mToken; 381 private Callback mCallback = null; 382 private final BluetoothProfileConnector<IBluetoothLeCallControl> mProfileConnector = 383 new BluetoothProfileConnector(this, BluetoothProfile.LE_CALL_CONTROL, 384 "BluetoothLeCallControl", IBluetoothLeCallControl.class.getName()) { 385 @Override 386 public IBluetoothLeCallControl getServiceInterface(IBinder service) { 387 return IBluetoothLeCallControl.Stub.asInterface(service); 388 } 389 }; 390 391 392 /** 393 * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth 394 * telephone bearer service. 395 */ BluetoothLeCallControl(Context context, ServiceListener listener)396 /* package */ BluetoothLeCallControl(Context context, ServiceListener listener) { 397 mAdapter = BluetoothAdapter.getDefaultAdapter(); 398 mAttributionSource = mAdapter.getAttributionSource(); 399 mProfileConnector.connect(context, listener); 400 } 401 close()402 /* package */ void close() { 403 if (VDBG) 404 log("close()"); 405 unregisterBearer(); 406 407 mProfileConnector.disconnect(); 408 } 409 getService()410 private IBluetoothLeCallControl getService() { 411 return mProfileConnector.getService(); 412 } 413 414 /** 415 * Not supported 416 * 417 * @throws UnsupportedOperationException 418 */ 419 @Override getConnectionState(@ullable BluetoothDevice device)420 public int getConnectionState(@Nullable BluetoothDevice device) { 421 throw new UnsupportedOperationException("not supported"); 422 } 423 424 /** 425 * Not supported 426 * 427 * @throws UnsupportedOperationException 428 */ 429 @Override getConnectedDevices()430 public @NonNull List<BluetoothDevice> getConnectedDevices() { 431 throw new UnsupportedOperationException("not supported"); 432 } 433 434 /** 435 * Not supported 436 * 437 * @throws UnsupportedOperationException 438 */ 439 @Override getDevicesMatchingConnectionStates( @onNull int[] states)440 public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( 441 @NonNull int[] states) { 442 throw new UnsupportedOperationException("not supported"); 443 } 444 445 /** 446 * Register Telephone Bearer exposing the interface that allows remote devices 447 * to track and control the call states. 448 * 449 * <p> 450 * This is an asynchronous call. The callback is used to notify success or 451 * failure if the function returns true. 452 * 453 * <p> 454 * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 455 * 456 * <!-- The UCI is a String identifier of the telephone bearer as defined at 457 * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers 458 * (login required). --> 459 * 460 * <!-- The examples of common URI schemes can be found in 461 * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml --> 462 * 463 * <!-- The Technology is an integer value. The possible values are defined at 464 * https://www.bluetooth.com/specifications/assigned-numbers (login required). 465 * --> 466 * 467 * @param uci Bearer Unique Client Identifier 468 * @param uriSchemes URI Schemes supported list 469 * @param capabilities bearer capabilities 470 * @param provider Network provider name 471 * @param technology Network technology 472 * @param executor {@link Executor} object on which callback will be 473 * executed. The Executor object is required. 474 * @param callback {@link Callback} object to which callback messages will 475 * be sent. The Callback object is required. 476 * @return true on success, false otherwise 477 * @hide 478 */ 479 @SuppressLint("ExecutorRegistration") 480 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) registerBearer(@ullable String uci, @NonNull List<String> uriSchemes, int capabilities, @NonNull String provider, int technology, @NonNull Executor executor, @NonNull Callback callback)481 public boolean registerBearer(@Nullable String uci, 482 @NonNull List<String> uriSchemes, int capabilities, 483 @NonNull String provider, int technology, 484 @NonNull Executor executor, @NonNull Callback callback) { 485 if (DBG) { 486 Log.d(TAG, "registerBearer"); 487 } 488 if (callback == null) { 489 throw new IllegalArgumentException("null parameter: " + callback); 490 } 491 if (mCcid != 0) { 492 return false; 493 } 494 495 mToken = uci; 496 497 final IBluetoothLeCallControl service = getService(); 498 if (service == null) { 499 Log.w(TAG, "Proxy not attached to service"); 500 return false; 501 } 502 503 if (mCallback != null) { 504 Log.e(TAG, "Bearer can be opened only once"); 505 return false; 506 } 507 508 mCallback = callback; 509 try { 510 CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback); 511 service.registerBearer(mToken, callbackWrapper, uci, uriSchemes, capabilities, 512 provider, technology, mAttributionSource); 513 514 } catch (RemoteException e) { 515 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 516 mCallback = null; 517 return false; 518 } 519 520 if (mCcid == 0) { 521 mCallback = null; 522 return false; 523 } 524 525 return true; 526 } 527 528 /** 529 * Unregister Telephone Bearer Service and destroy all the associated data. 530 * 531 * @hide 532 */ 533 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) unregisterBearer()534 public void unregisterBearer() { 535 if (DBG) { 536 Log.d(TAG, "unregisterBearer"); 537 } 538 if (mCcid == 0) { 539 return; 540 } 541 542 final IBluetoothLeCallControl service = getService(); 543 if (service == null) { 544 Log.w(TAG, "Proxy not attached to service"); 545 return; 546 } 547 548 int ccid = mCcid; 549 mCcid = 0; 550 mCallback = null; 551 552 try { 553 service.unregisterBearer(mToken, mAttributionSource); 554 } catch (RemoteException e) { 555 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 556 } 557 } 558 559 /** 560 * Get the Content Control ID (CCID) value. 561 * 562 * @return ccid Content Control ID value 563 * @hide 564 */ 565 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getContentControlId()566 public int getContentControlId() { 567 return mCcid; 568 } 569 570 /** 571 * Notify about the newly added call. 572 * 573 * <p> 574 * This shall be called as early as possible after the call has been added. 575 * 576 * <p> 577 * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 578 * 579 * @param call Newly added call 580 * @hide 581 */ 582 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) onCallAdded(@onNull BluetoothLeCall call)583 public void onCallAdded(@NonNull BluetoothLeCall call) { 584 if (DBG) { 585 Log.d(TAG, "onCallAdded: call=" + call); 586 } 587 if (mCcid == 0) { 588 return; 589 } 590 591 final IBluetoothLeCallControl service = getService(); 592 if (service == null) { 593 Log.w(TAG, "Proxy not attached to service"); 594 return; 595 } 596 597 try { 598 service.callAdded(mCcid, call, mAttributionSource); 599 } catch (RemoteException e) { 600 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 601 } 602 } 603 604 /** 605 * Notify about the removed call. 606 * 607 * <p> 608 * This shall be called as early as possible after the call has been removed. 609 * 610 * <p> 611 * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 612 * 613 * @param callId The Id of a call that has been removed 614 * @param reason Call termination reason 615 * @hide 616 */ 617 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) onCallRemoved(@onNull UUID callId, @TerminationReason int reason)618 public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) { 619 if (DBG) { 620 Log.d(TAG, "callRemoved: callId=" + callId); 621 } 622 if (mCcid == 0) { 623 return; 624 } 625 626 final IBluetoothLeCallControl service = getService(); 627 if (service == null) { 628 Log.w(TAG, "Proxy not attached to service"); 629 return; 630 } 631 try { 632 service.callRemoved(mCcid, new ParcelUuid(callId), reason, mAttributionSource); 633 } catch (RemoteException e) { 634 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 635 } 636 637 } 638 639 /** 640 * Notify the call state change 641 * 642 * <p> 643 * This shall be called as early as possible after the state of the call has 644 * changed. 645 * 646 * <p> 647 * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 648 * 649 * @param callId The call Id that state has been changed 650 * @param state Call state 651 * @hide 652 */ 653 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) onCallStateChanged(@onNull UUID callId, @BluetoothLeCall.State int state)654 public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) { 655 if (DBG) { 656 Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state); 657 } 658 if (mCcid == 0) { 659 return; 660 } 661 662 final IBluetoothLeCallControl service = getService(); 663 if (service == null) { 664 Log.w(TAG, "Proxy not attached to service"); 665 return; 666 } 667 668 try { 669 service.callStateChanged(mCcid, new ParcelUuid(callId), state, 670 mAttributionSource); 671 } catch (RemoteException e) { 672 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 673 } 674 } 675 676 /** 677 * Provide the current calls list 678 * 679 * <p> 680 * This function must be invoked after registration if application has any 681 * calls. 682 * 683 * @param calls current calls list 684 * @hide 685 */ 686 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) currentCallsList(@onNull List<BluetoothLeCall> calls)687 public void currentCallsList(@NonNull List<BluetoothLeCall> calls) { 688 final IBluetoothLeCallControl service = getService(); 689 if (service == null) { 690 Log.w(TAG, "Proxy not attached to service"); 691 return; 692 } 693 694 try { 695 service.currentCallsList(mCcid, calls, mAttributionSource); 696 } catch (RemoteException e) { 697 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 698 } 699 700 } 701 702 /** 703 * Provide the network current status 704 * 705 * <p> 706 * This function must be invoked on change of network state. 707 * 708 * <p> 709 * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 710 * 711 * <!-- The Technology is an integer value. The possible values are defined at 712 * https://www.bluetooth.com/specifications/assigned-numbers (login required). 713 * --> 714 * 715 * @param provider Network provider name 716 * @param technology Network technology 717 * @hide 718 */ 719 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) networkStateChanged(@onNull String provider, int technology)720 public void networkStateChanged(@NonNull String provider, int technology) { 721 if (DBG) { 722 Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology); 723 } 724 if (mCcid == 0) { 725 return; 726 } 727 728 final IBluetoothLeCallControl service = getService(); 729 if (service == null) { 730 Log.w(TAG, "Proxy not attached to service"); 731 return; 732 } 733 734 try { 735 service.networkStateChanged(mCcid, provider, technology, mAttributionSource); 736 } catch (RemoteException e) { 737 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 738 } 739 } 740 741 /** 742 * Send a response to a call control request to a remote device. 743 * 744 * <p> 745 * This function must be invoked in when a request is received by one of these 746 * callback methods: 747 * 748 * <ul> 749 * <li>{@link Callback#onAcceptCall} 750 * <li>{@link Callback#onTerminateCall} 751 * <li>{@link Callback#onHoldCall} 752 * <li>{@link Callback#onUnholdCall} 753 * <li>{@link Callback#onPlaceCall} 754 * <li>{@link Callback#onJoinCalls} 755 * </ul> 756 * 757 * @param requestId The ID of the request that was received with the callback 758 * @param result The result of the request to be sent to the remote devices 759 */ 760 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) requestResult(int requestId, @Result int result)761 public void requestResult(int requestId, @Result int result) { 762 if (DBG) { 763 Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result); 764 } 765 if (mCcid == 0) { 766 return; 767 } 768 769 final IBluetoothLeCallControl service = getService(); 770 if (service == null) { 771 Log.w(TAG, "Proxy not attached to service"); 772 return; 773 } 774 775 try { 776 service.requestResult(mCcid, requestId, result, mAttributionSource); 777 } catch (RemoteException e) { 778 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 779 } 780 } 781 782 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) isValidDevice(@ullable BluetoothDevice device)783 private static boolean isValidDevice(@Nullable BluetoothDevice device) { 784 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 785 } 786 log(String msg)787 private static void log(String msg) { 788 Log.d(TAG, msg); 789 } 790 } 791