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