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