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