• 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                         updateHeadsetWithCallState(true /* force */);
412                     }
413                 }
414 
415                 @Override
416                 public void onServiceDisconnected(int profile) {
417                     synchronized (mLock) {
418                         mBluetoothHeadset = null;
419                     }
420                 }
421             };
422 
423     /**
424      * Receives events for global state changes of the bluetooth adapter.
425      */
426     @VisibleForTesting
427     public final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
428         @Override
429         public void onReceive(Context context, Intent intent) {
430             synchronized (mLock) {
431                 int state = intent
432                         .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
433                 Log.d(TAG, "Bluetooth Adapter state: %d", state);
434                 if (state == BluetoothAdapter.STATE_ON) {
435                     try {
436                         mBinder.queryPhoneState();
437                     } catch (RemoteException e) {
438                         // Remote exception not expected
439                     }
440                 }
441             }
442         }
443     };
444 
445     private BluetoothAdapterProxy mBluetoothAdapter;
446     private BluetoothHeadsetProxy mBluetoothHeadset;
447 
448     // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
449     private Map<Call, Integer> mClccIndexMap = new HashMap<>();
450 
451     private boolean mHeadsetUpdatedRecently = false;
452 
453     private final Context mContext;
454     private final TelecomSystem.SyncRoot mLock;
455     private final CallsManager mCallsManager;
456     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
457 
getBinder()458     public IBinder getBinder() {
459         return mBinder;
460     }
461 
BluetoothPhoneServiceImpl( Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, BluetoothAdapterProxy bluetoothAdapter, PhoneAccountRegistrar phoneAccountRegistrar)462     public BluetoothPhoneServiceImpl(
463             Context context,
464             TelecomSystem.SyncRoot lock,
465             CallsManager callsManager,
466             BluetoothAdapterProxy bluetoothAdapter,
467             PhoneAccountRegistrar phoneAccountRegistrar) {
468         Log.d(this, "onCreate");
469 
470         mContext = context;
471         mLock = lock;
472         mCallsManager = callsManager;
473         mPhoneAccountRegistrar = phoneAccountRegistrar;
474 
475         mBluetoothAdapter = bluetoothAdapter;
476         if (mBluetoothAdapter == null) {
477             Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found.");
478             return;
479         }
480         mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
481 
482         IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
483         context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
484 
485         mCallsManager.addListener(mCallsManagerListener);
486         updateHeadsetWithCallState(false /* force */);
487     }
488 
489     @VisibleForTesting
setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset)490     public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) {
491         mBluetoothHeadset = bluetoothHeadset;
492     }
493 
processChld(int chld)494     private boolean processChld(int chld) {
495         Call activeCall = mCallsManager.getActiveCall();
496         Call ringingCall = mCallsManager.getRingingCall();
497         Call heldCall = mCallsManager.getHeldCall();
498 
499         // TODO: Keeping as Log.i for now.  Move to Log.d after L release if BT proves stable.
500         Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
501 
502         if (chld == CHLD_TYPE_RELEASEHELD) {
503             if (ringingCall != null) {
504                 mCallsManager.rejectCall(ringingCall, false, null);
505                 return true;
506             } else if (heldCall != null) {
507                 mCallsManager.disconnectCall(heldCall);
508                 return true;
509             }
510         } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
511             if (activeCall == null && ringingCall == null && heldCall == null)
512                 return false;
513             if (activeCall != null) {
514                 mCallsManager.disconnectCall(activeCall);
515                 if (ringingCall != null) {
516                     mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
517                 }
518                 return true;
519             }
520             if (ringingCall != null) {
521                 mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
522             } else if (heldCall != null) {
523                 mCallsManager.unholdCall(heldCall);
524             }
525             return true;
526         } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
527             if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
528                 activeCall.swapConference();
529                 Log.i(TAG, "CDMA calls in conference swapped, updating headset");
530                 updateHeadsetWithCallState(true /* force */);
531                 return true;
532             } else if (ringingCall != null) {
533                 mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
534                 return true;
535             } else if (heldCall != null) {
536                 // CallsManager will hold any active calls when unhold() is called on a
537                 // currently-held call.
538                 mCallsManager.unholdCall(heldCall);
539                 return true;
540             } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
541                 mCallsManager.holdCall(activeCall);
542                 return true;
543             }
544         } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
545             if (activeCall != null) {
546                 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
547                     activeCall.mergeConference();
548                     return true;
549                 } else {
550                     List<Call> conferenceable = activeCall.getConferenceableCalls();
551                     if (!conferenceable.isEmpty()) {
552                         mCallsManager.conference(activeCall, conferenceable.get(0));
553                         return true;
554                     }
555                 }
556             }
557         }
558         return false;
559     }
560 
enforceModifyPermission()561     private void enforceModifyPermission() {
562         mContext.enforceCallingOrSelfPermission(
563                 android.Manifest.permission.MODIFY_PHONE_STATE, null);
564     }
565 
sendListOfCalls(boolean shouldLog)566     private void sendListOfCalls(boolean shouldLog) {
567         Collection<Call> mCalls = mCallsManager.getCalls();
568         for (Call call : mCalls) {
569             // We don't send the parent conference call to the bluetooth device.
570             // We do, however want to send conferences that have no children to the bluetooth
571             // device (e.g. IMS Conference).
572             if (!call.isConference() ||
573                     (call.isConference() && call
574                             .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) {
575                 sendClccForCall(call, shouldLog);
576             }
577         }
578         sendClccEndMarker();
579     }
580 
581     /**
582      * Sends a single clcc (C* List Current Calls) event for the specified call.
583      */
sendClccForCall(Call call, boolean shouldLog)584     private void sendClccForCall(Call call, boolean shouldLog) {
585         boolean isForeground = mCallsManager.getForegroundCall() == call;
586         int state = getBtCallState(call, isForeground);
587         boolean isPartOfConference = false;
588         boolean isConferenceWithNoChildren = call.isConference() && call
589                 .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
590 
591         if (state == CALL_STATE_IDLE) {
592             return;
593         }
594 
595         Call conferenceCall = call.getParentCall();
596         if (conferenceCall != null) {
597             isPartOfConference = true;
598 
599             // Run some alternative states for Conference-level merge/swap support.
600             // Basically, if call supports swapping or merging at the conference-level, then we need
601             // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the
602             // functionality won't show up on the bluetooth device.
603 
604             // Before doing any special logic, ensure that we are dealing with an ACTIVE call and
605             // that the conference itself has a notion of the current "active" child call.
606             Call activeChild = conferenceCall.getConferenceLevelActiveCall();
607             if (state == CALL_STATE_ACTIVE && activeChild != null) {
608                 // Reevaluate state if we can MERGE or if we can SWAP without previously having
609                 // MERGED.
610                 boolean shouldReevaluateState =
611                         conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
612                         (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
613                         !conferenceCall.wasConferencePreviouslyMerged());
614 
615                 if (shouldReevaluateState) {
616                     isPartOfConference = false;
617                     if (call == activeChild) {
618                         state = CALL_STATE_ACTIVE;
619                     } else {
620                         // At this point we know there is an "active" child and we know that it is
621                         // not this call, so set it to HELD instead.
622                         state = CALL_STATE_HELD;
623                     }
624                 }
625             }
626             if (conferenceCall.getState() == CallState.ON_HOLD &&
627                     conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) {
628                 // If the parent IMS CEP conference call is on hold, we should mark this call as
629                 // being on hold regardless of what the other children are doing.
630                 state = CALL_STATE_HELD;
631             }
632         } else if (isConferenceWithNoChildren) {
633             // Handle the special case of an IMS conference call without conference event package
634             // support.  The call will be marked as a conference, but the conference will not have
635             // child calls where conference event packages are not used by the carrier.
636             isPartOfConference = true;
637         }
638 
639         int index = getIndexForCall(call);
640         int direction = call.isIncoming() ? 1 : 0;
641         final Uri addressUri;
642         if (call.getGatewayInfo() != null) {
643             addressUri = call.getGatewayInfo().getOriginalAddress();
644         } else {
645             addressUri = call.getHandle();
646         }
647 
648         String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
649         if (address != null) {
650             address = PhoneNumberUtils.stripSeparators(address);
651         }
652 
653         int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
654 
655         if (shouldLog) {
656             Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
657                     index, direction, state, isPartOfConference, Log.piiHandle(address),
658                     addressType);
659         }
660 
661         if (mBluetoothHeadset != null) {
662             mBluetoothHeadset.clccResponse(
663                     index, direction, state, 0, isPartOfConference, address, addressType);
664         }
665     }
666 
sendClccEndMarker()667     private void sendClccEndMarker() {
668         // End marker is recognized with an index value of 0. All other parameters are ignored.
669         if (mBluetoothHeadset != null) {
670             mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
671         }
672     }
673 
674     /**
675      * Returns the caches index for the specified call.  If no such index exists, then an index is
676      * given (smallest number starting from 1 that isn't already taken).
677      */
getIndexForCall(Call call)678     private int getIndexForCall(Call call) {
679         if (mClccIndexMap.containsKey(call)) {
680             return mClccIndexMap.get(call);
681         }
682 
683         int i = 1;  // Indexes for bluetooth clcc are 1-based.
684         while (mClccIndexMap.containsValue(i)) {
685             i++;
686         }
687 
688         // NOTE: Indexes are removed in {@link #onCallRemoved}.
689         mClccIndexMap.put(call, i);
690         return i;
691     }
692 
693     /**
694      * Sends an update of the current call state to the current Headset.
695      *
696      * @param force {@code true} if the headset state should be sent regardless if no changes to the
697      *      state have occurred, {@code false} if the state should only be sent if the state has
698      *      changed.
699      */
updateHeadsetWithCallState(boolean force)700     private void updateHeadsetWithCallState(boolean force) {
701         Call activeCall = mCallsManager.getActiveCall();
702         Call ringingCall = mCallsManager.getRingingCall();
703         Call heldCall = mCallsManager.getHeldCall();
704 
705         int bluetoothCallState = getBluetoothCallStateForUpdate();
706 
707         String ringingAddress = null;
708         int ringingAddressType = 128;
709         String ringingName = null;
710         if (ringingCall != null && ringingCall.getHandle() != null
711             && !ringingCall.isSilentRingingRequested()) {
712             ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
713             if (ringingAddress != null) {
714                 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
715             }
716             ringingName = ringingCall.getCallerDisplayName();
717             if (TextUtils.isEmpty(ringingName)) {
718                 ringingName = ringingCall.getName();
719             }
720         }
721         if (ringingAddress == null) {
722             ringingAddress = "";
723         }
724 
725         int numActiveCalls = activeCall == null ? 0 : 1;
726         int numHeldCalls = mCallsManager.getNumHeldCalls();
727         int numChildrenOfActiveCall = activeCall == null ? 0 : activeCall.getChildCalls().size();
728 
729         // Intermediate state for GSM calls which are in the process of being swapped.
730         // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
731         //       are held?
732         boolean callsPendingSwitch = (numHeldCalls == 2);
733 
734         // For conference calls which support swapping the active call within the conference
735         // (namely CDMA calls) we need to expose that as a held call in order for the BT device
736         // to show "swap" and "merge" functionality.
737         boolean ignoreHeldCallChange = false;
738         if (activeCall != null && activeCall.isConference() &&
739                 !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
740             if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
741                 // Indicate that BT device should show SWAP command by indicating that there is a
742                 // call on hold, but only if the conference wasn't previously merged.
743                 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
744             } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
745                 numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
746             }
747 
748             for (Call childCall : activeCall.getChildCalls()) {
749                 // Held call has changed due to it being combined into a CDMA conference. Keep
750                 // track of this and ignore any future update since it doesn't really count as
751                 // a call change.
752                 if (mOldHeldCall == childCall) {
753                     ignoreHeldCallChange = true;
754                     break;
755                 }
756             }
757         }
758 
759         if (mBluetoothHeadset != null &&
760                 (force ||
761                         (!callsPendingSwitch &&
762                                 (numActiveCalls != mNumActiveCalls ||
763                                         numChildrenOfActiveCall != mNumChildrenOfActiveCall ||
764                                         numHeldCalls != mNumHeldCalls ||
765                                         bluetoothCallState != mBluetoothCallState ||
766                                         !TextUtils.equals(ringingAddress, mRingingAddress) ||
767                                         ringingAddressType != mRingingAddressType ||
768                                 (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
769 
770             // If the call is transitioning into the alerting state, send DIALING first.
771             // Some devices expect to see a DIALING state prior to seeing an ALERTING state
772             // so we need to send it first.
773             boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
774                     bluetoothCallState == CALL_STATE_ALERTING;
775 
776             mOldHeldCall = heldCall;
777             mNumActiveCalls = numActiveCalls;
778             mNumChildrenOfActiveCall = numChildrenOfActiveCall;
779             mNumHeldCalls = numHeldCalls;
780             mBluetoothCallState = bluetoothCallState;
781             mRingingAddress = ringingAddress;
782             mRingingAddressType = ringingAddressType;
783 
784             if (sendDialingFirst) {
785                 // Log in full to make logs easier to debug.
786                 Log.i(TAG, "updateHeadsetWithCallState " +
787                         "numActive %s, " +
788                         "numHeld %s, " +
789                         "callState %s, " +
790                         "ringing number %s, " +
791                         "ringing type %s, " +
792                         "ringing name %s",
793                         mNumActiveCalls,
794                         mNumHeldCalls,
795                         CALL_STATE_DIALING,
796                         Log.pii(mRingingAddress),
797                         mRingingAddressType,
798                         Log.pii(ringingName));
799                 mBluetoothHeadset.phoneStateChanged(
800                         mNumActiveCalls,
801                         mNumHeldCalls,
802                         CALL_STATE_DIALING,
803                         mRingingAddress,
804                         mRingingAddressType,
805                         ringingName);
806             }
807 
808             Log.i(TAG, "updateHeadsetWithCallState " +
809                     "numActive %s, " +
810                     "numHeld %s, " +
811                     "callState %s, " +
812                     "ringing number %s, " +
813                     "ringing type %s, " +
814                     "ringing name %s",
815                     mNumActiveCalls,
816                     mNumHeldCalls,
817                     mBluetoothCallState,
818                     Log.pii(mRingingAddress),
819                     mRingingAddressType,
820                     Log.pii(ringingName));
821 
822             mBluetoothHeadset.phoneStateChanged(
823                     mNumActiveCalls,
824                     mNumHeldCalls,
825                     mBluetoothCallState,
826                     mRingingAddress,
827                     mRingingAddressType,
828                     ringingName);
829 
830             mHeadsetUpdatedRecently = true;
831         }
832     }
833 
getBluetoothCallStateForUpdate()834     private int getBluetoothCallStateForUpdate() {
835         Call ringingCall = mCallsManager.getRingingCall();
836         Call dialingCall = mCallsManager.getOutgoingCall();
837         boolean hasOnlyDisconnectedCalls = mCallsManager.hasOnlyDisconnectedCalls();
838 
839         //
840         // !! WARNING !!
841         // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
842         // used in this version of the call state mappings.  This is on purpose.
843         // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
844         // listCalls*() method are WAITING and ACTIVE used.
845         // Using the unsupported states here caused problems with inconsistent state in some
846         // bluetooth devices (like not getting out of ringing state after answering a call).
847         //
848         int bluetoothCallState = CALL_STATE_IDLE;
849         if (ringingCall != null && !ringingCall.isSilentRingingRequested()) {
850             bluetoothCallState = CALL_STATE_INCOMING;
851         } else if (dialingCall != null) {
852             bluetoothCallState = CALL_STATE_ALERTING;
853         } else if (hasOnlyDisconnectedCalls || mIsDisconnectedTonePlaying) {
854             // Keep the DISCONNECTED state until the disconnect tone's playback is done
855             bluetoothCallState = CALL_STATE_DISCONNECTED;
856         }
857         return bluetoothCallState;
858     }
859 
getBtCallState(Call call, boolean isForeground)860     private int getBtCallState(Call call, boolean isForeground) {
861         switch (call.getState()) {
862             case CallState.NEW:
863             case CallState.ABORTED:
864             case CallState.DISCONNECTED:
865                 return CALL_STATE_IDLE;
866 
867             case CallState.ACTIVE:
868                 return CALL_STATE_ACTIVE;
869 
870             case CallState.CONNECTING:
871             case CallState.SELECT_PHONE_ACCOUNT:
872             case CallState.DIALING:
873             case CallState.PULLING:
874                 // Yes, this is correctly returning ALERTING.
875                 // "Dialing" for BT means that we have sent information to the service provider
876                 // to place the call but there is no confirmation that the call is going through.
877                 // When there finally is confirmation, the ringback is played which is referred to
878                 // as an "alert" tone, thus, ALERTING.
879                 // TODO: We should consider using the ALERTING terms in Telecom because that
880                 // seems to be more industry-standard.
881                 return CALL_STATE_ALERTING;
882 
883             case CallState.ON_HOLD:
884                 return CALL_STATE_HELD;
885 
886             case CallState.RINGING:
887             case CallState.ANSWERED:
888                 if (call.isSilentRingingRequested()) {
889                     return CALL_STATE_IDLE;
890                 } else if (isForeground) {
891                     return CALL_STATE_INCOMING;
892                 } else {
893                     return CALL_STATE_WAITING;
894                 }
895         }
896         return CALL_STATE_IDLE;
897     }
898 
899     /**
900      * Returns the best phone account to use for the given state of all calls.
901      * First, tries to return the phone account for the foreground call, second the default
902      * phone account for PhoneAccount.SCHEME_TEL.
903      */
getBestPhoneAccount()904     private PhoneAccount getBestPhoneAccount() {
905         if (mPhoneAccountRegistrar == null) {
906             return null;
907         }
908 
909         Call call = mCallsManager.getForegroundCall();
910 
911         PhoneAccount account = null;
912         if (call != null) {
913             // First try to get the network name of the foreground call.
914             account = mPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
915                     call.getTargetPhoneAccount());
916         }
917 
918         if (account == null) {
919             // Second, Try to get the label for the default Phone Account.
920             account = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
921                     mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
922                             PhoneAccount.SCHEME_TEL));
923         }
924         return account;
925     }
926 }
927