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