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