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