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