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