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