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