• 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 
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