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.BluetoothLeAudio;
22 import android.bluetooth.BluetoothLeCall;
23 import android.bluetooth.BluetoothLeCallControl;
24 import android.bluetooth.IBluetoothLeCallControlCallback;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.media.AudioManager;
30 import android.net.Uri;
31 import android.os.ParcelUuid;
32 import android.os.RemoteException;
33 import android.util.Log;
34 
35 import com.android.bluetooth.btservice.ServiceFactory;
36 import com.android.bluetooth.le_audio.ContentControlIdKeeper;
37 import com.android.bluetooth.le_audio.LeAudioService;
38 import com.android.internal.annotations.VisibleForTesting;
39 
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.HashSet;
44 import java.util.LinkedHashSet;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.TreeMap;
48 import java.util.UUID;
49 
50 /** Container class to store TBS instances */
51 public class TbsGeneric {
52 
53     private static final String TAG = "TbsGeneric";
54 
55     private static final String UCI = "GTBS";
56     private static final String DEFAULT_PROVIDER_NAME = "none";
57     /* Use GSM as default technology value. It is used only
58      * when bearer is not registered. It will be updated on the phone call
59      */
60     private static final int DEFAULT_BEARER_TECHNOLOGY =
61             BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM;
62     private static final String UNKNOWN_FRIENDLY_NAME = "unknown";
63 
64     /** Class representing the pending request sent to the application */
65     private static class Request {
66         BluetoothDevice device;
67         List<UUID> callIdList;
68         int requestedOpcode;
69         int callIndex;
70 
Request(BluetoothDevice device, UUID callId, int requestedOpcode, int callIndex)71         public Request(BluetoothDevice device, UUID callId, int requestedOpcode, int callIndex) {
72             this.device = device;
73             this.callIdList = Arrays.asList(callId);
74             this.requestedOpcode = requestedOpcode;
75             this.callIndex = callIndex;
76         }
77 
Request( BluetoothDevice device, List<ParcelUuid> callIds, int requestedOpcode, int callIndex)78         public Request(
79                 BluetoothDevice device,
80                 List<ParcelUuid> callIds,
81                 int requestedOpcode,
82                 int callIndex) {
83             this.device = device;
84             this.callIdList = new ArrayList<>();
85             for (ParcelUuid callId : callIds) {
86                 this.callIdList.add(callId.getUuid());
87             }
88             this.requestedOpcode = requestedOpcode;
89             this.callIndex = callIndex;
90         }
91     }
92 
93     /* Application-registered TBS instance */
94     private static class Bearer {
95         final String token;
96         final IBluetoothLeCallControlCallback callback;
97         List<String> uriSchemes;
98         final int capabilities;
99         final int ccid;
100         String providerName;
101         int technology;
102         Map<UUID, Integer> callIdIndexMap = new HashMap<>();
103         Map<Integer, Request> requestMap = new HashMap<>();
104 
Bearer( String token, IBluetoothLeCallControlCallback callback, List<String> uriSchemes, int capabilities, String providerName, int technology, int ccid)105         Bearer(
106                 String token,
107                 IBluetoothLeCallControlCallback callback,
108                 List<String> uriSchemes,
109                 int capabilities,
110                 String providerName,
111                 int technology,
112                 int ccid) {
113             this.token = token;
114             this.callback = callback;
115             this.uriSchemes = uriSchemes;
116             this.capabilities = capabilities;
117             this.providerName = providerName;
118             this.technology = technology;
119             this.ccid = ccid;
120         }
121     }
122 
123     private boolean mIsInitialized = false;
124     private TbsGatt mTbsGatt = null;
125     private List<Bearer> mBearerList = new ArrayList<>();
126     private int mLastIndexAssigned = TbsCall.INDEX_UNASSIGNED;
127     private Map<Integer, TbsCall> mCurrentCallsList = new TreeMap<>();
128     private Bearer mForegroundBearer = null;
129     private int mLastRequestIdAssigned = 0;
130     private List<String> mUriSchemes = new ArrayList<>(Arrays.asList("tel"));
131     private Receiver mReceiver = null;
132     private int mStoredRingerMode = -1;
133     private final ServiceFactory mFactory = new ServiceFactory();
134     private LeAudioService mLeAudioService;
135 
136     private final class Receiver extends BroadcastReceiver {
137         @Override
onReceive(Context context, Intent intent)138         public void onReceive(Context context, Intent intent) {
139             synchronized (TbsGeneric.this) {
140                 if (!mIsInitialized) {
141                     Log.w(TAG, "onReceive called while not initialized.");
142                     return;
143                 }
144 
145                 final String action = intent.getAction();
146                 if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
147                     int ringerMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
148 
149                     if (ringerMode < 0 || ringerMode == mStoredRingerMode) return;
150 
151                     mStoredRingerMode = ringerMode;
152 
153                     if (isSilentModeEnabled()) {
154                         mTbsGatt.setSilentModeFlag();
155                     } else {
156                         mTbsGatt.clearSilentModeFlag();
157                     }
158                 }
159             }
160         }
161     }
162     ;
163 
init(TbsGatt tbsGatt)164     public synchronized boolean init(TbsGatt tbsGatt) {
165         Log.d(TAG, "init");
166         mTbsGatt = tbsGatt;
167 
168         int ccid =
169                 ContentControlIdKeeper.acquireCcid(
170                         new ParcelUuid(TbsGatt.UUID_GTBS),
171                         BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL);
172         if (!isCcidValid(ccid)) {
173             Log.e(TAG, " CCID is not valid");
174             cleanup();
175             return false;
176         }
177 
178         if (!mTbsGatt.init(
179                 ccid,
180                 UCI,
181                 mUriSchemes,
182                 true,
183                 true,
184                 DEFAULT_PROVIDER_NAME,
185                 DEFAULT_BEARER_TECHNOLOGY,
186                 mTbsGattCallback)) {
187             Log.e(TAG, " TbsGatt init failed");
188             cleanup();
189             return false;
190         }
191 
192         AudioManager audioManager = mTbsGatt.getContext().getSystemService(AudioManager.class);
193         if (audioManager == null) {
194             Log.w(TAG, " AudioManager is not available");
195             cleanup();
196             return false;
197         }
198 
199         // read initial value of ringer mode
200         mStoredRingerMode = audioManager.getRingerMode();
201 
202         if (isSilentModeEnabled()) {
203             mTbsGatt.setSilentModeFlag();
204         } else {
205             mTbsGatt.clearSilentModeFlag();
206         }
207 
208         mReceiver = new Receiver();
209         IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
210         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
211         mTbsGatt.getContext().registerReceiver(mReceiver, filter);
212 
213         mIsInitialized = true;
214         return true;
215     }
216 
cleanup()217     public synchronized void cleanup() {
218         Log.d(TAG, "cleanup");
219 
220         if (mTbsGatt != null) {
221             if (mReceiver != null) {
222                 mTbsGatt.getContext().unregisterReceiver(mReceiver);
223             }
224             mTbsGatt.cleanup();
225             mTbsGatt = null;
226         }
227 
228         mIsInitialized = false;
229     }
230 
231     /**
232      * Inform TBS GATT instance about authorization change for device.
233      *
234      * @param device device for which authorization is changed
235      */
onDeviceAuthorizationSet(BluetoothDevice device)236     public void onDeviceAuthorizationSet(BluetoothDevice device) {
237         // Notify TBS GATT service instance in case of pending operations
238         if (mTbsGatt != null) {
239             mTbsGatt.onDeviceAuthorizationSet(device);
240         }
241     }
242 
243     /**
244      * Set inband ringtone for the device. When set, notification will be sent to given device.
245      *
246      * @param device device for which inband ringtone has been set
247      */
setInbandRingtoneSupport(BluetoothDevice device)248     public synchronized void setInbandRingtoneSupport(BluetoothDevice device) {
249         if (mTbsGatt == null) {
250             Log.w(TAG, "setInbandRingtoneSupport, mTbsGatt is null");
251             return;
252         }
253         mTbsGatt.setInbandRingtoneFlag(device);
254     }
255 
256     /**
257      * Clear inband ringtone for the device. When set, notification will be sent to given device.
258      *
259      * @param device device for which inband ringtone has been cleared
260      */
clearInbandRingtoneSupport(BluetoothDevice device)261     public synchronized void clearInbandRingtoneSupport(BluetoothDevice device) {
262         if (mTbsGatt == null) {
263             Log.w(TAG, "setInbandRingtoneSupport, mTbsGatt is null");
264             return;
265         }
266         mTbsGatt.clearInbandRingtoneFlag(device);
267     }
268 
isSilentModeEnabled()269     private synchronized boolean isSilentModeEnabled() {
270         return mStoredRingerMode != AudioManager.RINGER_MODE_NORMAL;
271     }
272 
getBearerByToken(String token)273     private synchronized Bearer getBearerByToken(String token) {
274         for (Bearer bearer : mBearerList) {
275             if (bearer.token.equals(token)) {
276                 return bearer;
277             }
278         }
279         return null;
280     }
281 
getBearerByCcid(int ccid)282     private synchronized Bearer getBearerByCcid(int ccid) {
283         for (Bearer bearer : mBearerList) {
284             if (bearer.ccid == ccid) {
285                 return bearer;
286             }
287         }
288         return null;
289     }
290 
getBearerSupportingUri(String uri)291     private synchronized Bearer getBearerSupportingUri(String uri) {
292         for (Bearer bearer : mBearerList) {
293             for (String s : bearer.uriSchemes) {
294                 if (uri.startsWith(s + ":")) {
295                     return bearer;
296                 }
297             }
298         }
299         return null;
300     }
301 
getCallIdByIndex(int callIndex)302     private synchronized Map.Entry<UUID, Bearer> getCallIdByIndex(int callIndex) {
303         for (Bearer bearer : mBearerList) {
304             for (Map.Entry<UUID, Integer> callIdToIndex : bearer.callIdIndexMap.entrySet()) {
305                 if (callIndex == callIdToIndex.getValue()) {
306                     return Map.entry(callIdToIndex.getKey(), bearer);
307                 }
308             }
309         }
310         return null;
311     }
312 
addBearer( String token, IBluetoothLeCallControlCallback callback, String uci, List<String> uriSchemes, int capabilities, String providerName, int technology)313     public synchronized boolean addBearer(
314             String token,
315             IBluetoothLeCallControlCallback callback,
316             String uci,
317             List<String> uriSchemes,
318             int capabilities,
319             String providerName,
320             int technology) {
321         Log.d(
322                 TAG,
323                 "addBearer: token="
324                         + token
325                         + " uci="
326                         + uci
327                         + " uriSchemes="
328                         + uriSchemes
329                         + " capabilities="
330                         + capabilities
331                         + " providerName="
332                         + providerName
333                         + " technology="
334                         + technology);
335         if (!mIsInitialized) {
336             Log.w(TAG, "addBearer called while not initialized.");
337             return false;
338         }
339 
340         if (getBearerByToken(token) != null) {
341             Log.w(TAG, "addBearer: token=" + token + " registered already");
342             return false;
343         }
344 
345         // Acquire CCID for TbsObject. The CCID is released on remove()
346         Bearer bearer =
347                 new Bearer(
348                         token,
349                         callback,
350                         uriSchemes,
351                         capabilities,
352                         providerName,
353                         technology,
354                         ContentControlIdKeeper.acquireCcid(
355                                 new ParcelUuid(UUID.randomUUID()),
356                                 BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL));
357         if (isCcidValid(bearer.ccid)) {
358             mBearerList.add(bearer);
359 
360             updateUriSchemesSupported();
361             if (mForegroundBearer == null) {
362                 setForegroundBearer(bearer);
363             }
364         } else {
365             Log.e(TAG, "Failed to acquire ccid");
366         }
367 
368         if (callback != null) {
369             try {
370                 Log.d(TAG, "ccid=" + bearer.ccid);
371                 callback.onBearerRegistered(bearer.ccid);
372             } catch (RemoteException e) {
373                 e.printStackTrace();
374             }
375         }
376 
377         return isCcidValid(bearer.ccid);
378     }
379 
removeBearer(String token)380     public synchronized void removeBearer(String token) {
381         Log.d(TAG, "removeBearer: token=" + token);
382 
383         if (!mIsInitialized) {
384             Log.w(TAG, "removeBearer called while not initialized.");
385             return;
386         }
387 
388         Bearer bearer = getBearerByToken(token);
389         if (bearer == null) {
390             return;
391         }
392 
393         // Remove the calls associated with this bearer
394         for (Integer callIndex : bearer.callIdIndexMap.values()) {
395             mCurrentCallsList.remove(callIndex);
396         }
397 
398         if (bearer.callIdIndexMap.size() > 0) {
399             notifyCclc();
400         }
401 
402         // Release the ccid acquired
403         ContentControlIdKeeper.releaseCcid(bearer.ccid);
404 
405         mBearerList.remove(bearer);
406 
407         updateUriSchemesSupported();
408         if (mForegroundBearer == bearer) {
409             setForegroundBearer(findNewForegroundBearer());
410         }
411     }
412 
checkRequestComplete(Bearer bearer, UUID callId, TbsCall tbsCall)413     private synchronized void checkRequestComplete(Bearer bearer, UUID callId, TbsCall tbsCall) {
414         // check if there's any pending request related to this call
415         Map.Entry<Integer, Request> requestEntry = null;
416         if (bearer.requestMap.size() > 0) {
417             for (Map.Entry<Integer, Request> entry : bearer.requestMap.entrySet()) {
418                 if (entry.getValue().callIdList.contains(callId)) {
419                     requestEntry = entry;
420                 }
421             }
422         }
423 
424         if (requestEntry == null) {
425             Log.d(TAG, "requestEntry is null");
426             return;
427         }
428 
429         int requestId = requestEntry.getKey();
430         Request request = requestEntry.getValue();
431 
432         int result;
433         if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE) {
434             if (mCurrentCallsList.get(request.callIndex) == null) {
435                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS;
436             } else {
437                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
438             }
439         } else if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT) {
440             if (tbsCall.getState() != BluetoothLeCall.STATE_INCOMING) {
441                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS;
442             } else {
443                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
444             }
445         } else if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD) {
446             if (tbsCall.getState() == BluetoothLeCall.STATE_LOCALLY_HELD
447                     || tbsCall.getState() == BluetoothLeCall.STATE_LOCALLY_AND_REMOTELY_HELD) {
448                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS;
449             } else {
450                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
451             }
452         } else if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE) {
453             if (tbsCall.getState() != BluetoothLeCall.STATE_LOCALLY_HELD
454                     && tbsCall.getState() != BluetoothLeCall.STATE_LOCALLY_AND_REMOTELY_HELD) {
455                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS;
456             } else {
457                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
458             }
459         } else if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE) {
460             if (bearer.callIdIndexMap.get(request.callIdList.get(0)) != null) {
461                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS;
462             } else {
463                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
464             }
465         } else if (request.requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_JOIN) {
466             /* While joining calls, those that are not in remotely held state should go to active */
467             if (bearer.callIdIndexMap.get(callId) == null
468                     || (tbsCall.getState() != BluetoothLeCall.STATE_ACTIVE
469                             && tbsCall.getState() != BluetoothLeCall.STATE_REMOTELY_HELD)) {
470                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
471             } else {
472                 /* Check if all of the pending calls transit to required state */
473                 for (UUID pendingCallId : request.callIdList) {
474                     Integer callIndex = bearer.callIdIndexMap.get(pendingCallId);
475                     TbsCall pendingTbsCall = mCurrentCallsList.get(callIndex);
476                     if (pendingTbsCall.getState() != BluetoothLeCall.STATE_ACTIVE
477                             && pendingTbsCall.getState() != BluetoothLeCall.STATE_REMOTELY_HELD) {
478                         /* Still waiting for more call state updates */
479                         return;
480                     }
481                 }
482                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS;
483             }
484         } else {
485             result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
486         }
487 
488         mTbsGatt.setCallControlPointResult(
489                 request.device, request.requestedOpcode, request.callIndex, result);
490 
491         bearer.requestMap.remove(requestId);
492     }
493 
getTbsResult(int result, int requestedOpcode)494     private synchronized int getTbsResult(int result, int requestedOpcode) {
495         if (result == BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID) {
496             return TbsGatt.CALL_CONTROL_POINT_RESULT_INVALID_CALL_INDEX;
497         }
498 
499         if (result == BluetoothLeCallControl.RESULT_ERROR_INVALID_URI
500                 && requestedOpcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE) {
501             return TbsGatt.CALL_CONTROL_POINT_RESULT_INVALID_OUTGOING_URI;
502         }
503 
504         return TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
505     }
506 
requestResult(int ccid, int requestId, int result)507     public synchronized void requestResult(int ccid, int requestId, int result) {
508         Log.d(TAG, "requestResult: ccid=" + ccid + " requestId=" + requestId + " result=" + result);
509 
510         if (!mIsInitialized) {
511             Log.w(TAG, "requestResult called while not initialized.");
512             return;
513         }
514 
515         Bearer bearer = getBearerByCcid(ccid);
516         if (bearer == null) {
517             Log.i(TAG, " Bearer for ccid " + ccid + " does not exist");
518             return;
519         }
520 
521         if (result == BluetoothLeCallControl.RESULT_SUCCESS) {
522             // don't send the success here, wait for state transition instead
523             return;
524         }
525 
526         // check if there's any pending request related to this call
527         Request request = bearer.requestMap.remove(requestId);
528         if (request == null) {
529             // already sent response
530             return;
531         }
532 
533         int tbsResult = getTbsResult(result, request.requestedOpcode);
534         mTbsGatt.setCallControlPointResult(
535                 request.device, request.requestedOpcode, request.callIndex, tbsResult);
536     }
537 
callAdded(int ccid, BluetoothLeCall call)538     public synchronized void callAdded(int ccid, BluetoothLeCall call) {
539         Log.d(TAG, "callAdded: ccid=" + ccid + " call=" + call);
540 
541         if (!mIsInitialized) {
542             Log.w(TAG, "callAdded called while not initialized.");
543             return;
544         }
545 
546         Bearer bearer = getBearerByCcid(ccid);
547         if (bearer == null) {
548             Log.e(TAG, "callAdded: unknown ccid=" + ccid);
549             return;
550         }
551 
552         UUID callId = call.getUuid();
553         if (bearer.callIdIndexMap.containsKey(callId)) {
554             Log.e(TAG, "callAdded: uuidId=" + callId + " already on list");
555             return;
556         }
557 
558         Integer callIndex = getFreeCallIndex();
559         if (callIndex == null) {
560             Log.e(TAG, "callAdded: out of call indices!");
561             return;
562         }
563 
564         bearer.callIdIndexMap.put(callId, callIndex);
565         TbsCall tbsCall = TbsCall.create(call);
566         mCurrentCallsList.put(callIndex, tbsCall);
567 
568         checkRequestComplete(bearer, callId, tbsCall);
569         if (tbsCall.isIncoming()) {
570             mTbsGatt.setIncomingCall(callIndex, tbsCall.getUri());
571         }
572 
573         String friendlyName = tbsCall.getFriendlyName();
574         if (friendlyName == null) {
575             friendlyName = UNKNOWN_FRIENDLY_NAME;
576         }
577         mTbsGatt.setCallFriendlyName(callIndex, friendlyName);
578 
579         notifyCclc();
580         if (mForegroundBearer != bearer) {
581             setForegroundBearer(bearer);
582         }
583     }
584 
callRemoved(int ccid, UUID callId, int reason)585     public synchronized void callRemoved(int ccid, UUID callId, int reason) {
586         Log.d(TAG, "callRemoved: ccid=" + ccid + "reason=" + reason);
587 
588         if (!mIsInitialized) {
589             Log.w(TAG, "callRemoved called while not initialized.");
590             return;
591         }
592 
593         Bearer bearer = getBearerByCcid(ccid);
594         if (bearer == null) {
595             Log.e(TAG, "callRemoved: unknown ccid=" + ccid);
596             return;
597         }
598 
599         Integer callIndex = bearer.callIdIndexMap.remove(callId);
600         if (callIndex == null) {
601             Log.e(TAG, "callIndex: is null for callId" + callId);
602             return;
603         }
604 
605         TbsCall tbsCall = mCurrentCallsList.remove(callIndex);
606         if (tbsCall == null) {
607             Log.e(TAG, "callRemoved: no such call");
608             return;
609         }
610 
611         checkRequestComplete(bearer, callId, tbsCall);
612         mTbsGatt.setTerminationReason(callIndex, reason);
613         notifyCclc();
614 
615         Integer incomingCallIndex = mTbsGatt.getIncomingCallIndex();
616         if (incomingCallIndex != null && incomingCallIndex.equals(callIndex)) {
617             mTbsGatt.clearIncomingCall();
618             // TODO: check if there's any incoming call more???
619         }
620 
621         Integer friendlyNameCallIndex = mTbsGatt.getCallFriendlyNameIndex();
622         if (friendlyNameCallIndex != null && friendlyNameCallIndex.equals(callIndex)) {
623             mTbsGatt.clearFriendlyName();
624             // TODO: check if there's any incoming/outgoing call more???
625         }
626     }
627 
callStateChanged(int ccid, UUID callId, int state)628     public synchronized void callStateChanged(int ccid, UUID callId, int state) {
629         Log.d(TAG, "callStateChanged: ccid=" + ccid + " callId=" + callId + " state=" + state);
630 
631         if (!mIsInitialized) {
632             Log.w(TAG, "callStateChanged called while not initialized.");
633             return;
634         }
635 
636         Bearer bearer = getBearerByCcid(ccid);
637         if (bearer == null) {
638             Log.e(TAG, "callStateChanged: unknown ccid=" + ccid);
639             return;
640         }
641 
642         Integer callIndex = bearer.callIdIndexMap.get(callId);
643         if (callIndex == null) {
644             Log.e(TAG, "callStateChanged: unknown callId=" + callId);
645             return;
646         }
647 
648         TbsCall tbsCall = mCurrentCallsList.get(callIndex);
649         if (tbsCall.getState() == state) {
650             return;
651         }
652 
653         tbsCall.setState(state);
654 
655         checkRequestComplete(bearer, callId, tbsCall);
656         notifyCclc();
657 
658         Integer incomingCallIndex = mTbsGatt.getIncomingCallIndex();
659         if (incomingCallIndex != null && incomingCallIndex.equals(callIndex)) {
660             mTbsGatt.clearIncomingCall();
661             // TODO: check if there's any incoming call more???
662         }
663     }
664 
currentCallsList(int ccid, List<BluetoothLeCall> calls)665     public synchronized void currentCallsList(int ccid, List<BluetoothLeCall> calls) {
666         Log.d(TAG, "currentCallsList: ccid=" + ccid + " callsNum=" + calls.size());
667 
668         if (!mIsInitialized) {
669             Log.w(TAG, "currentCallsList called while not initialized.");
670             return;
671         }
672 
673         Bearer bearer = getBearerByCcid(ccid);
674         if (bearer == null) {
675             Log.e(TAG, "currentCallsList: unknown ccid=" + ccid);
676             return;
677         }
678 
679         boolean cclc = false;
680         Map<UUID, Integer> storedCallIdList = new HashMap<>(bearer.callIdIndexMap);
681         bearer.callIdIndexMap = new HashMap<>();
682         for (BluetoothLeCall call : calls) {
683             UUID callId = call.getUuid();
684             Integer callIndex = storedCallIdList.get(callId);
685             if (callIndex == null) {
686                 // new call
687                 callIndex = getFreeCallIndex();
688                 if (callIndex == null) {
689                     Log.e(TAG, "currentCallsList: out of call indices!");
690                     continue;
691                 }
692 
693                 mCurrentCallsList.put(callIndex, TbsCall.create(call));
694                 cclc |= true;
695             } else {
696                 TbsCall tbsCall = mCurrentCallsList.get(callIndex);
697                 TbsCall tbsCallNew = TbsCall.create(call);
698                 if (tbsCall != tbsCallNew) {
699                     mCurrentCallsList.replace(callIndex, tbsCallNew);
700                     cclc |= true;
701                 }
702             }
703 
704             bearer.callIdIndexMap.put(callId, callIndex);
705         }
706 
707         for (Map.Entry<UUID, Integer> callIdToIndex : storedCallIdList.entrySet()) {
708             if (!bearer.callIdIndexMap.containsKey(callIdToIndex.getKey())) {
709                 mCurrentCallsList.remove(callIdToIndex.getValue());
710                 cclc |= true;
711             }
712         }
713 
714         if (cclc) {
715             notifyCclc();
716         }
717     }
718 
networkStateChanged(int ccid, String providerName, int technology)719     public synchronized void networkStateChanged(int ccid, String providerName, int technology) {
720         Log.d(
721                 TAG,
722                 "networkStateChanged: ccid="
723                         + ccid
724                         + " providerName="
725                         + providerName
726                         + " technology="
727                         + technology);
728 
729         if (!mIsInitialized) {
730             Log.w(TAG, "networkStateChanged called while not initialized.");
731             return;
732         }
733 
734         Bearer bearer = getBearerByCcid(ccid);
735         if (bearer == null) {
736             return;
737         }
738 
739         boolean providerChanged = !bearer.providerName.equals(providerName);
740         if (providerChanged) {
741             bearer.providerName = providerName;
742         }
743 
744         boolean technologyChanged = bearer.technology != technology;
745         if (technologyChanged) {
746             bearer.technology = technology;
747         }
748 
749         if (bearer == mForegroundBearer) {
750             if (providerChanged) {
751                 mTbsGatt.setBearerProviderName(bearer.providerName);
752             }
753 
754             if (technologyChanged) {
755                 mTbsGatt.setBearerTechnology(bearer.technology);
756             }
757         }
758     }
759 
processOriginateCall(BluetoothDevice device, String uri)760     private synchronized int processOriginateCall(BluetoothDevice device, String uri) {
761         if (uri.startsWith("tel")) {
762             /*
763              * FIXME: For now, process telephone call originate request here, as
764              * BluetoothInCallService might be not running. The BluetoothInCallService is active
765              * when there is a call only.
766              */
767             Log.i(TAG, "originate uri=" + uri);
768             Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, Uri.parse(uri));
769             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
770             mTbsGatt.getContext().startActivity(intent);
771             mTbsGatt.setCallControlPointResult(
772                     device,
773                     TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE,
774                     TbsCall.INDEX_UNASSIGNED,
775                     TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS);
776         } else {
777             UUID callId = UUID.randomUUID();
778             int requestId = mLastRequestIdAssigned + 1;
779             Request request =
780                     new Request(
781                             device,
782                             callId,
783                             TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE,
784                             TbsCall.INDEX_UNASSIGNED);
785 
786             Bearer bearer = getBearerSupportingUri(uri);
787             if (bearer == null) {
788                 return TbsGatt.CALL_CONTROL_POINT_RESULT_INVALID_OUTGOING_URI;
789             }
790 
791             try {
792                 bearer.callback.onPlaceCall(requestId, new ParcelUuid(callId), uri);
793             } catch (RemoteException e) {
794                 e.printStackTrace();
795                 return TbsGatt.CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
796             }
797 
798             bearer.requestMap.put(requestId, request);
799             mLastIndexAssigned = requestId;
800         }
801 
802         setActiveLeDevice(device);
803         return TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS;
804     }
805 
806     private final TbsGatt.Callback mTbsGattCallback =
807             new TbsGatt.Callback() {
808 
809                 @Override
810                 public void onServiceAdded(boolean success) {
811                     synchronized (TbsGeneric.this) {
812                         Log.d(TAG, "onServiceAdded: success=" + success);
813                     }
814                 }
815 
816                 @Override
817                 public boolean isInbandRingtoneEnabled(BluetoothDevice device) {
818                     if (!isLeAudioServiceAvailable()) {
819                         Log.i(TAG, "LeAudio service not available");
820                         return false;
821                     }
822                     int groupId = mLeAudioService.getGroupId(device);
823                     return mLeAudioService.isInbandRingtoneEnabled(groupId);
824                 }
825 
826                 @Override
827                 public void onCallControlPointRequest(
828                         BluetoothDevice device, int opcode, byte[] args) {
829                     synchronized (TbsGeneric.this) {
830                         Log.d(
831                                 TAG,
832                                 "onCallControlPointRequest: device="
833                                         + device
834                                         + " opcode="
835                                         + callControlRequestOpcodeStr(opcode)
836                                         + "("
837                                         + opcode
838                                         + ")"
839                                         + " argsLen="
840                                         + args.length);
841 
842                         if (!mIsInitialized) {
843                             Log.w(TAG, "onCallControlPointRequest called while not initialized.");
844                             return;
845                         }
846 
847                         int result;
848 
849                         switch (opcode) {
850                             case TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT:
851                             case TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE:
852                             case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD:
853                             case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE:
854                                 {
855                                     if (args.length == 0) {
856                                         result =
857                                                 TbsGatt
858                                                         .CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
859                                         break;
860                                     }
861 
862                                     int callIndex = args[0];
863                                     Map.Entry<UUID, Bearer> entry = getCallIdByIndex(callIndex);
864                                     if (entry == null) {
865                                         result =
866                                                 TbsGatt
867                                                         .CALL_CONTROL_POINT_RESULT_INVALID_CALL_INDEX;
868                                         break;
869                                     }
870 
871                                     TbsCall call = mCurrentCallsList.get(callIndex);
872                                     if (!isCallStateTransitionValid(call.getState(), opcode)) {
873                                         result = TbsGatt.CALL_CONTROL_POINT_RESULT_STATE_MISMATCH;
874                                         break;
875                                     }
876 
877                                     Bearer bearer = entry.getValue();
878                                     UUID callId = entry.getKey();
879                                     int requestId = mLastRequestIdAssigned + 1;
880                                     Request request =
881                                             new Request(device, callId, opcode, callIndex);
882                                     try {
883                                         if (opcode == TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT) {
884                                             setActiveLeDevice(device);
885                                             bearer.callback.onAcceptCall(
886                                                     requestId, new ParcelUuid(callId));
887                                         } else if (opcode
888                                                 == TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE) {
889                                             bearer.callback.onTerminateCall(
890                                                     requestId, new ParcelUuid(callId));
891                                         } else if (opcode
892                                                 == TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD) {
893                                             if ((bearer.capabilities
894                                                             & BluetoothLeCallControl
895                                                                     .CAPABILITY_HOLD_CALL)
896                                                     == 0) {
897                                                 result =
898                                                         TbsGatt
899                                                                 .CALL_CONTROL_POINT_RESULT_OPCODE_NOT_SUPPORTED;
900                                                 break;
901                                             }
902                                             bearer.callback.onHoldCall(
903                                                     requestId, new ParcelUuid(callId));
904                                         } else {
905                                             if ((bearer.capabilities
906                                                             & BluetoothLeCallControl
907                                                                     .CAPABILITY_HOLD_CALL)
908                                                     == 0) {
909                                                 result =
910                                                         TbsGatt
911                                                                 .CALL_CONTROL_POINT_RESULT_OPCODE_NOT_SUPPORTED;
912                                                 break;
913                                             }
914                                             bearer.callback.onUnholdCall(
915                                                     requestId, new ParcelUuid(callId));
916                                         }
917                                     } catch (RemoteException e) {
918                                         e.printStackTrace();
919                                         result =
920                                                 TbsGatt
921                                                         .CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
922                                         break;
923                                     }
924 
925                                     bearer.requestMap.put(requestId, request);
926                                     mLastRequestIdAssigned = requestId;
927 
928                                     result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS;
929                                     break;
930                                 }
931 
932                             case TbsGatt.CALL_CONTROL_POINT_OPCODE_ORIGINATE:
933                                 {
934                                     result = processOriginateCall(device, new String(args));
935                                     break;
936                                 }
937 
938                             case TbsGatt.CALL_CONTROL_POINT_OPCODE_JOIN:
939                                 {
940                                     // at least 2 call indices are required
941                                     if (args.length < 2) {
942                                         result =
943                                                 TbsGatt
944                                                         .CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
945                                         break;
946                                     }
947 
948                                     Map.Entry<UUID, Bearer> firstEntry = null;
949                                     List<ParcelUuid> parcelUuids = new ArrayList<>();
950                                     result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS;
951                                     for (int callIndex : args) {
952                                         Map.Entry<UUID, Bearer> entry = getCallIdByIndex(callIndex);
953                                         if (entry == null) {
954                                             result =
955                                                     TbsGatt
956                                                             .CALL_CONTROL_POINT_RESULT_INVALID_CALL_INDEX;
957                                             break;
958                                         }
959 
960                                         // state transition is valid, because a call in any state
961                                         // can requested to join
962 
963                                         if (firstEntry == null) {
964                                             firstEntry = entry;
965                                         }
966 
967                                         if (firstEntry.getValue() != entry.getValue()) {
968                                             Log.w(TAG, "Cannot join calls from different bearers!");
969                                             result =
970                                                     TbsGatt
971                                                             .CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
972                                             break;
973                                         }
974 
975                                         parcelUuids.add(new ParcelUuid(entry.getKey()));
976                                     }
977 
978                                     if (result != TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS) {
979                                         break;
980                                     }
981 
982                                     Bearer bearer = firstEntry.getValue();
983                                     Request request =
984                                             new Request(device, parcelUuids, opcode, args[0]);
985                                     int requestId = mLastRequestIdAssigned + 1;
986                                     try {
987                                         bearer.callback.onJoinCalls(requestId, parcelUuids);
988                                     } catch (RemoteException e) {
989                                         e.printStackTrace();
990                                         result =
991                                                 TbsGatt
992                                                         .CALL_CONTROL_POINT_RESULT_OPERATION_NOT_POSSIBLE;
993                                         break;
994                                     }
995 
996                                     bearer.requestMap.put(requestId, request);
997                                     mLastIndexAssigned = requestId;
998 
999                                     result = TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS;
1000                                     break;
1001                                 }
1002 
1003                             default:
1004                                 result = TbsGatt.CALL_CONTROL_POINT_RESULT_OPCODE_NOT_SUPPORTED;
1005                                 break;
1006                         }
1007 
1008                         if (result == TbsGatt.CALL_CONTROL_POINT_RESULT_SUCCESS) {
1009                             // return here and wait for the request completition from application
1010                             return;
1011                         }
1012 
1013                         mTbsGatt.setCallControlPointResult(device, opcode, 0, result);
1014                     }
1015                 }
1016             };
1017 
callControlRequestOpcodeStr(int opcode)1018     private String callControlRequestOpcodeStr(int opcode) {
1019         switch (opcode) {
1020             case TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT:
1021                 return "ACCEPT";
1022             case TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE:
1023                 return "TERMINATE";
1024             case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD:
1025                 return "LOCAL_HOLD";
1026             case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE:
1027                 return "LOCAL_RETRIEVE";
1028             default:
1029                 return "UNKNOWN";
1030         }
1031     }
1032 
isCcidValid(int ccid)1033     private static boolean isCcidValid(int ccid) {
1034         return ccid != ContentControlIdKeeper.CCID_INVALID;
1035     }
1036 
isCallIndexAssigned(int callIndex)1037     private static boolean isCallIndexAssigned(int callIndex) {
1038         return callIndex != TbsCall.INDEX_UNASSIGNED;
1039     }
1040 
getFreeCallIndex()1041     private synchronized Integer getFreeCallIndex() {
1042         int callIndex = mLastIndexAssigned;
1043         for (int i = TbsCall.INDEX_MIN; i <= TbsCall.INDEX_MAX; i++) {
1044             callIndex = (callIndex + 1) % TbsCall.INDEX_MAX;
1045             if (!isCallIndexAssigned(callIndex)) {
1046                 continue;
1047             }
1048 
1049             if (mCurrentCallsList.keySet().contains(callIndex)) {
1050                 continue;
1051             }
1052 
1053             mLastIndexAssigned = callIndex;
1054 
1055             return callIndex;
1056         }
1057 
1058         return null;
1059     }
1060 
getCallByStates( LinkedHashSet<Integer> states)1061     private synchronized Map.Entry<Integer, TbsCall> getCallByStates(
1062             LinkedHashSet<Integer> states) {
1063         for (Map.Entry<Integer, TbsCall> entry : mCurrentCallsList.entrySet()) {
1064             if (states.contains(entry.getValue().getState())) {
1065                 return entry;
1066             }
1067         }
1068 
1069         return null;
1070     }
1071 
getForegroundCall()1072     private synchronized Map.Entry<Integer, TbsCall> getForegroundCall() {
1073         LinkedHashSet<Integer> states = new LinkedHashSet<Integer>();
1074         Map.Entry<Integer, TbsCall> foregroundCall;
1075 
1076         if (mCurrentCallsList.size() == 0) {
1077             return null;
1078         }
1079 
1080         states.add(BluetoothLeCall.STATE_INCOMING);
1081         foregroundCall = getCallByStates(states);
1082         if (foregroundCall != null) {
1083             return foregroundCall;
1084         }
1085 
1086         states.clear();
1087         states.add(BluetoothLeCall.STATE_DIALING);
1088         states.add(BluetoothLeCall.STATE_ALERTING);
1089         foregroundCall = getCallByStates(states);
1090         if (foregroundCall != null) {
1091             return foregroundCall;
1092         }
1093 
1094         states.clear();
1095         states.add(BluetoothLeCall.STATE_ACTIVE);
1096         foregroundCall = getCallByStates(states);
1097         if (foregroundCall != null) {
1098             return foregroundCall;
1099         }
1100 
1101         return null;
1102     }
1103 
findNewForegroundBearer()1104     private synchronized Bearer findNewForegroundBearer() {
1105         if (mBearerList.size() == 0) {
1106             return null;
1107         }
1108 
1109         // the bearer that owns the foreground call
1110         Map.Entry<Integer, TbsCall> foregroundCall = getForegroundCall();
1111         if (foregroundCall != null) {
1112             for (Bearer bearer : mBearerList) {
1113                 if (bearer.callIdIndexMap.values().contains(foregroundCall.getKey())) {
1114                     return bearer;
1115                 }
1116             }
1117         }
1118 
1119         // the last bearer registered
1120         return mBearerList.get(mBearerList.size() - 1);
1121     }
1122 
setForegroundBearer(Bearer bearer)1123     private synchronized void setForegroundBearer(Bearer bearer) {
1124         Log.d(TAG, "setForegroundBearer: bearer=" + bearer);
1125 
1126         if (bearer == null) {
1127             mTbsGatt.setBearerProviderName(DEFAULT_PROVIDER_NAME);
1128             mTbsGatt.setBearerTechnology(DEFAULT_BEARER_TECHNOLOGY);
1129         } else if (mForegroundBearer == null) {
1130             mTbsGatt.setBearerProviderName(bearer.providerName);
1131             mTbsGatt.setBearerTechnology(bearer.technology);
1132         } else {
1133             if (!bearer.providerName.equals(mForegroundBearer.providerName)) {
1134                 mTbsGatt.setBearerProviderName(bearer.providerName);
1135             }
1136 
1137             if (bearer.technology != mForegroundBearer.technology) {
1138                 mTbsGatt.setBearerTechnology(bearer.technology);
1139             }
1140         }
1141 
1142         mForegroundBearer = bearer;
1143     }
1144 
isLeAudioServiceAvailable()1145     private boolean isLeAudioServiceAvailable() {
1146         if (mLeAudioService != null) {
1147             return true;
1148         }
1149 
1150         mLeAudioService = mFactory.getLeAudioService();
1151         if (mLeAudioService == null) {
1152             Log.e(TAG, "leAudioService not available");
1153             return false;
1154         }
1155 
1156         return true;
1157     }
1158 
1159     @VisibleForTesting
setLeAudioServiceForTesting(LeAudioService leAudioService)1160     void setLeAudioServiceForTesting(LeAudioService leAudioService) {
1161         mLeAudioService = leAudioService;
1162     }
1163 
notifyCclc()1164     private synchronized void notifyCclc() {
1165         Log.d(TAG, "notifyCclc");
1166 
1167         if (isLeAudioServiceAvailable()) {
1168             if (mCurrentCallsList.size() > 0) {
1169                 mLeAudioService.setInCall(true);
1170             } else {
1171                 mLeAudioService.setInCall(false);
1172             }
1173         }
1174 
1175         mTbsGatt.setCallState(mCurrentCallsList);
1176         mTbsGatt.setBearerListCurrentCalls(mCurrentCallsList);
1177     }
1178 
updateUriSchemesSupported()1179     private synchronized void updateUriSchemesSupported() {
1180         List<String> newUriSchemes = new ArrayList<>();
1181         for (Bearer bearer : mBearerList) {
1182             newUriSchemes.addAll(bearer.uriSchemes);
1183         }
1184 
1185         // filter duplicates
1186         newUriSchemes = new ArrayList<>(new HashSet<>(newUriSchemes));
1187         if (newUriSchemes.equals(mUriSchemes)) {
1188             return;
1189         }
1190 
1191         mUriSchemes = new ArrayList<>(newUriSchemes);
1192         mTbsGatt.setBearerUriSchemesSupportedList(mUriSchemes);
1193     }
1194 
setActiveLeDevice(BluetoothDevice device)1195     private void setActiveLeDevice(BluetoothDevice device) {
1196         if (device == null) {
1197             Log.w(TAG, "setActiveLeDevice: ignore null device");
1198             return;
1199         }
1200         if (!isLeAudioServiceAvailable()) {
1201             Log.w(TAG, "mLeAudioService not available");
1202             return;
1203         }
1204         mLeAudioService.setActiveDevice(device);
1205     }
1206 
isCallStateTransitionValid(int callState, int requestedOpcode)1207     private static boolean isCallStateTransitionValid(int callState, int requestedOpcode) {
1208         switch (requestedOpcode) {
1209             case TbsGatt.CALL_CONTROL_POINT_OPCODE_ACCEPT:
1210                 if (callState == BluetoothLeCall.STATE_INCOMING) {
1211                     return true;
1212                 }
1213                 break;
1214 
1215             case TbsGatt.CALL_CONTROL_POINT_OPCODE_TERMINATE:
1216                 // Any call can be terminated.
1217                 return true;
1218 
1219             case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_HOLD:
1220                 if (callState == BluetoothLeCall.STATE_INCOMING
1221                         || callState == BluetoothLeCall.STATE_ACTIVE
1222                         || callState == BluetoothLeCall.STATE_REMOTELY_HELD) {
1223                     return true;
1224                 }
1225                 break;
1226 
1227             case TbsGatt.CALL_CONTROL_POINT_OPCODE_LOCAL_RETRIEVE:
1228                 if (callState == BluetoothLeCall.STATE_LOCALLY_HELD
1229                         || callState == BluetoothLeCall.STATE_LOCALLY_AND_REMOTELY_HELD) {
1230                     return true;
1231                 }
1232                 break;
1233 
1234             default:
1235                 Log.e(TAG, "unhandled opcode " + requestedOpcode);
1236         }
1237 
1238         return false;
1239     }
1240 
1241     /**
1242      * Dump status of TBS service along with related objects
1243      *
1244      * @param sb string builder object that TBS module will be appending
1245      */
dump(StringBuilder sb)1246     public void dump(StringBuilder sb) {
1247         sb.append("\tRinger Mode: " + mStoredRingerMode);
1248 
1249         sb.append("\n\tCurrent call list:");
1250         for (TbsCall call : mCurrentCallsList.values()) {
1251             sb.append("\n\t\tFriendly name: " + call.getSafeFriendlyName());
1252             sb.append("\n\t\t\tState: " + TbsCall.stateToString(call.getState()));
1253             sb.append("\n\t\t\tURI: " + call.getSafeUri());
1254             sb.append("\n\t\t\tFlags: " + TbsCall.flagsToString(call.getFlags()));
1255         }
1256 
1257         mTbsGatt.dump(sb);
1258     }
1259 }
1260