• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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 com.android.bluetooth.tbs;
19 
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothLeCall;
22 import android.bluetooth.BluetoothProfile;
23 import android.bluetooth.IBluetoothLeCallControl;
24 import android.bluetooth.IBluetoothLeCallControlCallback;
25 import android.content.AttributionSource;
26 import android.os.ParcelUuid;
27 import android.os.RemoteException;
28 import android.sysprop.BluetoothProperties;
29 import android.util.Log;
30 
31 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
32 
33 import com.android.bluetooth.Utils;
34 import com.android.bluetooth.btservice.ProfileService;
35 import com.android.bluetooth.le_audio.LeAudioService;
36 import com.android.internal.annotations.VisibleForTesting;
37 
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.UUID;
42 
43 public class TbsService extends ProfileService {
44 
45     private static final String TAG = "TbsService";
46     private static final boolean DBG = true;
47 
48     private static TbsService sTbsService;
49     private Map<BluetoothDevice, Integer> mDeviceAuthorizations = new HashMap<>();
50 
51     private final TbsGeneric mTbsGeneric = new TbsGeneric();
52 
isEnabled()53     public static boolean isEnabled() {
54         return BluetoothProperties.isProfileCcpServerEnabled().orElse(false);
55     }
56 
57     @Override
initBinder()58     protected IProfileServiceBinder initBinder() {
59         return new TbsServerBinder(this);
60     }
61 
62     @Override
create()63     protected void create() {
64         if (DBG) {
65             Log.d(TAG, "create()");
66         }
67     }
68 
69     @Override
start()70     protected boolean start() {
71 
72         if (DBG) {
73             Log.d(TAG, "start()");
74         }
75         if (sTbsService != null) {
76             throw new IllegalStateException("start() called twice");
77         }
78 
79         // Mark service as started
80         setTbsService(this);
81 
82         mTbsGeneric.init(new TbsGatt(this));
83 
84         return true;
85     }
86 
87     @Override
stop()88     protected boolean stop() {
89         if (DBG) {
90             Log.d(TAG, "stop()");
91         }
92         if (sTbsService == null) {
93             Log.w(TAG, "stop() called before start()");
94             return true;
95         }
96 
97         // Mark service as stopped
98         setTbsService(null);
99 
100         if (mTbsGeneric != null) {
101             mTbsGeneric.cleanup();
102         }
103 
104         return true;
105     }
106 
107     @Override
cleanup()108     protected void cleanup() {
109         if (DBG) {
110             Log.d(TAG, "cleanup()");
111         }
112         mDeviceAuthorizations.clear();
113     }
114 
115     /**
116      * Get the TbsService instance
117      *
118      * @return TbsService instance
119      */
getTbsService()120     public static synchronized TbsService getTbsService() {
121         if (sTbsService == null) {
122             Log.w(TAG, "getTbsService: service is NULL");
123             return null;
124         }
125 
126         if (!sTbsService.isAvailable()) {
127             Log.w(TAG, "getTbsService: service is not available");
128             return null;
129         }
130 
131         return sTbsService;
132     }
133 
setTbsService(TbsService instance)134     private static synchronized void setTbsService(TbsService instance) {
135         if (DBG) {
136             Log.d(TAG, "setTbsService: set to=" + instance);
137         }
138 
139         sTbsService = instance;
140     }
141 
onDeviceUnauthorized(BluetoothDevice device)142     public void onDeviceUnauthorized(BluetoothDevice device) {
143         if (Utils.isPtsTestMode()) {
144             Log.d(TAG, "PTS test: setDeviceAuthorized");
145             setDeviceAuthorized(device, true);
146             return;
147         }
148         Log.w(TAG, "onDeviceUnauthorized - authorization notification not implemented yet ");
149         setDeviceAuthorized(device, false);
150     }
151 
152     /**
153      * Sets device authorization for TBS.
154      *
155      * @param device device that would be authorized
156      * @param isAuthorized boolean value of authorization permission
157      * @hide
158      */
setDeviceAuthorized(BluetoothDevice device, boolean isAuthorized)159     public void setDeviceAuthorized(BluetoothDevice device, boolean isAuthorized) {
160         Log.i(TAG, "setDeviceAuthorized(): device: " + device + ", isAuthorized: " + isAuthorized);
161         int authorization = isAuthorized ? BluetoothDevice.ACCESS_ALLOWED
162                 : BluetoothDevice.ACCESS_REJECTED;
163         mDeviceAuthorizations.put(device, authorization);
164 
165         if (mTbsGeneric != null) {
166             mTbsGeneric.onDeviceAuthorizationSet(device);
167         }
168     }
169 
170     /**
171      * Returns authorization value for given device.
172      *
173      * @param device device that would be authorized
174      * @return authorization value for device
175      *
176      * Possible authorization values:
177      * {@link BluetoothDevice.ACCESS_UNKNOWN},
178      * {@link BluetoothDevice.ACCESS_ALLOWED}
179      * @hide
180      */
getDeviceAuthorization(BluetoothDevice device)181     public int getDeviceAuthorization(BluetoothDevice device) {
182         /* Telephony Bearer Service is allowed for
183          * 1. in PTS mode
184          * 2. authorized devices
185          * 3. Any LeAudio devices which are allowed to connect
186          */
187         int authorization = mDeviceAuthorizations.getOrDefault(device, Utils.isPtsTestMode()
188                 ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_UNKNOWN);
189         if (authorization != BluetoothDevice.ACCESS_UNKNOWN) {
190             return authorization;
191         }
192 
193         LeAudioService leAudioService = LeAudioService.getLeAudioService();
194         if (leAudioService == null) {
195             Log.e(TAG, "TBS access not permited. LeAudioService not available");
196             return BluetoothDevice.ACCESS_UNKNOWN;
197         }
198 
199         if (leAudioService.getConnectionPolicy(device)
200                 > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
201             if (DBG) {
202                 Log.d(TAG, "TBS authorization allowed based on supported LeAudio service");
203             }
204             setDeviceAuthorized(device, true);
205             return BluetoothDevice.ACCESS_ALLOWED;
206         }
207 
208         Log.e(TAG, "TBS access not permited");
209         return BluetoothDevice.ACCESS_UNKNOWN;
210     }
211 
212     /**
213      * Set inband ringtone for the device.
214      * When set, notification will be sent to given device.
215      *
216      * @param device    device for which inband ringtone has been set
217      */
setInbandRingtoneSupport(BluetoothDevice device)218     public void setInbandRingtoneSupport(BluetoothDevice device) {
219         if (mTbsGeneric == null) {
220             Log.i(TAG, "setInbandRingtoneSupport, mTbsGeneric not available");
221             return;
222         }
223         mTbsGeneric.setInbandRingtoneSupport(device);
224     }
225 
226     /**
227      * Clear inband ringtone for the device.
228      * When set, notification will be sent to given device.
229      *
230      * @param device    device for which inband ringtone has been clear
231      */
clearInbandRingtoneSupport(BluetoothDevice device)232     public void clearInbandRingtoneSupport(BluetoothDevice device) {
233         if (mTbsGeneric == null) {
234             Log.i(TAG, "clearInbandRingtoneSupport, mTbsGeneric not available");
235             return;
236         }
237         mTbsGeneric.clearInbandRingtoneSupport(device);
238     }
239 
240 
241     /** Binder object: must be a static class or memory leak may occur */
242     @VisibleForTesting
243     static class TbsServerBinder extends IBluetoothLeCallControl.Stub implements IProfileServiceBinder {
244         private TbsService mService;
245 
getService(AttributionSource source)246         private TbsService getService(AttributionSource source) {
247             if (!Utils.checkServiceAvailable(mService, TAG)
248                     || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)
249                     || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
250                 Log.w(TAG, "TbsService call not allowed for non-active user");
251                 return null;
252             }
253 
254             if (mService != null) {
255                 if (DBG) {
256                     Log.d(TAG, "Service available");
257                 }
258 
259                 enforceBluetoothPrivilegedPermission(mService);
260                 return mService;
261             }
262 
263             return null;
264         }
265 
TbsServerBinder(TbsService service)266         TbsServerBinder(TbsService service) {
267             mService = service;
268         }
269 
270         @Override
cleanup()271         public void cleanup() {
272             mService = null;
273         }
274 
275         @Override
registerBearer(String token, IBluetoothLeCallControlCallback callback, String uci, List<String> uriSchemes, int capabilities, String providerName, int technology, AttributionSource source)276         public void registerBearer(String token, IBluetoothLeCallControlCallback callback, String uci,
277                 List<String> uriSchemes, int capabilities, String providerName, int technology,
278                 AttributionSource source) {
279             TbsService service = getService(source);
280             if (service != null) {
281                 service.registerBearer(token, callback, uci, uriSchemes, capabilities, providerName,
282                         technology);
283             } else {
284                 Log.w(TAG, "Service not active");
285             }
286         }
287 
288         @Override
unregisterBearer(String token, AttributionSource source)289         public void unregisterBearer(String token,
290                 AttributionSource source) {
291             TbsService service = getService(source);
292             if (service != null) {
293                 service.unregisterBearer(token);
294             } else {
295                 Log.w(TAG, "Service not active");
296             }
297         }
298 
299         @Override
requestResult(int ccid, int requestId, int result, AttributionSource source)300         public void requestResult(int ccid, int requestId, int result,
301                 AttributionSource source) {
302             TbsService service = getService(source);
303             if (service != null) {
304                 service.requestResult(ccid, requestId, result);
305             } else {
306                 Log.w(TAG, "Service not active");
307             }
308         }
309 
310         @Override
callAdded(int ccid, BluetoothLeCall call, AttributionSource source)311         public void callAdded(int ccid, BluetoothLeCall call,
312                 AttributionSource source) {
313             TbsService service = getService(source);
314             if (service != null) {
315                 service.callAdded(ccid, call);
316             } else {
317                 Log.w(TAG, "Service not active");
318             }
319         }
320 
321         @Override
callRemoved(int ccid, ParcelUuid callId, int reason, AttributionSource source)322         public void callRemoved(int ccid, ParcelUuid callId, int reason,
323                 AttributionSource source) {
324             TbsService service = getService(source);
325             if (service != null) {
326                 service.callRemoved(ccid, callId.getUuid(), reason);
327             } else {
328                 Log.w(TAG, "Service not active");
329             }
330         }
331 
332         @Override
callStateChanged(int ccid, ParcelUuid callId, int state, AttributionSource source)333         public void callStateChanged(int ccid, ParcelUuid callId, int state,
334                 AttributionSource source) {
335             TbsService service = getService(source);
336             if (service != null) {
337                 service.callStateChanged(ccid, callId.getUuid(), state);
338             } else {
339                 Log.w(TAG, "Service not active");
340             }
341         }
342 
343         @Override
currentCallsList(int ccid, List<BluetoothLeCall> calls, AttributionSource source)344         public void currentCallsList(int ccid, List<BluetoothLeCall> calls,
345                 AttributionSource source) {
346             TbsService service = getService(source);
347             if (service != null) {
348                 service.currentCallsList(ccid, calls);
349             } else {
350                 Log.w(TAG, "Service not active");
351             }
352         }
353 
354         @Override
networkStateChanged(int ccid, String providerName, int technology, AttributionSource source)355         public void networkStateChanged(int ccid, String providerName, int technology,
356                 AttributionSource source) {
357             TbsService service = getService(source);
358             if (service != null) {
359                 service.networkStateChanged(ccid, providerName, technology);
360             } else {
361                 Log.w(TAG, "Service not active");
362             }
363         }
364     }
365 
366     @VisibleForTesting
registerBearer(String token, IBluetoothLeCallControlCallback callback, String uci, List<String> uriSchemes, int capabilities, String providerName, int technology)367     void registerBearer(String token, IBluetoothLeCallControlCallback callback, String uci,
368             List<String> uriSchemes, int capabilities, String providerName, int technology) {
369         if (DBG) {
370             Log.d(TAG, "registerBearer: token=" + token);
371         }
372 
373         boolean success = mTbsGeneric.addBearer(token, callback, uci, uriSchemes, capabilities,
374                 providerName, technology);
375         if (success) {
376             try {
377                 callback.asBinder().linkToDeath(() -> {
378                     Log.e(TAG, token + " application died, removing...");
379                     unregisterBearer(token);
380                 }, 0);
381             } catch (RemoteException e) {
382                 e.printStackTrace();
383             }
384         }
385 
386         if (DBG) {
387             Log.d(TAG, "registerBearer: token=" + token + " success=" + success);
388         }
389     }
390 
391     @VisibleForTesting
unregisterBearer(String token)392     void unregisterBearer(String token) {
393         if (DBG) {
394             Log.d(TAG, "unregisterBearer: token=" + token);
395         }
396 
397         mTbsGeneric.removeBearer(token);
398     }
399 
400     @VisibleForTesting
requestResult(int ccid, int requestId, int result)401     public void requestResult(int ccid, int requestId, int result) {
402         if (DBG) {
403             Log.d(TAG, "requestResult: ccid=" + ccid + " requestId=" + requestId + " result="
404                     + result);
405         }
406 
407         mTbsGeneric.requestResult(ccid, requestId, result);
408     }
409 
410     @VisibleForTesting
callAdded(int ccid, BluetoothLeCall call)411     void callAdded(int ccid, BluetoothLeCall call) {
412         if (DBG) {
413             Log.d(TAG, "callAdded: ccid=" + ccid + " call=" + call);
414         }
415 
416         mTbsGeneric.callAdded(ccid, call);
417     }
418 
419     @VisibleForTesting
callRemoved(int ccid, UUID callId, int reason)420     void callRemoved(int ccid, UUID callId, int reason) {
421         if (DBG) {
422             Log.d(TAG, "callRemoved: ccid=" + ccid + " callId=" + callId + " reason=" + reason);
423         }
424 
425         mTbsGeneric.callRemoved(ccid, callId, reason);
426     }
427 
428     @VisibleForTesting
callStateChanged(int ccid, UUID callId, int state)429     void callStateChanged(int ccid, UUID callId, int state) {
430         if (DBG) {
431             Log.d(TAG, "callStateChanged: ccid=" + ccid + " callId=" + callId + " state=" + state);
432         }
433 
434         mTbsGeneric.callStateChanged(ccid, callId, state);
435     }
436 
437     @VisibleForTesting
currentCallsList(int ccid, List<BluetoothLeCall> calls)438     void currentCallsList(int ccid, List<BluetoothLeCall> calls) {
439         if (DBG) {
440             Log.d(TAG, "currentCallsList: ccid=" + ccid + " calls=" + calls);
441         }
442 
443         mTbsGeneric.currentCallsList(ccid, calls);
444     }
445 
446     @VisibleForTesting
networkStateChanged(int ccid, String providerName, int technology)447     void networkStateChanged(int ccid, String providerName, int technology) {
448         if (DBG) {
449             Log.d(TAG, "networkStateChanged: ccid=" + ccid + " providerName=" + providerName
450                     + " technology=" + technology);
451         }
452 
453         mTbsGeneric.networkStateChanged(ccid, providerName, technology);
454     }
455 
456     @Override
dump(StringBuilder sb)457     public void dump(StringBuilder sb) {
458         super.dump(sb);
459         sb.append("TbsService instance:\n");
460 
461         mTbsGeneric.dump(sb);
462 
463         for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceAuthorizations.entrySet()) {
464             String accessString;
465             if (entry.getValue() == BluetoothDevice.ACCESS_REJECTED) {
466                 accessString = "ACCESS_REJECTED";
467             } else if (entry.getValue() == BluetoothDevice.ACCESS_ALLOWED) {
468                 accessString = "ACCESS_ALLOWED";
469             } else {
470                 accessString = "ACCESS_UNKNOWN";
471             }
472             sb.append("\n\tDevice: " + entry.getKey() + ", access: " + accessString);
473         }
474     }
475 }
476