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