• 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 static android.Manifest.permission.BLUETOOTH_CONNECT;
21 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
22 import static android.bluetooth.BluetoothUtils.executeFromBinder;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.RequiresNoPermission;
27 import android.annotation.RequiresPermission;
28 import android.annotation.SuppressLint;
29 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
30 import android.content.AttributionSource;
31 import android.content.Context;
32 import android.os.IBinder;
33 import android.os.ParcelUuid;
34 import android.os.RemoteException;
35 import android.util.Log;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.UUID;
40 import java.util.concurrent.Executor;
41 
42 /**
43  * This class provides the APIs to control the Call Control profile.
44  *
45  * <p>This class provides Bluetooth Telephone Bearer Service functionality, allowing applications to
46  * expose a GATT Service based interface to control the state of the calls by remote devices such as
47  * LE audio devices.
48  *
49  * <p>BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer
50  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothLeCallControl
51  * proxy object.
52  *
53  * @hide
54  */
55 public final class BluetoothLeCallControl implements BluetoothProfile {
56     private static final String TAG = BluetoothLeCallControl.class.getSimpleName();
57 
58     /**
59      * The template class is used to call callback functions on events from the TBS server. Callback
60      * functions are wrapped in this class and registered to the Android system during app
61      * registration.
62      *
63      * @hide
64      */
65     public abstract static class Callback {
66         private static final String TAG =
67                 BluetoothLeCallControl.TAG + "." + Callback.class.getSimpleName();
68 
69         /**
70          * Called when a remote client requested to accept the call.
71          *
72          * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the
73          * request.
74          *
75          * @param requestId The Id of the request
76          * @param callId The call Id requested to be accepted
77          * @hide
78          */
onAcceptCall(int requestId, @NonNull UUID callId)79         public abstract void onAcceptCall(int requestId, @NonNull UUID callId);
80 
81         /**
82          * A remote client has requested to terminate the call.
83          *
84          * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the
85          * request.
86          *
87          * @param requestId The Id of the request
88          * @param callId The call Id requested to terminate
89          * @hide
90          */
onTerminateCall(int requestId, @NonNull UUID callId)91         public abstract void onTerminateCall(int requestId, @NonNull UUID callId);
92 
93         /**
94          * A remote client has requested to hold the call.
95          *
96          * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the
97          * request.
98          *
99          * @param requestId The Id of the request
100          * @param callId The call Id requested to be put on hold
101          * @hide
102          */
onHoldCall(int requestId, @NonNull UUID callId)103         public void onHoldCall(int requestId, @NonNull UUID callId) {
104             Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!");
105         }
106 
107         /**
108          * A remote client has requested to unhold the call.
109          *
110          * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the
111          * request.
112          *
113          * @param requestId The Id of the request
114          * @param callId The call Id requested to unhold
115          * @hide
116          */
onUnholdCall(int requestId, @NonNull UUID callId)117         public void onUnholdCall(int requestId, @NonNull UUID callId) {
118             Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!");
119         }
120 
121         /**
122          * A remote client has requested to place a call.
123          *
124          * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the
125          * request.
126          *
127          * @param requestId The Id of the request
128          * @param callId The Id to be assigned for the new call
129          * @param uri The caller URI requested
130          * @hide
131          */
onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri)132         public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri);
133 
134         /**
135          * A remote client has requested to join the calls.
136          *
137          * <p>An application must call {@link BluetoothLeCallControl#requestResult} to complete the
138          * request.
139          *
140          * @param requestId The Id of the request
141          * @param callIds The call Id list requested to join
142          * @hide
143          */
onJoinCalls(int requestId, @NonNull List<UUID> callIds)144         public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) {
145             Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!");
146         }
147     }
148 
149     private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub {
150 
151         private final Executor mExecutor;
152         private final Callback mCallback;
153 
CallbackWrapper(Executor executor, Callback callback)154         CallbackWrapper(Executor executor, Callback callback) {
155             mExecutor = executor;
156             mCallback = callback;
157         }
158 
159         @Override
onBearerRegistered(int ccid)160         public void onBearerRegistered(int ccid) {
161             if (mCallback != null) {
162                 Log.d(TAG, "onBearerRegistered: ccid is " + ccid);
163                 mCcid = ccid;
164             } else {
165                 // registration timeout
166                 Log.e(TAG, "onBearerRegistered: mCallback is null");
167             }
168         }
169 
170         @Override
onAcceptCall(int requestId, ParcelUuid uuid)171         public void onAcceptCall(int requestId, ParcelUuid uuid) {
172             executeFromBinder(mExecutor, () -> mCallback.onAcceptCall(requestId, uuid.getUuid()));
173         }
174 
175         @Override
onTerminateCall(int requestId, ParcelUuid uuid)176         public void onTerminateCall(int requestId, ParcelUuid uuid) {
177             executeFromBinder(
178                     mExecutor, () -> mCallback.onTerminateCall(requestId, uuid.getUuid()));
179         }
180 
181         @Override
onHoldCall(int requestId, ParcelUuid uuid)182         public void onHoldCall(int requestId, ParcelUuid uuid) {
183             executeFromBinder(mExecutor, () -> mCallback.onHoldCall(requestId, uuid.getUuid()));
184         }
185 
186         @Override
onUnholdCall(int requestId, ParcelUuid uuid)187         public void onUnholdCall(int requestId, ParcelUuid uuid) {
188             executeFromBinder(mExecutor, () -> mCallback.onUnholdCall(requestId, uuid.getUuid()));
189         }
190 
191         @Override
onPlaceCall(int requestId, ParcelUuid uuid, String uri)192         public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) {
193             executeFromBinder(
194                     mExecutor, () -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri));
195         }
196 
197         @Override
onJoinCalls(int requestId, List<ParcelUuid> parcelUuids)198         public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) {
199             List<UUID> uuids = new ArrayList<>();
200             for (ParcelUuid parcelUuid : parcelUuids) {
201                 uuids.add(parcelUuid.getUuid());
202             }
203 
204             executeFromBinder(mExecutor, () -> mCallback.onJoinCalls(requestId, uuids));
205         }
206     }
207     ;
208 
209     private final BluetoothAdapter mAdapter;
210     private final AttributionSource mAttributionSource;
211     private int mCcid = 0;
212     private String mToken;
213     private Callback mCallback = null;
214 
215     private IBluetoothLeCallControl mService;
216 
217     /**
218      * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth
219      * telephone bearer service.
220      */
BluetoothLeCallControl(Context context, BluetoothAdapter adapter)221     /* package */ BluetoothLeCallControl(Context context, BluetoothAdapter adapter) {
222         mAdapter = adapter;
223         mAttributionSource = mAdapter.getAttributionSource();
224         mService = null;
225     }
226 
227     /** @hide */
close()228     public void close() {
229         Log.v(TAG, "close()");
230 
231         mAdapter.closeProfileProxy(this);
232     }
233 
234     /** @hide */
235     @Override
236     @RequiresNoPermission
onServiceConnected(IBinder service)237     public void onServiceConnected(IBinder service) {
238         mService = IBluetoothLeCallControl.Stub.asInterface(service);
239     }
240 
241     /** @hide */
242     @Override
243     @RequiresNoPermission
onServiceDisconnected()244     public void onServiceDisconnected() {
245         mService = null;
246     }
247 
getService()248     private IBluetoothLeCallControl getService() {
249         return mService;
250     }
251 
252     /** @hide */
253     @Override
254     @RequiresNoPermission
getAdapter()255     public BluetoothAdapter getAdapter() {
256         return mAdapter;
257     }
258 
259     /**
260      * Not supported
261      *
262      * @throws UnsupportedOperationException on every call
263      */
264     @Override
265     @RequiresNoPermission
getConnectionState(@ullable BluetoothDevice device)266     public int getConnectionState(@Nullable BluetoothDevice device) {
267         throw new UnsupportedOperationException("not supported");
268     }
269 
270     /**
271      * Not supported
272      *
273      * @throws UnsupportedOperationException on every call
274      */
275     @Override
276     @RequiresNoPermission
getConnectedDevices()277     public @NonNull List<BluetoothDevice> getConnectedDevices() {
278         throw new UnsupportedOperationException("not supported");
279     }
280 
281     /**
282      * Not supported
283      *
284      * @throws UnsupportedOperationException on every call
285      */
286     @Override
287     @RequiresNoPermission
288     @NonNull
getDevicesMatchingConnectionStates(@onNull int[] states)289     public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) {
290         throw new UnsupportedOperationException("not supported");
291     }
292 
293     /**
294      * Register Telephone Bearer exposing the interface that allows remote devices to track and
295      * control the call states.
296      *
297      * <p>This is an asynchronous call. The callback is used to notify success or failure if the
298      * function returns true.
299      *
300      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
301      * <!-- The UCI is a String identifier of the telephone bearer as defined at
302      * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers
303      * (login required). -->
304      * <!-- The examples of common URI schemes can be found in
305      * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml -->
306      * <!-- The Technology is an integer value. The possible values are defined at
307      * https://www.bluetooth.com/specifications/assigned-numbers (login required).
308      * -->
309      *
310      * @param uci Bearer Unique Client Identifier
311      * @param uriSchemes URI Schemes supported list
312      * @param capabilities bearer capabilities
313      * @param provider Network provider name
314      * @param technology Network technology
315      * @param executor {@link Executor} object on which callback will be executed. The Executor
316      *     object is required.
317      * @param callback {@link Callback} object to which callback messages will be sent. The Callback
318      *     object is required.
319      * @return true on success, false otherwise
320      * @hide
321      */
322     @SuppressLint("ExecutorRegistration")
323     @RequiresBluetoothConnectPermission
324     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
registerBearer( @ullable String uci, @NonNull List<String> uriSchemes, int capabilities, @NonNull String provider, int technology, @NonNull Executor executor, @NonNull Callback callback)325     public boolean registerBearer(
326             @Nullable String uci,
327             @NonNull List<String> uriSchemes,
328             int capabilities,
329             @NonNull String provider,
330             int technology,
331             @NonNull Executor executor,
332             @NonNull Callback callback) {
333         Log.d(TAG, "registerBearer");
334         if (callback == null) {
335             throw new IllegalArgumentException("null parameter: " + callback);
336         }
337         if (mCcid != 0) {
338             Log.e(TAG, "Ccid is already set to " + mCcid);
339             return false;
340         }
341 
342         mToken = uci;
343 
344         final IBluetoothLeCallControl service = getService();
345         if (service == null) {
346             Log.w(TAG, "Proxy not attached to service");
347             return false;
348         }
349 
350         if (mCallback != null) {
351             Log.e(TAG, "Bearer can be opened only once");
352             return false;
353         }
354 
355         mCallback = callback;
356         try {
357             CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback);
358             service.registerBearer(
359                     mToken,
360                     callbackWrapper,
361                     uci,
362                     uriSchemes,
363                     capabilities,
364                     provider,
365                     technology,
366                     mAttributionSource);
367 
368         } catch (RemoteException e) {
369             Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
370             mCallback = null;
371             return false;
372         }
373 
374         if (mCcid == 0) {
375             mCallback = null;
376             return false;
377         }
378 
379         return true;
380     }
381 
382     /**
383      * Unregister Telephone Bearer Service and destroy all the associated data.
384      *
385      * @hide
386      */
387     @RequiresBluetoothConnectPermission
388     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
unregisterBearer()389     public void unregisterBearer() {
390         Log.d(TAG, "unregisterBearer");
391         if (mCcid == 0) {
392             return;
393         }
394 
395         final IBluetoothLeCallControl service = getService();
396         if (service == null) {
397             Log.w(TAG, "Proxy not attached to service");
398             return;
399         }
400 
401         mCcid = 0;
402         mCallback = null;
403 
404         try {
405             service.unregisterBearer(mToken, mAttributionSource);
406         } catch (RemoteException e) {
407             Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
408         }
409     }
410 
411     /**
412      * Notify about the newly added call.
413      *
414      * <p>This shall be called as early as possible after the call has been added.
415      *
416      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
417      *
418      * @param call Newly added call
419      * @hide
420      */
421     @RequiresBluetoothConnectPermission
422     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
onCallAdded(@onNull BluetoothLeCall call)423     public void onCallAdded(@NonNull BluetoothLeCall call) {
424         Log.d(TAG, "onCallAdded: call=" + call);
425         if (mCcid == 0) {
426             return;
427         }
428 
429         final IBluetoothLeCallControl service = getService();
430         if (service == null) {
431             Log.w(TAG, "Proxy not attached to service");
432             return;
433         }
434 
435         try {
436             service.callAdded(mCcid, call, mAttributionSource);
437         } catch (RemoteException e) {
438             Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
439         }
440     }
441 
442     /**
443      * Notify about the removed call.
444      *
445      * <p>This shall be called as early as possible after the call has been removed.
446      *
447      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
448      *
449      * @param callId The Id of a call that has been removed
450      * @param reason Call termination reason
451      * @hide
452      */
453     @RequiresBluetoothConnectPermission
454     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
onCallRemoved(@onNull UUID callId, int reason)455     public void onCallRemoved(@NonNull UUID callId, int reason) {
456         Log.d(TAG, "callRemoved: callId=" + callId);
457         if (mCcid == 0) {
458             return;
459         }
460 
461         final IBluetoothLeCallControl service = getService();
462         if (service == null) {
463             Log.w(TAG, "Proxy not attached to service");
464             return;
465         }
466         try {
467             service.callRemoved(mCcid, new ParcelUuid(callId), reason, mAttributionSource);
468         } catch (RemoteException e) {
469             Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
470         }
471     }
472 
473     /**
474      * Notify the call state change
475      *
476      * <p>This shall be called as early as possible after the state of the call has changed.
477      *
478      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
479      *
480      * @param callId The call Id that state has been changed
481      * @param state Call state
482      * @hide
483      */
484     @RequiresBluetoothConnectPermission
485     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
onCallStateChanged(@onNull UUID callId, @BluetoothLeCall.State int state)486     public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) {
487         Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state);
488         if (mCcid == 0) {
489             return;
490         }
491 
492         final IBluetoothLeCallControl service = getService();
493         if (service == null) {
494             Log.w(TAG, "Proxy not attached to service");
495             return;
496         }
497 
498         try {
499             service.callStateChanged(mCcid, new ParcelUuid(callId), state, mAttributionSource);
500         } catch (RemoteException e) {
501             Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
502         }
503     }
504 
505     /**
506      * Provide the current calls list
507      *
508      * <p>This function must be invoked after registration if application has any calls.
509      *
510      * @param calls current calls list
511      * @hide
512      */
513     @RequiresBluetoothConnectPermission
514     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
currentCallsList(@onNull List<BluetoothLeCall> calls)515     public void currentCallsList(@NonNull List<BluetoothLeCall> calls) {
516         final IBluetoothLeCallControl service = getService();
517         if (service == null) {
518             Log.w(TAG, "Proxy not attached to service");
519             return;
520         }
521 
522         try {
523             service.currentCallsList(mCcid, calls, mAttributionSource);
524         } catch (RemoteException e) {
525             Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
526         }
527     }
528 
529     /**
530      * Send a response to a call control request to a remote device.
531      *
532      * <p>This function must be invoked in when a request is received by one of these callback
533      * methods:
534      *
535      * <ul>
536      *   <li>{@link Callback#onAcceptCall}
537      *   <li>{@link Callback#onTerminateCall}
538      *   <li>{@link Callback#onHoldCall}
539      *   <li>{@link Callback#onUnholdCall}
540      *   <li>{@link Callback#onPlaceCall}
541      *   <li>{@link Callback#onJoinCalls}
542      * </ul>
543      *
544      * @param requestId The ID of the request that was received with the callback
545      * @param result The result of the request to be sent to the remote devices
546      */
547     @RequiresBluetoothConnectPermission
548     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
requestResult(int requestId, int result)549     public void requestResult(int requestId, int result) {
550         Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result);
551         if (mCcid == 0) {
552             return;
553         }
554 
555         final IBluetoothLeCallControl service = getService();
556         if (service == null) {
557             Log.w(TAG, "Proxy not attached to service");
558             return;
559         }
560 
561         try {
562             service.requestResult(mCcid, requestId, result, mAttributionSource);
563         } catch (RemoteException e) {
564             Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
565         }
566     }
567 }
568