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.BluetoothProfile;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.pm.PackageManager;
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.InCallService;
38 import android.telecom.PhoneAccount;
39 import android.telecom.PhoneAccountHandle;
40 import android.telecom.TelecomManager;
41 import android.telecom.VideoProfile;
42 import android.telephony.PhoneNumberUtils;
43 import android.telephony.TelephonyManager;
44 import android.text.TextUtils;
45 import android.util.Log;
46 
47 import com.android.bluetooth.btservice.AdapterService;
48 import com.android.bluetooth.hfp.BluetoothHeadsetProxy;
49 import com.android.bluetooth.hfp.HeadsetService;
50 
51 import androidx.annotation.VisibleForTesting;
52 
53 import java.util.ArrayList;
54 import java.util.Collection;
55 import java.util.HashMap;
56 import java.util.LinkedHashSet;
57 import java.util.List;
58 import java.util.Map;
59 
60 /**
61  * Used to receive updates about calls from the Telecom component. This service is bound to Telecom
62  * while there exist calls which potentially require UI. This includes ringing (incoming), dialing
63  * (outgoing), and active calls. When the last BluetoothCall is disconnected, Telecom will unbind
64  * to the service triggering InCallActivity (via CallList) to finish soon after.
65  */
66 public class BluetoothInCallService extends InCallService {
67 
68     private static final String TAG = "BluetoothInCallService";
69 
70     // match up with bthf_call_state_t of bt_hf.h
71     private static final int CALL_STATE_ACTIVE = 0;
72     private static final int CALL_STATE_HELD = 1;
73     private static final int CALL_STATE_DIALING = 2;
74     private static final int CALL_STATE_ALERTING = 3;
75     private static final int CALL_STATE_INCOMING = 4;
76     private static final int CALL_STATE_WAITING = 5;
77     private static final int CALL_STATE_IDLE = 6;
78     private static final int CALL_STATE_DISCONNECTED = 7;
79 
80     // match up with bthf_call_state_t of bt_hf.h
81     // Terminate all held or set UDUB("busy") to a waiting call
82     private static final int CHLD_TYPE_RELEASEHELD = 0;
83     // Terminate all active calls and accepts a waiting/held call
84     private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
85     // Hold all active calls and accepts a waiting/held call
86     private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
87     // Add all held calls to a conference
88     private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
89 
90     // Indicates that no BluetoothCall is ringing
91     private static final int DEFAULT_RINGING_ADDRESS_TYPE = 128;
92 
93     private int mNumActiveCalls = 0;
94     private int mNumHeldCalls = 0;
95     private int mNumChildrenOfActiveCall = 0;
96     private int mBluetoothCallState = CALL_STATE_IDLE;
97     private String mRingingAddress = "";
98     private int mRingingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
99     private BluetoothCall mOldHeldCall = null;
100     private boolean mHeadsetUpdatedRecently = false;
101     private boolean mIsDisconnectedTonePlaying = false;
102 
103     private static final Object LOCK = new Object();
104     private BluetoothHeadsetProxy mBluetoothHeadset;
105 
106     @VisibleForTesting
107     public TelephonyManager mTelephonyManager;
108 
109     @VisibleForTesting
110     public TelecomManager mTelecomManager;
111 
112     @VisibleForTesting
113     public final HashMap<String, CallStateCallback> mCallbacks = new HashMap<>();
114 
115     @VisibleForTesting
116     public final HashMap<String, BluetoothCall> mBluetoothCallHashMap = new HashMap<>();
117 
118     // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
119     private final Map<String, Integer> mClccIndexMap = new HashMap<>();
120 
121     private static BluetoothInCallService sInstance = null;
122 
123     public CallInfo mCallInfo = new CallInfo();
124 
125     /**
126      * Listens to connections and disconnections of bluetooth headsets.  We need to save the current
127      * bluetooth headset so that we know where to send BluetoothCall updates.
128      */
129     @VisibleForTesting
130     public BluetoothProfile.ServiceListener mProfileListener =
131             new BluetoothProfile.ServiceListener() {
132                 @Override
133                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
134                     synchronized (LOCK) {
135                         setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy));
136                         updateHeadsetWithCallState(true /* force */);
137                     }
138                 }
139 
140                 @Override
141                 public void onServiceDisconnected(int profile) {
142                     synchronized (LOCK) {
143                         setBluetoothHeadset(null);
144                     }
145                 }
146             };
147 
148     public class BluetoothAdapterReceiver extends BroadcastReceiver {
149         @Override
onReceive(Context context, Intent intent)150         public void onReceive(Context context, Intent intent) {
151             synchronized (LOCK) {
152                 if (intent.getAction() != BluetoothAdapter.ACTION_STATE_CHANGED) {
153                     Log.w(TAG, "BluetoothAdapterReceiver: Intent action " + intent.getAction());
154                     return;
155                 }
156                 int state = intent
157                         .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
158                 Log.d(TAG, "Bluetooth Adapter state: " + state);
159                 if (state == BluetoothAdapter.STATE_ON) {
160                     queryPhoneState();
161                 }
162             }
163         }
164     };
165 
166     /**
167      * Receives events for global state changes of the bluetooth adapter.
168      */
169     // TODO: The code is moved from Telecom stack. Since we're running in the BT process itself,
170     // we may be able to simplify this in a future patch.
171     @VisibleForTesting
172     public BluetoothAdapterReceiver mBluetoothAdapterReceiver;
173 
174     @VisibleForTesting
175     public class CallStateCallback extends Call.Callback {
176         public int mLastState;
177 
CallStateCallback(int initialState)178         public CallStateCallback(int initialState) {
179             mLastState = initialState;
180         }
181 
getLastState()182         public int getLastState() {
183             return mLastState;
184         }
185 
onStateChanged(BluetoothCall call, int state)186         public void onStateChanged(BluetoothCall call, int state) {
187             if (mCallInfo.isNullCall(call)) {
188                 return;
189             }
190             if (call.isExternalCall()) {
191                 return;
192             }
193             if (state == Call.STATE_DISCONNECTING) {
194                 mLastState = state;
195                 return;
196             }
197 
198             // If a BluetoothCall is being put on hold because of a new connecting call, ignore the
199             // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
200             // state atomically.
201             // When the BluetoothCall later transitions to DIALING/DISCONNECTED we will then
202             // send out the aggregated update.
203             if (getLastState() == Call.STATE_ACTIVE && state == Call.STATE_HOLDING) {
204                 for (BluetoothCall otherCall : mCallInfo.getBluetoothCalls()) {
205                     if (otherCall.getState() == Call.STATE_CONNECTING) {
206                         mLastState = state;
207                         return;
208                     }
209                 }
210             }
211 
212             // To have an active BluetoothCall and another dialing at the same time is an invalid BT
213             // state. We can assume that the active BluetoothCall will be automatically held
214             // which will send another update at which point we will be in the right state.
215             BluetoothCall activeCall = mCallInfo.getActiveCall();
216             if (!mCallInfo.isNullCall(activeCall)
217                     && getLastState() == Call.STATE_CONNECTING
218                     && (state == Call.STATE_DIALING || state == Call.STATE_PULLING_CALL)) {
219                 mLastState = state;
220                 return;
221             }
222             mLastState = state;
223             updateHeadsetWithCallState(false /* force */);
224         }
225 
226         @Override
onStateChanged(Call call, int state)227         public void onStateChanged(Call call, int state) {
228             super.onStateChanged(call, state);
229             onStateChanged(getBluetoothCallById(call.getDetails().getTelecomCallId()), state);
230         }
231 
onDetailsChanged(BluetoothCall call, Call.Details details)232         public void onDetailsChanged(BluetoothCall call, Call.Details details) {
233             if (mCallInfo.isNullCall(call)) {
234                 return;
235             }
236             if (call.isExternalCall()) {
237                 onCallRemoved(call);
238             } else {
239                 onCallAdded(call);
240             }
241         }
242 
243         @Override
onDetailsChanged(Call call, Call.Details details)244         public void onDetailsChanged(Call call, Call.Details details) {
245             super.onDetailsChanged(call, details);
246             onDetailsChanged(getBluetoothCallById(call.getDetails().getTelecomCallId()), details);
247         }
248 
onParentChanged(BluetoothCall call)249         public void onParentChanged(BluetoothCall call) {
250             if (call.isExternalCall()) {
251                 return;
252             }
253             if (call.getParentId() != null) {
254                 // If this BluetoothCall is newly conferenced, ignore the callback.
255                 // We only care about the one sent for the parent conference call.
256                 Log.d(TAG,
257                         "Ignoring onIsConferenceChanged from child BluetoothCall with new parent");
258                 return;
259             }
260             updateHeadsetWithCallState(false /* force */);
261         }
262 
263         @Override
onParentChanged(Call call, Call parent)264         public void onParentChanged(Call call, Call parent) {
265             super.onParentChanged(call, parent);
266             onParentChanged(
267                     getBluetoothCallById(call.getDetails().getTelecomCallId()));
268         }
269 
onChildrenChanged(BluetoothCall call, List<BluetoothCall> children)270         public void onChildrenChanged(BluetoothCall call, List<BluetoothCall> children) {
271             if (call.isExternalCall()) {
272                 return;
273             }
274             if (call.getChildrenIds().size() == 1) {
275                 // If this is a parent BluetoothCall with only one child,
276                 // ignore the callback as well since the minimum number of child calls to
277                 // start a conference BluetoothCall is 2. We expect this to be called again
278                 // when the parent BluetoothCall has another child BluetoothCall added.
279                 Log.d(TAG,
280                         "Ignoring onIsConferenceChanged from parent with only one child call");
281                 return;
282             }
283             updateHeadsetWithCallState(false /* force */);
284         }
285 
286         @Override
onChildrenChanged(Call call, List<Call> children)287         public void onChildrenChanged(Call call, List<Call> children) {
288             super.onChildrenChanged(call, children);
289             onChildrenChanged(
290                     getBluetoothCallById(call.getDetails().getTelecomCallId()),
291                     getBluetoothCallsByIds(BluetoothCall.getIds(children)));
292         }
293     }
294 
295     @Override
onBind(Intent intent)296     public IBinder onBind(Intent intent) {
297         Log.i(TAG, "onBind. Intent: " + intent);
298         BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
299         if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
300             Log.i(TAG, "Bluetooth is off");
301             ComponentName componentName
302                     = new ComponentName(getPackageName(), this.getClass().getName());
303             getPackageManager().setComponentEnabledSetting(
304                     componentName,
305                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
306                     PackageManager.DONT_KILL_APP);
307             return null;
308         }
309         IBinder binder = super.onBind(intent);
310         mTelephonyManager = getSystemService(TelephonyManager.class);
311         mTelecomManager = getSystemService(TelecomManager.class);
312         return binder;
313     }
314 
315     @Override
onUnbind(Intent intent)316     public boolean onUnbind(Intent intent) {
317         Log.i(TAG, "onUnbind. Intent: " + intent);
318         BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
319         if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
320             Log.i(TAG, "Bluetooth is off when unbind, disable BluetoothInCallService");
321             AdapterService adapterService = AdapterService.getAdapterService();
322             adapterService.enableBluetoothInCallService(false);
323 
324         }
325         return super.onUnbind(intent);
326     }
327 
BluetoothInCallService()328     public BluetoothInCallService() {
329         Log.i(TAG, "BluetoothInCallService is created");
330         sInstance = this;
331     }
332 
getInstance()333     public static BluetoothInCallService getInstance() {
334         return sInstance;
335     }
336 
337     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
enforceModifyPermission()338     protected void enforceModifyPermission() {
339         enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
340     }
341 
342     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
answerCall()343     public boolean answerCall() {
344         synchronized (LOCK) {
345             enforceModifyPermission();
346             Log.i(TAG, "BT - answering call");
347             BluetoothCall call = mCallInfo.getRingingOrSimulatedRingingCall();
348             if (mCallInfo.isNullCall(call)) {
349                 return false;
350             }
351             call.answer(VideoProfile.STATE_AUDIO_ONLY);
352             return true;
353         }
354     }
355 
356     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
hangupCall()357     public boolean hangupCall() {
358         synchronized (LOCK) {
359             enforceModifyPermission();
360             Log.i(TAG, "BT - hanging up call");
361             BluetoothCall call = mCallInfo.getForegroundCall();
362             if (mCallInfo.isNullCall(call)) {
363                 return false;
364             }
365             // release the parent if there is a conference call
366             BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId());
367             if (!mCallInfo.isNullCall(conferenceCall)
368                     && conferenceCall.getState() == Call.STATE_ACTIVE) {
369                 Log.i(TAG, "BT - hanging up conference call");
370                 call = conferenceCall;
371             }
372             if (call.getState() == Call.STATE_RINGING) {
373                 call.reject(false, "");
374             } else {
375                 call.disconnect();
376             }
377             return true;
378         }
379     }
380 
381     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
sendDtmf(int dtmf)382     public boolean sendDtmf(int dtmf) {
383         synchronized (LOCK) {
384             enforceModifyPermission();
385             Log.i(TAG, "BT - sendDtmf " + dtmf);
386             BluetoothCall call = mCallInfo.getForegroundCall();
387             if (mCallInfo.isNullCall(call)) {
388                 return false;
389             }
390             // TODO: Consider making this a queue instead of starting/stopping
391             // in quick succession.
392             call.playDtmfTone((char) dtmf);
393             call.stopDtmfTone();
394             return true;
395         }
396     }
397 
398     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
getNetworkOperator()399     public String getNetworkOperator()  {
400         synchronized (LOCK) {
401             enforceModifyPermission();
402             Log.i(TAG, "getNetworkOperator");
403             PhoneAccount account = mCallInfo.getBestPhoneAccount();
404             if (account != null && account.getLabel() != null) {
405                 return account.getLabel().toString();
406             }
407             // Finally, just get the network name from telephony.
408             return mTelephonyManager.getNetworkOperatorName();
409         }
410     }
411 
412     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
getSubscriberNumber()413     public String getSubscriberNumber() {
414         synchronized (LOCK) {
415             enforceModifyPermission();
416             Log.i(TAG, "getSubscriberNumber");
417             String address = null;
418             PhoneAccount account = mCallInfo.getBestPhoneAccount();
419             if (account != null) {
420                 Uri addressUri = account.getAddress();
421                 if (addressUri != null) {
422                     address = addressUri.getSchemeSpecificPart();
423                 }
424             }
425             if (TextUtils.isEmpty(address)) {
426                 address = mTelephonyManager.getLine1Number();
427                 if (address == null) address = "";
428             }
429             return address;
430         }
431     }
432 
433     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
listCurrentCalls()434     public boolean listCurrentCalls() {
435         synchronized (LOCK) {
436             enforceModifyPermission();
437             // only log if it is after we recently updated the headset state or else it can
438             // clog the android log since this can be queried every second.
439             boolean logQuery = mHeadsetUpdatedRecently;
440             mHeadsetUpdatedRecently = false;
441 
442             if (logQuery) {
443                 Log.i(TAG, "listcurrentCalls");
444             }
445 
446             sendListOfCalls(logQuery);
447             return true;
448         }
449     }
450 
451     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
queryPhoneState()452     public boolean queryPhoneState() {
453         synchronized (LOCK) {
454             enforceModifyPermission();
455             Log.i(TAG, "queryPhoneState");
456             updateHeadsetWithCallState(true);
457             return true;
458         }
459     }
460 
461     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
processChld(int chld)462     public boolean processChld(int chld) {
463         synchronized (LOCK) {
464             enforceModifyPermission();
465             long token = Binder.clearCallingIdentity();
466             Log.i(TAG, "processChld " + chld);
467             return _processChld(chld);
468         }
469     }
470 
onCallAdded(BluetoothCall call)471     public void onCallAdded(BluetoothCall call) {
472         if (call.isExternalCall()) {
473             return;
474         }
475         if (!mBluetoothCallHashMap.containsKey(call.getTelecomCallId())) {
476             Log.d(TAG, "onCallAdded");
477             CallStateCallback callback = new CallStateCallback(call.getState());
478             mCallbacks.put(call.getTelecomCallId(), callback);
479             call.registerCallback(callback);
480 
481             mBluetoothCallHashMap.put(call.getTelecomCallId(), call);
482             updateHeadsetWithCallState(false /* force */);
483         }
484     }
485 
sendBluetoothCallQualityReport( long timestamp, int rssi, int snr, int retransmissionCount, int packetsNotReceiveCount, int negativeAcknowledgementCount)486     public void sendBluetoothCallQualityReport(
487             long timestamp,
488             int rssi,
489             int snr,
490             int retransmissionCount,
491             int packetsNotReceiveCount,
492             int negativeAcknowledgementCount) {
493         BluetoothCall call = mCallInfo.getForegroundCall();
494         if (mCallInfo.isNullCall(call)) {
495             Log.w(TAG, "No foreground call while trying to send BQR");
496             return;
497         }
498         Bundle b = new Bundle();
499         b.putParcelable(
500                 BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT,
501                 new BluetoothCallQualityReport.Builder()
502                         .setSentTimestampMillis(timestamp)
503                         .setChoppyVoice(true)
504                         .setRssiDbm(rssi)
505                         .setSnrDb(snr)
506                         .setRetransmittedPacketsCount(retransmissionCount)
507                         .setPacketsNotReceivedCount(packetsNotReceiveCount)
508                         .setNegativeAcknowledgementCount(negativeAcknowledgementCount)
509                         .build());
510         call.sendCallEvent(
511                 BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT, b);
512     }
513 
514     @Override
onCallAdded(Call call)515     public void onCallAdded(Call call) {
516         super.onCallAdded(call);
517         onCallAdded(new BluetoothCall(call));
518     }
519 
onCallRemoved(BluetoothCall call)520     public void onCallRemoved(BluetoothCall call) {
521         if (call.isExternalCall()) {
522             return;
523         }
524         Log.d(TAG, "onCallRemoved");
525         CallStateCallback callback = getCallback(call);
526         if (callback != null) {
527             call.unregisterCallback(callback);
528         }
529 
530         if (mBluetoothCallHashMap.containsKey(call.getTelecomCallId())) {
531             mBluetoothCallHashMap.remove(call.getTelecomCallId());
532         }
533 
534         mClccIndexMap.remove(getClccMapKey(call));
535         updateHeadsetWithCallState(false /* force */);
536     }
537 
538     @Override
onCallRemoved(Call call)539     public void onCallRemoved(Call call) {
540         super.onCallRemoved(call);
541         BluetoothCall bluetoothCall = getBluetoothCallById(call.getDetails().getTelecomCallId());
542         if (bluetoothCall == null) {
543             Log.w(TAG, "onCallRemoved, BluetoothCall is removed before registered");
544             return;
545         }
546         onCallRemoved(bluetoothCall);
547     }
548 
549     @Override
onCallAudioStateChanged(CallAudioState audioState)550     public void onCallAudioStateChanged(CallAudioState audioState) {
551         super.onCallAudioStateChanged(audioState);
552         Log.d(TAG, "onCallAudioStateChanged, audioState == " + audioState);
553     }
554 
555 
556     @Override
onCreate()557     public void onCreate() {
558         Log.d(TAG, "onCreate");
559         super.onCreate();
560         BluetoothAdapter.getDefaultAdapter()
561                 .getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
562         mBluetoothAdapterReceiver = new BluetoothAdapterReceiver();
563         IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
564         registerReceiver(mBluetoothAdapterReceiver, intentFilter);
565     }
566 
567     @Override
onDestroy()568     public void onDestroy() {
569         Log.d(TAG, "onDestroy");
570         if (mBluetoothAdapterReceiver != null) {
571             unregisterReceiver(mBluetoothAdapterReceiver);
572             mBluetoothAdapterReceiver = null;
573         }
574         sInstance = null;
575         super.onDestroy();
576     }
577 
sendListOfCalls(boolean shouldLog)578     private void sendListOfCalls(boolean shouldLog) {
579         Collection<BluetoothCall> calls = mCallInfo.getBluetoothCalls();
580         for (BluetoothCall call : calls) {
581             // We don't send the parent conference BluetoothCall to the bluetooth device.
582             // We do, however want to send conferences that have no children to the bluetooth
583             // device (e.g. IMS Conference).
584             if (!call.isConference()
585                     || (call.isConference()
586                             && call.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) {
587                 sendClccForCall(call, shouldLog);
588             }
589         }
590         sendClccEndMarker();
591     }
592 
sendClccEndMarker()593     private void sendClccEndMarker() {
594         // End marker is recognized with an index value of 0. All other parameters are ignored.
595         if (mBluetoothHeadset != null) {
596             mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
597         }
598     }
599 
600     /**
601      * Sends a single clcc (C* List Current Calls) event for the specified call.
602      */
sendClccForCall(BluetoothCall call, boolean shouldLog)603     private void sendClccForCall(BluetoothCall call, boolean shouldLog) {
604         boolean isForeground = mCallInfo.getForegroundCall() == call;
605         int state = getBtCallState(call, isForeground);
606         boolean isPartOfConference = false;
607         boolean isConferenceWithNoChildren = call.isConference()
608                 && call.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
609 
610         if (state == CALL_STATE_IDLE) {
611             return;
612         }
613 
614         BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId());
615         if (!mCallInfo.isNullCall(conferenceCall)
616                 && conferenceCall.hasProperty(Call.Details.PROPERTY_GENERIC_CONFERENCE)) {
617             isPartOfConference = true;
618 
619             // Run some alternative states for Conference-level merge/swap support.
620             // Basically, if BluetoothCall supports swapping or merging at the conference-level,
621             // then we need to expose the calls as having distinct states
622             // (ACTIVE vs CAPABILITY_HOLD) or
623             // the functionality won't show up on the bluetooth device.
624 
625             // Before doing any special logic, ensure that we are dealing with an
626             // ACTIVE BluetoothCall and that the conference itself has a notion of
627             // the current "active" child call.
628             BluetoothCall activeChild = getBluetoothCallById(
629                     conferenceCall.getGenericConferenceActiveChildCallId());
630             if (state == CALL_STATE_ACTIVE && !mCallInfo.isNullCall(activeChild)) {
631                 // Reevaluate state if we can MERGE or if we can SWAP without previously having
632                 // MERGED.
633                 boolean shouldReevaluateState =
634                         conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)
635                                 || (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)
636                                         && !conferenceCall.wasConferencePreviouslyMerged());
637 
638                 if (shouldReevaluateState) {
639                     isPartOfConference = false;
640                     if (call == activeChild) {
641                         state = CALL_STATE_ACTIVE;
642                     } else {
643                         // At this point we know there is an "active" child and we know that it is
644                         // not this call, so set it to HELD instead.
645                         state = CALL_STATE_HELD;
646                     }
647                 }
648             }
649             if (conferenceCall.getState() == Call.STATE_HOLDING
650                     && conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) {
651                 // If the parent IMS CEP conference BluetoothCall is on hold, we should mark
652                 // this BluetoothCall as being on hold regardless of what the other
653                 // children are doing.
654                 state = CALL_STATE_HELD;
655             }
656         } else if (isConferenceWithNoChildren) {
657             // Handle the special case of an IMS conference BluetoothCall without conference
658             // event package support.
659             // The BluetoothCall will be marked as a conference, but the conference will not have
660             // child calls where conference event packages are not used by the carrier.
661             isPartOfConference = true;
662         }
663 
664         int index = getIndexForCall(call);
665         int direction = call.isIncoming() ? 1 : 0;
666         final Uri addressUri;
667         if (call.getGatewayInfo() != null) {
668             addressUri = call.getGatewayInfo().getOriginalAddress();
669         } else {
670             addressUri = call.getHandle();
671         }
672 
673         String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
674         if (address != null) {
675             address = PhoneNumberUtils.stripSeparators(address);
676         }
677 
678         int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
679 
680         if (shouldLog) {
681             Log.i(TAG, "sending clcc for BluetoothCall "
682                             + index + ", "
683                             + direction + ", "
684                             + state + ", "
685                             + isPartOfConference + ", "
686                             + addressType);
687         }
688 
689         if (mBluetoothHeadset == null) {
690             Log.w(TAG, "mBluetoothHeasdset is null when sending clcc for BluetoothCall "
691                     + index + ", "
692                     + direction + ", "
693                     + state + ", "
694                     + isPartOfConference + ", "
695                     + addressType);
696         } else {
697             mBluetoothHeadset.clccResponse(
698                     index, direction, state, 0, isPartOfConference, address, addressType);
699         }
700     }
701 
getClccMapKey(BluetoothCall call)702     private String getClccMapKey(BluetoothCall call) {
703         if (mCallInfo.isNullCall(call) || call.getHandle() == null) {
704             return "";
705         }
706         Uri handle = call.getHandle();
707         String key;
708         if (call.hasProperty(Call.Details.PROPERTY_SELF_MANAGED)) {
709             key = handle.toString() + " self managed " + call.getTelecomCallId();
710         } else {
711             key = handle.toString();
712         }
713         return key;
714     }
715 
716     /**
717      * Returns the caches index for the specified call.  If no such index exists, then an index is
718      * given (smallest number starting from 1 that isn't already taken).
719      */
getIndexForCall(BluetoothCall call)720     private int getIndexForCall(BluetoothCall call) {
721         String key = getClccMapKey(call);
722         if (mClccIndexMap.containsKey(key)) {
723             return mClccIndexMap.get(key);
724         }
725 
726         int i = 1;  // Indexes for bluetooth clcc are 1-based.
727         while (mClccIndexMap.containsValue(i)) {
728             i++;
729         }
730 
731         // NOTE: Indexes are removed in {@link #onCallRemoved}.
732         mClccIndexMap.put(key, i);
733         return i;
734     }
735 
_processChld(int chld)736     private boolean _processChld(int chld) {
737         BluetoothCall activeCall = mCallInfo.getActiveCall();
738         BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
739         if (ringingCall == null) {
740             Log.i(TAG, "asdf ringingCall null");
741         } else {
742             Log.i(TAG, "asdf ringingCall not null " + ringingCall.hashCode());
743         }
744 
745         BluetoothCall heldCall = mCallInfo.getHeldCall();
746 
747         Log.i(TAG, "Active: " + activeCall
748                 + " Ringing: " + ringingCall
749                 + " Held: " + heldCall);
750         Log.i(TAG, "asdf chld " + chld);
751 
752         if (chld == CHLD_TYPE_RELEASEHELD) {
753             Log.i(TAG, "asdf CHLD_TYPE_RELEASEHELD");
754             if (!mCallInfo.isNullCall(ringingCall)) {
755                 Log.i(TAG, "asdf reject " + ringingCall.hashCode());
756                 ringingCall.reject(false, null);
757                 return true;
758             } else if (!mCallInfo.isNullCall(heldCall)) {
759                 heldCall.disconnect();
760                 return true;
761             }
762         } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
763             if (mCallInfo.isNullCall(activeCall)
764                     && mCallInfo.isNullCall(ringingCall)
765                     && mCallInfo.isNullCall(heldCall)) {
766                 return false;
767             }
768             if (!mCallInfo.isNullCall(activeCall)) {
769                 BluetoothCall conferenceCall = getBluetoothCallById(activeCall.getParentId());
770                 if (!mCallInfo.isNullCall(conferenceCall)
771                         && conferenceCall.getState() == Call.STATE_ACTIVE) {
772                     Log.i(TAG, "CHLD: disconnect conference call");
773                     conferenceCall.disconnect();
774                 } else {
775                     activeCall.disconnect();
776                 }
777             }
778             if (!mCallInfo.isNullCall(ringingCall)) {
779                 ringingCall.answer(ringingCall.getVideoState());
780             } else if (!mCallInfo.isNullCall(heldCall)) {
781                 heldCall.unhold();
782             }
783             return true;
784         } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
785             if (!mCallInfo.isNullCall(activeCall)
786                     && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
787                 activeCall.swapConference();
788                 Log.i(TAG, "CDMA calls in conference swapped, updating headset");
789                 updateHeadsetWithCallState(true /* force */);
790                 return true;
791             } else if (!mCallInfo.isNullCall(ringingCall)) {
792                 ringingCall.answer(VideoProfile.STATE_AUDIO_ONLY);
793                 return true;
794             } else if (!mCallInfo.isNullCall(heldCall)) {
795                 // CallsManager will hold any active calls when unhold() is called on a
796                 // currently-held call.
797                 heldCall.unhold();
798                 return true;
799             } else if (!mCallInfo.isNullCall(activeCall)
800                     && activeCall.can(Connection.CAPABILITY_HOLD)) {
801                 activeCall.hold();
802                 return true;
803             }
804         } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
805             if (!mCallInfo.isNullCall(activeCall)) {
806                 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
807                     activeCall.mergeConference();
808                     return true;
809                 } else {
810                     List<BluetoothCall> conferenceable = getBluetoothCallsByIds(
811                             activeCall.getConferenceableCalls());
812                     if (!conferenceable.isEmpty()) {
813                         activeCall.conference(conferenceable.get(0));
814                         return true;
815                     }
816                 }
817             }
818         }
819         return false;
820     }
821 
822     /**
823      * Sends an update of the current BluetoothCall state to the current Headset.
824      *
825      * @param force {@code true} if the headset state should be sent regardless if no changes to
826      * the state have occurred, {@code false} if the state should only be sent if the state
827      * has changed.
828      */
updateHeadsetWithCallState(boolean force)829     private void updateHeadsetWithCallState(boolean force) {
830         BluetoothCall activeCall = mCallInfo.getActiveCall();
831         BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
832         BluetoothCall heldCall = mCallInfo.getHeldCall();
833 
834         int bluetoothCallState = getBluetoothCallStateForUpdate();
835 
836         String ringingAddress = null;
837         int ringingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
838         String ringingName = null;
839         if (!mCallInfo.isNullCall(ringingCall) && ringingCall.getHandle() != null
840                 && !ringingCall.isSilentRingingRequested()) {
841             ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
842             if (ringingAddress != null) {
843                 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
844             }
845             ringingName = ringingCall.getCallerDisplayName();
846             if (TextUtils.isEmpty(ringingName)) {
847                 ringingName = ringingCall.getContactDisplayName();
848             }
849         }
850         if (ringingAddress == null) {
851             ringingAddress = "";
852         }
853 
854         int numActiveCalls = mCallInfo.isNullCall(activeCall) ? 0 : 1;
855         int numHeldCalls = mCallInfo.getNumHeldCalls();
856         int numChildrenOfActiveCall =
857                 mCallInfo.isNullCall(activeCall) ? 0 : activeCall.getChildrenIds().size();
858 
859         // Intermediate state for GSM calls which are in the process of being swapped.
860         // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
861         //       are held?
862         boolean callsPendingSwitch = (numHeldCalls == 2);
863 
864         // For conference calls which support swapping the active BluetoothCall within the
865         // conference (namely CDMA calls) we need to expose that as a held BluetoothCall
866         // in order for the BT device to show "swap" and "merge" functionality.
867         boolean ignoreHeldCallChange = false;
868         if (!mCallInfo.isNullCall(activeCall) && activeCall.isConference()
869                 && !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
870             if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
871                 // Indicate that BT device should show SWAP command by indicating that there is a
872                 // BluetoothCall on hold, but only if the conference wasn't previously merged.
873                 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
874             } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
875                 numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
876             }
877 
878             for (String id : activeCall.getChildrenIds()) {
879                 // Held BluetoothCall has changed due to it being combined into a CDMA conference.
880                 // Keep track of this and ignore any future update since it doesn't really count
881                 // as a BluetoothCall change.
882                 if (mOldHeldCall != null && mOldHeldCall.getTelecomCallId() == id) {
883                     ignoreHeldCallChange = true;
884                     break;
885                 }
886             }
887         }
888 
889         if (mBluetoothHeadset != null
890                 && (force
891                     || (!callsPendingSwitch
892                         && (numActiveCalls != mNumActiveCalls
893                             || numChildrenOfActiveCall != mNumChildrenOfActiveCall
894                             || numHeldCalls != mNumHeldCalls
895                             || bluetoothCallState != mBluetoothCallState
896                             || !TextUtils.equals(ringingAddress, mRingingAddress)
897                             || ringingAddressType != mRingingAddressType
898                             || (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
899 
900             // If the BluetoothCall is transitioning into the alerting state, send DIALING first.
901             // Some devices expect to see a DIALING state prior to seeing an ALERTING state
902             // so we need to send it first.
903             boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState
904                     && bluetoothCallState == CALL_STATE_ALERTING;
905 
906             mOldHeldCall = heldCall;
907             mNumActiveCalls = numActiveCalls;
908             mNumChildrenOfActiveCall = numChildrenOfActiveCall;
909             mNumHeldCalls = numHeldCalls;
910             mBluetoothCallState = bluetoothCallState;
911             mRingingAddress = ringingAddress;
912             mRingingAddressType = ringingAddressType;
913 
914             if (sendDialingFirst) {
915                 // Log in full to make logs easier to debug.
916                 Log.i(TAG, "updateHeadsetWithCallState "
917                                 + "numActive " + mNumActiveCalls + ", "
918                                 + "numHeld " + mNumHeldCalls + ", "
919                                 + "callState " + CALL_STATE_DIALING + ", "
920                                 + "ringing type " + mRingingAddressType);
921                 mBluetoothHeadset.phoneStateChanged(
922                         mNumActiveCalls,
923                         mNumHeldCalls,
924                         CALL_STATE_DIALING,
925                         mRingingAddress,
926                         mRingingAddressType,
927                         ringingName);
928             }
929 
930             Log.i(TAG, "updateHeadsetWithCallState "
931                     + "numActive " + mNumActiveCalls + ", "
932                     + "numHeld " + mNumHeldCalls + ", "
933                     + "callState " + mBluetoothCallState + ", "
934                     + "ringing type " + mRingingAddressType);
935 
936             mBluetoothHeadset.phoneStateChanged(
937                     mNumActiveCalls,
938                     mNumHeldCalls,
939                     mBluetoothCallState,
940                     mRingingAddress,
941                     mRingingAddressType,
942                     ringingName);
943 
944             mHeadsetUpdatedRecently = true;
945         }
946     }
947 
getBluetoothCallStateForUpdate()948     private int getBluetoothCallStateForUpdate() {
949         BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
950         BluetoothCall dialingCall = mCallInfo.getOutgoingCall();
951         boolean hasOnlyDisconnectedCalls = mCallInfo.hasOnlyDisconnectedCalls();
952 
953         //
954         // !! WARNING !!
955         // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
956         // used in this version of the BluetoothCall state mappings.  This is on purpose.
957         // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
958         // listCalls*() method are WAITING and ACTIVE used.
959         // Using the unsupported states here caused problems with inconsistent state in some
960         // bluetooth devices (like not getting out of ringing state after answering a call).
961         //
962         int bluetoothCallState = CALL_STATE_IDLE;
963         if (!mCallInfo.isNullCall(ringingCall) && !ringingCall.isSilentRingingRequested()) {
964             bluetoothCallState = CALL_STATE_INCOMING;
965         } else if (!mCallInfo.isNullCall(dialingCall)) {
966             bluetoothCallState = CALL_STATE_ALERTING;
967         } else if (hasOnlyDisconnectedCalls || mIsDisconnectedTonePlaying) {
968             // Keep the DISCONNECTED state until the disconnect tone's playback is done
969             bluetoothCallState = CALL_STATE_DISCONNECTED;
970         }
971         return bluetoothCallState;
972     }
973 
getBtCallState(BluetoothCall call, boolean isForeground)974     private int getBtCallState(BluetoothCall call, boolean isForeground) {
975         switch (call.getState()) {
976             case Call.STATE_NEW:
977             case Call.STATE_DISCONNECTED:
978             case Call.STATE_AUDIO_PROCESSING:
979                 return CALL_STATE_IDLE;
980 
981             case Call.STATE_ACTIVE:
982                 return CALL_STATE_ACTIVE;
983 
984             case Call.STATE_CONNECTING:
985             case Call.STATE_SELECT_PHONE_ACCOUNT:
986             case Call.STATE_DIALING:
987             case Call.STATE_PULLING_CALL:
988                 // Yes, this is correctly returning ALERTING.
989                 // "Dialing" for BT means that we have sent information to the service provider
990                 // to place the BluetoothCall but there is no confirmation that the BluetoothCall
991                 // is going through. When there finally is confirmation, the ringback is
992                 // played which is referred to as an "alert" tone, thus, ALERTING.
993                 // TODO: We should consider using the ALERTING terms in Telecom because that
994                 // seems to be more industry-standard.
995                 return CALL_STATE_ALERTING;
996 
997             case Call.STATE_HOLDING:
998                 return CALL_STATE_HELD;
999 
1000             case Call.STATE_RINGING:
1001             case Call.STATE_SIMULATED_RINGING:
1002                 if (call.isSilentRingingRequested()) {
1003                     return CALL_STATE_IDLE;
1004                 } else if (isForeground) {
1005                     return CALL_STATE_INCOMING;
1006                 } else {
1007                     return CALL_STATE_WAITING;
1008                 }
1009         }
1010         return CALL_STATE_IDLE;
1011     }
1012 
1013     @VisibleForTesting
getCallback(BluetoothCall call)1014     public CallStateCallback getCallback(BluetoothCall call) {
1015         return mCallbacks.get(call.getTelecomCallId());
1016     }
1017 
1018     @VisibleForTesting
setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset)1019     public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) {
1020         mBluetoothHeadset = bluetoothHeadset;
1021     }
1022 
1023     @VisibleForTesting
getBluetoothCallById(String id)1024     public BluetoothCall getBluetoothCallById(String id) {
1025         if (mBluetoothCallHashMap.containsKey(id)) {
1026             return mBluetoothCallHashMap.get(id);
1027         }
1028         return null;
1029     }
1030 
1031     @VisibleForTesting
getBluetoothCallsByIds(List<String> ids)1032     public List<BluetoothCall> getBluetoothCallsByIds(List<String> ids) {
1033         List<BluetoothCall> calls = new ArrayList<>();
1034         for (String id : ids) {
1035             BluetoothCall call = getBluetoothCallById(id);
1036             if (!mCallInfo.isNullCall(call)) {
1037                 calls.add(call);
1038             }
1039         }
1040         return calls;
1041     }
1042 
1043     // extract call information functions out into this part, so we can mock it in testing
1044     @VisibleForTesting
1045     public class CallInfo {
1046 
getForegroundCall()1047         public BluetoothCall getForegroundCall() {
1048             LinkedHashSet<Integer> states = new LinkedHashSet<Integer>();
1049             BluetoothCall foregroundCall;
1050 
1051             states.add(Call.STATE_CONNECTING);
1052             foregroundCall = getCallByStates(states);
1053             if (!mCallInfo.isNullCall(foregroundCall)) {
1054                 return foregroundCall;
1055             }
1056 
1057             states.clear();
1058             states.add(Call.STATE_ACTIVE);
1059             states.add(Call.STATE_DIALING);
1060             states.add(Call.STATE_PULLING_CALL);
1061             foregroundCall = getCallByStates(states);
1062             if (!mCallInfo.isNullCall(foregroundCall)) {
1063                 return foregroundCall;
1064             }
1065 
1066             states.clear();
1067             states.add(Call.STATE_RINGING);
1068             foregroundCall = getCallByStates(states);
1069             if (!mCallInfo.isNullCall(foregroundCall)) {
1070                 return foregroundCall;
1071             }
1072 
1073             return null;
1074         }
1075 
getCallByStates(LinkedHashSet<Integer> states)1076         public BluetoothCall getCallByStates(LinkedHashSet<Integer> states) {
1077             List<BluetoothCall> calls = getBluetoothCalls();
1078             for (BluetoothCall call : calls) {
1079                 if (states.contains(call.getState())) {
1080                     return call;
1081                 }
1082             }
1083             return null;
1084         }
1085 
getCallByState(int state)1086         public BluetoothCall getCallByState(int state) {
1087             List<BluetoothCall> calls = getBluetoothCalls();
1088             for (BluetoothCall call : calls) {
1089                 if (state == call.getState()) {
1090                     return call;
1091                 }
1092             }
1093             return null;
1094         }
1095 
getNumHeldCalls()1096         public int getNumHeldCalls() {
1097             int number = 0;
1098             List<BluetoothCall> calls = getBluetoothCalls();
1099             for (BluetoothCall call : calls) {
1100                 if (call.getState() == Call.STATE_HOLDING) {
1101                     number++;
1102                 }
1103             }
1104             return number;
1105         }
1106 
hasOnlyDisconnectedCalls()1107         public boolean hasOnlyDisconnectedCalls() {
1108             List<BluetoothCall> calls = getBluetoothCalls();
1109             if (calls.size() == 0) {
1110                 return false;
1111             }
1112             for (BluetoothCall call : calls) {
1113                 if (call.getState() != Call.STATE_DISCONNECTED) {
1114                     return false;
1115                 }
1116             }
1117             return true;
1118         }
1119 
getBluetoothCalls()1120         public List<BluetoothCall> getBluetoothCalls() {
1121             return getBluetoothCallsByIds(BluetoothCall.getIds(getCalls()));
1122         }
1123 
getOutgoingCall()1124         public BluetoothCall getOutgoingCall() {
1125             LinkedHashSet<Integer> states = new LinkedHashSet<Integer>();
1126             states.add(Call.STATE_CONNECTING);
1127             states.add(Call.STATE_DIALING);
1128             states.add(Call.STATE_PULLING_CALL);
1129             return getCallByStates(states);
1130         }
1131 
getRingingOrSimulatedRingingCall()1132         public BluetoothCall getRingingOrSimulatedRingingCall() {
1133             LinkedHashSet<Integer> states = new LinkedHashSet<Integer>();
1134             states.add(Call.STATE_RINGING);
1135             states.add(Call.STATE_SIMULATED_RINGING);
1136             return getCallByStates(states);
1137         }
1138 
getActiveCall()1139         public BluetoothCall getActiveCall() {
1140             return getCallByState(Call.STATE_ACTIVE);
1141         }
1142 
getHeldCall()1143         public BluetoothCall getHeldCall() {
1144             return getCallByState(Call.STATE_HOLDING);
1145         }
1146 
1147         /**
1148          * Returns the best phone account to use for the given state of all calls.
1149          * First, tries to return the phone account for the foreground call, second the default
1150          * phone account for PhoneAccount.SCHEME_TEL.
1151          */
getBestPhoneAccount()1152         public PhoneAccount getBestPhoneAccount() {
1153             BluetoothCall call = getForegroundCall();
1154 
1155             PhoneAccount account = null;
1156             if (!mCallInfo.isNullCall(call)) {
1157                 PhoneAccountHandle handle = call.getAccountHandle();
1158                 if (handle != null) {
1159                     // First try to get the network name of the foreground call.
1160                     account = mTelecomManager.getPhoneAccount(handle);
1161                 }
1162             }
1163 
1164             if (account == null) {
1165                 // Second, Try to get the label for the default Phone Account.
1166                 List<PhoneAccountHandle> handles =
1167                         mTelecomManager.getPhoneAccountsSupportingScheme(PhoneAccount.SCHEME_TEL);
1168                 while (handles.iterator().hasNext()) {
1169                     account = mTelecomManager.getPhoneAccount(handles.iterator().next());
1170                     if (account != null) {
1171                         return account;
1172                     }
1173                 }
1174             }
1175             return null;
1176         }
1177 
isNullCall(BluetoothCall call)1178         public boolean isNullCall(BluetoothCall call) {
1179             return call == null || call.isCallNull();
1180         }
1181     };
1182 };
1183