Home | History | Annotate | Download | only in imsphone
      1 /*
      2  * Copyright (C) 2013 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.internal.telephony.imsphone;
     18 
     19 import java.io.FileDescriptor;
     20 import java.io.PrintWriter;
     21 import java.util.ArrayList;
     22 import java.util.List;
     23 
     24 import android.app.PendingIntent;
     25 import android.content.BroadcastReceiver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.IntentFilter;
     29 import android.content.SharedPreferences;
     30 import android.os.AsyncResult;
     31 import android.os.Bundle;
     32 import android.os.Handler;
     33 import android.os.Message;
     34 import android.os.Registrant;
     35 import android.os.RegistrantList;
     36 import android.os.RemoteException;
     37 import android.os.SystemProperties;
     38 import android.provider.Settings;
     39 import android.preference.PreferenceManager;
     40 import android.telecom.ConferenceParticipant;
     41 import android.telecom.VideoProfile;
     42 import android.telephony.DisconnectCause;
     43 import android.telephony.PhoneNumberUtils;
     44 import android.telephony.Rlog;
     45 import android.telephony.ServiceState;
     46 
     47 import com.android.ims.ImsCall;
     48 import com.android.ims.ImsCallProfile;
     49 import com.android.ims.ImsConfig;
     50 import com.android.ims.ImsConnectionStateListener;
     51 import com.android.ims.ImsEcbm;
     52 import com.android.ims.ImsException;
     53 import com.android.ims.ImsManager;
     54 import com.android.ims.ImsReasonInfo;
     55 import com.android.ims.ImsServiceClass;
     56 import com.android.ims.ImsUtInterface;
     57 import com.android.ims.internal.IImsVideoCallProvider;
     58 import com.android.ims.internal.ImsVideoCallProviderWrapper;
     59 import com.android.internal.telephony.Call;
     60 import com.android.internal.telephony.CallStateException;
     61 import com.android.internal.telephony.CallTracker;
     62 import com.android.internal.telephony.CommandException;
     63 import com.android.internal.telephony.CommandsInterface;
     64 import com.android.internal.telephony.Connection;
     65 import com.android.internal.telephony.Phone;
     66 import com.android.internal.telephony.PhoneBase;
     67 import com.android.internal.telephony.PhoneConstants;
     68 import com.android.internal.telephony.TelephonyProperties;
     69 
     70 /**
     71  * {@hide}
     72  */
     73 public final class ImsPhoneCallTracker extends CallTracker {
     74     static final String LOG_TAG = "ImsPhoneCallTracker";
     75 
     76     private static final boolean DBG = true;
     77 
     78     // When true, dumps the state of ImsPhoneCallTracker after changes to foreground and background
     79     // calls.  This is helpful for debugging.
     80     private static final boolean VERBOSE_STATE_LOGGING = false; /* stopship if true */
     81 
     82     //Indices map to ImsConfig.FeatureConstants
     83     private boolean[] mImsFeatureEnabled = {false, false, false, false};
     84     private final String[] mImsFeatureStrings = {"VoLTE", "ViLTE", "VoWiFi", "ViWiFi"};
     85 
     86     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
     87         @Override
     88         public void onReceive(Context context, Intent intent) {
     89             if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) {
     90                 if (DBG) log("onReceive : incoming call intent");
     91 
     92                 if (mImsManager == null) return;
     93 
     94                 if (mServiceId < 0) return;
     95 
     96                 try {
     97                     // Network initiated USSD will be treated by mImsUssdListener
     98                     boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false);
     99                     if (isUssd) {
    100                         if (DBG) log("onReceive : USSD");
    101                         mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener);
    102                         if (mUssdSession != null) {
    103                             mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE);
    104                         }
    105                         return;
    106                     }
    107 
    108                     // Normal MT call
    109                     ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener);
    110                     ImsPhoneConnection conn = new ImsPhoneConnection(mPhone.getContext(), imsCall,
    111                             ImsPhoneCallTracker.this, mRingingCall);
    112                     addConnection(conn);
    113 
    114                     setVideoCallProvider(conn, imsCall);
    115 
    116                     if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) ||
    117                             (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) {
    118                         conn.update(imsCall, ImsPhoneCall.State.WAITING);
    119                     }
    120 
    121                     mPhone.notifyNewRingingConnection(conn);
    122                     mPhone.notifyIncomingRing();
    123 
    124                     updatePhoneState();
    125                     mPhone.notifyPreciseCallStateChanged();
    126                 } catch (ImsException e) {
    127                     loge("onReceive : exception " + e);
    128                 } catch (RemoteException e) {
    129                 }
    130             }
    131         }
    132     };
    133 
    134     //***** Constants
    135 
    136     static final int MAX_CONNECTIONS = 7;
    137     static final int MAX_CONNECTIONS_PER_CALL = 5;
    138 
    139     private static final int EVENT_HANGUP_PENDINGMO = 18;
    140     private static final int EVENT_RESUME_BACKGROUND = 19;
    141     private static final int EVENT_DIAL_PENDINGMO = 20;
    142 
    143     private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
    144 
    145     //***** Instance Variables
    146     private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>();
    147     private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
    148     private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
    149 
    150     final ImsPhoneCall mRingingCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_RINGING);
    151     final ImsPhoneCall mForegroundCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_FOREGROUND);
    152     final ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_BACKGROUND);
    153     final ImsPhoneCall mHandoverCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_HANDOVER);
    154 
    155     private ImsPhoneConnection mPendingMO;
    156     private int mClirMode = CommandsInterface.CLIR_DEFAULT;
    157     private Object mSyncHold = new Object();
    158 
    159     private ImsCall mUssdSession = null;
    160     private Message mPendingUssd = null;
    161 
    162     ImsPhone mPhone;
    163 
    164     private boolean mDesiredMute = false;    // false = mute off
    165     private boolean mOnHoldToneStarted = false;
    166 
    167     PhoneConstants.State mState = PhoneConstants.State.IDLE;
    168 
    169     private ImsManager mImsManager;
    170     private int mServiceId = -1;
    171 
    172     private Call.SrvccState mSrvccState = Call.SrvccState.NONE;
    173 
    174     private boolean mIsInEmergencyCall = false;
    175 
    176     private int pendingCallClirMode;
    177     private int mPendingCallVideoState;
    178     private boolean pendingCallInEcm = false;
    179     private boolean mSwitchingFgAndBgCalls = false;
    180     private ImsCall mCallExpectedToResume = null;
    181 
    182     //***** Events
    183 
    184 
    185     //***** Constructors
    186 
    187     ImsPhoneCallTracker(ImsPhone phone) {
    188         this.mPhone = phone;
    189 
    190         IntentFilter intentfilter = new IntentFilter();
    191         intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL);
    192         mPhone.getContext().registerReceiver(mReceiver, intentfilter);
    193 
    194         Thread t = new Thread() {
    195             public void run() {
    196                 getImsService();
    197             }
    198         };
    199         t.start();
    200     }
    201 
    202     private PendingIntent createIncomingCallPendingIntent() {
    203         Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
    204         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    205         return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
    206                 PendingIntent.FLAG_UPDATE_CURRENT);
    207     }
    208 
    209     private void getImsService() {
    210         if (DBG) log("getImsService");
    211         mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId());
    212         try {
    213             mServiceId = mImsManager.open(ImsServiceClass.MMTEL,
    214                     createIncomingCallPendingIntent(),
    215                     mImsConnectionStateListener);
    216 
    217             // Get the ECBM interface and set IMSPhone's listener object for notifications
    218             getEcbmInterface().setEcbmStateListener(mPhone.mImsEcbmStateListener);
    219             if (mPhone.isInEcm()) {
    220                 // Call exit ECBM which will invoke onECBMExited
    221                 mPhone.exitEmergencyCallbackMode();
    222             }
    223             int mPreferredTtyMode = Settings.Secure.getInt(
    224                 mPhone.getContext().getContentResolver(),
    225                 Settings.Secure.PREFERRED_TTY_MODE,
    226                 Phone.TTY_MODE_OFF);
    227            mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, mPreferredTtyMode, null);
    228 
    229         } catch (ImsException e) {
    230             loge("getImsService: " + e);
    231             //Leave mImsManager as null, then CallStateException will be thrown when dialing
    232             mImsManager = null;
    233         }
    234     }
    235 
    236     public void dispose() {
    237         if (DBG) log("dispose");
    238         mRingingCall.dispose();
    239         mBackgroundCall.dispose();
    240         mForegroundCall.dispose();
    241         mHandoverCall.dispose();
    242 
    243         clearDisconnected();
    244         mPhone.getContext().unregisterReceiver(mReceiver);
    245     }
    246 
    247     @Override
    248     protected void finalize() {
    249         log("ImsPhoneCallTracker finalized");
    250     }
    251 
    252     //***** Instance Methods
    253 
    254     //***** Public Methods
    255     @Override
    256     public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
    257         Registrant r = new Registrant(h, what, obj);
    258         mVoiceCallStartedRegistrants.add(r);
    259     }
    260 
    261     @Override
    262     public void unregisterForVoiceCallStarted(Handler h) {
    263         mVoiceCallStartedRegistrants.remove(h);
    264     }
    265 
    266     @Override
    267     public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
    268         Registrant r = new Registrant(h, what, obj);
    269         mVoiceCallEndedRegistrants.add(r);
    270     }
    271 
    272     @Override
    273     public void unregisterForVoiceCallEnded(Handler h) {
    274         mVoiceCallEndedRegistrants.remove(h);
    275     }
    276 
    277     Connection
    278     dial(String dialString, int videoState, Bundle intentExtras) throws CallStateException {
    279         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
    280         int oirMode = sp.getInt(PhoneBase.CLIR_KEY, CommandsInterface.CLIR_DEFAULT);
    281         return dial(dialString, oirMode, videoState, intentExtras);
    282     }
    283 
    284     /**
    285      * oirMode is one of the CLIR_ constants
    286      */
    287     synchronized Connection
    288     dial(String dialString, int clirMode, int videoState, Bundle intentExtras)
    289             throws CallStateException {
    290         boolean isPhoneInEcmMode = SystemProperties.getBoolean(
    291                 TelephonyProperties.PROPERTY_INECM_MODE, false);
    292         boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString);
    293 
    294         if (DBG) log("dial clirMode=" + clirMode);
    295 
    296         // note that this triggers call state changed notif
    297         clearDisconnected();
    298 
    299         if (mImsManager == null) {
    300             throw new CallStateException("service not available");
    301         }
    302 
    303         if (!canDial()) {
    304             throw new CallStateException("cannot dial in current state");
    305         }
    306 
    307         if (isPhoneInEcmMode && isEmergencyNumber) {
    308             handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER);
    309         }
    310 
    311         boolean holdBeforeDial = false;
    312 
    313         // The new call must be assigned to the foreground call.
    314         // That call must be idle, so place anything that's
    315         // there on hold
    316         if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
    317             if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
    318                 //we should have failed in !canDial() above before we get here
    319                 throw new CallStateException("cannot dial in current state");
    320             }
    321             // foreground call is empty for the newly dialed connection
    322             holdBeforeDial = true;
    323             // Cache the video state for pending MO call.
    324             mPendingCallVideoState = videoState;
    325             switchWaitingOrHoldingAndActive();
    326         }
    327 
    328         ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
    329         ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
    330 
    331         mClirMode = clirMode;
    332 
    333         synchronized (mSyncHold) {
    334             if (holdBeforeDial) {
    335                 fgState = mForegroundCall.getState();
    336                 bgState = mBackgroundCall.getState();
    337 
    338                 //holding foreground call failed
    339                 if (fgState == ImsPhoneCall.State.ACTIVE) {
    340                     throw new CallStateException("cannot dial in current state");
    341                 }
    342 
    343                 //holding foreground call succeeded
    344                 if (bgState == ImsPhoneCall.State.HOLDING) {
    345                     holdBeforeDial = false;
    346                 }
    347             }
    348 
    349             mPendingMO = new ImsPhoneConnection(mPhone.getContext(),
    350                     checkForTestEmergencyNumber(dialString), this, mForegroundCall);
    351         }
    352         addConnection(mPendingMO);
    353 
    354         if (!holdBeforeDial) {
    355             if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
    356                 dialInternal(mPendingMO, clirMode, videoState);
    357             } else {
    358                 try {
    359                     getEcbmInterface().exitEmergencyCallbackMode();
    360                 } catch (ImsException e) {
    361                     e.printStackTrace();
    362                     throw new CallStateException("service not available");
    363                 }
    364                 mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
    365                 pendingCallClirMode = clirMode;
    366                 mPendingCallVideoState = videoState;
    367                 pendingCallInEcm = true;
    368             }
    369         }
    370 
    371         updatePhoneState();
    372         mPhone.notifyPreciseCallStateChanged();
    373 
    374         return mPendingMO;
    375     }
    376 
    377     private void handleEcmTimer(int action) {
    378         mPhone.handleTimerInEmergencyCallbackMode(action);
    379         switch (action) {
    380             case ImsPhone.CANCEL_ECM_TIMER:
    381                 break;
    382             case ImsPhone.RESTART_ECM_TIMER:
    383                 break;
    384             default:
    385                 log("handleEcmTimer, unsupported action " + action);
    386         }
    387     }
    388 
    389     private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState) {
    390         if (conn == null) {
    391             return;
    392         }
    393 
    394         if (conn.getAddress()== null || conn.getAddress().length() == 0
    395                 || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
    396             // Phone number is invalid
    397             conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER);
    398             sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
    399             return;
    400         }
    401 
    402         // Always unmute when initiating a new call
    403         setMute(false);
    404         int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ?
    405                 ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
    406         int callType = ImsCallProfile.getCallTypeFromVideoState(videoState);
    407         //TODO(vt): Is this sufficient?  At what point do we know the video state of the call?
    408         conn.setVideoState(videoState);
    409 
    410         try {
    411             String[] callees = new String[] { conn.getAddress() };
    412             ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
    413                     serviceType, callType);
    414             profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
    415 
    416             ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
    417                     callees, mImsCallListener);
    418             conn.setImsCall(imsCall);
    419 
    420             setVideoCallProvider(conn, imsCall);
    421         } catch (ImsException e) {
    422             loge("dialInternal : " + e);
    423             conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
    424             sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
    425         } catch (RemoteException e) {
    426         }
    427     }
    428 
    429     /**
    430      * Accepts a call with the specified video state.  The video state is the video state that the
    431      * user has agreed upon in the InCall UI.
    432      *
    433      * @param videoState The video State
    434      * @throws CallStateException
    435      */
    436     void acceptCall (int videoState) throws CallStateException {
    437         if (DBG) log("acceptCall");
    438 
    439         if (mForegroundCall.getState().isAlive()
    440                 && mBackgroundCall.getState().isAlive()) {
    441             throw new CallStateException("cannot accept call");
    442         }
    443 
    444         if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING)
    445                 && mForegroundCall.getState().isAlive()) {
    446             setMute(false);
    447             // Cache video state for pending MT call.
    448             mPendingCallVideoState = videoState;
    449             switchWaitingOrHoldingAndActive();
    450         } else if (mRingingCall.getState().isRinging()) {
    451             if (DBG) log("acceptCall: incoming...");
    452             // Always unmute when answering a new call
    453             setMute(false);
    454             try {
    455                 ImsCall imsCall = mRingingCall.getImsCall();
    456                 if (imsCall != null) {
    457                     imsCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
    458                 } else {
    459                     throw new CallStateException("no valid ims call");
    460                 }
    461             } catch (ImsException e) {
    462                 throw new CallStateException("cannot accept call");
    463             }
    464         } else {
    465             throw new CallStateException("phone not ringing");
    466         }
    467     }
    468 
    469     void
    470     rejectCall () throws CallStateException {
    471         if (DBG) log("rejectCall");
    472 
    473         if (mRingingCall.getState().isRinging()) {
    474             hangup(mRingingCall);
    475         } else {
    476             throw new CallStateException("phone not ringing");
    477         }
    478     }
    479 
    480 
    481     private void switchAfterConferenceSuccess() {
    482         if (DBG) log("switchAfterConferenceSuccess fg =" + mForegroundCall.getState() +
    483                 ", bg = " + mBackgroundCall.getState());
    484 
    485         if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
    486             log("switchAfterConferenceSuccess");
    487             mForegroundCall.switchWith(mBackgroundCall);
    488         }
    489     }
    490 
    491     void
    492     switchWaitingOrHoldingAndActive() throws CallStateException {
    493         if (DBG) log("switchWaitingOrHoldingAndActive");
    494 
    495         if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
    496             throw new CallStateException("cannot be in the incoming state");
    497         }
    498 
    499         if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
    500             ImsCall imsCall = mForegroundCall.getImsCall();
    501             if (imsCall == null) {
    502                 throw new CallStateException("no ims call");
    503             }
    504 
    505             // Swap the ImsCalls pointed to by the foreground and background ImsPhoneCalls.
    506             // If hold or resume later fails, we will swap them back.
    507             mSwitchingFgAndBgCalls = true;
    508             mCallExpectedToResume = mBackgroundCall.getImsCall();
    509             mForegroundCall.switchWith(mBackgroundCall);
    510 
    511             // Hold the foreground call; once the foreground call is held, the background call will
    512             // be resumed.
    513             try {
    514                 imsCall.hold();
    515 
    516                 // If there is no background call to resume, then don't expect there to be a switch.
    517                 if (mCallExpectedToResume == null) {
    518                     mSwitchingFgAndBgCalls = false;
    519                 }
    520             } catch (ImsException e) {
    521                 mForegroundCall.switchWith(mBackgroundCall);
    522                 throw new CallStateException(e.getMessage());
    523             }
    524         } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
    525             resumeWaitingOrHolding();
    526         }
    527     }
    528 
    529     void
    530     conference() {
    531         if (DBG) log("conference");
    532 
    533         ImsCall fgImsCall = mForegroundCall.getImsCall();
    534         if (fgImsCall == null) {
    535             log("conference no foreground ims call");
    536             return;
    537         }
    538 
    539         ImsCall bgImsCall = mBackgroundCall.getImsCall();
    540         if (bgImsCall == null) {
    541             log("conference no background ims call");
    542             return;
    543         }
    544 
    545         // Keep track of the connect time of the earliest call so that it can be set on the
    546         // {@code ImsConference} when it is created.
    547         long foregroundConnectTime = mForegroundCall.getEarliestConnectTime();
    548         long backgroundConnectTime = mBackgroundCall.getEarliestConnectTime();
    549         long conferenceConnectTime;
    550         if (foregroundConnectTime > 0 && backgroundConnectTime > 0) {
    551             conferenceConnectTime = Math.min(mForegroundCall.getEarliestConnectTime(),
    552                     mBackgroundCall.getEarliestConnectTime());
    553             log("conference - using connect time = " + conferenceConnectTime);
    554         } else if (foregroundConnectTime > 0) {
    555             log("conference - bg call connect time is 0; using fg = " + foregroundConnectTime);
    556             conferenceConnectTime = foregroundConnectTime;
    557         } else {
    558             log("conference - fg call connect time is 0; using bg = " + backgroundConnectTime);
    559             conferenceConnectTime = backgroundConnectTime;
    560         }
    561 
    562         ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection();
    563         if (foregroundConnection != null) {
    564             foregroundConnection.setConferenceConnectTime(conferenceConnectTime);
    565         }
    566 
    567         try {
    568             fgImsCall.merge(bgImsCall);
    569         } catch (ImsException e) {
    570             log("conference " + e.getMessage());
    571         }
    572     }
    573 
    574     void
    575     explicitCallTransfer() {
    576         //TODO : implement
    577     }
    578 
    579     void
    580     clearDisconnected() {
    581         if (DBG) log("clearDisconnected");
    582 
    583         internalClearDisconnected();
    584 
    585         updatePhoneState();
    586         mPhone.notifyPreciseCallStateChanged();
    587     }
    588 
    589     boolean
    590     canConference() {
    591         return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
    592             && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
    593             && !mBackgroundCall.isFull()
    594             && !mForegroundCall.isFull();
    595     }
    596 
    597     boolean
    598     canDial() {
    599         boolean ret;
    600         int serviceState = mPhone.getServiceState().getState();
    601         String disableCall = SystemProperties.get(
    602                 TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
    603 
    604         ret = (serviceState != ServiceState.STATE_POWER_OFF)
    605             && mPendingMO == null
    606             && !mRingingCall.isRinging()
    607             && !disableCall.equals("true")
    608             && (!mForegroundCall.getState().isAlive()
    609                     || !mBackgroundCall.getState().isAlive());
    610 
    611         return ret;
    612     }
    613 
    614     boolean
    615     canTransfer() {
    616         return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
    617             && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
    618     }
    619 
    620     //***** Private Instance Methods
    621 
    622     private void
    623     internalClearDisconnected() {
    624         mRingingCall.clearDisconnected();
    625         mForegroundCall.clearDisconnected();
    626         mBackgroundCall.clearDisconnected();
    627         mHandoverCall.clearDisconnected();
    628     }
    629 
    630     private void
    631     updatePhoneState() {
    632         PhoneConstants.State oldState = mState;
    633 
    634         if (mRingingCall.isRinging()) {
    635             mState = PhoneConstants.State.RINGING;
    636         } else if (mPendingMO != null ||
    637                 !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
    638             mState = PhoneConstants.State.OFFHOOK;
    639         } else {
    640             mState = PhoneConstants.State.IDLE;
    641         }
    642 
    643         if (mState == PhoneConstants.State.IDLE && oldState != mState) {
    644             mVoiceCallEndedRegistrants.notifyRegistrants(
    645                     new AsyncResult(null, null, null));
    646         } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
    647             mVoiceCallStartedRegistrants.notifyRegistrants (
    648                     new AsyncResult(null, null, null));
    649         }
    650 
    651         if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
    652 
    653         if (mState != oldState) {
    654             mPhone.notifyPhoneStateChanged();
    655         }
    656     }
    657 
    658     private void
    659     handleRadioNotAvailable() {
    660         // handlePollCalls will clear out its
    661         // call list when it gets the CommandException
    662         // error result from this
    663         pollCallsWhenSafe();
    664     }
    665 
    666     private void
    667     dumpState() {
    668         List l;
    669 
    670         log("Phone State:" + mState);
    671 
    672         log("Ringing call: " + mRingingCall.toString());
    673 
    674         l = mRingingCall.getConnections();
    675         for (int i = 0, s = l.size(); i < s; i++) {
    676             log(l.get(i).toString());
    677         }
    678 
    679         log("Foreground call: " + mForegroundCall.toString());
    680 
    681         l = mForegroundCall.getConnections();
    682         for (int i = 0, s = l.size(); i < s; i++) {
    683             log(l.get(i).toString());
    684         }
    685 
    686         log("Background call: " + mBackgroundCall.toString());
    687 
    688         l = mBackgroundCall.getConnections();
    689         for (int i = 0, s = l.size(); i < s; i++) {
    690             log(l.get(i).toString());
    691         }
    692 
    693     }
    694 
    695     //***** Called from ImsPhone
    696 
    697     void setUiTTYMode(int uiTtyMode, Message onComplete) {
    698         try {
    699             mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, uiTtyMode, onComplete);
    700         } catch (ImsException e) {
    701             loge("setTTYMode : " + e);
    702             mPhone.sendErrorResponse(onComplete, e);
    703         }
    704     }
    705 
    706     /*package*/ void setMute(boolean mute) {
    707         mDesiredMute = mute;
    708         mForegroundCall.setMute(mute);
    709     }
    710 
    711     /*package*/ boolean getMute() {
    712         return mDesiredMute;
    713     }
    714 
    715     /* package */ void sendDtmf(char c, Message result) {
    716         if (DBG) log("sendDtmf");
    717 
    718         ImsCall imscall = mForegroundCall.getImsCall();
    719         if (imscall != null) {
    720             imscall.sendDtmf(c, result);
    721         }
    722     }
    723 
    724     /*package*/ void
    725     startDtmf(char c) {
    726         if (DBG) log("startDtmf");
    727 
    728         ImsCall imscall = mForegroundCall.getImsCall();
    729         if (imscall != null) {
    730             imscall.startDtmf(c);
    731         } else {
    732             loge("startDtmf : no foreground call");
    733         }
    734     }
    735 
    736     /*package*/ void
    737     stopDtmf() {
    738         if (DBG) log("stopDtmf");
    739 
    740         ImsCall imscall = mForegroundCall.getImsCall();
    741         if (imscall != null) {
    742             imscall.stopDtmf();
    743         } else {
    744             loge("stopDtmf : no foreground call");
    745         }
    746     }
    747 
    748     //***** Called from ImsPhoneConnection
    749 
    750     /*package*/ void
    751     hangup (ImsPhoneConnection conn) throws CallStateException {
    752         if (DBG) log("hangup connection");
    753 
    754         if (conn.getOwner() != this) {
    755             throw new CallStateException ("ImsPhoneConnection " + conn
    756                     + "does not belong to ImsPhoneCallTracker " + this);
    757         }
    758 
    759         hangup(conn.getCall());
    760     }
    761 
    762     //***** Called from ImsPhoneCall
    763 
    764     /* package */ void
    765     hangup (ImsPhoneCall call) throws CallStateException {
    766         if (DBG) log("hangup call");
    767 
    768         if (call.getConnections().size() == 0) {
    769             throw new CallStateException("no connections");
    770         }
    771 
    772         ImsCall imsCall = call.getImsCall();
    773         boolean rejectCall = false;
    774 
    775         if (call == mRingingCall) {
    776             if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
    777             rejectCall = true;
    778         } else if (call == mForegroundCall) {
    779             if (call.isDialingOrAlerting()) {
    780                 if (Phone.DEBUG_PHONE) {
    781                     log("(foregnd) hangup dialing or alerting...");
    782                 }
    783             } else {
    784                 if (Phone.DEBUG_PHONE) {
    785                     log("(foregnd) hangup foreground");
    786                 }
    787                 //held call will be resumed by onCallTerminated
    788             }
    789         } else if (call == mBackgroundCall) {
    790             if (Phone.DEBUG_PHONE) {
    791                 log("(backgnd) hangup waiting or background");
    792             }
    793         } else {
    794             throw new CallStateException ("ImsPhoneCall " + call +
    795                     "does not belong to ImsPhoneCallTracker " + this);
    796         }
    797 
    798         call.onHangupLocal();
    799 
    800         try {
    801             if (imsCall != null) {
    802                 if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
    803                 else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
    804             } else if (mPendingMO != null && call == mForegroundCall) {
    805                 // is holding a foreground call
    806                 mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
    807                 mPendingMO.onDisconnect();
    808                 removeConnection(mPendingMO);
    809                 mPendingMO = null;
    810                 updatePhoneState();
    811                 removeMessages(EVENT_DIAL_PENDINGMO);
    812             }
    813         } catch (ImsException e) {
    814             throw new CallStateException(e.getMessage());
    815         }
    816 
    817         mPhone.notifyPreciseCallStateChanged();
    818     }
    819 
    820     void callEndCleanupHandOverCallIfAny() {
    821         if (mHandoverCall.mConnections.size() > 0) {
    822             if (DBG) log("callEndCleanupHandOverCallIfAny, mHandoverCall.mConnections="
    823                     + mHandoverCall.mConnections);
    824             mHandoverCall.mConnections.clear();
    825             mState = PhoneConstants.State.IDLE;
    826         }
    827     }
    828 
    829     /* package */
    830     void resumeWaitingOrHolding() throws CallStateException {
    831         if (DBG) log("resumeWaitingOrHolding");
    832 
    833         try {
    834             if (mForegroundCall.getState().isAlive()) {
    835                 //resume foreground call after holding background call
    836                 //they were switched before holding
    837                 ImsCall imsCall = mForegroundCall.getImsCall();
    838                 if (imsCall != null) imsCall.resume();
    839             } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
    840                 //accept waiting call after holding background call
    841                 ImsCall imsCall = mRingingCall.getImsCall();
    842                 if (imsCall != null) {
    843                     imsCall.accept(
    844                         ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState));
    845                 }
    846             } else {
    847                 //Just resume background call.
    848                 //To distinguish resuming call with swapping calls
    849                 //we do not switch calls.here
    850                 //ImsPhoneConnection.update will chnage the parent when completed
    851                 ImsCall imsCall = mBackgroundCall.getImsCall();
    852                 if (imsCall != null) imsCall.resume();
    853             }
    854         } catch (ImsException e) {
    855             throw new CallStateException(e.getMessage());
    856         }
    857     }
    858 
    859     /* package */
    860     void sendUSSD (String ussdString, Message response) {
    861         if (DBG) log("sendUSSD");
    862 
    863         try {
    864             if (mUssdSession != null) {
    865                 mUssdSession.sendUssd(ussdString);
    866                 AsyncResult.forMessage(response, null, null);
    867                 response.sendToTarget();
    868                 return;
    869             }
    870 
    871             String[] callees = new String[] { ussdString };
    872             ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
    873                     ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
    874             profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
    875                     ImsCallProfile.DIALSTRING_USSD);
    876 
    877             mUssdSession = mImsManager.makeCall(mServiceId, profile,
    878                     callees, mImsUssdListener);
    879         } catch (ImsException e) {
    880             loge("sendUSSD : " + e);
    881             mPhone.sendErrorResponse(response, e);
    882         }
    883     }
    884 
    885     /* package */
    886     void cancelUSSD() {
    887         if (mUssdSession == null) return;
    888 
    889         try {
    890             mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
    891         } catch (ImsException e) {
    892         }
    893 
    894     }
    895 
    896     private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) {
    897         for (ImsPhoneConnection conn : mConnections) {
    898             if (conn.getImsCall() == imsCall) {
    899                 return conn;
    900             }
    901         }
    902         return null;
    903     }
    904 
    905     private synchronized void removeConnection(ImsPhoneConnection conn) {
    906         mConnections.remove(conn);
    907     }
    908 
    909     private synchronized void addConnection(ImsPhoneConnection conn) {
    910         mConnections.add(conn);
    911     }
    912 
    913     private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
    914         if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
    915         // This method is called on onCallUpdate() where there is not necessarily a call state
    916         // change. In these situations, we'll ignore the state related updates and only process
    917         // the change in media capabilities (as expected).  The default is to not ignore state
    918         // changes so we do not change existing behavior.
    919         processCallStateChange(imsCall, state, cause, false /* do not ignore state update */);
    920     }
    921 
    922     private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause,
    923             boolean ignoreState) {
    924         if (DBG) {
    925             log("processCallStateChange state=" + state + " cause=" + cause
    926                     + " ignoreState=" + ignoreState);
    927         }
    928 
    929         if (imsCall == null) return;
    930 
    931         boolean changed = false;
    932         ImsPhoneConnection conn = findConnection(imsCall);
    933 
    934         if (conn == null) {
    935             // TODO : what should be done?
    936             return;
    937         }
    938 
    939         // processCallStateChange is triggered for onCallUpdated as well.
    940         // onCallUpdated should not modify the state of the call
    941         // It should modify only other capabilities of call through updateMediaCapabilities
    942         // State updates will be triggered through individual callbacks
    943         // i.e. onCallHeld, onCallResume, etc and conn.update will be responsible for the update
    944         if (ignoreState) {
    945             conn.updateMediaCapabilities(imsCall);
    946             return;
    947         }
    948 
    949         changed = conn.update(imsCall, state);
    950         if (state == ImsPhoneCall.State.DISCONNECTED) {
    951             changed = conn.onDisconnect(cause) || changed;
    952             //detach the disconnected connections
    953             conn.getCall().detach(conn);
    954             removeConnection(conn);
    955         }
    956 
    957         if (changed) {
    958             if (conn.getCall() == mHandoverCall) return;
    959             updatePhoneState();
    960             mPhone.notifyPreciseCallStateChanged();
    961         }
    962     }
    963 
    964     private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
    965         int cause = DisconnectCause.ERROR_UNSPECIFIED;
    966 
    967         //int type = reasonInfo.getReasonType();
    968         int code = reasonInfo.getCode();
    969         switch (code) {
    970             case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
    971             case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
    972                 return DisconnectCause.NUMBER_UNREACHABLE;
    973 
    974             case ImsReasonInfo.CODE_SIP_BUSY:
    975                 return DisconnectCause.BUSY;
    976 
    977             case ImsReasonInfo.CODE_USER_TERMINATED:
    978                 return DisconnectCause.LOCAL;
    979 
    980             case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
    981                 return DisconnectCause.INCOMING_REJECTED;
    982 
    983             case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
    984                 return DisconnectCause.NORMAL;
    985 
    986             case ImsReasonInfo.CODE_SIP_REDIRECTED:
    987             case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
    988             case ImsReasonInfo.CODE_SIP_FORBIDDEN:
    989             case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
    990             case ImsReasonInfo.CODE_SIP_USER_REJECTED:
    991             case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
    992                 return DisconnectCause.SERVER_ERROR;
    993 
    994             case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
    995             case ImsReasonInfo.CODE_SIP_NOT_FOUND:
    996             case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
    997                 return DisconnectCause.SERVER_UNREACHABLE;
    998 
    999             case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
   1000             case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
   1001             case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
   1002             case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
   1003             case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
   1004             case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
   1005             case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
   1006             case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
   1007                 return DisconnectCause.OUT_OF_SERVICE;
   1008 
   1009             case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
   1010             case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
   1011             case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
   1012             case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
   1013                 return DisconnectCause.TIMED_OUT;
   1014 
   1015             case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
   1016             case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
   1017                 return DisconnectCause.POWER_OFF;
   1018 
   1019             default:
   1020         }
   1021 
   1022         return cause;
   1023     }
   1024 
   1025     /**
   1026      * Listen to the IMS call state change
   1027      */
   1028     private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
   1029         @Override
   1030         public void onCallProgressing(ImsCall imsCall) {
   1031             if (DBG) log("onCallProgressing");
   1032 
   1033             mPendingMO = null;
   1034             processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
   1035                     DisconnectCause.NOT_DISCONNECTED);
   1036         }
   1037 
   1038         @Override
   1039         public void onCallStarted(ImsCall imsCall) {
   1040             if (DBG) log("onCallStarted");
   1041 
   1042             mPendingMO = null;
   1043             processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
   1044                     DisconnectCause.NOT_DISCONNECTED);
   1045         }
   1046 
   1047         @Override
   1048         public void onCallUpdated(ImsCall imsCall) {
   1049             if (DBG) log("onCallUpdated");
   1050             if (imsCall == null) {
   1051                 return;
   1052             }
   1053             ImsPhoneConnection conn = findConnection(imsCall);
   1054             if (conn != null) {
   1055                 processCallStateChange(imsCall, conn.getCall().mState,
   1056                         DisconnectCause.NOT_DISCONNECTED, true /*ignore state update*/);
   1057             }
   1058         }
   1059 
   1060         /**
   1061          * onCallStartFailed will be invoked when:
   1062          * case 1) Dialing fails
   1063          * case 2) Ringing call is disconnected by local or remote user
   1064          */
   1065         @Override
   1066         public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
   1067             if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
   1068 
   1069             if (mPendingMO != null) {
   1070                 // To initiate dialing circuit-switched call
   1071                 if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
   1072                         && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
   1073                         && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
   1074                     mForegroundCall.detach(mPendingMO);
   1075                     removeConnection(mPendingMO);
   1076                     mPendingMO.finalize();
   1077                     mPendingMO = null;
   1078                     mPhone.initiateSilentRedial();
   1079                     return;
   1080                 } else {
   1081                     int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
   1082                     processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
   1083                 }
   1084                 mPendingMO = null;
   1085             }
   1086         }
   1087 
   1088         @Override
   1089         public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
   1090             if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
   1091 
   1092             ImsPhoneCall.State oldState = mForegroundCall.getState();
   1093             int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
   1094             ImsPhoneConnection conn = findConnection(imsCall);
   1095             if (DBG) log("cause = " + cause + " conn = " + conn);
   1096 
   1097             if (conn != null && conn.isIncoming() && conn.getConnectTime() == 0) {
   1098                 // Missed
   1099                 if (cause == DisconnectCause.NORMAL) {
   1100                     cause = DisconnectCause.INCOMING_MISSED;
   1101                 }
   1102                 if (DBG) log("Incoming connection of 0 connect time detected - translated cause = "
   1103                         + cause);
   1104 
   1105             }
   1106 
   1107             if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) {
   1108                 // Call was terminated while it is merged instead of a remote disconnect.
   1109                 cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY;
   1110             }
   1111 
   1112             processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
   1113         }
   1114 
   1115         @Override
   1116         public void onCallHeld(ImsCall imsCall) {
   1117             if (DBG) {
   1118                 if (mForegroundCall.getImsCall() == imsCall) {
   1119                     log("onCallHeld (fg) " + imsCall);
   1120                 } else if (mBackgroundCall.getImsCall() == imsCall) {
   1121                     log("onCallHeld (bg) " + imsCall);
   1122                 }
   1123             }
   1124 
   1125             synchronized (mSyncHold) {
   1126                 ImsPhoneCall.State oldState = mBackgroundCall.getState();
   1127                 processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
   1128                         DisconnectCause.NOT_DISCONNECTED);
   1129 
   1130                 // Note: If we're performing a switchWaitingOrHoldingAndActive, the call to
   1131                 // processCallStateChange above may have caused the mBackgroundCall and
   1132                 // mForegroundCall references below to change meaning.  Watch out for this if you
   1133                 // are reading through this code.
   1134                 if (oldState == ImsPhoneCall.State.ACTIVE) {
   1135                     // Note: This case comes up when we have just held a call in response to a
   1136                     // switchWaitingOrHoldingAndActive.  We now need to resume the background call.
   1137                     // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called.
   1138                     if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
   1139                             || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
   1140 
   1141                             sendEmptyMessage(EVENT_RESUME_BACKGROUND);
   1142                     } else {
   1143                         //when multiple connections belong to background call,
   1144                         //only the first callback reaches here
   1145                         //otherwise the oldState is already HOLDING
   1146                         if (mPendingMO != null) {
   1147                             sendEmptyMessage(EVENT_DIAL_PENDINGMO);
   1148                         }
   1149 
   1150                         // In this case there will be no call resumed, so we can assume that we
   1151                         // are done switching fg and bg calls now.
   1152                         // This may happen if there is no BG call and we are holding a call so that
   1153                         // we can dial another one.
   1154                         mSwitchingFgAndBgCalls = false;
   1155                     }
   1156                 }
   1157             }
   1158         }
   1159 
   1160         @Override
   1161         public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
   1162             if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
   1163 
   1164             synchronized (mSyncHold) {
   1165                 ImsPhoneCall.State bgState = mBackgroundCall.getState();
   1166                 if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
   1167                     // disconnected while processing hold
   1168                     if (mPendingMO != null) {
   1169                         sendEmptyMessage(EVENT_DIAL_PENDINGMO);
   1170                     }
   1171                 } else if (bgState == ImsPhoneCall.State.ACTIVE) {
   1172                     mForegroundCall.switchWith(mBackgroundCall);
   1173 
   1174                     if (mPendingMO != null) {
   1175                         mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
   1176                         sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
   1177                     }
   1178                 }
   1179             }
   1180         }
   1181 
   1182         @Override
   1183         public void onCallResumed(ImsCall imsCall) {
   1184             if (DBG) log("onCallResumed");
   1185 
   1186             // If we are the in midst of swapping FG and BG calls and the call we end up resuming
   1187             // is not the one we expected, we likely had a resume failure and we need to swap the
   1188             // FG and BG calls back.
   1189             if (mSwitchingFgAndBgCalls && imsCall != mCallExpectedToResume) {
   1190                 if (DBG) {
   1191                     log("onCallResumed : switching " + mForegroundCall + " with "
   1192                             + mBackgroundCall);
   1193                 }
   1194                 mForegroundCall.switchWith(mBackgroundCall);
   1195                 mSwitchingFgAndBgCalls = false;
   1196                 mCallExpectedToResume = null;
   1197             }
   1198             processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
   1199                     DisconnectCause.NOT_DISCONNECTED);
   1200         }
   1201 
   1202         @Override
   1203         public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
   1204             // If we are in the midst of swapping the FG and BG calls and we got a resume fail, we
   1205             // need to swap back the FG and BG calls.
   1206             if (mSwitchingFgAndBgCalls && imsCall == mCallExpectedToResume) {
   1207                 if (DBG) {
   1208                     log("onCallResumeFailed : switching " + mForegroundCall + " with "
   1209                             + mBackgroundCall);
   1210                 }
   1211                 mForegroundCall.switchWith(mBackgroundCall);
   1212                 mCallExpectedToResume = null;
   1213                 mSwitchingFgAndBgCalls = false;
   1214             }
   1215             mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME);
   1216         }
   1217 
   1218         @Override
   1219         public void onCallResumeReceived(ImsCall imsCall) {
   1220             if (DBG) log("onCallResumeReceived");
   1221 
   1222             if (mOnHoldToneStarted) {
   1223                 mPhone.stopOnHoldTone();
   1224                 mOnHoldToneStarted = false;
   1225             }
   1226         }
   1227 
   1228         @Override
   1229         public void onCallHoldReceived(ImsCall imsCall) {
   1230             if (DBG) log("onCallHoldReceived");
   1231 
   1232             ImsPhoneConnection conn = findConnection(imsCall);
   1233             if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
   1234                 if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
   1235                     mPhone.startOnHoldTone();
   1236                     mOnHoldToneStarted = true;
   1237                 }
   1238             }
   1239         }
   1240 
   1241         @Override
   1242         public void onCallMerged(final ImsCall call, final ImsCall peerCall, boolean swapCalls) {
   1243             if (DBG) log("onCallMerged");
   1244 
   1245             ImsPhoneCall foregroundImsPhoneCall = findConnection(call).getCall();
   1246             ImsPhoneConnection peerConnection = findConnection(peerCall);
   1247             ImsPhoneCall peerImsPhoneCall = peerConnection == null ? null
   1248                     : peerConnection.getCall();
   1249 
   1250             if (swapCalls) {
   1251                 switchAfterConferenceSuccess();
   1252             }
   1253             foregroundImsPhoneCall.merge(peerImsPhoneCall, ImsPhoneCall.State.ACTIVE);
   1254 
   1255             // TODO Temporary code. Remove the try-catch block from the runnable once thread
   1256             // synchronization is fixed.
   1257             Runnable r = new Runnable() {
   1258                 @Override
   1259                 public void run() {
   1260                     try {
   1261                         final ImsPhoneConnection conn = findConnection(call);
   1262                         log("onCallMerged: ImsPhoneConnection=" + conn);
   1263                         log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
   1264                         setVideoCallProvider(conn, call);
   1265                         log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
   1266                     } catch (Exception e) {
   1267                         loge("onCallMerged: exception " + e);
   1268                     }
   1269                 }
   1270             };
   1271 
   1272             ImsPhoneCallTracker.this.post(r);
   1273 
   1274             // After merge complete, update foreground as Active
   1275             // and background call as Held, if background call exists
   1276             processCallStateChange(mForegroundCall.getImsCall(), ImsPhoneCall.State.ACTIVE,
   1277                     DisconnectCause.NOT_DISCONNECTED);
   1278             if (peerConnection != null) {
   1279                 processCallStateChange(mBackgroundCall.getImsCall(), ImsPhoneCall.State.HOLDING,
   1280                     DisconnectCause.NOT_DISCONNECTED);
   1281             }
   1282 
   1283             // Check if the merge was requested by an existing conference call. In that
   1284             // case, no further action is required.
   1285             if (!call.isMergeRequestedByConf()) {
   1286                 log("onCallMerged :: calling onMultipartyStateChanged()");
   1287                 onMultipartyStateChanged(call, true);
   1288             } else {
   1289                 log("onCallMerged :: Merge requested by existing conference.");
   1290                 // Reset the flag.
   1291                 call.resetIsMergeRequestedByConf(false);
   1292             }
   1293             logState();
   1294         }
   1295 
   1296         @Override
   1297         public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
   1298             if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo);
   1299 
   1300             // TODO: the call to notifySuppServiceFailed throws up the "merge failed" dialog
   1301             // We should move this into the InCallService so that it is handled appropriately
   1302             // based on the user facing UI.
   1303             mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
   1304 
   1305             // Start plumbing this even through Telecom so other components can take
   1306             // appropriate action.
   1307             ImsPhoneConnection conn = findConnection(call);
   1308             if (conn != null) {
   1309                 conn.onConferenceMergeFailed();
   1310             }
   1311         }
   1312 
   1313         /**
   1314          * Called when the state of IMS conference participant(s) has changed.
   1315          *
   1316          * @param call the call object that carries out the IMS call.
   1317          * @param participants the participant(s) and their new state information.
   1318          */
   1319         @Override
   1320         public void onConferenceParticipantsStateChanged(ImsCall call,
   1321                 List<ConferenceParticipant> participants) {
   1322             if (DBG) log("onConferenceParticipantsStateChanged");
   1323 
   1324             ImsPhoneConnection conn = findConnection(call);
   1325             if (conn != null) {
   1326                 conn.updateConferenceParticipants(participants);
   1327             }
   1328         }
   1329 
   1330         @Override
   1331         public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
   1332             mPhone.onTtyModeReceived(mode);
   1333         }
   1334 
   1335         @Override
   1336         public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
   1337             ImsReasonInfo reasonInfo) {
   1338             if (DBG) {
   1339                 log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
   1340                     targetAccessTech + ", reasonInfo=" + reasonInfo);
   1341             }
   1342         }
   1343 
   1344         @Override
   1345         public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
   1346             ImsReasonInfo reasonInfo) {
   1347             if (DBG) {
   1348                 log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech +
   1349                     ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo);
   1350             }
   1351         }
   1352 
   1353         /**
   1354          * Handles a change to the multiparty state for an {@code ImsCall}.  Notifies the associated
   1355          * {@link ImsPhoneConnection} of the change.
   1356          *
   1357          * @param imsCall The IMS call.
   1358          * @param isMultiParty {@code true} if the call became multiparty, {@code false}
   1359          *      otherwise.
   1360          */
   1361         @Override
   1362         public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
   1363             if (DBG) log("onMultipartyStateChanged to " + (isMultiParty ? "Y" : "N"));
   1364 
   1365             ImsPhoneConnection conn = findConnection(imsCall);
   1366             if (conn != null) {
   1367                 conn.updateMultipartyState(isMultiParty);
   1368             }
   1369         }
   1370     };
   1371 
   1372     /**
   1373      * Listen to the IMS call state change
   1374      */
   1375     private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
   1376         @Override
   1377         public void onCallStarted(ImsCall imsCall) {
   1378             if (DBG) log("mImsUssdListener onCallStarted");
   1379 
   1380             if (imsCall == mUssdSession) {
   1381                 if (mPendingUssd != null) {
   1382                     AsyncResult.forMessage(mPendingUssd);
   1383                     mPendingUssd.sendToTarget();
   1384                     mPendingUssd = null;
   1385                 }
   1386             }
   1387         }
   1388 
   1389         @Override
   1390         public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
   1391             if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
   1392 
   1393             onCallTerminated(imsCall, reasonInfo);
   1394         }
   1395 
   1396         @Override
   1397         public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
   1398             if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
   1399 
   1400             if (imsCall == mUssdSession) {
   1401                 mUssdSession = null;
   1402                 if (mPendingUssd != null) {
   1403                     CommandException ex =
   1404                             new CommandException(CommandException.Error.GENERIC_FAILURE);
   1405                     AsyncResult.forMessage(mPendingUssd, null, ex);
   1406                     mPendingUssd.sendToTarget();
   1407                     mPendingUssd = null;
   1408                 }
   1409             }
   1410             imsCall.close();
   1411         }
   1412 
   1413         @Override
   1414         public void onCallUssdMessageReceived(ImsCall call,
   1415                 int mode, String ussdMessage) {
   1416             if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
   1417 
   1418             int ussdMode = -1;
   1419 
   1420             switch(mode) {
   1421                 case ImsCall.USSD_MODE_REQUEST:
   1422                     ussdMode = CommandsInterface.USSD_MODE_REQUEST;
   1423                     break;
   1424 
   1425                 case ImsCall.USSD_MODE_NOTIFY:
   1426                     ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
   1427                     break;
   1428             }
   1429 
   1430             mPhone.onIncomingUSSD(ussdMode, ussdMessage);
   1431         }
   1432     };
   1433 
   1434     /**
   1435      * Listen to the IMS service state change
   1436      *
   1437      */
   1438     private ImsConnectionStateListener mImsConnectionStateListener =
   1439         new ImsConnectionStateListener() {
   1440         @Override
   1441         public void onImsConnected() {
   1442             if (DBG) log("onImsConnected");
   1443             mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
   1444             mPhone.setImsRegistered(true);
   1445         }
   1446 
   1447         @Override
   1448         public void onImsDisconnected(ImsReasonInfo imsReasonInfo) {
   1449             if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
   1450             mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
   1451             mPhone.setImsRegistered(false);
   1452             mPhone.processDisconnectReason(imsReasonInfo);
   1453         }
   1454 
   1455         @Override
   1456         public void onImsProgressing() {
   1457             if (DBG) log("onImsProgressing");
   1458         }
   1459 
   1460         @Override
   1461         public void onImsResumed() {
   1462             if (DBG) log("onImsResumed");
   1463             mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
   1464         }
   1465 
   1466         @Override
   1467         public void onImsSuspended() {
   1468             if (DBG) log("onImsSuspended");
   1469             mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
   1470         }
   1471 
   1472         @Override
   1473         public void onFeatureCapabilityChanged(int serviceClass,
   1474                 int[] enabledFeatures, int[] disabledFeatures) {
   1475             if (serviceClass == ImsServiceClass.MMTEL) {
   1476                 boolean tmpIsVideoCallEnabled = isVideoCallEnabled();
   1477                 // Check enabledFeatures to determine capabilities. We ignore disabledFeatures.
   1478                 for (int  i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE;
   1479                         i <= ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI; i++) {
   1480                     if (enabledFeatures[i] == i) {
   1481                         // If the feature is set to its own integer value it is enabled.
   1482                         if (DBG) log("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i] + "): value=true");
   1483                         mImsFeatureEnabled[i] = true;
   1484                     } else if (enabledFeatures[i]
   1485                             == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) {
   1486                         // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled.
   1487                         if (DBG) log("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i] + "): value=false");
   1488                         mImsFeatureEnabled[i] = false;
   1489                     } else {
   1490                         // Feature has unknown state; it is not its own value or -1.
   1491                         if (DBG) {
   1492                             loge("onFeatureCapabilityChanged(" + i + ", " +mImsFeatureStrings[i] + "): unexpectedValue="
   1493                                 + enabledFeatures[i]);
   1494                         }
   1495                     }
   1496                 }
   1497                 if (tmpIsVideoCallEnabled != isVideoCallEnabled()) {
   1498                     mPhone.notifyForVideoCapabilityChanged(isVideoCallEnabled());
   1499                 }
   1500 
   1501                 // TODO: Use the ImsCallSession or ImsCallProfile to tell the initial Wifi state and
   1502                 // {@link ImsCallSession.Listener#callSessionHandover} to listen for changes to
   1503                 // wifi capability caused by a handover.
   1504                 if (DBG) log("onFeatureCapabilityChanged: isVowifiEnabled=" + isVowifiEnabled());
   1505                 for (ImsPhoneConnection connection : mConnections) {
   1506                     connection.updateWifiState();
   1507                 }
   1508 
   1509                 mPhone.onFeatureCapabilityChanged();
   1510             }
   1511         }
   1512     };
   1513 
   1514     /* package */
   1515     ImsUtInterface getUtInterface() throws ImsException {
   1516         if (mImsManager == null) {
   1517             throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
   1518         }
   1519 
   1520         ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
   1521         return ut;
   1522     }
   1523 
   1524     private void transferHandoverConnections(ImsPhoneCall call) {
   1525         if (call.mConnections != null) {
   1526             for (Connection c : call.mConnections) {
   1527                 c.mPreHandoverState = call.mState;
   1528                 log ("Connection state before handover is " + c.getStateBeforeHandover());
   1529             }
   1530         }
   1531         if (mHandoverCall.mConnections == null ) {
   1532             mHandoverCall.mConnections = call.mConnections;
   1533         } else { // Multi-call SRVCC
   1534             mHandoverCall.mConnections.addAll(call.mConnections);
   1535         }
   1536         if (mHandoverCall.mConnections != null) {
   1537             if (call.getImsCall() != null) {
   1538                 call.getImsCall().close();
   1539             }
   1540             for (Connection c : mHandoverCall.mConnections) {
   1541                 ((ImsPhoneConnection)c).changeParent(mHandoverCall);
   1542                 ((ImsPhoneConnection)c).releaseWakeLock();
   1543             }
   1544         }
   1545         if (call.getState().isAlive()) {
   1546             log ("Call is alive and state is " + call.mState);
   1547             mHandoverCall.mState = call.mState;
   1548         }
   1549         call.mConnections.clear();
   1550         call.mState = ImsPhoneCall.State.IDLE;
   1551     }
   1552 
   1553     /* package */
   1554     void notifySrvccState(Call.SrvccState state) {
   1555         if (DBG) log("notifySrvccState state=" + state);
   1556 
   1557         mSrvccState = state;
   1558 
   1559         if (mSrvccState == Call.SrvccState.COMPLETED) {
   1560             transferHandoverConnections(mForegroundCall);
   1561             transferHandoverConnections(mBackgroundCall);
   1562             transferHandoverConnections(mRingingCall);
   1563         }
   1564     }
   1565 
   1566     //****** Overridden from Handler
   1567 
   1568     @Override
   1569     public void
   1570     handleMessage (Message msg) {
   1571         AsyncResult ar;
   1572         if (DBG) log("handleMessage what=" + msg.what);
   1573 
   1574         switch (msg.what) {
   1575             case EVENT_HANGUP_PENDINGMO:
   1576                 if (mPendingMO != null) {
   1577                     mPendingMO.onDisconnect();
   1578                     removeConnection(mPendingMO);
   1579                     mPendingMO = null;
   1580                 }
   1581 
   1582                 updatePhoneState();
   1583                 mPhone.notifyPreciseCallStateChanged();
   1584                 break;
   1585             case EVENT_RESUME_BACKGROUND:
   1586                 try {
   1587                     resumeWaitingOrHolding();
   1588                 } catch (CallStateException e) {
   1589                     if (Phone.DEBUG_PHONE) {
   1590                         loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
   1591                     }
   1592                 }
   1593                 break;
   1594             case EVENT_DIAL_PENDINGMO:
   1595                 dialInternal(mPendingMO, mClirMode, mPendingCallVideoState);
   1596                 break;
   1597 
   1598             case EVENT_EXIT_ECM_RESPONSE_CDMA:
   1599                 // no matter the result, we still do the same here
   1600                 if (pendingCallInEcm) {
   1601                     dialInternal(mPendingMO, pendingCallClirMode,
   1602                             mPendingCallVideoState);
   1603                     pendingCallInEcm = false;
   1604                 }
   1605                 mPhone.unsetOnEcbModeExitResponse(this);
   1606                 break;
   1607         }
   1608     }
   1609 
   1610     @Override
   1611     protected void log(String msg) {
   1612         Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
   1613     }
   1614 
   1615     protected void loge(String msg) {
   1616         Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
   1617     }
   1618 
   1619     /**
   1620      * Logs the current state of the ImsPhoneCallTracker.  Useful for debugging issues with
   1621      * call tracking.
   1622      */
   1623     /* package */
   1624     void logState() {
   1625         if (!VERBOSE_STATE_LOGGING) {
   1626             return;
   1627         }
   1628 
   1629         StringBuilder sb = new StringBuilder();
   1630         sb.append("Current IMS PhoneCall State:\n");
   1631         sb.append(" Foreground: ");
   1632         sb.append(mForegroundCall);
   1633         sb.append("\n");
   1634         sb.append(" Background: ");
   1635         sb.append(mBackgroundCall);
   1636         sb.append("\n");
   1637         sb.append(" Ringing: ");
   1638         sb.append(mRingingCall);
   1639         sb.append("\n");
   1640         sb.append(" Handover: ");
   1641         sb.append(mHandoverCall);
   1642         sb.append("\n");
   1643         Rlog.v(LOG_TAG, sb.toString());
   1644     }
   1645 
   1646     @Override
   1647     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   1648         pw.println("ImsPhoneCallTracker extends:");
   1649         super.dump(fd, pw, args);
   1650         pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
   1651         pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
   1652         pw.println(" mRingingCall=" + mRingingCall);
   1653         pw.println(" mForegroundCall=" + mForegroundCall);
   1654         pw.println(" mBackgroundCall=" + mBackgroundCall);
   1655         pw.println(" mHandoverCall=" + mHandoverCall);
   1656         pw.println(" mPendingMO=" + mPendingMO);
   1657         //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
   1658         pw.println(" mPhone=" + mPhone);
   1659         pw.println(" mDesiredMute=" + mDesiredMute);
   1660         pw.println(" mState=" + mState);
   1661     }
   1662 
   1663     @Override
   1664     protected void handlePollCalls(AsyncResult ar) {
   1665     }
   1666 
   1667     /* package */
   1668     ImsEcbm getEcbmInterface() throws ImsException {
   1669         if (mImsManager == null) {
   1670             throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
   1671         }
   1672 
   1673         ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
   1674         return ecbm;
   1675     }
   1676 
   1677     public boolean isInEmergencyCall() {
   1678         return mIsInEmergencyCall;
   1679     }
   1680 
   1681     public boolean isVolteEnabled() {
   1682         return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE];
   1683     }
   1684 
   1685     public boolean isVowifiEnabled() {
   1686         return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI];
   1687     }
   1688 
   1689     public boolean isVideoCallEnabled() {
   1690         return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE]
   1691                 || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]);
   1692     }
   1693 
   1694     @Override
   1695     public PhoneConstants.State getState() {
   1696         return mState;
   1697     }
   1698 
   1699     private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
   1700             throws RemoteException {
   1701         IImsVideoCallProvider imsVideoCallProvider =
   1702                 imsCall.getCallSession().getVideoCallProvider();
   1703         if (imsVideoCallProvider != null) {
   1704             ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
   1705                     new ImsVideoCallProviderWrapper(imsVideoCallProvider);
   1706             conn.setVideoProvider(imsVideoCallProviderWrapper);
   1707         }
   1708     }
   1709 }
   1710