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