• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bluetooth.telephony;
18 
19 
20 import static java.util.Objects.requireNonNull;
21 import static java.util.Objects.requireNonNullElseGet;
22 
23 import android.annotation.NonNull;
24 import android.bluetooth.BluetoothAdapter;
25 import android.bluetooth.BluetoothLeCall;
26 import android.bluetooth.BluetoothLeCallControl;
27 import android.bluetooth.BluetoothManager;
28 import android.bluetooth.BluetoothProfile;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.net.Uri;
34 import android.os.Binder;
35 import android.os.Bundle;
36 import android.os.IBinder;
37 import android.telecom.BluetoothCallQualityReport;
38 import android.telecom.Call;
39 import android.telecom.CallAudioState;
40 import android.telecom.Connection;
41 import android.telecom.DisconnectCause;
42 import android.telecom.InCallService;
43 import android.telecom.PhoneAccount;
44 import android.telecom.PhoneAccountHandle;
45 import android.telecom.TelecomManager;
46 import android.telecom.VideoProfile;
47 import android.telephony.PhoneNumberUtils;
48 import android.telephony.TelephonyManager;
49 import android.text.TextUtils;
50 import android.util.Log;
51 
52 import androidx.annotation.VisibleForTesting;
53 
54 import com.android.bluetooth.Utils;
55 import com.android.bluetooth.flags.Flags;
56 import com.android.bluetooth.hfp.HeadsetService;
57 import com.android.bluetooth.tbs.BluetoothLeCallControlProxy;
58 
59 import java.util.ArrayDeque;
60 import java.util.ArrayList;
61 import java.util.Collection;
62 import java.util.HashMap;
63 import java.util.LinkedHashSet;
64 import java.util.List;
65 import java.util.Objects;
66 import java.util.Queue;
67 import java.util.Set;
68 import java.util.SortedMap;
69 import java.util.SortedSet;
70 import java.util.TreeMap;
71 import java.util.TreeSet;
72 import java.util.UUID;
73 import java.util.concurrent.ExecutorService;
74 import java.util.concurrent.Executors;
75 
76 /**
77  * Used to receive updates about calls from the Telecom component. This service is bound to Telecom
78  * while there exist calls which potentially require UI. This includes ringing (incoming), dialing
79  * (outgoing), and active calls. When the last BluetoothCall is disconnected, Telecom will unbind to
80  * the service triggering InCallActivity (via CallList) to finish soon after.
81  */
82 public class BluetoothInCallService extends InCallService {
83     private static final String TAG = BluetoothInCallService.class.getSimpleName();
84 
85     // match up with bthf_call_state_t of bt_hf.h
86     private static class CallState {
CallState()87         private CallState() {}
88 
89         static final int ACTIVE = 0;
90         static final int HELD = 1;
91         static final int DIALING = 2;
92         static final int ALERTING = 3;
93         static final int INCOMING = 4;
94         static final int WAITING = 5;
95         static final int IDLE = 6;
96         static final int DISCONNECTED = 7;
97     }
98 
99     @VisibleForTesting
100     static class TerminationReason {
TerminationReason()101         private TerminationReason() {}
102 
103         static final int INVALID_URI = 0x00;
104         static final int FAIL = 0x01;
105         static final int REMOTE_HANGUP = 0x02;
106         static final int SERVER_HANGUP = 0x03;
107         static final int LINE_BUSY = 0x04;
108         static final int NETWORK_CONGESTION = 0x05;
109         static final int CLIENT_HANGUP = 0x06;
110         static final int NO_SERVICE = 0x07;
111         static final int NO_ANSWER = 0x08;
112     }
113 
114     public static class Result {
Result()115         private Result() {}
116 
117         public static final int SUCCESS = 0;
118         public static final int ERROR_UNKNOWN_CALL_ID = 1;
119         public static final int ERROR_INVALID_URI = 2;
120         public static final int ERROR_APPLICATION = 3;
121     }
122 
123     public static class Capability {
Capability()124         private Capability() {}
125 
126         public static final int HOLD_CALL = 0x00000001;
127         public static final int JOIN_CALLS = 0x00000002;
128     }
129 
130     // match up with bthf_call_state_t of bt_hf.h
131     // Terminate all held or set UDUB("busy") to a waiting call
132     private static final int CHLD_TYPE_RELEASEHELD = 0;
133     // Terminate all active calls and accepts a waiting/held call
134     private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
135     // Hold all active calls and accepts a waiting/held call
136     private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
137     // Add all held calls to a conference
138     private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
139 
140     // Indicates that no BluetoothCall is ringing
141     private static final int DEFAULT_RINGING_ADDRESS_TYPE = 128;
142 
143     private int mNumActiveCalls = 0;
144     private int mNumHeldCalls = 0;
145     private int mNumChildrenOfActiveCall = 0;
146     private int mBluetoothCallState = CallState.IDLE;
147     private String mRingingAddress = "";
148     private int mRingingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
149     private BluetoothCall mOldHeldCall = null;
150     private boolean mHeadsetUpdatedRecently = false;
151 
152     @VisibleForTesting boolean mIsTerminatedByClient = false;
153 
154     private static final Object LOCK = new Object();
155 
156     @VisibleForTesting BluetoothLeCallControlProxy mBluetoothLeCallControl;
157     private final ExecutorService mExecutor;
158 
159     private TelephonyManager mTelephonyManager;
160     private TelecomManager mTelecomManager;
161 
162     @VisibleForTesting
163     public final HashMap<Integer, CallStateCallback> mCallbacks = new HashMap<>();
164 
165     @VisibleForTesting
166     public final HashMap<Integer, BluetoothCall> mBluetoothCallHashMap = new HashMap<>();
167 
168     private final HashMap<Integer, BluetoothCall> mBluetoothConferenceCallInference =
169             new HashMap<>();
170 
171     private final HashMap<String, Integer> mConferenceCallClccIndexMap = new HashMap<>();
172 
173     // A queue record the removal order of bluetooth calls
174     private final Queue<Integer> mBluetoothCallQueue = new ArrayDeque<>();
175 
176     private static BluetoothInCallService sInstance = null;
177 
178     private final CallInfo mCallInfo;
179 
180     private int mMaxNumberOfCalls = 0;
181 
182     private BluetoothAdapter mAdapter = null;
183 
184     private final BluetoothProfile.ServiceListener mProfileListener =
185             new BluetoothProfile.ServiceListener() {
186                 @Override
187                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
188                     Log.d(TAG, "onServiceConnected for profile: " + profile);
189                     synchronized (LOCK) {
190                         mBluetoothLeCallControl =
191                                 new BluetoothLeCallControlProxy((BluetoothLeCallControl) proxy);
192 
193                         boolean isBearerRegistered =
194                                 mBluetoothLeCallControl.registerBearer(
195                                         TAG,
196                                         List.of("tel"),
197                                         Capability.HOLD_CALL,
198                                         getNetworkOperator(),
199                                         getBearerTechnology(),
200                                         mExecutor,
201                                         mBluetoothLeCallControlCallback);
202                         Log.d(TAG, "isBearerRegistered: " + isBearerRegistered);
203                         sendTbsCurrentCallsList();
204                     }
205                     Log.d(TAG, "Calls updated for profile: " + profile);
206                 }
207 
208                 @Override
209                 public void onServiceDisconnected(int profile) {
210                     synchronized (LOCK) {
211                         mBluetoothLeCallControl = null;
212                     }
213                 }
214             };
215 
216     public class BluetoothAdapterReceiver extends BroadcastReceiver {
217         @Override
onReceive(Context context, Intent intent)218         public void onReceive(Context context, Intent intent) {
219             synchronized (LOCK) {
220                 int state =
221                         intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
222                 Log.d(TAG, "Bluetooth Adapter state: " + state);
223                 if (state == BluetoothAdapter.STATE_ON) {
224                     queryPhoneState(HeadsetService.getHeadsetService());
225                 } else if (state == BluetoothAdapter.STATE_TURNING_OFF) {
226                     clear();
227                 }
228             }
229         }
230     }
231 
232     /** Receives events for global state changes of the bluetooth adapter. */
233     // TODO: The code is moved from Telecom stack. Since we're running in the BT process itself,
234     // we may be able to simplify this in a future patch.
235     @VisibleForTesting public BluetoothAdapterReceiver mBluetoothAdapterReceiver;
236 
237     @VisibleForTesting
238     public class CallStateCallback extends Call.Callback {
239         public int mLastState;
240 
CallStateCallback(int initialState)241         public CallStateCallback(int initialState) {
242             mLastState = initialState;
243         }
244 
getLastState()245         public int getLastState() {
246             return mLastState;
247         }
248 
onStateChanged(BluetoothCall call, int state)249         void onStateChanged(BluetoothCall call, int state) {
250             if (mCallInfo.isNullCall(call)) {
251                 return;
252             }
253             if (call.isExternalCall()) {
254                 return;
255             }
256             if (state == Call.STATE_DISCONNECTING) {
257                 mLastState = state;
258                 return;
259             }
260 
261             Integer tbsCallState = getTbsCallState(call);
262             if (mBluetoothLeCallControl != null && tbsCallState != null) {
263                 mBluetoothLeCallControl.onCallStateChanged(call.getTbsCallId(), tbsCallState);
264             }
265 
266             // If a BluetoothCall is being put on hold because of a new connecting call, ignore the
267             // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
268             // state atomically.
269             // When the BluetoothCall later transitions to DIALING/DISCONNECTED we will then
270             // send out the aggregated update.
271             if (getLastState() == Call.STATE_ACTIVE && state == Call.STATE_HOLDING) {
272                 for (BluetoothCall otherCall : mCallInfo.getBluetoothCalls()) {
273                     if (otherCall.getState() == Call.STATE_CONNECTING) {
274                         mLastState = state;
275                         return;
276                     }
277                 }
278             }
279 
280             // To have an active BluetoothCall and another dialing at the same time is an invalid BT
281             // state. We can assume that the active BluetoothCall will be automatically held
282             // which will send another update at which point we will be in the right state.
283             BluetoothCall activeCall = mCallInfo.getActiveCall();
284             if (!mCallInfo.isNullCall(activeCall)
285                     && getLastState() == Call.STATE_CONNECTING
286                     && (state == Call.STATE_DIALING || state == Call.STATE_PULLING_CALL)) {
287                 mLastState = state;
288                 return;
289             }
290             mLastState = state;
291             updateHeadsetWithCallState(HeadsetService.getHeadsetService(), false /* force */);
292         }
293 
294         @Override
onStateChanged(Call call, int state)295         public void onStateChanged(Call call, int state) {
296             super.onStateChanged(call, state);
297             onStateChanged(getBluetoothCallById(System.identityHashCode(call)), state);
298         }
299 
300         @VisibleForTesting
onDetailsChanged( HeadsetService headsetService, BluetoothCall call, Call.Details details)301         void onDetailsChanged(
302                 HeadsetService headsetService, BluetoothCall call, Call.Details details) {
303             if (mCallInfo.isNullCall(call)) {
304                 return;
305             }
306             if (call.isExternalCall()) {
307                 onCallRemoved(headsetService, call, false /* forceRemoveCallback */);
308             } else {
309                 onCallAdded(headsetService, call);
310             }
311         }
312 
313         @Override
onDetailsChanged(Call call, Call.Details details)314         public void onDetailsChanged(Call call, Call.Details details) {
315             super.onDetailsChanged(call, details);
316             onDetailsChanged(
317                     HeadsetService.getHeadsetService(),
318                     getBluetoothCallById(System.identityHashCode(call)),
319                     details);
320         }
321 
322         @VisibleForTesting
onParentChanged(HeadsetService headsetService, BluetoothCall call)323         void onParentChanged(HeadsetService headsetService, BluetoothCall call) {
324             if (mCallInfo.isNullCall(call) || call.isExternalCall()) {
325                 Log.w(TAG, "null call or external call");
326                 return;
327             }
328             if (call.getParentId() != null) {
329                 // If this BluetoothCall is newly conferenced, ignore the callback.
330                 // We only care about the one sent for the parent conference call.
331                 Log.d(
332                         TAG,
333                         "Ignoring onIsConferenceChanged from child BluetoothCall with new parent");
334                 return;
335             }
336             updateHeadsetWithCallState(headsetService, false /* force */);
337         }
338 
339         @Override
onParentChanged(Call call, Call parent)340         public void onParentChanged(Call call, Call parent) {
341             super.onParentChanged(call, parent);
342             onParentChanged(
343                     HeadsetService.getHeadsetService(),
344                     getBluetoothCallById(System.identityHashCode(call)));
345         }
346 
347         @VisibleForTesting
onChildrenChanged( HeadsetService headsetService, BluetoothCall call, List<BluetoothCall> children)348         void onChildrenChanged(
349                 HeadsetService headsetService, BluetoothCall call, List<BluetoothCall> children) {
350             if (mCallInfo.isNullCall(call) || call.isExternalCall()) {
351                 Log.w(TAG, "null call or external call");
352                 return;
353             }
354             if (call.getChildrenIds().size() == 1) {
355                 // If this is a parent BluetoothCall with only one child,
356                 // ignore the callback as well since the minimum number of child calls to
357                 // start a conference BluetoothCall is 2. We expect this to be called again
358                 // when the parent BluetoothCall has another child BluetoothCall added.
359                 Log.d(TAG, "Ignoring onIsConferenceChanged from parent with only one child call");
360                 return;
361             }
362             updateHeadsetWithCallState(headsetService, false /* force */);
363         }
364 
365         @Override
onChildrenChanged(Call call, List<Call> children)366         public void onChildrenChanged(Call call, List<Call> children) {
367             super.onChildrenChanged(call, children);
368             onChildrenChanged(
369                     HeadsetService.getHeadsetService(),
370                     getBluetoothCallById(System.identityHashCode(call)),
371                     getBluetoothCallsByIds(BluetoothCall.getIds(children)));
372         }
373     }
374 
375     @Override
onBind(Intent intent)376     public IBinder onBind(Intent intent) {
377         Log.i(TAG, "onBind. Intent: " + intent);
378         return super.onBind(intent);
379     }
380 
381     @Override
onUnbind(Intent intent)382     public boolean onUnbind(Intent intent) {
383         Log.i(TAG, "onUnbind. Intent: " + intent);
384         return super.onUnbind(intent);
385     }
386 
BluetoothInCallService(CallInfo callInfo)387     private BluetoothInCallService(CallInfo callInfo) {
388         Log.i(TAG, "BluetoothInCallService is created");
389         mCallInfo = requireNonNullElseGet(callInfo, () -> new CallInfo());
390         mExecutor = Executors.newSingleThreadExecutor();
391     }
392 
BluetoothInCallService()393     public BluetoothInCallService() {
394         this(null);
395     }
396 
397     @VisibleForTesting
BluetoothInCallService( Context context, CallInfo callInfo, BluetoothLeCallControlProxy leCallControl)398     BluetoothInCallService(
399             Context context,
400             CallInfo callInfo,
401             BluetoothLeCallControlProxy leCallControl) {
402         this(callInfo);
403         mBluetoothLeCallControl = leCallControl;
404         attachBaseContext(context);
405     }
406 
getInstance()407     public static BluetoothInCallService getInstance() {
408         return sInstance;
409     }
410 
answerCall()411     public boolean answerCall() {
412         synchronized (LOCK) {
413             Log.i(TAG, "BT - answering call");
414             BluetoothCall call = mCallInfo.getRingingOrSimulatedRingingCall();
415             if (mCallInfo.isNullCall(call)) {
416                 return false;
417             }
418             call.answer(VideoProfile.STATE_AUDIO_ONLY);
419             return true;
420         }
421     }
422 
hangupCall()423     public boolean hangupCall() {
424         synchronized (LOCK) {
425             Log.i(TAG, "BT - hanging up call");
426             BluetoothCall call = mCallInfo.getForegroundCall();
427             if (mCallInfo.isNullCall(call)) {
428                 return false;
429             }
430             // release the parent if there is a conference call
431             BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId());
432             if (!mCallInfo.isNullCall(conferenceCall)
433                     && conferenceCall.getState() == Call.STATE_ACTIVE) {
434                 Log.i(TAG, "BT - hanging up conference call");
435                 call = conferenceCall;
436             } else if (Flags.nonConferenceCallHangup()
437                     && !mCallInfo.isNullCall(conferenceCall)
438                     && conferenceCall.getState() == Call.STATE_HOLDING) {
439                 Log.i(TAG, "BT - hanging up active call other than conference call");
440                 /* Find active call other than conference */
441                 for (BluetoothCall btCall : mCallInfo.getBluetoothCalls()) {
442                     if (!btCall.isConference()
443                             && btCall.getState() == Call.STATE_ACTIVE
444                             && btCall.getParentId() == null) {
445                         call = btCall;
446                         break;
447                     }
448                 }
449             }
450             if (call.getState() == Call.STATE_RINGING) {
451                 call.reject(false, "");
452             } else {
453                 call.disconnect();
454             }
455             return true;
456         }
457     }
458 
sendDtmf(int dtmf)459     public boolean sendDtmf(int dtmf) {
460         synchronized (LOCK) {
461             Log.i(TAG, "BT - sendDtmf " + dtmf);
462             BluetoothCall call = mCallInfo.getForegroundCall();
463             if (mCallInfo.isNullCall(call)) {
464                 return false;
465             }
466             // TODO: Consider making this a queue instead of starting/stopping in quick succession.
467             call.playDtmfTone((char) dtmf);
468             call.stopDtmfTone();
469             return true;
470         }
471     }
472 
getNetworkOperator()473     public String getNetworkOperator() {
474         synchronized (LOCK) {
475             Log.i(TAG, "getNetworkOperator");
476             PhoneAccount account = mCallInfo.getBestPhoneAccount();
477             if (account != null && account.getLabel() != null) {
478                 return account.getLabel().toString();
479             }
480             // Finally, just get the network name from telephony.
481             return mTelephonyManager.getNetworkOperatorName();
482         }
483     }
484 
485     /**
486      * Gets the bearer technology.
487      *
488      * @return bearer technology as defined in Bluetooth Assigned Numbers
489      */
490     @VisibleForTesting
getBearerTechnology()491     int getBearerTechnology() {
492         synchronized (LOCK) {
493             Log.i(TAG, "getBearerTechnology");
494             // Get the network name from telephony.
495             int dataNetworkType = mTelephonyManager.getDataNetworkType();
496             switch (dataNetworkType) {
497                 case TelephonyManager.NETWORK_TYPE_UNKNOWN:
498                 case TelephonyManager.NETWORK_TYPE_GSM:
499                     return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM;
500 
501                 case TelephonyManager.NETWORK_TYPE_GPRS:
502                     return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_2G;
503 
504                 case TelephonyManager.NETWORK_TYPE_EDGE:
505                 case TelephonyManager.NETWORK_TYPE_EVDO_0:
506                 case TelephonyManager.NETWORK_TYPE_EVDO_A:
507                 case TelephonyManager.NETWORK_TYPE_HSDPA:
508                 case TelephonyManager.NETWORK_TYPE_HSUPA:
509                 case TelephonyManager.NETWORK_TYPE_HSPA:
510                 case TelephonyManager.NETWORK_TYPE_IDEN:
511                 case TelephonyManager.NETWORK_TYPE_EVDO_B:
512                     return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_3G;
513 
514                 case TelephonyManager.NETWORK_TYPE_UMTS:
515                 case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
516                     return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_WCDMA;
517 
518                 case TelephonyManager.NETWORK_TYPE_LTE:
519                     return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_LTE;
520 
521                 case TelephonyManager.NETWORK_TYPE_EHRPD:
522                 case TelephonyManager.NETWORK_TYPE_CDMA:
523                 case TelephonyManager.NETWORK_TYPE_1xRTT:
524                     return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_CDMA;
525 
526                 case TelephonyManager.NETWORK_TYPE_HSPAP:
527                     return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_4G;
528 
529                 case TelephonyManager.NETWORK_TYPE_IWLAN:
530                     return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_WIFI;
531 
532                 case TelephonyManager.NETWORK_TYPE_NR:
533                     return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_5G;
534             }
535 
536             return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM;
537         }
538     }
539 
getSubscriberNumber()540     public String getSubscriberNumber() {
541         synchronized (LOCK) {
542             Log.i(TAG, "getSubscriberNumber");
543             String address = null;
544             PhoneAccount account = mCallInfo.getBestPhoneAccount();
545             if (account != null) {
546                 Uri addressUri = account.getAddress();
547                 if (addressUri != null) {
548                     address = addressUri.getSchemeSpecificPart();
549                 }
550             }
551             if (TextUtils.isEmpty(address)) {
552                 address = mTelephonyManager.getLine1Number();
553                 if (address == null) address = "";
554             }
555             return address;
556         }
557     }
558 
listCurrentCalls(HeadsetService headsetService)559     public boolean listCurrentCalls(HeadsetService headsetService) {
560         synchronized (LOCK) {
561             // only log if it is after we recently updated the headset state or else it can
562             // clog the android log since this can be queried every second.
563             boolean logQuery = mHeadsetUpdatedRecently;
564             mHeadsetUpdatedRecently = false;
565 
566             if (logQuery) {
567                 Log.i(TAG, "listCurrentCalls");
568             }
569 
570             sendListOfCalls(headsetService, logQuery);
571             return true;
572         }
573     }
574 
queryPhoneState(HeadsetService headsetService)575     public boolean queryPhoneState(HeadsetService headsetService) {
576         synchronized (LOCK) {
577             Log.i(TAG, "queryPhoneState");
578             updateHeadsetWithCallState(headsetService, true);
579             return true;
580         }
581     }
582 
583     /** Check for HD codec for voice call */
isHighDefCallInProgress()584     public boolean isHighDefCallInProgress() {
585         boolean isHighDef = false;
586         /* TODO: Add as an API in TelephonyManager aosp/2679237 */
587         int phoneTypeIms = 5;
588         int phoneTypeCdmaLte = 6;
589         BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
590         BluetoothCall dialingCall = mCallInfo.getOutgoingCall();
591         BluetoothCall activeCall = mCallInfo.getActiveCall();
592 
593         /* If it's an incoming call we will have codec info in dialing state */
594         if (ringingCall != null) {
595             isHighDef = ringingCall.isHighDefAudio();
596         } else if (dialingCall != null) {
597             /* CS dialing call has codec info in dialing state */
598             Bundle extras = dialingCall.getDetails().getExtras();
599             if (extras != null) {
600                 int phoneType = extras.getInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE);
601                 if (phoneType == TelephonyManager.PHONE_TYPE_GSM
602                         || phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
603                     isHighDef = dialingCall.isHighDefAudio();
604                     /* For IMS calls codec info is not present in dialing state */
605                 } else if (phoneType == phoneTypeIms || phoneType == phoneTypeCdmaLte) {
606                     isHighDef = true;
607                 }
608             }
609         } else if (activeCall != null) {
610             isHighDef = activeCall.isHighDefAudio();
611         }
612         Log.i(TAG, "isHighDefCallInProgress: Call is High Def " + isHighDef);
613         return isHighDef;
614     }
615 
processChld(HeadsetService headsetService, int chld)616     public boolean processChld(HeadsetService headsetService, int chld) {
617         synchronized (LOCK) {
618             final long token = Binder.clearCallingIdentity();
619             try {
620                 Log.i(TAG, "processChld " + chld);
621                 return processChldLocked(headsetService, chld);
622             } finally {
623                 Binder.restoreCallingIdentity(token);
624             }
625         }
626     }
627 
628     @VisibleForTesting
onCallAdded(HeadsetService headsetService, BluetoothCall call)629     void onCallAdded(HeadsetService headsetService, BluetoothCall call) {
630         synchronized (LOCK) {
631             if (call.isExternalCall()) {
632                 Log.d(TAG, "onCallAdded: external call");
633                 return;
634             }
635             if (!mBluetoothCallHashMap.containsKey(call.getId())) {
636                 Log.i(TAG, "onCallAdded");
637                 CallStateCallback callback = new CallStateCallback(call.getState());
638                 mCallbacks.put(call.getId(), callback);
639                 call.registerCallback(callback);
640 
641                 mBluetoothCallHashMap.put(call.getId(), call);
642                 if (!call.isConference()) {
643                     mMaxNumberOfCalls =
644                             Integer.max(mMaxNumberOfCalls, mBluetoothCallHashMap.size());
645                 }
646                 updateHeadsetWithCallState(headsetService, false /* force */);
647 
648                 BluetoothLeCall tbsCall = createTbsCall(call);
649                 if (mBluetoothLeCallControl != null && tbsCall != null) {
650                     mBluetoothLeCallControl.onCallAdded(tbsCall);
651                 }
652             } else {
653                 Log.i(TAG, "onCallAdded: call already exists");
654             }
655         }
656     }
657 
sendBluetoothCallQualityReport( long timestamp, int rssi, int snr, int retransmissionCount, int packetsNotReceiveCount, int negativeAcknowledgementCount)658     public void sendBluetoothCallQualityReport(
659             long timestamp,
660             int rssi,
661             int snr,
662             int retransmissionCount,
663             int packetsNotReceiveCount,
664             int negativeAcknowledgementCount) {
665         BluetoothCall call = mCallInfo.getForegroundCall();
666         if (mCallInfo.isNullCall(call)) {
667             Log.w(TAG, "No foreground call while trying to send BQR");
668             return;
669         }
670         Bundle b = new Bundle();
671         b.putParcelable(
672                 BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT,
673                 new BluetoothCallQualityReport.Builder()
674                         .setSentTimestampMillis(timestamp)
675                         .setChoppyVoice(true)
676                         .setRssiDbm(rssi)
677                         .setSnrDb(snr)
678                         .setRetransmittedPacketsCount(retransmissionCount)
679                         .setPacketsNotReceivedCount(packetsNotReceiveCount)
680                         .setNegativeAcknowledgementCount(negativeAcknowledgementCount)
681                         .build());
682         call.sendCallEvent(BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT, b);
683     }
684 
685     @Override
onCallAdded(Call call)686     public void onCallAdded(Call call) {
687         super.onCallAdded(call);
688         onCallAdded(HeadsetService.getHeadsetService(), new BluetoothCall(call));
689     }
690 
691     /**
692      * Called when a {@code BluetoothCall} has been removed from this in-call session.
693      *
694      * @param call the {@code BluetoothCall} to remove
695      * @param forceRemoveCallback if true, this will always unregister this {@code InCallService} as
696      *     a callback for the given {@code BluetoothCall}, when false, this will not remove the
697      *     callback when the {@code BluetoothCall} is external so that the call can be added back if
698      *     no longer external.
699      */
onCallRemoved( HeadsetService headsetService, BluetoothCall call, boolean forceRemoveCallback)700     public void onCallRemoved(
701             HeadsetService headsetService, BluetoothCall call, boolean forceRemoveCallback) {
702         synchronized (LOCK) {
703             Log.i(TAG, "onCallRemoved, forceRemoveCallback=" + forceRemoveCallback);
704             CallStateCallback callback = getCallback(call);
705             if (callback != null && (forceRemoveCallback || !call.isExternalCall())) {
706                 call.unregisterCallback(callback);
707             }
708 
709             if (mBluetoothCallHashMap.containsKey(call.getId())) {
710                 mBluetoothCallHashMap.remove(call.getId());
711 
712                 DisconnectCause cause = call.getDisconnectCause();
713                 if (cause != null && cause.getCode() == DisconnectCause.OTHER) {
714                     Log.d(TAG, "add inference call with reason: " + cause.getReason());
715                     mBluetoothCallQueue.add(call.getId());
716                     mBluetoothConferenceCallInference.put(call.getId(), call);
717                     if (Flags.maintainCallIndexAfterConference()) {
718                         // If the disconnect is due to call merge, store the index for future use.
719                         if (cause.getReason() != null
720                                 && cause.getReason().equals("IMS_MERGED_SUCCESSFULLY")) {
721                             if (!mConferenceCallClccIndexMap.containsKey(getClccMapKey(call))) {
722                                 if (call.mClccIndex > -1) {
723                                     mConferenceCallClccIndexMap.put(
724                                             getClccMapKey(call), call.mClccIndex);
725                                 }
726                             }
727                         }
728                     }
729 
730                     // queue size limited to 2 because merge operation only happens on 2 calls
731                     // we are only interested in last 2 calls merged
732                     if (mBluetoothCallQueue.size() > 2) {
733                         Integer callId = mBluetoothCallQueue.peek();
734                         mBluetoothCallQueue.remove();
735                         mBluetoothConferenceCallInference.remove(callId);
736                     }
737                 }
738                 // As there is at most 1 conference call, so clear inference when parent call ends
739                 if (call.isConference()) {
740                     Log.d(TAG, "conference call ends, clear inference");
741                     mBluetoothConferenceCallInference.clear();
742                     mBluetoothCallQueue.clear();
743                 }
744             }
745 
746             updateHeadsetWithCallState(headsetService, false /* force */);
747 
748             if (Flags.maintainCallIndexAfterConference()
749                     && mConferenceCallClccIndexMap.size() > 0) {
750                 int anyActiveCalls = mCallInfo.isNullCall(mCallInfo.getActiveCall()) ? 0 : 1;
751                 int numHeldCalls = mCallInfo.getNumHeldCalls();
752                 // If no call is active or held clear the hashmap.
753                 if (anyActiveCalls == 0 && numHeldCalls == 0) {
754                     mConferenceCallClccIndexMap.clear();
755                 }
756             }
757 
758             if (mBluetoothLeCallControl != null) {
759                 mBluetoothLeCallControl.onCallRemoved(
760                         call.getTbsCallId(), getTbsTerminationReason(call));
761             }
762         }
763     }
764 
765     @Override
onCallRemoved(Call call)766     public void onCallRemoved(Call call) {
767         super.onCallRemoved(call);
768         BluetoothCall bluetoothCall = getBluetoothCallById(System.identityHashCode(call));
769         if (bluetoothCall == null) {
770             Log.w(TAG, "onCallRemoved, BluetoothCall is removed before registered");
771             return;
772         }
773         onCallRemoved(
774                 HeadsetService.getHeadsetService(), bluetoothCall, true /* forceRemoveCallback */);
775     }
776 
777     @Override
onCallAudioStateChanged(CallAudioState audioState)778     public void onCallAudioStateChanged(CallAudioState audioState) {
779         super.onCallAudioStateChanged(audioState);
780         Log.d(TAG, "onCallAudioStateChanged, audioState == " + audioState);
781     }
782 
783     @Override
onCreate()784     public void onCreate() {
785         super.onCreate();
786         synchronized (LOCK) {
787             Log.d(TAG, "onCreate");
788             mAdapter = requireNonNull(getSystemService(BluetoothManager.class)).getAdapter();
789             mTelephonyManager = requireNonNull(getSystemService(TelephonyManager.class));
790             mTelecomManager = requireNonNull(getSystemService(TelecomManager.class));
791             mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.LE_CALL_CONTROL);
792             mBluetoothAdapterReceiver = new BluetoothAdapterReceiver();
793             IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
794             intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
795             registerReceiver(mBluetoothAdapterReceiver, intentFilter);
796             sInstance = this;
797         }
798     }
799 
800     @Override
onDestroy()801     public void onDestroy() {
802         synchronized (LOCK) {
803             Log.d(TAG, "onDestroy");
804             clear();
805         }
806         super.onDestroy();
807     }
808 
809     @Override
810     @VisibleForTesting
attachBaseContext(Context base)811     public void attachBaseContext(Context base) {
812         super.attachBaseContext(base);
813     }
814 
815     @VisibleForTesting
clear()816     void clear() {
817         Log.d(TAG, "clear");
818         if (mBluetoothAdapterReceiver != null) {
819             unregisterReceiver(mBluetoothAdapterReceiver);
820             mBluetoothAdapterReceiver = null;
821         }
822         if (mBluetoothLeCallControl != null) {
823             mBluetoothLeCallControl.unregisterBearer();
824             mBluetoothLeCallControl.closeBluetoothLeCallControlProxy(mAdapter);
825             mBluetoothLeCallControl = null;
826         }
827         sInstance = null;
828         mCallbacks.clear();
829         mBluetoothCallHashMap.clear();
830         mBluetoothConferenceCallInference.clear();
831         mBluetoothCallQueue.clear();
832         mMaxNumberOfCalls = 0;
833     }
834 
isConferenceWithNoChildren(BluetoothCall call)835     private static boolean isConferenceWithNoChildren(BluetoothCall call) {
836         return call.isConference()
837                 && (call.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)
838                         || call.getChildrenIds().isEmpty());
839     }
840 
sendListOfCalls(HeadsetService headsetService, boolean shouldLog)841     private void sendListOfCalls(HeadsetService headsetService, boolean shouldLog) {
842         Collection<BluetoothCall> calls = mCallInfo.getBluetoothCalls();
843 
844         // either do conference call CLCC index inference or normal conference call
845         BluetoothCall conferenceCallChildrenNotReady = null;
846         for (BluetoothCall call : calls) {
847             // find the conference call parent among calls
848             if (call.isConference() && !mBluetoothConferenceCallInference.isEmpty()) {
849                 Log.d(
850                         TAG,
851                         "conference call inferred size: "
852                                 + mBluetoothConferenceCallInference.size()
853                                 + " current size: "
854                                 + mBluetoothCallHashMap.size());
855                 // Do conference call inference until at least 2 children arrive
856                 // If carrier sends children info, then inference will end when info arrives.
857                 // If carrier doesn't send children info, then inference won't impact actual value.
858                 if (call.getChildrenIds().size() >= 2) {
859                     mBluetoothConferenceCallInference.clear();
860                     break;
861                 }
862                 conferenceCallChildrenNotReady = call;
863             }
864         }
865         if (conferenceCallChildrenNotReady != null) {
866             SortedMap<Integer, Object[]> clccResponseMap = new TreeMap<>();
867             for (BluetoothCall inferredCall : mBluetoothConferenceCallInference.values()) {
868                 if (inferredCall.isCallNull() || inferredCall.getHandle() == null) {
869                     Log.w(TAG, "inferredCall does not have handle");
870                     continue;
871                 }
872                 // save the index so later on when real children arrive, index is the same
873                 int index = inferredCall.mClccIndex;
874                 if (index == -1) {
875                     Log.w(TAG, "inferred index is not valid");
876                     continue;
877                 }
878 
879                 // associate existing bluetoothCall with inferredCall based on call handle
880                 for (BluetoothCall bluetoothCall : mBluetoothCallHashMap.values()) {
881                     if (bluetoothCall.getHandle() == null) {
882                         Log.w(TAG, "call id: " + bluetoothCall.getId() + " handle is null");
883                         continue;
884                     }
885                     boolean isSame =
886                             PhoneNumberUtils.areSamePhoneNumber(
887                                     bluetoothCall.getHandle().toString(),
888                                     inferredCall.getHandle().toString(),
889                                     mTelephonyManager.getNetworkCountryIso());
890                     if (isSame) {
891                         Log.d(
892                                 TAG,
893                                 "found conference call children that has same call handle, "
894                                         + "call id: "
895                                         + bluetoothCall.getId());
896                         bluetoothCall.mClccIndex = inferredCall.mClccIndex;
897                         break;
898                     }
899                 }
900 
901                 int direction = inferredCall.isIncoming() ? 1 : 0;
902                 int state = CallState.ACTIVE;
903                 boolean isPartOfConference = true;
904                 final Uri addressUri;
905                 if (inferredCall.getGatewayInfo() != null) {
906                     addressUri = inferredCall.getGatewayInfo().getOriginalAddress();
907                 } else {
908                     addressUri = inferredCall.getHandle();
909                 }
910                 String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
911                 if (address != null) {
912                     address = PhoneNumberUtils.stripSeparators(address);
913                 }
914                 int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
915                 clccResponseMap.put(
916                         index,
917                         new Object[] {
918                             index, direction, state, 0, isPartOfConference, address, addressType
919                         });
920             }
921             // sort CLCC response based on index
922             for (Object[] response : clccResponseMap.values()) {
923                 if (response.length < 7) {
924                     Log.e(TAG, "clccResponseMap entry too short");
925                     continue;
926                 }
927                 Log.i(
928                         TAG,
929                         Utils.formatSimple(
930                                 "sending inferred clcc for BluetoothCall: index %d, direction"
931                                         + " %d, state %d, isPartOfConference %b, addressType %d",
932                                 (int) response[0],
933                                 (int) response[1],
934                                 (int) response[2],
935                                 (boolean) response[4],
936                                 (int) response[6]));
937                 headsetService.clccResponse(
938                         (int) response[0],
939                         (int) response[1],
940                         (int) response[2],
941                         (int) response[3],
942                         (boolean) response[4],
943                         (String) response[5],
944                         (int) response[6]);
945             }
946             headsetService.clccResponse(0 /* index */, 0, 0, 0, false, null, 0); // End marker
947             return;
948         }
949 
950         for (BluetoothCall call : calls) {
951             // We don't send the parent conference BluetoothCall to the bluetooth device.
952             // We do, however want to send conferences that have no children to the bluetooth
953             // device (e.g. IMS Conference).
954             boolean isConferenceWithNoChildren = isConferenceWithNoChildren(call);
955             Log.i(
956                     TAG,
957                     "sendListOfCalls isConferenceWithNoChildren "
958                             + isConferenceWithNoChildren
959                             + ", call.getChildrenIds() size "
960                             + call.getChildrenIds().size());
961             if (!call.isConference() || isConferenceWithNoChildren) {
962                 sendClccForCall(headsetService, call, shouldLog);
963             }
964         }
965         headsetService.clccResponse(0 /* index */, 0, 0, 0, false, null, 0); // End marker
966     }
967 
968     /** Sends a single clcc (C* List Current Calls) event for the specified call. */
sendClccForCall( HeadsetService headsetService, BluetoothCall call, boolean shouldLog)969     private void sendClccForCall(
970             HeadsetService headsetService, BluetoothCall call, boolean shouldLog) {
971         boolean isForeground = call.equals(mCallInfo.getForegroundCall());
972         int state = getBtCallState(call, isForeground);
973         boolean isPartOfConference = false;
974         boolean isConferenceWithNoChildren = isConferenceWithNoChildren(call);
975 
976         if (state == CallState.IDLE) {
977             return;
978         }
979 
980         BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId());
981         if (!mCallInfo.isNullCall(conferenceCall)) {
982             isPartOfConference = true;
983 
984             if (conferenceCall.hasProperty(Call.Details.PROPERTY_GENERIC_CONFERENCE)) {
985                 // Run some alternative states for CDMA Conference-level merge/swap support.
986                 // Basically, if BluetoothCall supports swapping or merging at the conference-level,
987                 // then we need to expose the calls as having distinct states
988                 // (ACTIVE vs CAPABILITY_HOLD) or
989                 // the functionality won't show up on the bluetooth device.
990 
991                 // Before doing any special logic, ensure that we are dealing with an
992                 // ACTIVE BluetoothCall and that the conference itself has a notion of
993                 // the current "active" child call.
994                 BluetoothCall activeChild =
995                         getBluetoothCallById(
996                                 conferenceCall.getGenericConferenceActiveChildCallId());
997                 if (state == CallState.ACTIVE && !mCallInfo.isNullCall(activeChild)) {
998                     // Reevaluate state if we can MERGE or if we can SWAP without previously having
999                     // MERGED.
1000                     boolean shouldReevaluateState =
1001                             conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)
1002                                     || (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)
1003                                             && !conferenceCall.wasConferencePreviouslyMerged());
1004 
1005                     if (shouldReevaluateState) {
1006                         isPartOfConference = false;
1007                         if (call.equals(activeChild)) {
1008                             state = CallState.ACTIVE;
1009                         } else {
1010                             // At this point we know there is an "active" child and we know that it
1011                             // is not this call, so set it to HELD instead.
1012                             state = CallState.HELD;
1013                         }
1014                     }
1015                 }
1016             }
1017             if (conferenceCall.getState() == Call.STATE_HOLDING
1018                     && conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) {
1019                 // If the parent IMS CEP conference BluetoothCall is on hold, we should mark
1020                 // this BluetoothCall as being on hold regardless of what the other
1021                 // children are doing.
1022                 state = CallState.HELD;
1023             }
1024         } else if (isConferenceWithNoChildren) {
1025             // Handle the special case of an IMS conference BluetoothCall without conference
1026             // event package support.
1027             // The BluetoothCall will be marked as a conference, but the conference will not have
1028             // child calls where conference event packages are not used by the carrier.
1029             isPartOfConference = true;
1030         }
1031 
1032         int index = getIndexForCall(call);
1033         int direction = call.isIncoming() ? 1 : 0;
1034         final Uri addressUri;
1035         if (call.getGatewayInfo() != null) {
1036             addressUri = call.getGatewayInfo().getOriginalAddress();
1037         } else {
1038             addressUri = call.getHandle();
1039         }
1040 
1041         String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
1042         if (address != null) {
1043             address = PhoneNumberUtils.stripSeparators(address);
1044         }
1045 
1046         int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
1047 
1048         if (shouldLog) {
1049             Log.i(
1050                     TAG,
1051                     "sending clcc for BluetoothCall "
1052                             + index
1053                             + ", "
1054                             + direction
1055                             + ", "
1056                             + state
1057                             + ", "
1058                             + isPartOfConference
1059                             + ", "
1060                             + addressType);
1061         }
1062 
1063         headsetService.clccResponse(
1064                 index, direction, state, 0, isPartOfConference, address, addressType);
1065     }
1066 
getNextAvailableClccIndex(int index)1067     int getNextAvailableClccIndex(int index) {
1068         // find the next available smallest index
1069         SortedSet<Integer> availableIndex = new TreeSet<>();
1070         for (int i = index; i <= mMaxNumberOfCalls + 1; i++) {
1071             availableIndex.add(i);
1072         }
1073         for (BluetoothCall bluetoothCall : mBluetoothCallHashMap.values()) {
1074             int callCLCCIndex = bluetoothCall.mClccIndex;
1075             if (availableIndex.contains(callCLCCIndex)) {
1076                 availableIndex.remove(callCLCCIndex);
1077             }
1078         }
1079         Log.d(TAG, "availableIndex first: " + availableIndex.first());
1080         return availableIndex.first();
1081     }
1082 
1083     @VisibleForTesting
1084     /* Function to extract and return call handle. */
getClccMapKey(BluetoothCall call)1085     private String getClccMapKey(BluetoothCall call) {
1086         if (mCallInfo.isNullCall(call) || call.getHandle() == null) {
1087             return "";
1088         }
1089         Uri handle = call.getHandle();
1090         String key;
1091         if (call.hasProperty(Call.Details.PROPERTY_SELF_MANAGED)) {
1092             key = handle.toString() + " self managed " + call.getId();
1093         } else {
1094             key = handle.toString();
1095         }
1096         Log.d(TAG, "getClccMapKey Key: " + key);
1097         return key;
1098     }
1099 
1100     /**
1101      * Returns the caches index for the specified call. If no such index exists, then an index is
1102      * given (the smallest number starting from 1 that isn't already taken).
1103      */
getIndexForCall(BluetoothCall call)1104     private int getIndexForCall(BluetoothCall call) {
1105         if (mCallInfo.isNullCall(call)) {
1106             Log.w(TAG, "empty or null call");
1107             return -1;
1108         }
1109 
1110         // Check if the call handle is already stored. Return the previously stored index.
1111         if (Flags.maintainCallIndexAfterConference()
1112                 && mConferenceCallClccIndexMap.containsKey(getClccMapKey(call))) {
1113             call.mClccIndex = mConferenceCallClccIndexMap.get(getClccMapKey(call));
1114         }
1115 
1116         if (call.mClccIndex >= 1) {
1117             return call.mClccIndex;
1118         }
1119 
1120         int index = 1; // Indexes for bluetooth clcc are 1-based.
1121         if (call.isConference()) {
1122             index = mMaxNumberOfCalls + 1; // The conference call should have a higher index
1123             Log.i(TAG, "getIndexForCall for conference call starting from " + mMaxNumberOfCalls);
1124         }
1125 
1126         // NOTE: Indexes are removed in {@link #onCallRemoved}.
1127         call.mClccIndex = getNextAvailableClccIndex(index);
1128         if (Flags.maintainCallIndexAfterConference()) {
1129             // Remove the index from conference hashmap, this can be later added if call merges in
1130             // conference
1131             mConferenceCallClccIndexMap
1132                     .entrySet()
1133                     .removeIf(entry -> entry.getValue() == call.mClccIndex);
1134         }
1135         Log.d(TAG, "call " + call.getId() + " CLCC index is " + call.mClccIndex);
1136         return call.mClccIndex;
1137     }
1138 
processChldLocked(HeadsetService headsetService, int chld)1139     private boolean processChldLocked(HeadsetService headsetService, int chld) {
1140         BluetoothCall activeCall = mCallInfo.getActiveCall();
1141         BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
1142         if (ringingCall == null) {
1143             Log.i(TAG, "ringingCall null at processChld");
1144         } else {
1145             Log.i(TAG, "ringingCall hashcode: " + ringingCall.hashCode());
1146         }
1147 
1148         BluetoothCall heldCall = mCallInfo.getHeldCall();
1149 
1150         Log.i(
1151                 TAG,
1152                 "Active: "
1153                         + activeCall
1154                         + " Ringing: "
1155                         + ringingCall
1156                         + " Held: "
1157                         + heldCall
1158                         + " chld: "
1159                         + chld);
1160 
1161         if (chld == CHLD_TYPE_RELEASEHELD) {
1162             Log.i(TAG, "chld is CHLD_TYPE_RELEASEHELD");
1163             if (!mCallInfo.isNullCall(ringingCall)) {
1164                 Log.i(TAG, "reject ringing call " + ringingCall.hashCode());
1165                 ringingCall.reject(false, null);
1166                 return true;
1167             } else if (!mCallInfo.isNullCall(heldCall)) {
1168                 heldCall.disconnect();
1169                 return true;
1170             }
1171         } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
1172             if (mCallInfo.isNullCall(activeCall)
1173                     && mCallInfo.isNullCall(ringingCall)
1174                     && mCallInfo.isNullCall(heldCall)) {
1175                 return false;
1176             }
1177             if (!mCallInfo.isNullCall(activeCall)) {
1178                 BluetoothCall conferenceCall = getBluetoothCallById(activeCall.getParentId());
1179                 if (!mCallInfo.isNullCall(conferenceCall)
1180                         && conferenceCall.getState() == Call.STATE_ACTIVE) {
1181                     Log.i(TAG, "CHLD: disconnect conference call");
1182                     conferenceCall.disconnect();
1183                 } else {
1184                     activeCall.disconnect();
1185                 }
1186             }
1187             if (!mCallInfo.isNullCall(ringingCall)) {
1188                 ringingCall.answer(ringingCall.getVideoState());
1189             } else if (!mCallInfo.isNullCall(heldCall)) {
1190                 heldCall.unhold();
1191             }
1192             return true;
1193         } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
1194             if (!mCallInfo.isNullCall(activeCall)
1195                     && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
1196                 activeCall.swapConference();
1197                 Log.i(TAG, "CDMA calls in conference swapped, updating headset");
1198                 updateHeadsetWithCallState(headsetService, true /* force */);
1199                 return true;
1200             } else if (!mCallInfo.isNullCall(ringingCall)) {
1201                 ringingCall.answer(VideoProfile.STATE_AUDIO_ONLY);
1202                 return true;
1203             } else if (!mCallInfo.isNullCall(heldCall)) {
1204                 // CallsManager will hold any active calls when unhold() is called on a
1205                 // currently-held call.
1206                 heldCall.unhold();
1207                 return true;
1208             } else if (!mCallInfo.isNullCall(activeCall)
1209                     && activeCall.can(Connection.CAPABILITY_HOLD)) {
1210                 activeCall.hold();
1211                 return true;
1212             }
1213         } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
1214             if (!mCallInfo.isNullCall(activeCall)) {
1215                 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
1216                     activeCall.mergeConference();
1217                     return true;
1218                 } else {
1219                     List<BluetoothCall> conferenceable =
1220                             getBluetoothCallsByIds(activeCall.getConferenceableCalls());
1221                     if (!conferenceable.isEmpty()) {
1222                         activeCall.conference(conferenceable.get(0));
1223                         return true;
1224                     }
1225                 }
1226             }
1227         }
1228         return false;
1229     }
1230 
1231     /**
1232      * Sends an update of the current BluetoothCall state to the current Headset.
1233      *
1234      * @param force {@code true} if the headset state should be sent regardless if no changes to the
1235      *     state have occurred, {@code false} if the state should only be sent if the state has
1236      *     changed.
1237      */
updateHeadsetWithCallState(HeadsetService headsetService, boolean force)1238     private void updateHeadsetWithCallState(HeadsetService headsetService, boolean force) {
1239         if (headsetService == null) {
1240             Log.i(TAG, "updateHeadsetWithCallState skipped: No headset service");
1241             return;
1242         }
1243 
1244         BluetoothCall activeCall = mCallInfo.getActiveCall();
1245         BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
1246         BluetoothCall heldCall = mCallInfo.getHeldCall();
1247 
1248         int bluetoothCallState = getBluetoothCallStateForUpdate();
1249 
1250         String ringingAddress = null;
1251         int ringingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
1252         String ringingName = null;
1253         if (!mCallInfo.isNullCall(ringingCall)
1254                 && ringingCall.getHandle() != null
1255                 && !ringingCall.isSilentRingingRequested()) {
1256             ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
1257             if (ringingAddress != null) {
1258                 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
1259             }
1260             ringingName = ringingCall.getCallerDisplayName();
1261             if (TextUtils.isEmpty(ringingName)) {
1262                 ringingName = ringingCall.getContactDisplayName();
1263             }
1264         }
1265         if (ringingAddress == null) {
1266             ringingAddress = "";
1267         }
1268 
1269         int numActiveCalls = mCallInfo.isNullCall(activeCall) ? 0 : 1;
1270         int numHeldCalls = mCallInfo.getNumHeldCalls();
1271         int numChildrenOfActiveCall =
1272                 mCallInfo.isNullCall(activeCall) ? 0 : activeCall.getChildrenIds().size();
1273 
1274         // Intermediate state for GSM calls which are in the process of being swapped.
1275         // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
1276         //       are held?
1277         boolean callsPendingSwitch = (numHeldCalls == 2);
1278 
1279         // For conference calls which support swapping the active BluetoothCall within the
1280         // conference (namely CDMA calls) we need to expose that as a held BluetoothCall
1281         // in order for the BT device to show "swap" and "merge" functionality.
1282         boolean ignoreHeldCallChange = false;
1283         if (!mCallInfo.isNullCall(activeCall)
1284                 && activeCall.isConference()
1285                 && !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
1286             if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
1287                 // Indicate that BT device should show SWAP command by indicating that there is a
1288                 // BluetoothCall on hold, but only if the conference wasn't previously merged.
1289                 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
1290             } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
1291                 numHeldCalls = 1; // Merge is available, so expose via numHeldCalls.
1292             }
1293 
1294             for (Integer id : activeCall.getChildrenIds()) {
1295                 // Held BluetoothCall has changed due to it being combined into a CDMA conference.
1296                 // Keep track of this and ignore any future update since it doesn't really count
1297                 // as a BluetoothCall change.
1298                 if (mOldHeldCall != null && Objects.equals(mOldHeldCall.getId(), id)) {
1299                     ignoreHeldCallChange = true;
1300                     break;
1301                 }
1302             }
1303         }
1304 
1305         boolean callsDetailsChanged =
1306                 numActiveCalls != mNumActiveCalls
1307                         || numChildrenOfActiveCall != mNumChildrenOfActiveCall
1308                         || numHeldCalls != mNumHeldCalls
1309                         || bluetoothCallState != mBluetoothCallState
1310                         || !TextUtils.equals(ringingAddress, mRingingAddress)
1311                         || ringingAddressType != mRingingAddressType
1312                         || (!Objects.equals(heldCall, mOldHeldCall) && !ignoreHeldCallChange);
1313 
1314         if (!(force || (!callsPendingSwitch && callsDetailsChanged))) {
1315             Log.i(TAG, "updateHeadsetWithCallState skipped");
1316             return;
1317         }
1318 
1319         mOldHeldCall = heldCall;
1320         mNumActiveCalls = numActiveCalls;
1321         mNumChildrenOfActiveCall = numChildrenOfActiveCall;
1322         mNumHeldCalls = numHeldCalls;
1323         mRingingAddress = ringingAddress;
1324         mRingingAddressType = ringingAddressType;
1325 
1326         // If the BluetoothCall is transitioning into the alerting state, send DIALING first.
1327         // Some devices expect to see a DIALING state prior to seeing an ALERTING state
1328         // so we need to send it first.
1329         if (mBluetoothCallState != bluetoothCallState && bluetoothCallState == CallState.ALERTING) {
1330             phoneStateChanged(headsetService, CallState.DIALING, ringingName);
1331         }
1332 
1333         phoneStateChanged(headsetService, bluetoothCallState, ringingName);
1334 
1335         mBluetoothCallState = bluetoothCallState;
1336         mHeadsetUpdatedRecently = true;
1337     }
1338 
phoneStateChanged( HeadsetService headsetService, int callState, String ringingName)1339     private void phoneStateChanged(
1340             HeadsetService headsetService, int callState, String ringingName) {
1341         Log.i(
1342                 TAG,
1343                 "updateHeadsetWithCallState "
1344                         + (" numActive=" + mNumActiveCalls)
1345                         + (" numHeld=" + mNumHeldCalls)
1346                         + (" callState=" + callState)
1347                         + (" ringingType=" + mRingingAddressType));
1348         headsetService.phoneStateChanged(
1349                 mNumActiveCalls,
1350                 mNumHeldCalls,
1351                 callState,
1352                 mRingingAddress,
1353                 mRingingAddressType,
1354                 ringingName,
1355                 false); // isVirtualCall
1356     }
1357 
getBluetoothCallStateForUpdate()1358     private int getBluetoothCallStateForUpdate() {
1359         BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
1360         BluetoothCall dialingCall = mCallInfo.getOutgoingCall();
1361         boolean hasOnlyDisconnectedCalls = mCallInfo.hasOnlyDisconnectedCalls();
1362 
1363         //
1364         // !! WARNING !!
1365         // You will note that WAITING, HELD, and ACTIVE are not used in this version of the
1366         // BluetoothCall state mappings.
1367         // This is on purpose.
1368         // phone_state_change() in btif_hf.c is not written to handle these states.
1369         // Only with the listCalls*() method are WAITING and ACTIVE used.
1370         // Using the unsupported states here caused problems with inconsistent state in some
1371         // bluetooth devices (like not getting out of ringing state after answering a call).
1372         //
1373 
1374         int bluetoothCallState = CallState.IDLE;
1375         if (!mCallInfo.isNullCall(ringingCall) && !ringingCall.isSilentRingingRequested()) {
1376             bluetoothCallState = CallState.INCOMING;
1377         } else if (!mCallInfo.isNullCall(dialingCall)) {
1378             bluetoothCallState = CallState.ALERTING;
1379         } else if (hasOnlyDisconnectedCalls) {
1380             // Keep the DISCONNECTED state until the disconnect tone's playback is done
1381             bluetoothCallState = CallState.DISCONNECTED;
1382         }
1383         return bluetoothCallState;
1384     }
1385 
getBtCallState(BluetoothCall call, boolean isForeground)1386     private static int getBtCallState(BluetoothCall call, boolean isForeground) {
1387         return switch (call.getState()) {
1388             case Call.STATE_ACTIVE -> CallState.ACTIVE;
1389             case Call.STATE_HOLDING -> CallState.HELD;
1390             case Call.STATE_NEW, Call.STATE_DISCONNECTED, Call.STATE_AUDIO_PROCESSING ->
1391                     CallState.IDLE;
1392 
1393             case Call.STATE_CONNECTING,
1394                             Call.STATE_SELECT_PHONE_ACCOUNT,
1395                             Call.STATE_DIALING,
1396                             Call.STATE_PULLING_CALL ->
1397                     // Yes, this is correctly returning ALERTING.
1398                     // "Dialing" for BT means that we have sent information to the service provider
1399                     // to place the BluetoothCall but there is no confirmation that the
1400                     // BluetoothCall
1401                     // is going through. When there finally is confirmation, the ringback is
1402                     // played which is referred to as an "alert" tone, thus, ALERTING.
1403                     // TODO: We should consider using the ALERTING terms in Telecom because that
1404                     // seems to be more industry-standard.
1405                     CallState.ALERTING;
1406 
1407             case Call.STATE_RINGING, Call.STATE_SIMULATED_RINGING -> {
1408                 if (call.isSilentRingingRequested()) {
1409                     yield CallState.IDLE;
1410                 } else if (isForeground) {
1411                     yield CallState.INCOMING;
1412                 } else {
1413                     yield CallState.WAITING;
1414                 }
1415             }
1416             default -> CallState.IDLE;
1417         };
1418     }
1419 
1420     @VisibleForTesting
getCallback(BluetoothCall call)1421     public CallStateCallback getCallback(BluetoothCall call) {
1422         return mCallbacks.get(call.getId());
1423     }
1424 
1425     @VisibleForTesting
getBluetoothCallById(Integer id)1426     public BluetoothCall getBluetoothCallById(Integer id) {
1427         if (mBluetoothCallHashMap.containsKey(id)) {
1428             return mBluetoothCallHashMap.get(id);
1429         }
1430         return null;
1431     }
1432 
1433     @VisibleForTesting
getBluetoothCallsByIds(List<Integer> ids)1434     public List<BluetoothCall> getBluetoothCallsByIds(List<Integer> ids) {
1435         List<BluetoothCall> calls = new ArrayList<>();
1436         for (Integer id : ids) {
1437             BluetoothCall call = getBluetoothCallById(id);
1438             if (!mCallInfo.isNullCall(call)) {
1439                 calls.add(call);
1440             }
1441         }
1442         return calls;
1443     }
1444 
1445     // extract call information functions out into this part, so we can mock it in testing
1446     @VisibleForTesting
1447     public class CallInfo {
1448 
getForegroundCall()1449         public BluetoothCall getForegroundCall() {
1450             LinkedHashSet<Integer> states = new LinkedHashSet<Integer>();
1451             BluetoothCall foregroundCall;
1452 
1453             states.add(Call.STATE_CONNECTING);
1454             foregroundCall = getCallByStates(states);
1455             if (!mCallInfo.isNullCall(foregroundCall)) {
1456                 return foregroundCall;
1457             }
1458 
1459             states.clear();
1460             states.add(Call.STATE_ACTIVE);
1461             states.add(Call.STATE_DIALING);
1462             states.add(Call.STATE_PULLING_CALL);
1463             foregroundCall = getCallByStates(states);
1464             if (!mCallInfo.isNullCall(foregroundCall)) {
1465                 return foregroundCall;
1466             }
1467 
1468             states.clear();
1469             states.add(Call.STATE_RINGING);
1470             foregroundCall = getCallByStates(states);
1471             if (!mCallInfo.isNullCall(foregroundCall)) {
1472                 return foregroundCall;
1473             }
1474 
1475             return null;
1476         }
1477 
getCallByStates(Set<Integer> states)1478         public BluetoothCall getCallByStates(Set<Integer> states) {
1479             List<BluetoothCall> calls = getBluetoothCalls();
1480             for (BluetoothCall call : calls) {
1481                 if (states.contains(call.getState())) {
1482                     return call;
1483                 }
1484             }
1485             return null;
1486         }
1487 
getCallByState(int state)1488         public BluetoothCall getCallByState(int state) {
1489             List<BluetoothCall> calls = getBluetoothCalls();
1490             for (BluetoothCall call : calls) {
1491                 if (state == call.getState()) {
1492                     return call;
1493                 }
1494             }
1495             return null;
1496         }
1497 
getNumHeldCalls()1498         public int getNumHeldCalls() {
1499             int number = 0;
1500             List<BluetoothCall> calls = getBluetoothCalls();
1501             for (BluetoothCall call : calls) {
1502                 if (call.getState() == Call.STATE_HOLDING) {
1503                     number++;
1504                 }
1505             }
1506             return number;
1507         }
1508 
hasOnlyDisconnectedCalls()1509         public boolean hasOnlyDisconnectedCalls() {
1510             List<BluetoothCall> calls = getBluetoothCalls();
1511             if (calls.size() == 0) {
1512                 return false;
1513             }
1514             for (BluetoothCall call : calls) {
1515                 if (call.getState() != Call.STATE_DISCONNECTED
1516                         && call.getState() != Call.STATE_DISCONNECTING) {
1517                     return false;
1518                 }
1519             }
1520             return true;
1521         }
1522 
getBluetoothCalls()1523         public List<BluetoothCall> getBluetoothCalls() {
1524             return getBluetoothCallsByIds(BluetoothCall.getIds(getCalls()));
1525         }
1526 
getOutgoingCall()1527         public BluetoothCall getOutgoingCall() {
1528             LinkedHashSet<Integer> states = new LinkedHashSet<Integer>();
1529             states.add(Call.STATE_CONNECTING);
1530             states.add(Call.STATE_DIALING);
1531             states.add(Call.STATE_PULLING_CALL);
1532             return getCallByStates(states);
1533         }
1534 
getRingingOrSimulatedRingingCall()1535         public BluetoothCall getRingingOrSimulatedRingingCall() {
1536             LinkedHashSet<Integer> states = new LinkedHashSet<Integer>();
1537             states.add(Call.STATE_RINGING);
1538             states.add(Call.STATE_SIMULATED_RINGING);
1539             return getCallByStates(states);
1540         }
1541 
getActiveCall()1542         public BluetoothCall getActiveCall() {
1543             return getCallByState(Call.STATE_ACTIVE);
1544         }
1545 
getHeldCall()1546         public BluetoothCall getHeldCall() {
1547             return getCallByState(Call.STATE_HOLDING);
1548         }
1549 
1550         /**
1551          * Returns the best phone account to use for the given state of all calls. First, tries to
1552          * return the phone account for the foreground call, second the default phone account for
1553          * PhoneAccount.SCHEME_TEL.
1554          */
getBestPhoneAccount()1555         public PhoneAccount getBestPhoneAccount() {
1556             BluetoothCall call = getForegroundCall();
1557 
1558             PhoneAccount account = null;
1559             if (!mCallInfo.isNullCall(call)) {
1560                 PhoneAccountHandle handle = call.getAccountHandle();
1561                 if (handle != null) {
1562                     // First try to get the network name of the foreground call.
1563                     account = mTelecomManager.getPhoneAccount(handle);
1564                 }
1565             }
1566 
1567             if (account == null) {
1568                 // Second, Try to get the label for the default Phone Account.
1569                 List<PhoneAccountHandle> handles =
1570                         mTelecomManager.getPhoneAccountsSupportingScheme(PhoneAccount.SCHEME_TEL);
1571                 while (handles.iterator().hasNext()) {
1572                     account = mTelecomManager.getPhoneAccount(handles.iterator().next());
1573                     if (account != null) {
1574                         return account;
1575                     }
1576                 }
1577             }
1578             return null;
1579         }
1580 
isNullCall(BluetoothCall call)1581         public boolean isNullCall(BluetoothCall call) {
1582             return call == null || call.isCallNull();
1583         }
1584 
getCallByCallId(UUID callId)1585         public BluetoothCall getCallByCallId(UUID callId) {
1586             List<BluetoothCall> calls = getBluetoothCalls();
1587             for (BluetoothCall call : calls) {
1588                 Log.i(TAG, "getCallByCallId lookingFor=" + callId + " has=" + call.getTbsCallId());
1589                 if (callId.equals(call.getTbsCallId())) {
1590                     return call;
1591                 }
1592             }
1593             return null;
1594         }
1595     }
1596 
getTbsCallState(BluetoothCall call)1597     private static Integer getTbsCallState(BluetoothCall call) {
1598         return switch (call.getState()) {
1599             case Call.STATE_ACTIVE -> BluetoothLeCall.STATE_ACTIVE;
1600             case Call.STATE_HOLDING -> BluetoothLeCall.STATE_LOCALLY_HELD;
1601             case Call.STATE_DIALING, Call.STATE_PULLING_CALL -> BluetoothLeCall.STATE_ALERTING;
1602             case Call.STATE_CONNECTING, Call.STATE_SELECT_PHONE_ACCOUNT ->
1603                     BluetoothLeCall.STATE_DIALING;
1604 
1605             case Call.STATE_RINGING, Call.STATE_SIMULATED_RINGING -> {
1606                 if (call.isSilentRingingRequested()) {
1607                     yield null;
1608                 } else {
1609                     yield BluetoothLeCall.STATE_INCOMING;
1610                 }
1611             }
1612             default -> null;
1613         };
1614     }
1615 
1616     @VisibleForTesting
1617     int getTbsTerminationReason(BluetoothCall call) {
1618         DisconnectCause cause = call.getDisconnectCause();
1619         if (cause == null) {
1620             Log.w(TAG, " termination cause is null");
1621             return TerminationReason.FAIL;
1622         }
1623 
1624         return switch (cause.getCode()) {
1625             case DisconnectCause.BUSY -> TerminationReason.LINE_BUSY;
1626             case DisconnectCause.ERROR -> TerminationReason.NETWORK_CONGESTION;
1627             case DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED -> TerminationReason.INVALID_URI;
1628             case DisconnectCause.REMOTE, DisconnectCause.REJECTED ->
1629                     TerminationReason.REMOTE_HANGUP;
1630             case DisconnectCause.LOCAL -> {
1631                 if (mIsTerminatedByClient) {
1632                     mIsTerminatedByClient = false;
1633                     yield TerminationReason.CLIENT_HANGUP;
1634                 }
1635                 yield TerminationReason.SERVER_HANGUP;
1636             }
1637             default -> TerminationReason.FAIL;
1638         };
1639     }
1640 
1641     private BluetoothLeCall createTbsCall(BluetoothCall call) {
1642         Integer state = getTbsCallState(call);
1643         boolean isConferenceWithNoChildren = isConferenceWithNoChildren(call);
1644 
1645         if (state == null) {
1646             return null;
1647         }
1648 
1649         BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId());
1650         if (!mCallInfo.isNullCall(conferenceCall)) {
1651             // Run some alternative states for Conference-level merge/swap support.
1652             // Basically, if BluetoothCall supports swapping or merging at the
1653             // conference-level,
1654             // then we need to expose the calls as having distinct states
1655             // (ACTIVE vs CAPABILITY_HOLD) or
1656             // the functionality won't show up on the bluetooth device.
1657 
1658             // Before doing any special logic, ensure that we are dealing with an
1659             // ACTIVE BluetoothCall and that the conference itself has a notion of
1660             // the current "active" child call.
1661             BluetoothCall activeChild =
1662                     getBluetoothCallById(conferenceCall.getGenericConferenceActiveChildCallId());
1663             if (state == BluetoothLeCall.STATE_ACTIVE && !mCallInfo.isNullCall(activeChild)) {
1664                 // Reevaluate state if we can MERGE or if we can SWAP without previously having
1665                 // MERGED.
1666                 boolean shouldReevaluateState =
1667                         conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)
1668                                 || (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)
1669                                         && !conferenceCall.wasConferencePreviouslyMerged());
1670 
1671                 if (shouldReevaluateState) {
1672                     if (call.equals(activeChild)) {
1673                         state = BluetoothLeCall.STATE_ACTIVE;
1674                     } else {
1675                         // At this point we know there is an "active" child and we know that it is
1676                         // not this call, so set it to HELD instead.
1677                         state = BluetoothLeCall.STATE_LOCALLY_HELD;
1678                     }
1679                 }
1680             }
1681             if (conferenceCall.getState() == Call.STATE_HOLDING
1682                     && conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) {
1683                 // If the parent IMS CEP conference BluetoothCall is on hold, we should mark
1684                 // this BluetoothCall as being on hold regardless of what the other
1685                 // children are doing.
1686                 state = BluetoothLeCall.STATE_LOCALLY_HELD;
1687             }
1688         } else if (isConferenceWithNoChildren) {
1689             // Handle the special case of an IMS conference BluetoothCall without conference
1690             // event package support.
1691             // The BluetoothCall will be marked as a conference, but the conference will not
1692             // have
1693             // child calls where conference event packages are not used by the carrier.
1694         }
1695 
1696         final Uri addressUri;
1697         if (call.getGatewayInfo() != null) {
1698             addressUri = call.getGatewayInfo().getOriginalAddress();
1699         } else {
1700             addressUri = call.getHandle();
1701         }
1702 
1703         String uri = addressUri == null ? null : addressUri.toString();
1704         int callFlags = call.isIncoming() ? 0 : BluetoothLeCall.FLAG_OUTGOING_CALL;
1705 
1706         String friendlyName = call.getCallerDisplayName();
1707         if (TextUtils.isEmpty(friendlyName)) {
1708             friendlyName = call.getContactDisplayName();
1709         }
1710 
1711         return new BluetoothLeCall(call.getTbsCallId(), uri, friendlyName, state, callFlags);
1712     }
1713 
1714     private void sendTbsCurrentCallsList() {
1715         List<BluetoothLeCall> tbsCalls = new ArrayList<>();
1716 
1717         for (BluetoothCall call : mBluetoothCallHashMap.values()) {
1718             BluetoothLeCall tbsCall = createTbsCall(call);
1719             if (tbsCall != null) {
1720                 tbsCalls.add(tbsCall);
1721             }
1722         }
1723 
1724         mBluetoothLeCallControl.currentCallsList(tbsCalls);
1725     }
1726 
1727     @VisibleForTesting
1728     final BluetoothLeCallControl.Callback mBluetoothLeCallControlCallback =
1729             new BluetoothLeCallControl.Callback() {
1730 
1731                 @Override
1732                 public void onAcceptCall(int requestId, UUID callId) {
1733                     synchronized (LOCK) {
1734                         Log.i(TAG, "TBS - accept call=" + callId);
1735                         int result = Result.SUCCESS;
1736                         BluetoothCall call = mCallInfo.getCallByCallId(callId);
1737                         if (mCallInfo.isNullCall(call)) {
1738                             result = Result.ERROR_UNKNOWN_CALL_ID;
1739                         } else {
1740                             call.answer(VideoProfile.STATE_AUDIO_ONLY);
1741                         }
1742                         mBluetoothLeCallControl.requestResult(requestId, result);
1743                     }
1744                 }
1745 
1746                 @Override
1747                 public void onTerminateCall(int requestId, UUID callId) {
1748                     synchronized (LOCK) {
1749                         Log.i(TAG, "TBS - terminate call=" + callId);
1750                         int result = Result.SUCCESS;
1751                         BluetoothCall call = mCallInfo.getCallByCallId(callId);
1752                         if (mCallInfo.isNullCall(call)) {
1753                             result = Result.ERROR_UNKNOWN_CALL_ID;
1754                         } else {
1755                             mIsTerminatedByClient = true;
1756                             call.disconnect();
1757                         }
1758                         mBluetoothLeCallControl.requestResult(requestId, result);
1759                     }
1760                 }
1761 
1762                 @Override
1763                 public void onHoldCall(int requestId, UUID callId) {
1764                     synchronized (LOCK) {
1765                         Log.i(TAG, "TBS - hold call=" + callId);
1766                         int result = Result.SUCCESS;
1767                         BluetoothCall call = mCallInfo.getCallByCallId(callId);
1768                         if (mCallInfo.isNullCall(call)) {
1769                             result = Result.ERROR_UNKNOWN_CALL_ID;
1770                         } else {
1771                             call.hold();
1772                         }
1773                         mBluetoothLeCallControl.requestResult(requestId, result);
1774                     }
1775                 }
1776 
1777                 @Override
1778                 public void onUnholdCall(int requestId, UUID callId) {
1779                     synchronized (LOCK) {
1780                         Log.i(TAG, "TBS - unhold call=" + callId);
1781                         int result = Result.SUCCESS;
1782                         BluetoothCall call = mCallInfo.getCallByCallId(callId);
1783                         if (mCallInfo.isNullCall(call)) {
1784                             result = Result.ERROR_UNKNOWN_CALL_ID;
1785                         } else {
1786                             call.unhold();
1787                         }
1788                         mBluetoothLeCallControl.requestResult(requestId, result);
1789                     }
1790                 }
1791 
1792                 @Override
1793                 public void onPlaceCall(int requestId, UUID callId, String uri) {
1794                     mBluetoothLeCallControl.requestResult(requestId, Result.ERROR_APPLICATION);
1795                 }
1796 
1797                 @Override
1798                 public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) {
1799                     synchronized (LOCK) {
1800                         Log.i(TAG, "TBS - onJoinCalls");
1801                         List<UUID> alreadyJoinedCalls = new ArrayList<>();
1802                         BluetoothCall baseCallInstance = null;
1803 
1804                         if (callIds.size() < 2) {
1805                             Log.e(
1806                                     TAG,
1807                                     "TBS - onJoinCalls, join call number is invalid: "
1808                                             + callIds.size());
1809                             mBluetoothLeCallControl.requestResult(
1810                                     requestId, Result.ERROR_UNKNOWN_CALL_ID);
1811                             return;
1812                         }
1813 
1814                         for (UUID callToJoinUuid : callIds) {
1815                             BluetoothCall callToJoinInstance =
1816                                     mCallInfo.getCallByCallId(callToJoinUuid);
1817 
1818                             /* Skip invalid and already add device */
1819                             if ((callToJoinInstance == null)
1820                                     || (alreadyJoinedCalls.contains(callToJoinUuid))) {
1821                                 continue;
1822                             }
1823 
1824                             /* Lets make first valid call the base call */
1825                             if (baseCallInstance == null) {
1826                                 baseCallInstance = callToJoinInstance;
1827                                 alreadyJoinedCalls.add(callToJoinUuid);
1828                                 continue;
1829                             }
1830 
1831                             baseCallInstance.conference(callToJoinInstance);
1832                             alreadyJoinedCalls.add(callToJoinUuid);
1833                         }
1834 
1835                         int result = Result.SUCCESS;
1836                         if ((baseCallInstance == null) || (alreadyJoinedCalls.size() < 2)) {
1837                             result = Result.ERROR_UNKNOWN_CALL_ID;
1838                         }
1839 
1840                         mBluetoothLeCallControl.requestResult(requestId, result);
1841                     }
1842                 }
1843             };
1844 }
1845