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