BluetoothPhoneServiceImpl.java revision 6916a88d04b40bc934bfd6059f3f3e701d7f7242
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        /**
304         * Where a call which was external becomes a regular call, or a regular call becomes
305         * external, treat as an add or remove, respectively.
306         *
307         * @param call The call.
308         * @param isExternalCall {@code True} if the call became external, {@code false} otherwise.
309         */
310        @Override
311        public void onExternalCallChanged(Call call, boolean isExternalCall) {
312            if (isExternalCall) {
313                onCallRemoved(call);
314            } else {
315                onCallAdded(call);
316            }
317        }
318
319        @Override
320        public void onCallStateChanged(Call call, int oldState, int newState) {
321            // If a call is being put on hold because of a new connecting call, ignore the
322            // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
323            // state atomically.
324            // When the call later transitions to DIALING/DISCONNECTED we will then send out the
325            // aggregated update.
326            if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
327                for (Call otherCall : mCallsManager.getCalls()) {
328                    if (otherCall.getState() == CallState.CONNECTING) {
329                        return;
330                    }
331                }
332            }
333
334            // To have an active call and another dialing at the same time is an invalid BT
335            // state. We can assume that the active call will be automatically held which will
336            // send another update at which point we will be in the right state.
337            if (mCallsManager.getActiveCall() != null
338                    && oldState == CallState.CONNECTING && newState == CallState.DIALING) {
339                return;
340            }
341            updateHeadsetWithCallState(false /* force */);
342        }
343
344        @Override
345        public void onIsConferencedChanged(Call call) {
346            /*
347             * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
348             * because conference change events are not atomic and multiple callbacks get fired
349             * when two calls are conferenced together. This confuses updateHeadsetWithCallState
350             * if it runs in the middle of two calls being conferenced and can cause spurious and
351             * incorrect headset state updates. One of the scenarios is described below for CDMA
352             * conference calls.
353             *
354             * 1) Call 1 and Call 2 are being merged into conference Call 3.
355             * 2) Call 1 has its parent set to Call 3, but Call 2 does not have a parent yet.
356             * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and
357             * Call 3) when there is actually only one active call (Call 3).
358             */
359            if (call.getParentCall() != null) {
360                // If this call is newly conferenced, ignore the callback. We only care about the
361                // one sent for the parent conference call.
362                Log.d(this, "Ignoring onIsConferenceChanged from child call with new parent");
363                return;
364            }
365            if (call.getChildCalls().size() == 1) {
366                // If this is a parent call with only one child, ignore the callback as well since
367                // the minimum number of child calls to start a conference call is 2. We expect
368                // this to be called again when the parent call has another child call added.
369                Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
370                return;
371            }
372            updateHeadsetWithCallState(false /* force */);
373        }
374    };
375
376    /**
377     * Listens to connections and disconnections of bluetooth headsets.  We need to save the current
378     * bluetooth headset so that we know where to send call updates.
379     */
380    @VisibleForTesting
381    public BluetoothProfile.ServiceListener mProfileListener =
382            new BluetoothProfile.ServiceListener() {
383                @Override
384                public void onServiceConnected(int profile, BluetoothProfile proxy) {
385                    synchronized (mLock) {
386                        setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy));
387                    }
388                }
389
390                @Override
391                public void onServiceDisconnected(int profile) {
392                    synchronized (mLock) {
393                        mBluetoothHeadset = null;
394                    }
395                }
396            };
397
398    /**
399     * Receives events for global state changes of the bluetooth adapter.
400     */
401    @VisibleForTesting
402    public final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
403        @Override
404        public void onReceive(Context context, Intent intent) {
405            synchronized (mLock) {
406                int state = intent
407                        .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
408                Log.d(TAG, "Bluetooth Adapter state: %d", state);
409                if (state == BluetoothAdapter.STATE_ON) {
410                    try {
411                        mBinder.queryPhoneState();
412                    } catch (RemoteException e) {
413                        // Remote exception not expected
414                    }
415                }
416            }
417        }
418    };
419
420    private BluetoothAdapterProxy mBluetoothAdapter;
421    private BluetoothHeadsetProxy mBluetoothHeadset;
422
423    // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
424    private Map<Call, Integer> mClccIndexMap = new HashMap<>();
425
426    private boolean mHeadsetUpdatedRecently = false;
427
428    private final Context mContext;
429    private final TelecomSystem.SyncRoot mLock;
430    private final CallsManager mCallsManager;
431    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
432
433    public IBinder getBinder() {
434        return mBinder;
435    }
436
437    public BluetoothPhoneServiceImpl(
438            Context context,
439            TelecomSystem.SyncRoot lock,
440            CallsManager callsManager,
441            BluetoothAdapterProxy bluetoothAdapter,
442            PhoneAccountRegistrar phoneAccountRegistrar) {
443        Log.d(this, "onCreate");
444
445        mContext = context;
446        mLock = lock;
447        mCallsManager = callsManager;
448        mPhoneAccountRegistrar = phoneAccountRegistrar;
449
450        mBluetoothAdapter = bluetoothAdapter;
451        if (mBluetoothAdapter == null) {
452            Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found.");
453            return;
454        }
455        mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
456
457        IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
458        context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
459
460        mCallsManager.addListener(mCallsManagerListener);
461        updateHeadsetWithCallState(false /* force */);
462    }
463
464    @VisibleForTesting
465    public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) {
466        mBluetoothHeadset = bluetoothHeadset;
467    }
468
469    private boolean processChld(int chld) {
470        Call activeCall = mCallsManager.getActiveCall();
471        Call ringingCall = mCallsManager.getRingingCall();
472        Call heldCall = mCallsManager.getHeldCall();
473
474        // TODO: Keeping as Log.i for now.  Move to Log.d after L release if BT proves stable.
475        Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
476
477        if (chld == CHLD_TYPE_RELEASEHELD) {
478            if (ringingCall != null) {
479                mCallsManager.rejectCall(ringingCall, false, null);
480                return true;
481            } else if (heldCall != null) {
482                mCallsManager.disconnectCall(heldCall);
483                return true;
484            }
485        } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
486            if (activeCall != null) {
487                mCallsManager.disconnectCall(activeCall);
488                if (ringingCall != null) {
489                    mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
490                } else if (heldCall != null) {
491                    mCallsManager.unholdCall(heldCall);
492                }
493                return true;
494            }
495        } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
496            if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
497                activeCall.swapConference();
498                Log.i(TAG, "CDMA calls in conference swapped, updating headset");
499                updateHeadsetWithCallState(true /* force */);
500                return true;
501            } else if (ringingCall != null) {
502                mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
503                return true;
504            } else if (heldCall != null) {
505                // CallsManager will hold any active calls when unhold() is called on a
506                // currently-held call.
507                mCallsManager.unholdCall(heldCall);
508                return true;
509            } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
510                mCallsManager.holdCall(activeCall);
511                return true;
512            }
513        } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
514            if (activeCall != null) {
515                if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
516                    activeCall.mergeConference();
517                    return true;
518                } else {
519                    List<Call> conferenceable = activeCall.getConferenceableCalls();
520                    if (!conferenceable.isEmpty()) {
521                        mCallsManager.conference(activeCall, conferenceable.get(0));
522                        return true;
523                    }
524                }
525            }
526        }
527        return false;
528    }
529
530    private void enforceModifyPermission() {
531        mContext.enforceCallingOrSelfPermission(
532                android.Manifest.permission.MODIFY_PHONE_STATE, null);
533    }
534
535    private void sendListOfCalls(boolean shouldLog) {
536        Collection<Call> mCalls = mCallsManager.getCalls();
537        for (Call call : mCalls) {
538            // We don't send the parent conference call to the bluetooth device.
539            // We do, however want to send conferences that have no children to the bluetooth
540            // device (e.g. IMS Conference).
541            if (!call.isConference() ||
542                    (call.isConference() && call
543                            .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) {
544                sendClccForCall(call, shouldLog);
545            }
546        }
547        sendClccEndMarker();
548    }
549
550    /**
551     * Sends a single clcc (C* List Current Calls) event for the specified call.
552     */
553    private void sendClccForCall(Call call, boolean shouldLog) {
554        boolean isForeground = mCallsManager.getForegroundCall() == call;
555        int state = convertCallState(call.getState(), isForeground);
556        boolean isPartOfConference = false;
557        boolean isConferenceWithNoChildren = call.isConference() && call
558                .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
559
560        if (state == CALL_STATE_IDLE) {
561            return;
562        }
563
564        Call conferenceCall = call.getParentCall();
565        if (conferenceCall != null) {
566            isPartOfConference = true;
567
568            // Run some alternative states for Conference-level merge/swap support.
569            // Basically, if call supports swapping or merging at the conference-level, then we need
570            // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the
571            // functionality won't show up on the bluetooth device.
572
573            // Before doing any special logic, ensure that we are dealing with an ACTIVE call and
574            // that the conference itself has a notion of the current "active" child call.
575            Call activeChild = conferenceCall.getConferenceLevelActiveCall();
576            if (state == CALL_STATE_ACTIVE && activeChild != null) {
577                // Reevaluate state if we can MERGE or if we can SWAP without previously having
578                // MERGED.
579                boolean shouldReevaluateState =
580                        conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
581                        (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
582                        !conferenceCall.wasConferencePreviouslyMerged());
583
584                if (shouldReevaluateState) {
585                    isPartOfConference = false;
586                    if (call == activeChild) {
587                        state = CALL_STATE_ACTIVE;
588                    } else {
589                        // At this point we know there is an "active" child and we know that it is
590                        // not this call, so set it to HELD instead.
591                        state = CALL_STATE_HELD;
592                    }
593                }
594            }
595        } else if (isConferenceWithNoChildren) {
596            // Handle the special case of an IMS conference call without conference event package
597            // support.  The call will be marked as a conference, but the conference will not have
598            // child calls where conference event packages are not used by the carrier.
599            isPartOfConference = true;
600        }
601
602        int index = getIndexForCall(call);
603        int direction = call.isIncoming() ? 1 : 0;
604        final Uri addressUri;
605        if (call.getGatewayInfo() != null) {
606            addressUri = call.getGatewayInfo().getOriginalAddress();
607        } else {
608            addressUri = call.getHandle();
609        }
610        String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
611        int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
612
613        if (shouldLog) {
614            Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
615                    index, direction, state, isPartOfConference, Log.piiHandle(address),
616                    addressType);
617        }
618
619        if (mBluetoothHeadset != null) {
620            mBluetoothHeadset.clccResponse(
621                    index, direction, state, 0, isPartOfConference, address, addressType);
622        }
623    }
624
625    private void sendClccEndMarker() {
626        // End marker is recognized with an index value of 0. All other parameters are ignored.
627        if (mBluetoothHeadset != null) {
628            mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
629        }
630    }
631
632    /**
633     * Returns the caches index for the specified call.  If no such index exists, then an index is
634     * given (smallest number starting from 1 that isn't already taken).
635     */
636    private int getIndexForCall(Call call) {
637        if (mClccIndexMap.containsKey(call)) {
638            return mClccIndexMap.get(call);
639        }
640
641        int i = 1;  // Indexes for bluetooth clcc are 1-based.
642        while (mClccIndexMap.containsValue(i)) {
643            i++;
644        }
645
646        // NOTE: Indexes are removed in {@link #onCallRemoved}.
647        mClccIndexMap.put(call, i);
648        return i;
649    }
650
651    /**
652     * Sends an update of the current call state to the current Headset.
653     *
654     * @param force {@code true} if the headset state should be sent regardless if no changes to the
655     *      state have occurred, {@code false} if the state should only be sent if the state has
656     *      changed.
657     */
658    private void updateHeadsetWithCallState(boolean force) {
659        Call activeCall = mCallsManager.getActiveCall();
660        Call ringingCall = mCallsManager.getRingingCall();
661        Call heldCall = mCallsManager.getHeldCall();
662
663        int bluetoothCallState = getBluetoothCallStateForUpdate();
664
665        String ringingAddress = null;
666        int ringingAddressType = 128;
667        if (ringingCall != null && ringingCall.getHandle() != null) {
668            ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
669            if (ringingAddress != null) {
670                ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
671            }
672        }
673        if (ringingAddress == null) {
674            ringingAddress = "";
675        }
676
677        int numActiveCalls = activeCall == null ? 0 : 1;
678        int numHeldCalls = mCallsManager.getNumHeldCalls();
679        // Intermediate state for GSM calls which are in the process of being swapped.
680        // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
681        //       are held?
682        boolean callsPendingSwitch = (numHeldCalls == 2);
683
684        // For conference calls which support swapping the active call within the conference
685        // (namely CDMA calls) we need to expose that as a held call in order for the BT device
686        // to show "swap" and "merge" functionality.
687        boolean ignoreHeldCallChange = false;
688        if (activeCall != null && activeCall.isConference() &&
689                !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
690            if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
691                // Indicate that BT device should show SWAP command by indicating that there is a
692                // call on hold, but only if the conference wasn't previously merged.
693                numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
694            } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
695                numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
696            }
697
698            for (Call childCall : activeCall.getChildCalls()) {
699                // Held call has changed due to it being combined into a CDMA conference. Keep
700                // track of this and ignore any future update since it doesn't really count as
701                // a call change.
702                if (mOldHeldCall == childCall) {
703                    ignoreHeldCallChange = true;
704                    break;
705                }
706            }
707        }
708
709        if (mBluetoothHeadset != null &&
710                (force ||
711                        (!callsPendingSwitch &&
712                                (numActiveCalls != mNumActiveCalls ||
713                                numHeldCalls != mNumHeldCalls ||
714                                bluetoothCallState != mBluetoothCallState ||
715                                !TextUtils.equals(ringingAddress, mRingingAddress) ||
716                                ringingAddressType != mRingingAddressType ||
717                                (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
718
719            // If the call is transitioning into the alerting state, send DIALING first.
720            // Some devices expect to see a DIALING state prior to seeing an ALERTING state
721            // so we need to send it first.
722            boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
723                    bluetoothCallState == CALL_STATE_ALERTING;
724
725            mOldHeldCall = heldCall;
726            mNumActiveCalls = numActiveCalls;
727            mNumHeldCalls = numHeldCalls;
728            mBluetoothCallState = bluetoothCallState;
729            mRingingAddress = ringingAddress;
730            mRingingAddressType = ringingAddressType;
731
732            if (sendDialingFirst) {
733                // Log in full to make logs easier to debug.
734                Log.i(TAG, "updateHeadsetWithCallState " +
735                        "numActive %s, " +
736                        "numHeld %s, " +
737                        "callState %s, " +
738                        "ringing number %s, " +
739                        "ringing type %s",
740                        mNumActiveCalls,
741                        mNumHeldCalls,
742                        CALL_STATE_DIALING,
743                        Log.pii(mRingingAddress),
744                        mRingingAddressType);
745                mBluetoothHeadset.phoneStateChanged(
746                        mNumActiveCalls,
747                        mNumHeldCalls,
748                        CALL_STATE_DIALING,
749                        mRingingAddress,
750                        mRingingAddressType);
751            }
752
753            Log.i(TAG, "updateHeadsetWithCallState " +
754                    "numActive %s, " +
755                    "numHeld %s, " +
756                    "callState %s, " +
757                    "ringing number %s, " +
758                    "ringing type %s",
759                    mNumActiveCalls,
760                    mNumHeldCalls,
761                    mBluetoothCallState,
762                    Log.pii(mRingingAddress),
763                    mRingingAddressType);
764
765            mBluetoothHeadset.phoneStateChanged(
766                    mNumActiveCalls,
767                    mNumHeldCalls,
768                    mBluetoothCallState,
769                    mRingingAddress,
770                    mRingingAddressType);
771
772            mHeadsetUpdatedRecently = true;
773        }
774    }
775
776    private int getBluetoothCallStateForUpdate() {
777        CallsManager callsManager = mCallsManager;
778        Call ringingCall = mCallsManager.getRingingCall();
779        Call dialingCall = mCallsManager.getOutgoingCall();
780
781        //
782        // !! WARNING !!
783        // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
784        // used in this version of the call state mappings.  This is on purpose.
785        // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
786        // listCalls*() method are WAITING and ACTIVE used.
787        // Using the unsupported states here caused problems with inconsistent state in some
788        // bluetooth devices (like not getting out of ringing state after answering a call).
789        //
790        int bluetoothCallState = CALL_STATE_IDLE;
791        if (ringingCall != null) {
792            bluetoothCallState = CALL_STATE_INCOMING;
793        } else if (dialingCall != null) {
794            bluetoothCallState = CALL_STATE_ALERTING;
795        }
796        return bluetoothCallState;
797    }
798
799    private int convertCallState(int callState, boolean isForegroundCall) {
800        switch (callState) {
801            case CallState.NEW:
802            case CallState.ABORTED:
803            case CallState.DISCONNECTED:
804                return CALL_STATE_IDLE;
805
806            case CallState.ACTIVE:
807                return CALL_STATE_ACTIVE;
808
809            case CallState.CONNECTING:
810            case CallState.SELECT_PHONE_ACCOUNT:
811            case CallState.DIALING:
812                // Yes, this is correctly returning ALERTING.
813                // "Dialing" for BT means that we have sent information to the service provider
814                // to place the call but there is no confirmation that the call is going through.
815                // When there finally is confirmation, the ringback is played which is referred to
816                // as an "alert" tone, thus, ALERTING.
817                // TODO: We should consider using the ALERTING terms in Telecom because that
818                // seems to be more industry-standard.
819                return CALL_STATE_ALERTING;
820
821            case CallState.ON_HOLD:
822                return CALL_STATE_HELD;
823
824            case CallState.RINGING:
825                if (isForegroundCall) {
826                    return CALL_STATE_INCOMING;
827                } else {
828                    return CALL_STATE_WAITING;
829                }
830        }
831        return CALL_STATE_IDLE;
832    }
833
834    /**
835     * Returns the best phone account to use for the given state of all calls.
836     * First, tries to return the phone account for the foreground call, second the default
837     * phone account for PhoneAccount.SCHEME_TEL.
838     */
839    private PhoneAccount getBestPhoneAccount() {
840        if (mPhoneAccountRegistrar == null) {
841            return null;
842        }
843
844        Call call = mCallsManager.getForegroundCall();
845
846        PhoneAccount account = null;
847        if (call != null) {
848            // First try to get the network name of the foreground call.
849            account = mPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
850                    call.getTargetPhoneAccount());
851        }
852
853        if (account == null) {
854            // Second, Try to get the label for the default Phone Account.
855            account = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
856                    mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
857                            PhoneAccount.SCHEME_TEL));
858        }
859        return account;
860    }
861}
862