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