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