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 402 /** @hide */ 403 @Override close()404 public void close() { 405 if (VDBG) log("close()"); 406 407 unregisterBearer(); 408 409 mProfileConnector.disconnect(); 410 } 411 getService()412 private IBluetoothLeCallControl getService() { 413 return mProfileConnector.getService(); 414 } 415 416 /** 417 * Not supported 418 * 419 * @throws UnsupportedOperationException 420 */ 421 @Override getConnectionState(@ullable BluetoothDevice device)422 public int getConnectionState(@Nullable BluetoothDevice device) { 423 throw new UnsupportedOperationException("not supported"); 424 } 425 426 /** 427 * Not supported 428 * 429 * @throws UnsupportedOperationException 430 */ 431 @Override getConnectedDevices()432 public @NonNull List<BluetoothDevice> getConnectedDevices() { 433 throw new UnsupportedOperationException("not supported"); 434 } 435 436 /** 437 * Not supported 438 * 439 * @throws UnsupportedOperationException 440 */ 441 @Override 442 @NonNull getDevicesMatchingConnectionStates(@onNull int[] states)443 public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) { 444 throw new UnsupportedOperationException("not supported"); 445 } 446 447 /** 448 * Register Telephone Bearer exposing the interface that allows remote devices 449 * to track and control the call states. 450 * 451 * <p> 452 * This is an asynchronous call. The callback is used to notify success or 453 * failure if the function returns true. 454 * 455 * <p> 456 * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 457 * 458 * <!-- The UCI is a String identifier of the telephone bearer as defined at 459 * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers 460 * (login required). --> 461 * 462 * <!-- The examples of common URI schemes can be found in 463 * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml --> 464 * 465 * <!-- The Technology is an integer value. The possible values are defined at 466 * https://www.bluetooth.com/specifications/assigned-numbers (login required). 467 * --> 468 * 469 * @param uci Bearer Unique Client Identifier 470 * @param uriSchemes URI Schemes supported list 471 * @param capabilities bearer capabilities 472 * @param provider Network provider name 473 * @param technology Network technology 474 * @param executor {@link Executor} object on which callback will be 475 * executed. The Executor object is required. 476 * @param callback {@link Callback} object to which callback messages will 477 * be sent. The Callback object is required. 478 * @return true on success, false otherwise 479 * @hide 480 */ 481 @SuppressLint("ExecutorRegistration") 482 @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)483 public boolean registerBearer(@Nullable String uci, 484 @NonNull List<String> uriSchemes, int capabilities, 485 @NonNull String provider, int technology, 486 @NonNull Executor executor, @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(mToken, callbackWrapper, uci, uriSchemes, capabilities, 514 provider, technology, mAttributionSource); 515 516 } catch (RemoteException e) { 517 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 518 mCallback = null; 519 return false; 520 } 521 522 if (mCcid == 0) { 523 mCallback = null; 524 return false; 525 } 526 527 return true; 528 } 529 530 /** 531 * Unregister Telephone Bearer Service and destroy all the associated data. 532 * 533 * @hide 534 */ 535 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) unregisterBearer()536 public void unregisterBearer() { 537 if (DBG) { 538 Log.d(TAG, "unregisterBearer"); 539 } 540 if (mCcid == 0) { 541 return; 542 } 543 544 final IBluetoothLeCallControl service = getService(); 545 if (service == null) { 546 Log.w(TAG, "Proxy not attached to service"); 547 return; 548 } 549 550 int ccid = mCcid; 551 mCcid = 0; 552 mCallback = null; 553 554 try { 555 service.unregisterBearer(mToken, mAttributionSource); 556 } catch (RemoteException e) { 557 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 558 } 559 } 560 561 /** 562 * Get the Content Control ID (CCID) value. 563 * 564 * @return ccid Content Control ID value 565 * @hide 566 */ 567 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getContentControlId()568 public int getContentControlId() { 569 return mCcid; 570 } 571 572 /** 573 * Notify about the newly added call. 574 * 575 * <p> 576 * This shall be called as early as possible after the call has been added. 577 * 578 * <p> 579 * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 580 * 581 * @param call Newly added call 582 * @hide 583 */ 584 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) onCallAdded(@onNull BluetoothLeCall call)585 public void onCallAdded(@NonNull BluetoothLeCall call) { 586 if (DBG) { 587 Log.d(TAG, "onCallAdded: call=" + call); 588 } 589 if (mCcid == 0) { 590 return; 591 } 592 593 final IBluetoothLeCallControl service = getService(); 594 if (service == null) { 595 Log.w(TAG, "Proxy not attached to service"); 596 return; 597 } 598 599 try { 600 service.callAdded(mCcid, call, mAttributionSource); 601 } catch (RemoteException e) { 602 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 603 } 604 } 605 606 /** 607 * Notify about the removed call. 608 * 609 * <p> 610 * This shall be called as early as possible after the call has been removed. 611 * 612 * <p> 613 * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 614 * 615 * @param callId The Id of a call that has been removed 616 * @param reason Call termination reason 617 * @hide 618 */ 619 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) onCallRemoved(@onNull UUID callId, @TerminationReason int reason)620 public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) { 621 if (DBG) { 622 Log.d(TAG, "callRemoved: callId=" + callId); 623 } 624 if (mCcid == 0) { 625 return; 626 } 627 628 final IBluetoothLeCallControl service = getService(); 629 if (service == null) { 630 Log.w(TAG, "Proxy not attached to service"); 631 return; 632 } 633 try { 634 service.callRemoved(mCcid, new ParcelUuid(callId), reason, mAttributionSource); 635 } catch (RemoteException e) { 636 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 637 } 638 639 } 640 641 /** 642 * Notify the call state change 643 * 644 * <p> 645 * This shall be called as early as possible after the state of the call has 646 * changed. 647 * 648 * <p> 649 * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 650 * 651 * @param callId The call Id that state has been changed 652 * @param state Call state 653 * @hide 654 */ 655 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) onCallStateChanged(@onNull UUID callId, @BluetoothLeCall.State int state)656 public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) { 657 if (DBG) { 658 Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state); 659 } 660 if (mCcid == 0) { 661 return; 662 } 663 664 final IBluetoothLeCallControl service = getService(); 665 if (service == null) { 666 Log.w(TAG, "Proxy not attached to service"); 667 return; 668 } 669 670 try { 671 service.callStateChanged(mCcid, new ParcelUuid(callId), state, mAttributionSource); 672 } catch (RemoteException e) { 673 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 674 } 675 } 676 677 /** 678 * Provide the current calls list 679 * 680 * <p> 681 * This function must be invoked after registration if application has any 682 * calls. 683 * 684 * @param calls current calls list 685 * @hide 686 */ 687 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) currentCallsList(@onNull List<BluetoothLeCall> calls)688 public void currentCallsList(@NonNull List<BluetoothLeCall> calls) { 689 final IBluetoothLeCallControl service = getService(); 690 if (service == null) { 691 Log.w(TAG, "Proxy not attached to service"); 692 return; 693 } 694 695 try { 696 service.currentCallsList(mCcid, calls, mAttributionSource); 697 } catch (RemoteException e) { 698 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 699 } 700 701 } 702 703 /** 704 * Provide the network current status 705 * 706 * <p> 707 * This function must be invoked on change of network state. 708 * 709 * <p> 710 * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 711 * 712 * <!-- The Technology is an integer value. The possible values are defined at 713 * https://www.bluetooth.com/specifications/assigned-numbers (login required). 714 * --> 715 * 716 * @param provider Network provider name 717 * @param technology Network technology 718 * @hide 719 */ 720 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) networkStateChanged(@onNull String provider, int technology)721 public void networkStateChanged(@NonNull String provider, int technology) { 722 if (DBG) { 723 Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology); 724 } 725 if (mCcid == 0) { 726 return; 727 } 728 729 final IBluetoothLeCallControl service = getService(); 730 if (service == null) { 731 Log.w(TAG, "Proxy not attached to service"); 732 return; 733 } 734 735 try { 736 service.networkStateChanged(mCcid, provider, technology, mAttributionSource); 737 } catch (RemoteException e) { 738 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 739 } 740 } 741 742 /** 743 * Send a response to a call control request to a remote device. 744 * 745 * <p> 746 * This function must be invoked in when a request is received by one of these 747 * callback methods: 748 * 749 * <ul> 750 * <li>{@link Callback#onAcceptCall} 751 * <li>{@link Callback#onTerminateCall} 752 * <li>{@link Callback#onHoldCall} 753 * <li>{@link Callback#onUnholdCall} 754 * <li>{@link Callback#onPlaceCall} 755 * <li>{@link Callback#onJoinCalls} 756 * </ul> 757 * 758 * @param requestId The ID of the request that was received with the callback 759 * @param result The result of the request to be sent to the remote devices 760 */ 761 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) requestResult(int requestId, @Result int result)762 public void requestResult(int requestId, @Result int result) { 763 if (DBG) { 764 Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result); 765 } 766 if (mCcid == 0) { 767 return; 768 } 769 770 final IBluetoothLeCallControl service = getService(); 771 if (service == null) { 772 Log.w(TAG, "Proxy not attached to service"); 773 return; 774 } 775 776 try { 777 service.requestResult(mCcid, requestId, result, mAttributionSource); 778 } catch (RemoteException e) { 779 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 780 } 781 } 782 783 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) isValidDevice(@ullable BluetoothDevice device)784 private static boolean isValidDevice(@Nullable BluetoothDevice device) { 785 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 786 } 787 log(String msg)788 private static void log(String msg) { 789 Log.d(TAG, msg); 790 } 791 } 792