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