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>This class provides Bluetooth Telephone Bearer Service functionality, allowing applications to 44 * expose a GATT Service based interface to control the state of the calls by remote devices such as 45 * LE audio devices. 46 * 47 * <p>BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer 48 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothLeCallControl 49 * proxy object. 50 * 51 * @hide 52 */ 53 public final class BluetoothLeCallControl implements BluetoothProfile { 54 private static final String TAG = "BluetoothLeCallControl"; 55 private static final boolean DBG = true; 56 private static final boolean VDBG = false; 57 58 /** @hide */ 59 @IntDef( 60 prefix = "RESULT_", 61 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 * Opcode write was successful. 72 * 73 * @hide 74 */ 75 public static final int RESULT_SUCCESS = 0; 76 77 /** 78 * Unknown call Id has been used in the operation. 79 * 80 * @hide 81 */ 82 public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1; 83 84 /** 85 * The URI provided in {@link Callback#onPlaceCallRequest} is invalid. 86 * 87 * @hide 88 */ 89 public static final int RESULT_ERROR_INVALID_URI = 2; 90 91 /** 92 * Application internal error. 93 * 94 * @hide 95 */ 96 public static final int RESULT_ERROR_APPLICATION = 3; 97 98 /** @hide */ 99 @IntDef( 100 prefix = "TERMINATION_REASON_", 101 value = { 102 TERMINATION_REASON_INVALID_URI, 103 TERMINATION_REASON_FAIL, 104 TERMINATION_REASON_REMOTE_HANGUP, 105 TERMINATION_REASON_SERVER_HANGUP, 106 TERMINATION_REASON_LINE_BUSY, 107 TERMINATION_REASON_NETWORK_CONGESTION, 108 TERMINATION_REASON_CLIENT_HANGUP, 109 TERMINATION_REASON_NO_SERVICE, 110 TERMINATION_REASON_NO_ANSWER 111 }) 112 @Retention(RetentionPolicy.SOURCE) 113 public @interface TerminationReason {} 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 /** 193 * The template class is used to call callback functions on events from the TBS server. Callback 194 * functions are wrapped in this class and registered to the Android system during app 195 * registration. 196 * 197 * @hide 198 */ 199 public abstract static class Callback { 200 201 private static final String TAG = "BluetoothLeCallControl.Callback"; 202 203 /** 204 * Called when a remote client requested to accept the call. 205 * 206 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 207 * request. 208 * 209 * @param requestId The Id of the request 210 * @param callId The call Id requested to be accepted 211 * @hide 212 */ onAcceptCall(int requestId, @NonNull UUID callId)213 public abstract void onAcceptCall(int requestId, @NonNull UUID callId); 214 215 /** 216 * A remote client has requested to terminate the call. 217 * 218 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 219 * request. 220 * 221 * @param requestId The Id of the request 222 * @param callId The call Id requested to terminate 223 * @hide 224 */ onTerminateCall(int requestId, @NonNull UUID callId)225 public abstract void onTerminateCall(int requestId, @NonNull UUID callId); 226 227 /** 228 * A remote client has requested to hold the call. 229 * 230 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 231 * request. 232 * 233 * @param requestId The Id of the request 234 * @param callId The call Id requested to be put on hold 235 * @hide 236 */ onHoldCall(int requestId, @NonNull UUID callId)237 public void onHoldCall(int requestId, @NonNull UUID callId) { 238 Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); 239 } 240 241 /** 242 * A remote client has requested to unhold the call. 243 * 244 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 245 * request. 246 * 247 * @param requestId The Id of the request 248 * @param callId The call Id requested to unhold 249 * @hide 250 */ onUnholdCall(int requestId, @NonNull UUID callId)251 public void onUnholdCall(int requestId, @NonNull UUID callId) { 252 Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); 253 } 254 255 /** 256 * A remote client has requested to place a call. 257 * 258 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 259 * request. 260 * 261 * @param requestId The Id of the request 262 * @param callId The Id to be assigned for the new call 263 * @param uri The caller URI requested 264 * @hide 265 */ onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri)266 public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri); 267 268 /** 269 * A remote client has requested to join the calls. 270 * 271 * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the 272 * request. 273 * 274 * @param requestId The Id of the request 275 * @param callIds The call Id list requested to join 276 * @hide 277 */ onJoinCalls(int requestId, @NonNull List<UUID> callIds)278 public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) { 279 Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!"); 280 } 281 } 282 283 private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub { 284 285 private final Executor mExecutor; 286 private final Callback mCallback; 287 CallbackWrapper(Executor executor, Callback callback)288 CallbackWrapper(Executor executor, Callback callback) { 289 mExecutor = executor; 290 mCallback = callback; 291 } 292 293 @Override onBearerRegistered(int ccid)294 public void onBearerRegistered(int ccid) { 295 if (mCallback != null) { 296 mCcid = ccid; 297 } else { 298 // registration timeout 299 Log.e(TAG, "onBearerRegistered: mCallback is null"); 300 } 301 } 302 303 @Override onAcceptCall(int requestId, ParcelUuid uuid)304 public void onAcceptCall(int requestId, ParcelUuid uuid) { 305 final long identityToken = Binder.clearCallingIdentity(); 306 try { 307 mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid())); 308 } finally { 309 Binder.restoreCallingIdentity(identityToken); 310 } 311 } 312 313 @Override onTerminateCall(int requestId, ParcelUuid uuid)314 public void onTerminateCall(int requestId, ParcelUuid uuid) { 315 final long identityToken = Binder.clearCallingIdentity(); 316 try { 317 mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid())); 318 } finally { 319 Binder.restoreCallingIdentity(identityToken); 320 } 321 } 322 323 @Override onHoldCall(int requestId, ParcelUuid uuid)324 public void onHoldCall(int requestId, ParcelUuid uuid) { 325 final long identityToken = Binder.clearCallingIdentity(); 326 try { 327 mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid())); 328 } finally { 329 Binder.restoreCallingIdentity(identityToken); 330 } 331 } 332 333 @Override onUnholdCall(int requestId, ParcelUuid uuid)334 public void onUnholdCall(int requestId, ParcelUuid uuid) { 335 final long identityToken = Binder.clearCallingIdentity(); 336 try { 337 mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid())); 338 } finally { 339 Binder.restoreCallingIdentity(identityToken); 340 } 341 } 342 343 @Override onPlaceCall(int requestId, ParcelUuid uuid, String uri)344 public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) { 345 final long identityToken = Binder.clearCallingIdentity(); 346 try { 347 mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri)); 348 } finally { 349 Binder.restoreCallingIdentity(identityToken); 350 } 351 } 352 353 @Override onJoinCalls(int requestId, List<ParcelUuid> parcelUuids)354 public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) { 355 List<UUID> uuids = new ArrayList<>(); 356 for (ParcelUuid parcelUuid : parcelUuids) { 357 uuids.add(parcelUuid.getUuid()); 358 } 359 360 final long identityToken = Binder.clearCallingIdentity(); 361 try { 362 mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids)); 363 } finally { 364 Binder.restoreCallingIdentity(identityToken); 365 } 366 } 367 } 368 ; 369 370 private BluetoothAdapter mAdapter; 371 private final AttributionSource mAttributionSource; 372 private int mCcid = 0; 373 private String mToken; 374 private Callback mCallback = null; 375 376 private IBluetoothLeCallControl mService; 377 378 /** 379 * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth 380 * telephone bearer service. 381 */ BluetoothLeCallControl(Context context, BluetoothAdapter adapter)382 /* package */ BluetoothLeCallControl(Context context, BluetoothAdapter adapter) { 383 mAdapter = adapter; 384 mAttributionSource = mAdapter.getAttributionSource(); 385 mService = null; 386 } 387 388 /** @hide */ close()389 public void close() { 390 if (VDBG) log("close()"); 391 392 mAdapter.closeProfileProxy(this); 393 } 394 395 /** @hide */ 396 @Override onServiceConnected(IBinder service)397 public void onServiceConnected(IBinder service) { 398 mService = IBluetoothLeCallControl.Stub.asInterface(service); 399 } 400 401 /** @hide */ 402 @Override onServiceDisconnected()403 public void onServiceDisconnected() { 404 mService = null; 405 } 406 getService()407 private IBluetoothLeCallControl getService() { 408 return mService; 409 } 410 411 /** @hide */ 412 @Override getAdapter()413 public BluetoothAdapter getAdapter() { 414 return mAdapter; 415 } 416 417 /** 418 * Not supported 419 * 420 * @throws UnsupportedOperationException on every call 421 */ 422 @Override getConnectionState(@ullable BluetoothDevice device)423 public int getConnectionState(@Nullable BluetoothDevice device) { 424 throw new UnsupportedOperationException("not supported"); 425 } 426 427 /** 428 * Not supported 429 * 430 * @throws UnsupportedOperationException on every call 431 */ 432 @Override getConnectedDevices()433 public @NonNull List<BluetoothDevice> getConnectedDevices() { 434 throw new UnsupportedOperationException("not supported"); 435 } 436 437 /** 438 * Not supported 439 * 440 * @throws UnsupportedOperationException on every call 441 */ 442 @Override 443 @NonNull getDevicesMatchingConnectionStates(@onNull int[] states)444 public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) { 445 throw new UnsupportedOperationException("not supported"); 446 } 447 448 /** 449 * Register Telephone Bearer exposing the interface that allows remote devices to track and 450 * control the call states. 451 * 452 * <p>This is an asynchronous call. The callback is used to notify success or failure if the 453 * function returns true. 454 * 455 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 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 * <!-- The examples of common URI schemes can be found in 460 * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml --> 461 * <!-- The Technology is an integer value. The possible values are defined at 462 * https://www.bluetooth.com/specifications/assigned-numbers (login required). 463 * --> 464 * 465 * @param uci Bearer Unique Client Identifier 466 * @param uriSchemes URI Schemes supported list 467 * @param capabilities bearer capabilities 468 * @param provider Network provider name 469 * @param technology Network technology 470 * @param executor {@link Executor} object on which callback will be executed. The Executor 471 * object is required. 472 * @param callback {@link Callback} object to which callback messages will be sent. The Callback 473 * object is required. 474 * @return true on success, false otherwise 475 * @hide 476 */ 477 @SuppressLint("ExecutorRegistration") 478 @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)479 public boolean registerBearer( 480 @Nullable String uci, 481 @NonNull List<String> uriSchemes, 482 int capabilities, 483 @NonNull String provider, 484 int technology, 485 @NonNull Executor executor, 486 @NonNull Callback callback) { 487 if (DBG) { 488 Log.d(TAG, "registerBearer"); 489 } 490 if (callback == null) { 491 throw new IllegalArgumentException("null parameter: " + callback); 492 } 493 if (mCcid != 0) { 494 return false; 495 } 496 497 mToken = uci; 498 499 final IBluetoothLeCallControl service = getService(); 500 if (service == null) { 501 Log.w(TAG, "Proxy not attached to service"); 502 return false; 503 } 504 505 if (mCallback != null) { 506 Log.e(TAG, "Bearer can be opened only once"); 507 return false; 508 } 509 510 mCallback = callback; 511 try { 512 CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback); 513 service.registerBearer( 514 mToken, 515 callbackWrapper, 516 uci, 517 uriSchemes, 518 capabilities, 519 provider, 520 technology, 521 mAttributionSource); 522 523 } catch (RemoteException e) { 524 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 525 mCallback = null; 526 return false; 527 } 528 529 if (mCcid == 0) { 530 mCallback = null; 531 return false; 532 } 533 534 return true; 535 } 536 537 /** 538 * Unregister Telephone Bearer Service and destroy all the associated data. 539 * 540 * @hide 541 */ 542 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) unregisterBearer()543 public void unregisterBearer() { 544 if (DBG) { 545 Log.d(TAG, "unregisterBearer"); 546 } 547 if (mCcid == 0) { 548 return; 549 } 550 551 final IBluetoothLeCallControl service = getService(); 552 if (service == null) { 553 Log.w(TAG, "Proxy not attached to service"); 554 return; 555 } 556 557 mCcid = 0; 558 mCallback = null; 559 560 try { 561 service.unregisterBearer(mToken, mAttributionSource); 562 } catch (RemoteException e) { 563 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 564 } 565 } 566 567 /** 568 * Get the Content Control ID (CCID) value. 569 * 570 * @return ccid Content Control ID value 571 * @hide 572 */ 573 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getContentControlId()574 public int getContentControlId() { 575 return mCcid; 576 } 577 578 /** 579 * Notify about the newly added call. 580 * 581 * <p>This shall be called as early as possible after the call has been added. 582 * 583 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 584 * 585 * @param call Newly added call 586 * @hide 587 */ 588 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) onCallAdded(@onNull BluetoothLeCall call)589 public void onCallAdded(@NonNull BluetoothLeCall call) { 590 if (DBG) { 591 Log.d(TAG, "onCallAdded: call=" + call); 592 } 593 if (mCcid == 0) { 594 return; 595 } 596 597 final IBluetoothLeCallControl service = getService(); 598 if (service == null) { 599 Log.w(TAG, "Proxy not attached to service"); 600 return; 601 } 602 603 try { 604 service.callAdded(mCcid, call, mAttributionSource); 605 } catch (RemoteException e) { 606 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 607 } 608 } 609 610 /** 611 * Notify about the removed call. 612 * 613 * <p>This shall be called as early as possible after the call has been removed. 614 * 615 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 616 * 617 * @param callId The Id of a call that has been removed 618 * @param reason Call termination reason 619 * @hide 620 */ 621 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) onCallRemoved(@onNull UUID callId, @TerminationReason int reason)622 public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) { 623 if (DBG) { 624 Log.d(TAG, "callRemoved: callId=" + callId); 625 } 626 if (mCcid == 0) { 627 return; 628 } 629 630 final IBluetoothLeCallControl service = getService(); 631 if (service == null) { 632 Log.w(TAG, "Proxy not attached to service"); 633 return; 634 } 635 try { 636 service.callRemoved(mCcid, new ParcelUuid(callId), reason, mAttributionSource); 637 } catch (RemoteException e) { 638 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 639 } 640 } 641 642 /** 643 * Notify the call state change 644 * 645 * <p>This shall be called as early as possible after the state of the call has changed. 646 * 647 * <p>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, mAttributionSource); 670 } catch (RemoteException e) { 671 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 672 } 673 } 674 675 /** 676 * Provide the current calls list 677 * 678 * <p>This function must be invoked after registration if application has any calls. 679 * 680 * @param calls current calls list 681 * @hide 682 */ 683 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) currentCallsList(@onNull List<BluetoothLeCall> calls)684 public void currentCallsList(@NonNull List<BluetoothLeCall> calls) { 685 final IBluetoothLeCallControl service = getService(); 686 if (service == null) { 687 Log.w(TAG, "Proxy not attached to service"); 688 return; 689 } 690 691 try { 692 service.currentCallsList(mCcid, calls, mAttributionSource); 693 } catch (RemoteException e) { 694 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 695 } 696 } 697 698 /** 699 * Provide the network current status 700 * 701 * <p>This function must be invoked on change of network state. 702 * 703 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 704 * <!-- The Technology is an integer value. The possible values are defined at 705 * https://www.bluetooth.com/specifications/assigned-numbers (login required). 706 * --> 707 * 708 * @param provider Network provider name 709 * @param technology Network technology 710 * @hide 711 */ 712 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) networkStateChanged(@onNull String provider, int technology)713 public void networkStateChanged(@NonNull String provider, int technology) { 714 if (DBG) { 715 Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology); 716 } 717 if (mCcid == 0) { 718 return; 719 } 720 721 final IBluetoothLeCallControl service = getService(); 722 if (service == null) { 723 Log.w(TAG, "Proxy not attached to service"); 724 return; 725 } 726 727 try { 728 service.networkStateChanged(mCcid, provider, technology, mAttributionSource); 729 } catch (RemoteException e) { 730 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 731 } 732 } 733 734 /** 735 * Send a response to a call control request to a remote device. 736 * 737 * <p>This function must be invoked in when a request is received by one of these callback 738 * methods: 739 * 740 * <ul> 741 * <li>{@link Callback#onAcceptCall} 742 * <li>{@link Callback#onTerminateCall} 743 * <li>{@link Callback#onHoldCall} 744 * <li>{@link Callback#onUnholdCall} 745 * <li>{@link Callback#onPlaceCall} 746 * <li>{@link Callback#onJoinCalls} 747 * </ul> 748 * 749 * @param requestId The ID of the request that was received with the callback 750 * @param result The result of the request to be sent to the remote devices 751 */ 752 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) requestResult(int requestId, @Result int result)753 public void requestResult(int requestId, @Result int result) { 754 if (DBG) { 755 Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result); 756 } 757 if (mCcid == 0) { 758 return; 759 } 760 761 final IBluetoothLeCallControl service = getService(); 762 if (service == null) { 763 Log.w(TAG, "Proxy not attached to service"); 764 return; 765 } 766 767 try { 768 service.requestResult(mCcid, requestId, result, mAttributionSource); 769 } catch (RemoteException e) { 770 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 771 } 772 } 773 log(String msg)774 private static void log(String msg) { 775 Log.d(TAG, msg); 776 } 777 } 778