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