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