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