1 /*
2  * Copyright (C) 2014 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.server.telecom;
18 
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothHeadset;
21 import android.bluetooth.BluetoothProfile;
22 import android.bluetooth.IBluetoothHeadsetPhone;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.net.Uri;
28 import android.os.Binder;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 import android.telecom.Connection;
32 import android.telecom.Log;
33 import android.telecom.PhoneAccount;
34 import android.telecom.VideoProfile;
35 import android.telephony.PhoneNumberUtils;
36 import android.telephony.TelephonyManager;
37 import android.text.TextUtils;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.server.telecom.CallsManager.CallsManagerListener;
41 
42 import java.util.Collection;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Map;
46 
47 /**
48  * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
49  * and accepts call-related commands to perform on behalf of the BT device.
50  */
51 public class BluetoothPhoneServiceImpl {
52 
53     public interface BluetoothPhoneServiceImplFactory {
makeBluetoothPhoneServiceImpl(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, PhoneAccountRegistrar phoneAccountRegistrar)54         BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context,
55                 TelecomSystem.SyncRoot lock, CallsManager callsManager,
56                 PhoneAccountRegistrar phoneAccountRegistrar);
57     }
58 
59     private static final String TAG = "BluetoothPhoneService";
60 
61     // match up with bthf_call_state_t of bt_hf.h
62     private static final int CALL_STATE_ACTIVE = 0;
63     private static final int CALL_STATE_HELD = 1;
64     private static final int CALL_STATE_DIALING = 2;
65     private static final int CALL_STATE_ALERTING = 3;
66     private static final int CALL_STATE_INCOMING = 4;
67     private static final int CALL_STATE_WAITING = 5;
68     private static final int CALL_STATE_IDLE = 6;
69     private static final int CALL_STATE_DISCONNECTED = 7;
70 
71     // match up with bthf_call_state_t of bt_hf.h
72     // Terminate all held or set UDUB("busy") to a waiting call
73     private static final int CHLD_TYPE_RELEASEHELD = 0;
74     // Terminate all active calls and accepts a waiting/held call
75     private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
76     // Hold all active calls and accepts a waiting/held call
77     private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
78     // Add all held calls to a conference
79     private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
80 
81     // Indicates that no call is ringing
82     private static final int DEFAULT_RINGING_ADDRESS_TYPE = 128;
83 
84     private int mNumActiveCalls = 0;
85     private int mNumHeldCalls = 0;
86     private int mNumChildrenOfActiveCall = 0;
87     private int mBluetoothCallState = CALL_STATE_IDLE;
88     private String mRingingAddress = "";
89     private int mRingingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
90     private Call mOldHeldCall = null;
91     private boolean mIsDisconnectedTonePlaying = false;
92 
93     /**
94      * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
95      * bluetooth headset code uses to control call.
96      */
97     @VisibleForTesting
98     public final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
99         @Override
100         public boolean answerCall() throws RemoteException {
101             synchronized (mLock) {
102                 enforceModifyPermission();
103                 Log.startSession("BPSI.aC");
104                 long token = Binder.clearCallingIdentity();
105                 try {
106                     Log.i(TAG, "BT - answering call");
107                     Call call = mCallsManager.getRingingOrSimulatedRingingCall();
108                     if (call != null) {
109                         mCallsManager.answerCall(call, VideoProfile.STATE_AUDIO_ONLY);
110                         return true;
111                     }
112                     return false;
113                 } finally {
114                     Binder.restoreCallingIdentity(token);
115                     Log.endSession();
116                 }
117 
118             }
119         }
120 
121         @Override
122         public boolean hangupCall() throws RemoteException {
123             synchronized (mLock) {
124                 enforceModifyPermission();
125                 Log.startSession("BPSI.hC");
126                 long token = Binder.clearCallingIdentity();
127                 try {
128                     Log.i(TAG, "BT - hanging up call");
129                     Call call = mCallsManager.getForegroundCall();
130                     if (call != null) {
131                         mCallsManager.disconnectCall(call);
132                         return true;
133                     }
134                     return false;
135                 } finally {
136                     Binder.restoreCallingIdentity(token);
137                     Log.endSession();
138                 }
139             }
140         }
141 
142         @Override
143         public boolean sendDtmf(int dtmf) throws RemoteException {
144             synchronized (mLock) {
145                 enforceModifyPermission();
146                 Log.startSession("BPSI.sD");
147                 long token = Binder.clearCallingIdentity();
148                 try {
149                     Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
150                     Call call = mCallsManager.getForegroundCall();
151                     if (call != null) {
152                         // TODO: Consider making this a queue instead of starting/stopping
153                         // in quick succession.
154                         mCallsManager.playDtmfTone(call, (char) dtmf);
155                         mCallsManager.stopDtmfTone(call);
156                         return true;
157                     }
158                     return false;
159                 } finally {
160                     Binder.restoreCallingIdentity(token);
161                     Log.endSession();
162                 }
163             }
164         }
165 
166         @Override
167         public String getNetworkOperator() throws RemoteException {
168             synchronized (mLock) {
169                 enforceModifyPermission();
170                 Log.startSession("BPSI.gNO");
171                 long token = Binder.clearCallingIdentity();
172                 try {
173                     Log.i(TAG, "getNetworkOperator");
174                     PhoneAccount account = getBestPhoneAccount();
175                     if (account != null && account.getLabel() != null) {
176                         return account.getLabel().toString();
177                     } else {
178                         // Finally, just get the network name from telephony.
179                         return mContext.getSystemService(TelephonyManager.class)
180                                 .getNetworkOperatorName();
181                     }
182                 } finally {
183                     Binder.restoreCallingIdentity(token);
184                     Log.endSession();
185                 }
186             }
187         }
188 
189         @Override
190         public String getSubscriberNumber() throws RemoteException {
191             synchronized (mLock) {
192                 enforceModifyPermission();
193                 Log.startSession("BPSI.gSN");
194                 long token = Binder.clearCallingIdentity();
195                 try {
196                     Log.i(TAG, "getSubscriberNumber");
197                     String address = null;
198                     PhoneAccount account = getBestPhoneAccount();
199                     if (account != null) {
200                         Uri addressUri = account.getAddress();
201                         if (addressUri != null) {
202                             address = addressUri.getSchemeSpecificPart();
203                         }
204                     }
205                     if (TextUtils.isEmpty(address)) {
206                         address = mContext.getSystemService(TelephonyManager.class)
207                                 .getLine1Number();
208                         if (address == null) address = "";
209                     }
210                     return address;
211                 } finally {
212                     Binder.restoreCallingIdentity(token);
213                     Log.endSession();
214                 }
215             }
216         }
217 
218         @Override
219         public boolean listCurrentCalls() throws RemoteException {
220             synchronized (mLock) {
221                 enforceModifyPermission();
222                 Log.startSession("BPSI.lCC");
223                 long token = Binder.clearCallingIdentity();
224                 try {
225                     // only log if it is after we recently updated the headset state or else it can
226                     // clog the android log since this can be queried every second.
227                     boolean logQuery = mHeadsetUpdatedRecently;
228                     mHeadsetUpdatedRecently = false;
229 
230                     if (logQuery) {
231                         Log.i(TAG, "listcurrentCalls");
232                     }
233 
234                     sendListOfCalls(logQuery);
235                     return true;
236                 } finally {
237                     Binder.restoreCallingIdentity(token);
238                     Log.endSession();
239                 }
240             }
241         }
242 
243         @Override
244         public boolean queryPhoneState() throws RemoteException {
245             synchronized (mLock) {
246                 enforceModifyPermission();
247                 Log.startSession("BPSI.qPS");
248                 long token = Binder.clearCallingIdentity();
249                 try {
250                     Log.i(TAG, "queryPhoneState");
251                     updateHeadsetWithCallState(true /* force */);
252                     return true;
253                 } finally {
254                     Binder.restoreCallingIdentity(token);
255                     Log.endSession();
256                 }
257             }
258         }
259 
260         @Override
261         public boolean processChld(int chld) throws RemoteException {
262             synchronized (mLock) {
263                 enforceModifyPermission();
264                 Log.startSession("BPSI.pC");
265                 long token = Binder.clearCallingIdentity();
266                 try {
267                     Log.i(TAG, "processChld %d", chld);
268                     return BluetoothPhoneServiceImpl.this.processChld(chld);
269                 } finally {
270                     Binder.restoreCallingIdentity(token);
271                     Log.endSession();
272                 }
273             }
274         }
275 
276         @Override
277         public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException {
278             Log.d(TAG, "RAT change - deprecated");
279             // deprecated
280         }
281 
282         @Override
283         public void cdmaSetSecondCallState(boolean state) throws RemoteException {
284             Log.d(TAG, "cdma 1 - deprecated");
285             // deprecated
286         }
287 
288         @Override
289         public void cdmaSwapSecondCallState() throws RemoteException {
290             Log.d(TAG, "cdma 2 - deprecated");
291             // deprecated
292         }
293     };
294 
295     /**
296      * Listens to call changes from the CallsManager and calls into methods to update the bluetooth
297      * headset with the new states.
298      */
299     @VisibleForTesting
300     public CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
301         @Override
302         public void onCallAdded(Call call) {
303             if (call.isExternalCall()) {
304                 return;
305             }
306             updateHeadsetWithCallState(false /* force */);
307         }
308 
309         @Override
310         public void onCallRemoved(Call call) {
311             if (call.isExternalCall()) {
312                 return;
313             }
314             mClccIndexMap.remove(call);
315             updateHeadsetWithCallState(false /* force */);
316         }
317 
318         /**
319          * Where a call which was external becomes a regular call, or a regular call becomes
320          * external, treat as an add or remove, respectively.
321          *
322          * @param call The call.
323          * @param isExternalCall {@code True} if the call became external, {@code false} otherwise.
324          */
325         @Override
326         public void onExternalCallChanged(Call call, boolean isExternalCall) {
327             if (isExternalCall) {
328                 onCallRemoved(call);
329             } else {
330                 onCallAdded(call);
331             }
332         }
333 
334         @Override
335         public void onCallStateChanged(Call call, int oldState, int newState) {
336             if (call.isExternalCall()) {
337                 return;
338             }
339             // If a call is being put on hold because of a new connecting call, ignore the
340             // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
341             // state atomically.
342             // When the call later transitions to DIALING/DISCONNECTED we will then send out the
343             // aggregated update.
344             if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
345                 for (Call otherCall : mCallsManager.getCalls()) {
346                     if (otherCall.getState() == CallState.CONNECTING) {
347                         return;
348                     }
349                 }
350             }
351 
352             // To have an active call and another dialing at the same time is an invalid BT
353             // state. We can assume that the active call will be automatically held which will
354             // send another update at which point we will be in the right state.
355             if (mCallsManager.getActiveCall() != null
356                     && oldState == CallState.CONNECTING &&
357                     (newState == CallState.DIALING || newState == CallState.PULLING)) {
358                 return;
359             }
360             updateHeadsetWithCallState(false /* force */);
361         }
362 
363         @Override
364         public void onIsConferencedChanged(Call call) {
365             if (call.isExternalCall()) {
366                 return;
367             }
368             /*
369              * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
370              * because conference change events are not atomic and multiple callbacks get fired
371              * when two calls are conferenced together. This confuses updateHeadsetWithCallState
372              * if it runs in the middle of two calls being conferenced and can cause spurious and
373              * incorrect headset state updates. One of the scenarios is described below for CDMA
374              * conference calls.
375              *
376              * 1) Call 1 and Call 2 are being merged into conference Call 3.
377              * 2) Call 1 has its parent set to Call 3, but Call 2 does not have a parent yet.
378              * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and
379              * Call 3) when there is actually only one active call (Call 3).
380              */
381             if (call.getParentCall() != null) {
382                 // If this call is newly conferenced, ignore the callback. We only care about the
383                 // one sent for the parent conference call.
384                 Log.d(this, "Ignoring onIsConferenceChanged from child call with new parent");
385                 return;
386             }
387             if (call.getChildCalls().size() == 1) {
388                 // If this is a parent call with only one child, ignore the callback as well since
389                 // the minimum number of child calls to start a conference call is 2. We expect
390                 // this to be called again when the parent call has another child call added.
391                 Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
392                 return;
393             }
394             updateHeadsetWithCallState(false /* force */);
395         }
396 
397         @Override
398         public void onDisconnectedTonePlaying(boolean isTonePlaying) {
399             mIsDisconnectedTonePlaying = isTonePlaying;
400             updateHeadsetWithCallState(false /* force */);
401         }
402     };
403 
404     /**
405      * Listens to connections and disconnections of bluetooth headsets.  We need to save the current
406      * bluetooth headset so that we know where to send call updates.
407      */
408     @VisibleForTesting
409     public BluetoothProfile.ServiceListener mProfileListener =
410             new BluetoothProfile.ServiceListener() {
411                 @Override
412                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
413                     synchronized (mLock) {
414                         setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy));
415                         updateHeadsetWithCallState(true /* force */);
416                     }
417                 }
418 
419                 @Override
420                 public void onServiceDisconnected(int profile) {
421                     synchronized (mLock) {
422                         mBluetoothHeadset = null;
423                     }
424                 }
425             };
426 
427     /**
428      * Receives events for global state changes of the bluetooth adapter.
429      */
430     @VisibleForTesting
431     public final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
432         @Override
433         public void onReceive(Context context, Intent intent) {
434             synchronized (mLock) {
435                 int state = intent
436                         .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
437                 Log.d(TAG, "Bluetooth Adapter state: %d", state);
438                 if (state == BluetoothAdapter.STATE_ON) {
439                     try {
440                         mBinder.queryPhoneState();
441                     } catch (RemoteException e) {
442                         // Remote exception not expected
443                     }
444                 }
445             }
446         }
447     };
448 
449     private BluetoothAdapterProxy mBluetoothAdapter;
450     private BluetoothHeadsetProxy mBluetoothHeadset;
451 
452     // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
453     private Map<Call, Integer> mClccIndexMap = new HashMap<>();
454 
455     private boolean mHeadsetUpdatedRecently = false;
456 
457     private final Context mContext;
458     private final TelecomSystem.SyncRoot mLock;
459     private final CallsManager mCallsManager;
460     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
461 
getBinder()462     public IBinder getBinder() {
463         return mBinder;
464     }
465 
BluetoothPhoneServiceImpl( Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, BluetoothAdapterProxy bluetoothAdapter, PhoneAccountRegistrar phoneAccountRegistrar)466     public BluetoothPhoneServiceImpl(
467             Context context,
468             TelecomSystem.SyncRoot lock,
469             CallsManager callsManager,
470             BluetoothAdapterProxy bluetoothAdapter,
471             PhoneAccountRegistrar phoneAccountRegistrar) {
472         Log.d(this, "onCreate");
473 
474         mContext = context;
475         mLock = lock;
476         mCallsManager = callsManager;
477         mPhoneAccountRegistrar = phoneAccountRegistrar;
478 
479         mBluetoothAdapter = bluetoothAdapter;
480         if (mBluetoothAdapter == null) {
481             Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found.");
482             return;
483         }
484         mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
485 
486         IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
487         context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
488 
489         mCallsManager.addListener(mCallsManagerListener);
490         updateHeadsetWithCallState(false /* force */);
491     }
492 
493     @VisibleForTesting
setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset)494     public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) {
495         mBluetoothHeadset = bluetoothHeadset;
496     }
497 
processChld(int chld)498     private boolean processChld(int chld) {
499         Call activeCall = mCallsManager.getActiveCall();
500         Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
501         Call heldCall = mCallsManager.getHeldCall();
502 
503         // TODO: Keeping as Log.i for now.  Move to Log.d after L release if BT proves stable.
504         Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
505 
506         if (chld == CHLD_TYPE_RELEASEHELD) {
507             if (ringingCall != null) {
508                 mCallsManager.rejectCall(ringingCall, false, null);
509                 return true;
510             } else if (heldCall != null) {
511                 mCallsManager.disconnectCall(heldCall);
512                 return true;
513             }
514         } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
515             if (activeCall == null && ringingCall == null && heldCall == null)
516                 return false;
517             if (activeCall != null) {
518                 mCallsManager.disconnectCall(activeCall);
519                 if (ringingCall != null) {
520                     mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
521                 }
522                 return true;
523             }
524             if (ringingCall != null) {
525                 mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
526             } else if (heldCall != null) {
527                 mCallsManager.unholdCall(heldCall);
528             }
529             return true;
530         } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
531             if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
532                 activeCall.swapConference();
533                 Log.i(TAG, "CDMA calls in conference swapped, updating headset");
534                 updateHeadsetWithCallState(true /* force */);
535                 return true;
536             } else if (ringingCall != null) {
537                 mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
538                 return true;
539             } else if (heldCall != null) {
540                 // CallsManager will hold any active calls when unhold() is called on a
541                 // currently-held call.
542                 mCallsManager.unholdCall(heldCall);
543                 return true;
544             } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
545                 mCallsManager.holdCall(activeCall);
546                 return true;
547             }
548         } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
549             if (activeCall != null) {
550                 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
551                     activeCall.mergeConference();
552                     return true;
553                 } else {
554                     List<Call> conferenceable = activeCall.getConferenceableCalls();
555                     if (!conferenceable.isEmpty()) {
556                         mCallsManager.conference(activeCall, conferenceable.get(0));
557                         return true;
558                     }
559                 }
560             }
561         }
562         return false;
563     }
564 
enforceModifyPermission()565     private void enforceModifyPermission() {
566         mContext.enforceCallingOrSelfPermission(
567                 android.Manifest.permission.MODIFY_PHONE_STATE, null);
568     }
569 
sendListOfCalls(boolean shouldLog)570     private void sendListOfCalls(boolean shouldLog) {
571         Collection<Call> mCalls = mCallsManager.getCalls();
572         for (Call call : mCalls) {
573             // We don't send the parent conference call to the bluetooth device.
574             // We do, however want to send conferences that have no children to the bluetooth
575             // device (e.g. IMS Conference).
576             if (!call.isConference() ||
577                     (call.isConference() && call
578                             .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) {
579                 sendClccForCall(call, shouldLog);
580             }
581         }
582         sendClccEndMarker();
583     }
584 
585     /**
586      * Sends a single clcc (C* List Current Calls) event for the specified call.
587      */
sendClccForCall(Call call, boolean shouldLog)588     private void sendClccForCall(Call call, boolean shouldLog) {
589         boolean isForeground = mCallsManager.getForegroundCall() == call;
590         int state = getBtCallState(call, isForeground);
591         boolean isPartOfConference = false;
592         boolean isConferenceWithNoChildren = call.isConference() && call
593                 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
594 
595         if (state == CALL_STATE_IDLE) {
596             return;
597         }
598 
599         Call conferenceCall = call.getParentCall();
600         if (conferenceCall != null) {
601             isPartOfConference = true;
602 
603             // Run some alternative states for Conference-level merge/swap support.
604             // Basically, if call supports swapping or merging at the conference-level, then we need
605             // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the
606             // functionality won't show up on the bluetooth device.
607 
608             // Before doing any special logic, ensure that we are dealing with an ACTIVE call and
609             // that the conference itself has a notion of the current "active" child call.
610             Call activeChild = conferenceCall.getConferenceLevelActiveCall();
611             if (state == CALL_STATE_ACTIVE && activeChild != null) {
612                 // Reevaluate state if we can MERGE or if we can SWAP without previously having
613                 // MERGED.
614                 boolean shouldReevaluateState =
615                         conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
616                         (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
617                         !conferenceCall.wasConferencePreviouslyMerged());
618 
619                 if (shouldReevaluateState) {
620                     isPartOfConference = false;
621                     if (call == activeChild) {
622                         state = CALL_STATE_ACTIVE;
623                     } else {
624                         // At this point we know there is an "active" child and we know that it is
625                         // not this call, so set it to HELD instead.
626                         state = CALL_STATE_HELD;
627                     }
628                 }
629             }
630             if (conferenceCall.getState() == CallState.ON_HOLD &&
631                     conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) {
632                 // If the parent IMS CEP conference call is on hold, we should mark this call as
633                 // being on hold regardless of what the other children are doing.
634                 state = CALL_STATE_HELD;
635             }
636         } else if (isConferenceWithNoChildren) {
637             // Handle the special case of an IMS conference call without conference event package
638             // support.  The call will be marked as a conference, but the conference will not have
639             // child calls where conference event packages are not used by the carrier.
640             isPartOfConference = true;
641         }
642 
643         int index = getIndexForCall(call);
644         int direction = call.isIncoming() ? 1 : 0;
645         final Uri addressUri;
646         if (call.getGatewayInfo() != null) {
647             addressUri = call.getGatewayInfo().getOriginalAddress();
648         } else {
649             addressUri = call.getHandle();
650         }
651 
652         String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
653         if (address != null) {
654             address = PhoneNumberUtils.stripSeparators(address);
655         }
656 
657         int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
658 
659         if (shouldLog) {
660             Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
661                     index, direction, state, isPartOfConference, Log.piiHandle(address),
662                     addressType);
663         }
664 
665         if (mBluetoothHeadset != null) {
666             mBluetoothHeadset.clccResponse(
667                     index, direction, state, 0, isPartOfConference, address, addressType);
668         }
669     }
670 
sendClccEndMarker()671     private void sendClccEndMarker() {
672         // End marker is recognized with an index value of 0. All other parameters are ignored.
673         if (mBluetoothHeadset != null) {
674             mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
675         }
676     }
677 
678     /**
679      * Returns the caches index for the specified call.  If no such index exists, then an index is
680      * given (smallest number starting from 1 that isn't already taken).
681      */
getIndexForCall(Call call)682     private int getIndexForCall(Call call) {
683         if (mClccIndexMap.containsKey(call)) {
684             return mClccIndexMap.get(call);
685         }
686 
687         int i = 1;  // Indexes for bluetooth clcc are 1-based.
688         while (mClccIndexMap.containsValue(i)) {
689             i++;
690         }
691 
692         // NOTE: Indexes are removed in {@link #onCallRemoved}.
693         mClccIndexMap.put(call, i);
694         return i;
695     }
696 
697     /**
698      * Sends an update of the current call state to the current Headset.
699      *
700      * @param force {@code true} if the headset state should be sent regardless if no changes to the
701      *      state have occurred, {@code false} if the state should only be sent if the state has
702      *      changed.
703      */
updateHeadsetWithCallState(boolean force)704     private void updateHeadsetWithCallState(boolean force) {
705         Call activeCall = mCallsManager.getActiveCall();
706         Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
707         Call heldCall = mCallsManager.getHeldCall();
708 
709         int bluetoothCallState = getBluetoothCallStateForUpdate();
710 
711         String ringingAddress = null;
712         int ringingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
713         String ringingName = null;
714         if (ringingCall != null && ringingCall.getHandle() != null
715             && !ringingCall.isSilentRingingRequested()) {
716             ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
717             if (ringingAddress != null) {
718                 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
719             }
720             ringingName = ringingCall.getCallerDisplayName();
721             if (TextUtils.isEmpty(ringingName)) {
722                 ringingName = ringingCall.getName();
723             }
724         }
725         if (ringingAddress == null) {
726             ringingAddress = "";
727         }
728 
729         int numActiveCalls = activeCall == null ? 0 : 1;
730         int numHeldCalls = mCallsManager.getNumHeldCalls();
731         int numChildrenOfActiveCall = activeCall == null ? 0 : activeCall.getChildCalls().size();
732 
733         // Intermediate state for GSM calls which are in the process of being swapped.
734         // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
735         //       are held?
736         boolean callsPendingSwitch = (numHeldCalls == 2);
737 
738         // For conference calls which support swapping the active call within the conference
739         // (namely CDMA calls) we need to expose that as a held call in order for the BT device
740         // to show "swap" and "merge" functionality.
741         boolean ignoreHeldCallChange = false;
742         if (activeCall != null && activeCall.isConference() &&
743                 !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
744             if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
745                 // Indicate that BT device should show SWAP command by indicating that there is a
746                 // call on hold, but only if the conference wasn't previously merged.
747                 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
748             } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
749                 numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
750             }
751 
752             for (Call childCall : activeCall.getChildCalls()) {
753                 // Held call has changed due to it being combined into a CDMA conference. Keep
754                 // track of this and ignore any future update since it doesn't really count as
755                 // a call change.
756                 if (mOldHeldCall == childCall) {
757                     ignoreHeldCallChange = true;
758                     break;
759                 }
760             }
761         }
762 
763         if (mBluetoothHeadset != null &&
764                 (force ||
765                         (!callsPendingSwitch &&
766                                 (numActiveCalls != mNumActiveCalls ||
767                                         numChildrenOfActiveCall != mNumChildrenOfActiveCall ||
768                                         numHeldCalls != mNumHeldCalls ||
769                                         bluetoothCallState != mBluetoothCallState ||
770                                         !TextUtils.equals(ringingAddress, mRingingAddress) ||
771                                         ringingAddressType != mRingingAddressType ||
772                                 (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
773 
774             // If the call is transitioning into the alerting state, send DIALING first.
775             // Some devices expect to see a DIALING state prior to seeing an ALERTING state
776             // so we need to send it first.
777             boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
778                     bluetoothCallState == CALL_STATE_ALERTING;
779 
780             mOldHeldCall = heldCall;
781             mNumActiveCalls = numActiveCalls;
782             mNumChildrenOfActiveCall = numChildrenOfActiveCall;
783             mNumHeldCalls = numHeldCalls;
784             mBluetoothCallState = bluetoothCallState;
785             mRingingAddress = ringingAddress;
786             mRingingAddressType = ringingAddressType;
787 
788             if (sendDialingFirst) {
789                 // Log in full to make logs easier to debug.
790                 Log.i(TAG, "updateHeadsetWithCallState " +
791                         "numActive %s, " +
792                         "numHeld %s, " +
793                         "callState %s, " +
794                         "ringing number %s, " +
795                         "ringing type %s, " +
796                         "ringing name %s",
797                         mNumActiveCalls,
798                         mNumHeldCalls,
799                         CALL_STATE_DIALING,
800                         Log.pii(mRingingAddress),
801                         mRingingAddressType,
802                         Log.pii(ringingName));
803                 mBluetoothHeadset.phoneStateChanged(
804                         mNumActiveCalls,
805                         mNumHeldCalls,
806                         CALL_STATE_DIALING,
807                         mRingingAddress,
808                         mRingingAddressType,
809                         ringingName);
810             }
811 
812             Log.i(TAG, "updateHeadsetWithCallState " +
813                     "numActive %s, " +
814                     "numHeld %s, " +
815                     "callState %s, " +
816                     "ringing number %s, " +
817                     "ringing type %s, " +
818                     "ringing name %s",
819                     mNumActiveCalls,
820                     mNumHeldCalls,
821                     mBluetoothCallState,
822                     Log.pii(mRingingAddress),
823                     mRingingAddressType,
824                     Log.pii(ringingName));
825 
826             mBluetoothHeadset.phoneStateChanged(
827                     mNumActiveCalls,
828                     mNumHeldCalls,
829                     mBluetoothCallState,
830                     mRingingAddress,
831                     mRingingAddressType,
832                     ringingName);
833 
834             mHeadsetUpdatedRecently = true;
835         }
836     }
837 
getBluetoothCallStateForUpdate()838     private int getBluetoothCallStateForUpdate() {
839         Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
840         Call dialingCall = mCallsManager.getOutgoingCall();
841         boolean hasOnlyDisconnectedCalls = mCallsManager.hasOnlyDisconnectedCalls();
842 
843         //
844         // !! WARNING !!
845         // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
846         // used in this version of the call state mappings.  This is on purpose.
847         // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
848         // listCalls*() method are WAITING and ACTIVE used.
849         // Using the unsupported states here caused problems with inconsistent state in some
850         // bluetooth devices (like not getting out of ringing state after answering a call).
851         //
852         int bluetoothCallState = CALL_STATE_IDLE;
853         if (ringingCall != null && !ringingCall.isSilentRingingRequested()) {
854             bluetoothCallState = CALL_STATE_INCOMING;
855         } else if (dialingCall != null) {
856             bluetoothCallState = CALL_STATE_ALERTING;
857         } else if (hasOnlyDisconnectedCalls || mIsDisconnectedTonePlaying) {
858             // Keep the DISCONNECTED state until the disconnect tone's playback is done
859             bluetoothCallState = CALL_STATE_DISCONNECTED;
860         }
861         return bluetoothCallState;
862     }
863 
getBtCallState(Call call, boolean isForeground)864     private int getBtCallState(Call call, boolean isForeground) {
865         switch (call.getState()) {
866             case CallState.NEW:
867             case CallState.ABORTED:
868             case CallState.DISCONNECTED:
869             case CallState.AUDIO_PROCESSING:
870                 return CALL_STATE_IDLE;
871 
872             case CallState.ACTIVE:
873                 return CALL_STATE_ACTIVE;
874 
875             case CallState.CONNECTING:
876             case CallState.SELECT_PHONE_ACCOUNT:
877             case CallState.DIALING:
878             case CallState.PULLING:
879                 // Yes, this is correctly returning ALERTING.
880                 // "Dialing" for BT means that we have sent information to the service provider
881                 // to place the call but there is no confirmation that the call is going through.
882                 // When there finally is confirmation, the ringback is played which is referred to
883                 // as an "alert" tone, thus, ALERTING.
884                 // TODO: We should consider using the ALERTING terms in Telecom because that
885                 // seems to be more industry-standard.
886                 return CALL_STATE_ALERTING;
887 
888             case CallState.ON_HOLD:
889                 return CALL_STATE_HELD;
890 
891             case CallState.RINGING:
892             case CallState.ANSWERED:
893             case CallState.SIMULATED_RINGING:
894                 if (call.isSilentRingingRequested()) {
895                     return CALL_STATE_IDLE;
896                 } else if (isForeground) {
897                     return CALL_STATE_INCOMING;
898                 } else {
899                     return CALL_STATE_WAITING;
900                 }
901         }
902         return CALL_STATE_IDLE;
903     }
904 
905     /**
906      * Returns the best phone account to use for the given state of all calls.
907      * First, tries to return the phone account for the foreground call, second the default
908      * phone account for PhoneAccount.SCHEME_TEL.
909      */
getBestPhoneAccount()910     private PhoneAccount getBestPhoneAccount() {
911         if (mPhoneAccountRegistrar == null) {
912             return null;
913         }
914 
915         Call call = mCallsManager.getForegroundCall();
916 
917         PhoneAccount account = null;
918         if (call != null) {
919             // First try to get the network name of the foreground call.
920             account = mPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
921                     call.getTargetPhoneAccount());
922         }
923 
924         if (account == null) {
925             // Second, Try to get the label for the default Phone Account.
926             account = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
927                     mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
928                             PhoneAccount.SCHEME_TEL));
929         }
930         return account;
931     }
932 }
933