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