• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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