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