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