ImsPhoneCallTracker.java revision c204cc0f515864b36187d60f6ab79af28885d36e
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.Handler;
32import android.os.Message;
33import android.os.Registrant;
34import android.os.RegistrantList;
35import android.os.RemoteException;
36import android.os.SystemProperties;
37import android.provider.Settings;
38import android.preference.PreferenceManager;
39import android.telecom.ConferenceParticipant;
40import android.telecom.VideoProfile;
41import android.telephony.DisconnectCause;
42import android.telephony.PhoneNumberUtils;
43import android.telephony.Rlog;
44import android.telephony.ServiceState;
45
46import com.android.ims.ImsCall;
47import com.android.ims.ImsCallProfile;
48import com.android.ims.ImsConfig;
49import com.android.ims.ImsConnectionStateListener;
50import com.android.ims.ImsEcbm;
51import com.android.ims.ImsException;
52import com.android.ims.ImsManager;
53import com.android.ims.ImsReasonInfo;
54import com.android.ims.ImsServiceClass;
55import com.android.ims.ImsUtInterface;
56import com.android.ims.internal.IImsVideoCallProvider;
57import com.android.ims.internal.ImsVideoCallProviderWrapper;
58import com.android.internal.telephony.Call;
59import com.android.internal.telephony.CallStateException;
60import com.android.internal.telephony.CallTracker;
61import com.android.internal.telephony.CommandException;
62import com.android.internal.telephony.CommandsInterface;
63import com.android.internal.telephony.Connection;
64import com.android.internal.telephony.Phone;
65import com.android.internal.telephony.PhoneBase;
66import com.android.internal.telephony.PhoneConstants;
67import com.android.internal.telephony.TelephonyProperties;
68
69/**
70 * {@hide}
71 */
72public final class ImsPhoneCallTracker extends CallTracker {
73    static final String LOG_TAG = "ImsPhoneCallTracker";
74
75    private static final boolean DBG = true;
76
77    private boolean mIsVolteEnabled = false;
78    private boolean mIsVtEnabled = false;
79
80    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
81        @Override
82        public void onReceive(Context context, Intent intent) {
83            if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) {
84                if (DBG) log("onReceive : incoming call intent");
85
86                if (mImsManager == null) return;
87
88                if (mServiceId < 0) return;
89
90                try {
91                    // Network initiated USSD will be treated by mImsUssdListener
92                    boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false);
93                    if (isUssd) {
94                        if (DBG) log("onReceive : USSD");
95                        mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener);
96                        if (mUssdSession != null) {
97                            mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE);
98                        }
99                        return;
100                    }
101
102                    // Normal MT call
103                    ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener);
104                    ImsPhoneConnection conn = new ImsPhoneConnection(mPhone.getContext(), imsCall,
105                            ImsPhoneCallTracker.this, mRingingCall);
106                    addConnection(conn);
107
108                    IImsVideoCallProvider imsVideoCallProvider =
109                            imsCall.getCallSession().getVideoCallProvider();
110                    if (imsVideoCallProvider != null) {
111                        ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
112                                new ImsVideoCallProviderWrapper(imsVideoCallProvider);
113                        conn.setVideoProvider(imsVideoCallProviderWrapper);
114                    }
115
116                    if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) ||
117                            (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) {
118                        conn.update(imsCall, ImsPhoneCall.State.WAITING);
119                    }
120
121                    mPhone.notifyNewRingingConnection(conn);
122                    mPhone.notifyIncomingRing();
123
124                    updatePhoneState();
125                    mPhone.notifyPreciseCallStateChanged();
126                } catch (ImsException e) {
127                    loge("onReceive : exception " + e);
128                } catch (RemoteException e) {
129                }
130            }
131        }
132    };
133
134    //***** Constants
135
136    static final int MAX_CONNECTIONS = 7;
137    static final int MAX_CONNECTIONS_PER_CALL = 5;
138
139    private static final int EVENT_HANGUP_PENDINGMO = 18;
140    private static final int EVENT_RESUME_BACKGROUND = 19;
141    private static final int EVENT_DIAL_PENDINGMO = 20;
142
143    private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
144
145    //***** Instance Variables
146    private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>();
147    private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
148    private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
149
150    ImsPhoneCall mRingingCall = new ImsPhoneCall(this);
151    ImsPhoneCall mForegroundCall = new ImsPhoneCall(this);
152    ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this);
153    ImsPhoneCall mHandoverCall = new ImsPhoneCall(this);
154
155    private ImsPhoneConnection mPendingMO;
156    private int mClirMode = CommandsInterface.CLIR_DEFAULT;
157    private Object mSyncHold = new Object();
158
159    private ImsCall mUssdSession = null;
160    private Message mPendingUssd = null;
161
162    ImsPhone mPhone;
163
164    private boolean mDesiredMute = false;    // false = mute off
165    private boolean mOnHoldToneStarted = false;
166
167    PhoneConstants.State mState = PhoneConstants.State.IDLE;
168
169    private ImsManager mImsManager;
170    private int mServiceId = -1;
171
172    private Call.SrvccState mSrvccState = Call.SrvccState.NONE;
173
174    private boolean mIsInEmergencyCall = false;
175
176    private int pendingCallClirMode;
177    private int pendingCallVideoState;
178    private boolean pendingCallInEcm = false;
179
180    //***** Events
181
182
183    //***** Constructors
184
185    ImsPhoneCallTracker(ImsPhone phone) {
186        this.mPhone = phone;
187
188        IntentFilter intentfilter = new IntentFilter();
189        intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL);
190        mPhone.getContext().registerReceiver(mReceiver, intentfilter);
191
192        Thread t = new Thread() {
193            public void run() {
194                getImsService();
195            }
196        };
197        t.start();
198    }
199
200    private PendingIntent createIncomingCallPendingIntent() {
201        Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
202        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
203        return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
204                PendingIntent.FLAG_UPDATE_CURRENT);
205    }
206
207    private void getImsService() {
208        if (DBG) log("getImsService");
209        mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId());
210        try {
211            mServiceId = mImsManager.open(ImsServiceClass.MMTEL,
212                    createIncomingCallPendingIntent(),
213                    mImsConnectionStateListener);
214
215            // Get the ECBM interface and set IMSPhone's listener object for notifications
216            getEcbmInterface().setEcbmStateListener(mPhone.mImsEcbmStateListener);
217            if (mPhone.isInEcm()) {
218                // Call exit ECBM which will invoke onECBMExited
219                mPhone.exitEmergencyCallbackMode();
220            }
221            int mPreferredTtyMode = Settings.Secure.getInt(
222                mPhone.getContext().getContentResolver(),
223                Settings.Secure.PREFERRED_TTY_MODE,
224                Phone.TTY_MODE_OFF);
225           mImsManager.setUiTTYMode(mServiceId, mPreferredTtyMode, null);
226
227        } catch (ImsException e) {
228            loge("getImsService: " + e);
229            //Leave mImsManager as null, then CallStateException will be thrown when dialing
230            mImsManager = null;
231        }
232    }
233
234    public void dispose() {
235        if (DBG) log("dispose");
236        mRingingCall.dispose();
237        mBackgroundCall.dispose();
238        mForegroundCall.dispose();
239        mHandoverCall.dispose();
240
241        clearDisconnected();
242        mPhone.getContext().unregisterReceiver(mReceiver);
243    }
244
245    @Override
246    protected void finalize() {
247        log("ImsPhoneCallTracker finalized");
248    }
249
250    //***** Instance Methods
251
252    //***** Public Methods
253    @Override
254    public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
255        Registrant r = new Registrant(h, what, obj);
256        mVoiceCallStartedRegistrants.add(r);
257    }
258
259    @Override
260    public void unregisterForVoiceCallStarted(Handler h) {
261        mVoiceCallStartedRegistrants.remove(h);
262    }
263
264    @Override
265    public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
266        Registrant r = new Registrant(h, what, obj);
267        mVoiceCallEndedRegistrants.add(r);
268    }
269
270    @Override
271    public void unregisterForVoiceCallEnded(Handler h) {
272        mVoiceCallEndedRegistrants.remove(h);
273    }
274
275    Connection
276    dial(String dialString, int videoState) throws CallStateException {
277        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
278        int oirMode = sp.getInt(PhoneBase.CLIR_KEY, CommandsInterface.CLIR_DEFAULT);
279        return dial(dialString, oirMode, videoState);
280    }
281
282    /**
283     * oirMode is one of the CLIR_ constants
284     */
285    synchronized Connection
286    dial(String dialString, int clirMode, int videoState) throws CallStateException {
287        boolean isPhoneInEcmMode = SystemProperties.getBoolean(
288                TelephonyProperties.PROPERTY_INECM_MODE, false);
289        boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString);
290
291        if (DBG) log("dial clirMode=" + clirMode);
292
293        // note that this triggers call state changed notif
294        clearDisconnected();
295
296        if (mImsManager == null) {
297            throw new CallStateException("service not available");
298        }
299
300        if (!canDial()) {
301            throw new CallStateException("cannot dial in current state");
302        }
303
304        if (isPhoneInEcmMode && isEmergencyNumber) {
305            handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER);
306        }
307
308        boolean holdBeforeDial = false;
309
310        // The new call must be assigned to the foreground call.
311        // That call must be idle, so place anything that's
312        // there on hold
313        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
314            if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
315                //we should have failed in !canDial() above before we get here
316                throw new CallStateException("cannot dial in current state");
317            }
318            // foreground call is empty for the newly dialed connection
319            holdBeforeDial = true;
320            switchWaitingOrHoldingAndActive();
321        }
322
323        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
324        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
325
326        mClirMode = clirMode;
327
328        synchronized (mSyncHold) {
329            if (holdBeforeDial) {
330                fgState = mForegroundCall.getState();
331                bgState = mBackgroundCall.getState();
332
333                //holding foreground call failed
334                if (fgState == ImsPhoneCall.State.ACTIVE) {
335                    throw new CallStateException("cannot dial in current state");
336                }
337
338                //holding foreground call succeeded
339                if (bgState == ImsPhoneCall.State.HOLDING) {
340                    holdBeforeDial = false;
341                }
342            }
343
344            mPendingMO = new ImsPhoneConnection(mPhone.getContext(),
345                    checkForTestEmergencyNumber(dialString), this, mForegroundCall);
346        }
347        addConnection(mPendingMO);
348
349        if (!holdBeforeDial) {
350            if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
351                dialInternal(mPendingMO, clirMode, videoState);
352            } else {
353                try {
354                    getEcbmInterface().exitEmergencyCallbackMode();
355                } catch (ImsException e) {
356                    e.printStackTrace();
357                    throw new CallStateException("service not available");
358                }
359                mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
360                pendingCallClirMode = clirMode;
361                pendingCallVideoState = videoState;
362                pendingCallInEcm = true;
363            }
364        }
365
366        updatePhoneState();
367        mPhone.notifyPreciseCallStateChanged();
368
369        return mPendingMO;
370    }
371
372    private void handleEcmTimer(int action) {
373        mPhone.handleTimerInEmergencyCallbackMode(action);
374        switch (action) {
375            case ImsPhone.CANCEL_ECM_TIMER:
376                break;
377            case ImsPhone.RESTART_ECM_TIMER:
378                break;
379            default:
380                log("handleEcmTimer, unsupported action " + action);
381        }
382    }
383
384    private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState) {
385        if (conn == null) {
386            return;
387        }
388
389        if (conn.getAddress()== null || conn.getAddress().length() == 0
390                || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
391            // Phone number is invalid
392            conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER);
393            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
394            return;
395        }
396
397        // Always unmute when initiating a new call
398        setMute(false);
399        int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ?
400                ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
401        int callType = ImsCallProfile.getCallTypeFromVideoState(videoState);
402        //TODO(vt): Is this sufficient?  At what point do we know the video state of the call?
403        conn.setVideoState(videoState);
404
405        try {
406            String[] callees = new String[] { conn.getAddress() };
407            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
408                    serviceType, callType);
409            profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
410
411            ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
412                    callees, mImsCallListener);
413            conn.setImsCall(imsCall);
414
415            IImsVideoCallProvider imsVideoCallProvider =
416                    imsCall.getCallSession().getVideoCallProvider();
417            if (imsVideoCallProvider != null) {
418                ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
419                        new ImsVideoCallProviderWrapper(imsVideoCallProvider);
420                conn.setVideoProvider(imsVideoCallProviderWrapper);
421            }
422        } catch (ImsException e) {
423            loge("dialInternal : " + e);
424            conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
425            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
426        } catch (RemoteException e) {
427        }
428    }
429
430    /**
431     * Accepts a call with the specified video state.  The video state is the video state that the
432     * user has agreed upon in the InCall UI.
433     *
434     * @param videoState The video State
435     * @throws CallStateException
436     */
437    void acceptCall (int videoState) throws CallStateException {
438        if (DBG) log("acceptCall");
439
440        if (mForegroundCall.getState().isAlive()
441                && mBackgroundCall.getState().isAlive()) {
442            throw new CallStateException("cannot accept call");
443        }
444
445        if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING)
446                && mForegroundCall.getState().isAlive()) {
447            setMute(false);
448            switchWaitingOrHoldingAndActive();
449        } else if (mRingingCall.getState().isRinging()) {
450            if (DBG) log("acceptCall: incoming...");
451            // Always unmute when answering a new call
452            setMute(false);
453            try {
454                ImsCall imsCall = mRingingCall.getImsCall();
455                if (imsCall != null) {
456                    imsCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
457                } else {
458                    throw new CallStateException("no valid ims call");
459                }
460            } catch (ImsException e) {
461                throw new CallStateException("cannot accept call");
462            }
463        } else {
464            throw new CallStateException("phone not ringing");
465        }
466    }
467
468    void
469    rejectCall () throws CallStateException {
470        if (DBG) log("rejectCall");
471
472        if (mRingingCall.getState().isRinging()) {
473            hangup(mRingingCall);
474        } else {
475            throw new CallStateException("phone not ringing");
476        }
477    }
478
479    void
480    switchWaitingOrHoldingAndActive() throws CallStateException {
481        if (DBG) log("switchWaitingOrHoldingAndActive");
482
483        if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
484            throw new CallStateException("cannot be in the incoming state");
485        }
486
487        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
488            ImsCall imsCall = mForegroundCall.getImsCall();
489            if (imsCall == null) {
490                throw new CallStateException("no ims call");
491            }
492
493            mForegroundCall.switchWith(mBackgroundCall);
494
495            try {
496                imsCall.hold();
497            } catch (ImsException e) {
498                mForegroundCall.switchWith(mBackgroundCall);
499                throw new CallStateException(e.getMessage());
500            }
501        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
502            resumeWaitingOrHolding();
503        }
504    }
505
506    void
507    conference() {
508        if (DBG) log("conference");
509
510        ImsCall fgImsCall = mForegroundCall.getImsCall();
511        if (fgImsCall == null) {
512            log("conference no foreground ims call");
513            return;
514        }
515
516        ImsCall bgImsCall = mBackgroundCall.getImsCall();
517        if (bgImsCall == null) {
518            log("conference no background ims call");
519            return;
520        }
521
522        try {
523            fgImsCall.merge(bgImsCall);
524        } catch (ImsException e) {
525            log("conference " + e.getMessage());
526        }
527    }
528
529    void
530    explicitCallTransfer() {
531        //TODO : implement
532    }
533
534    void
535    clearDisconnected() {
536        if (DBG) log("clearDisconnected");
537
538        internalClearDisconnected();
539
540        updatePhoneState();
541        mPhone.notifyPreciseCallStateChanged();
542    }
543
544    boolean
545    canConference() {
546        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
547            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
548            && !mBackgroundCall.isFull()
549            && !mForegroundCall.isFull();
550    }
551
552    boolean
553    canDial() {
554        boolean ret;
555        int serviceState = mPhone.getServiceState().getState();
556        String disableCall = SystemProperties.get(
557                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
558
559        ret = (serviceState != ServiceState.STATE_POWER_OFF)
560            && mPendingMO == null
561            && !mRingingCall.isRinging()
562            && !disableCall.equals("true")
563            && (!mForegroundCall.getState().isAlive()
564                    || !mBackgroundCall.getState().isAlive());
565
566        return ret;
567    }
568
569    boolean
570    canTransfer() {
571        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
572            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
573    }
574
575    //***** Private Instance Methods
576
577    private void
578    internalClearDisconnected() {
579        mRingingCall.clearDisconnected();
580        mForegroundCall.clearDisconnected();
581        mBackgroundCall.clearDisconnected();
582        mHandoverCall.clearDisconnected();
583    }
584
585    private void
586    updatePhoneState() {
587        PhoneConstants.State oldState = mState;
588
589        if (mRingingCall.isRinging()) {
590            mState = PhoneConstants.State.RINGING;
591        } else if (mPendingMO != null ||
592                !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
593            mState = PhoneConstants.State.OFFHOOK;
594        } else {
595            mState = PhoneConstants.State.IDLE;
596        }
597
598        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
599            mVoiceCallEndedRegistrants.notifyRegistrants(
600                    new AsyncResult(null, null, null));
601        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
602            mVoiceCallStartedRegistrants.notifyRegistrants (
603                    new AsyncResult(null, null, null));
604        }
605
606        if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
607
608        if (mState != oldState) {
609            mPhone.notifyPhoneStateChanged();
610        }
611    }
612
613    private void
614    handleRadioNotAvailable() {
615        // handlePollCalls will clear out its
616        // call list when it gets the CommandException
617        // error result from this
618        pollCallsWhenSafe();
619    }
620
621    private void
622    dumpState() {
623        List l;
624
625        log("Phone State:" + mState);
626
627        log("Ringing call: " + mRingingCall.toString());
628
629        l = mRingingCall.getConnections();
630        for (int i = 0, s = l.size(); i < s; i++) {
631            log(l.get(i).toString());
632        }
633
634        log("Foreground call: " + mForegroundCall.toString());
635
636        l = mForegroundCall.getConnections();
637        for (int i = 0, s = l.size(); i < s; i++) {
638            log(l.get(i).toString());
639        }
640
641        log("Background call: " + mBackgroundCall.toString());
642
643        l = mBackgroundCall.getConnections();
644        for (int i = 0, s = l.size(); i < s; i++) {
645            log(l.get(i).toString());
646        }
647
648    }
649
650    //***** Called from ImsPhone
651
652    void setUiTTYMode(int uiTtyMode, Message onComplete) {
653        try {
654            mImsManager.setUiTTYMode(mServiceId, uiTtyMode, onComplete);
655        } catch (ImsException e) {
656            loge("setTTYMode : " + e);
657            mPhone.sendErrorResponse(onComplete, e);
658        }
659    }
660
661    /*package*/ void
662    setMute(boolean mute) {
663        mDesiredMute = mute;
664        mForegroundCall.setMute(mute);
665    }
666
667    /*package*/ boolean
668    getMute() {
669        return mDesiredMute;
670    }
671
672    /*package*/ void
673    sendDtmf(char c) {
674        if (DBG) log("sendDtmf");
675
676        ImsCall imscall = mForegroundCall.getImsCall();
677        if (imscall != null) {
678            imscall.sendDtmf(c);
679        }
680    }
681
682    /*package*/ void
683    startDtmf(char c) {
684        if (DBG) log("startDtmf");
685
686        ImsCall imscall = mForegroundCall.getImsCall();
687        if (imscall != null) {
688            imscall.startDtmf(c);
689        } else {
690            loge("startDtmf : no foreground call");
691        }
692    }
693
694    /*package*/ void
695    stopDtmf() {
696        if (DBG) log("stopDtmf");
697
698        ImsCall imscall = mForegroundCall.getImsCall();
699        if (imscall != null) {
700            imscall.stopDtmf();
701        } else {
702            loge("stopDtmf : no foreground call");
703        }
704    }
705
706    //***** Called from ImsPhoneConnection
707
708    /*package*/ void
709    hangup (ImsPhoneConnection conn) throws CallStateException {
710        if (DBG) log("hangup connection");
711
712        if (conn.getOwner() != this) {
713            throw new CallStateException ("ImsPhoneConnection " + conn
714                    + "does not belong to ImsPhoneCallTracker " + this);
715        }
716
717        hangup(conn.getCall());
718    }
719
720    //***** Called from ImsPhoneCall
721
722    /* package */ void
723    hangup (ImsPhoneCall call) throws CallStateException {
724        if (DBG) log("hangup call");
725
726        if (call.getConnections().size() == 0) {
727            throw new CallStateException("no connections");
728        }
729
730        ImsCall imsCall = call.getImsCall();
731        boolean rejectCall = false;
732
733        if (call == mRingingCall) {
734            if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
735            rejectCall = true;
736        } else if (call == mForegroundCall) {
737            if (call.isDialingOrAlerting()) {
738                if (Phone.DEBUG_PHONE) {
739                    log("(foregnd) hangup dialing or alerting...");
740                }
741            } else {
742                if (Phone.DEBUG_PHONE) {
743                    log("(foregnd) hangup foreground");
744                }
745                //held call will be resumed by onCallTerminated
746            }
747        } else if (call == mBackgroundCall) {
748            if (Phone.DEBUG_PHONE) {
749                log("(backgnd) hangup waiting or background");
750            }
751        } else {
752            throw new CallStateException ("ImsPhoneCall " + call +
753                    "does not belong to ImsPhoneCallTracker " + this);
754        }
755
756        call.onHangupLocal();
757
758        try {
759            if (imsCall != null) {
760                if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
761                else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
762            } else if (mPendingMO != null && call == mForegroundCall) {
763                // is holding a foreground call
764                mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
765                mPendingMO.onDisconnect();
766                removeConnection(mPendingMO);
767                mPendingMO = null;
768                updatePhoneState();
769                removeMessages(EVENT_DIAL_PENDINGMO);
770            }
771        } catch (ImsException e) {
772            throw new CallStateException(e.getMessage());
773        }
774
775        mPhone.notifyPreciseCallStateChanged();
776    }
777
778    /* package */
779    void resumeWaitingOrHolding() throws CallStateException {
780        if (DBG) log("resumeWaitingOrHolding");
781
782        try {
783            if (mForegroundCall.getState().isAlive()) {
784                //resume foreground call after holding background call
785                //they were switched before holding
786                ImsCall imsCall = mForegroundCall.getImsCall();
787                if (imsCall != null) imsCall.resume();
788            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
789                //accept waiting call after holding background call
790                ImsCall imsCall = mRingingCall.getImsCall();
791                if (imsCall != null) imsCall.accept(ImsCallProfile.CALL_TYPE_VOICE);
792            } else {
793                //Just resume background call.
794                //To distinguish resuming call with swapping calls
795                //we do not switch calls.here
796                //ImsPhoneConnection.update will chnage the parent when completed
797                ImsCall imsCall = mBackgroundCall.getImsCall();
798                if (imsCall != null) imsCall.resume();
799            }
800        } catch (ImsException e) {
801            throw new CallStateException(e.getMessage());
802        }
803    }
804
805    /* package */
806    void sendUSSD (String ussdString, Message response) {
807        if (DBG) log("sendUSSD");
808
809        try {
810            if (mUssdSession != null) {
811                mUssdSession.sendUssd(ussdString);
812                AsyncResult.forMessage(response, null, null);
813                response.sendToTarget();
814                return;
815            }
816
817            String[] callees = new String[] { ussdString };
818            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
819                    ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
820            profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
821                    ImsCallProfile.DIALSTRING_USSD);
822
823            mUssdSession = mImsManager.makeCall(mServiceId, profile,
824                    callees, mImsUssdListener);
825        } catch (ImsException e) {
826            loge("sendUSSD : " + e);
827            mPhone.sendErrorResponse(response, e);
828        }
829    }
830
831    /* package */
832    void cancelUSSD() {
833        if (mUssdSession == null) return;
834
835        try {
836            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
837        } catch (ImsException e) {
838        }
839
840    }
841
842    private synchronized ImsPhoneConnection findConnection(ImsCall imsCall) {
843        for (ImsPhoneConnection conn : mConnections) {
844            if (conn.getImsCall() == imsCall) {
845                return conn;
846            }
847        }
848        return null;
849    }
850
851    private synchronized void removeConnection(ImsPhoneConnection conn) {
852        mConnections.remove(conn);
853    }
854
855    private synchronized void addConnection(ImsPhoneConnection conn) {
856        mConnections.add(conn);
857    }
858
859    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
860        if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
861
862        if (imsCall == null) return;
863
864        boolean changed = false;
865        ImsPhoneConnection conn = findConnection(imsCall);
866
867        if (conn == null) {
868            // TODO : what should be done?
869            return;
870        }
871
872        changed = conn.update(imsCall, state);
873
874        if (state == ImsPhoneCall.State.DISCONNECTED) {
875            changed = conn.onDisconnect(cause) || changed;
876            removeConnection(conn);
877        }
878
879        if (changed) {
880            if (conn.getCall() == mHandoverCall) return;
881            updatePhoneState();
882            mPhone.notifyPreciseCallStateChanged();
883        }
884    }
885
886    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
887        int cause = DisconnectCause.ERROR_UNSPECIFIED;
888
889        //int type = reasonInfo.getReasonType();
890        int code = reasonInfo.getCode();
891        switch (code) {
892            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
893            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
894                return DisconnectCause.NUMBER_UNREACHABLE;
895
896            case ImsReasonInfo.CODE_SIP_BUSY:
897                return DisconnectCause.BUSY;
898
899            case ImsReasonInfo.CODE_USER_TERMINATED:
900                return DisconnectCause.LOCAL;
901
902            case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
903                return DisconnectCause.INCOMING_REJECTED;
904
905            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
906                return DisconnectCause.NORMAL;
907
908            case ImsReasonInfo.CODE_SIP_REDIRECTED:
909            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
910            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
911            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
912            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
913            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
914                return DisconnectCause.SERVER_ERROR;
915
916            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
917            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
918            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
919                return DisconnectCause.SERVER_UNREACHABLE;
920
921            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
922            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
923            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
924            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
925            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
926            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
927            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
928            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
929                return DisconnectCause.OUT_OF_SERVICE;
930
931            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
932            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
933            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
934            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
935                return DisconnectCause.TIMED_OUT;
936
937            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
938            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
939                return DisconnectCause.POWER_OFF;
940
941            default:
942        }
943
944        return cause;
945    }
946
947    /**
948     * Listen to the IMS call state change
949     */
950    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
951        @Override
952        public void onCallProgressing(ImsCall imsCall) {
953            if (DBG) log("onCallProgressing");
954
955            mPendingMO = null;
956            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
957                    DisconnectCause.NOT_DISCONNECTED);
958        }
959
960        @Override
961        public void onCallStarted(ImsCall imsCall) {
962            if (DBG) log("onCallStarted");
963
964            mPendingMO = null;
965            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
966                    DisconnectCause.NOT_DISCONNECTED);
967        }
968
969        /**
970         * onCallStartFailed will be invoked when:
971         * case 1) Dialing fails
972         * case 2) Ringing call is disconnected by local or remote user
973         */
974        @Override
975        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
976            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
977
978            if (mPendingMO != null) {
979                // To initiate dialing circuit-switched call
980                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
981                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
982                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
983                    mForegroundCall.detach(mPendingMO);
984                    removeConnection(mPendingMO);
985                    mPendingMO.finalize();
986                    mPendingMO = null;
987                    mPhone.initiateSilentRedial();
988                    return;
989                }
990                mPendingMO = null;
991            }
992        }
993
994        @Override
995        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
996            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
997
998            ImsPhoneCall.State oldState = mForegroundCall.getState();
999            int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1000            ImsPhoneConnection conn = findConnection(imsCall);
1001            if (DBG) log("cause = " + cause + " conn = " + conn);
1002
1003            if (conn != null && conn.isIncoming() && conn.getConnectTime() == 0) {
1004                // Missed
1005                if (cause == DisconnectCause.NORMAL) {
1006                    cause = DisconnectCause.INCOMING_MISSED;
1007                }
1008                if (DBG) log("Incoming connection of 0 connect time detected - translated cause = "
1009                        + cause);
1010
1011            }
1012
1013            if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) {
1014                // Call was terminated while it is merged instead of a remote disconnect.
1015                cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY;
1016            }
1017
1018            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1019        }
1020
1021        @Override
1022        public void onCallHeld(ImsCall imsCall) {
1023            if (DBG) log("onCallHeld");
1024
1025            synchronized (mSyncHold) {
1026                ImsPhoneCall.State oldState = mBackgroundCall.getState();
1027                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
1028                        DisconnectCause.NOT_DISCONNECTED);
1029                if (oldState == ImsPhoneCall.State.ACTIVE) {
1030                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
1031                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
1032
1033                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1034                    } else {
1035                        //when multiple connections belong to background call,
1036                        //only the first callback reaches here
1037                        //otherwise the oldState is already HOLDING
1038                        if (mPendingMO != null) {
1039                            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1040                        }
1041                    }
1042                }
1043            }
1044        }
1045
1046        @Override
1047        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1048            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
1049
1050            synchronized (mSyncHold) {
1051                ImsPhoneCall.State bgState = mBackgroundCall.getState();
1052                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
1053                    // disconnected while processing hold
1054                    if (mPendingMO != null) {
1055                        sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1056                    }
1057                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
1058                    mForegroundCall.switchWith(mBackgroundCall);
1059
1060                    if (mPendingMO != null) {
1061                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
1062                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
1063                    }
1064                }
1065            }
1066        }
1067
1068        @Override
1069        public void onCallResumed(ImsCall imsCall) {
1070            if (DBG) log("onCallResumed");
1071
1072            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1073                    DisconnectCause.NOT_DISCONNECTED);
1074        }
1075
1076        @Override
1077        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1078            // TODO : What should be done?
1079        }
1080
1081        @Override
1082        public void onCallResumeReceived(ImsCall imsCall) {
1083            if (DBG) log("onCallResumeReceived");
1084
1085            if (mOnHoldToneStarted) {
1086                mPhone.stopOnHoldTone();
1087                mOnHoldToneStarted = false;
1088            }
1089        }
1090
1091        @Override
1092        public void onCallHoldReceived(ImsCall imsCall) {
1093            if (DBG) log("onCallHoldReceived");
1094
1095            ImsPhoneConnection conn = findConnection(imsCall);
1096            if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
1097                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
1098                    mPhone.startOnHoldTone();
1099                    mOnHoldToneStarted = true;
1100                }
1101            }
1102        }
1103
1104        @Override
1105        public void onCallMerged(ImsCall call) {
1106            if (DBG) log("onCallMerged");
1107
1108            mForegroundCall.merge(mBackgroundCall, mForegroundCall.getState());
1109            updatePhoneState();
1110            mPhone.notifyPreciseCallStateChanged();
1111        }
1112
1113        @Override
1114        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
1115            if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo);
1116            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
1117        }
1118
1119        /**
1120         * Called when the state of IMS conference participant(s) has changed.
1121         *
1122         * @param call the call object that carries out the IMS call.
1123         * @param participants the participant(s) and their new state information.
1124         */
1125        @Override
1126        public void onConferenceParticipantsStateChanged(ImsCall call,
1127                List<ConferenceParticipant> participants) {
1128            if (DBG) log("onConferenceParticipantsStateChanged");
1129
1130            ImsPhoneConnection conn = findConnection(call);
1131            if (conn != null) {
1132                conn.updateConferenceParticipants(participants);
1133            }
1134        }
1135    };
1136
1137    /**
1138     * Listen to the IMS call state change
1139     */
1140    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
1141        @Override
1142        public void onCallStarted(ImsCall imsCall) {
1143            if (DBG) log("mImsUssdListener onCallStarted");
1144
1145            if (imsCall == mUssdSession) {
1146                if (mPendingUssd != null) {
1147                    AsyncResult.forMessage(mPendingUssd);
1148                    mPendingUssd.sendToTarget();
1149                    mPendingUssd = null;
1150                }
1151            }
1152        }
1153
1154        @Override
1155        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1156            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
1157
1158            onCallTerminated(imsCall, reasonInfo);
1159        }
1160
1161        @Override
1162        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1163            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
1164
1165            if (imsCall == mUssdSession) {
1166                mUssdSession = null;
1167                if (mPendingUssd != null) {
1168                    CommandException ex =
1169                            new CommandException(CommandException.Error.GENERIC_FAILURE);
1170                    AsyncResult.forMessage(mPendingUssd, null, ex);
1171                    mPendingUssd.sendToTarget();
1172                    mPendingUssd = null;
1173                }
1174            }
1175            imsCall.close();
1176        }
1177
1178        @Override
1179        public void onCallUssdMessageReceived(ImsCall call,
1180                int mode, String ussdMessage) {
1181            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
1182
1183            int ussdMode = -1;
1184
1185            switch(mode) {
1186                case ImsCall.USSD_MODE_REQUEST:
1187                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
1188                    break;
1189
1190                case ImsCall.USSD_MODE_NOTIFY:
1191                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
1192                    break;
1193            }
1194
1195            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
1196        }
1197    };
1198
1199    /**
1200     * Listen to the IMS service state change
1201     *
1202     */
1203    private ImsConnectionStateListener mImsConnectionStateListener =
1204        new ImsConnectionStateListener() {
1205        @Override
1206        public void onImsConnected() {
1207            if (DBG) log("onImsConnected");
1208            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1209        }
1210
1211        @Override
1212        public void onImsDisconnected() {
1213            if (DBG) log("onImsDisconnected");
1214            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1215        }
1216
1217        @Override
1218        public void onImsResumed() {
1219            if (DBG) log("onImsResumed");
1220            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1221        }
1222
1223        @Override
1224        public void onImsSuspended() {
1225            if (DBG) log("onImsSuspended");
1226            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1227        }
1228
1229        @Override
1230        public void onFeatureCapabilityChanged(int serviceClass,
1231                int[] enabledFeatures, int[] disabledFeatures) {
1232            if (serviceClass == ImsServiceClass.MMTEL) {
1233                if (enabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE] ==
1234                        ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE) {
1235                    mIsVolteEnabled = true;
1236                }
1237                if (enabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE] ==
1238                        ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE) {
1239                    mIsVtEnabled = true;
1240                }
1241                if (disabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE] ==
1242                        ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE) {
1243                    mIsVolteEnabled = false;
1244                }
1245                if (disabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE] ==
1246                        ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE) {
1247                    mIsVtEnabled = false;
1248                }
1249            }
1250            if (DBG) log("onFeatureCapabilityChanged, mIsVolteEnabled = " +  mIsVolteEnabled
1251                    + " mIsVtEnabled = " + mIsVtEnabled);
1252        }
1253    };
1254
1255    /* package */
1256    ImsUtInterface getUtInterface() throws ImsException {
1257        if (mImsManager == null) {
1258            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1259        }
1260
1261        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
1262        return ut;
1263    }
1264
1265    /* package */
1266    void notifySrvccState(Call.SrvccState state) {
1267        if (DBG) log("notifySrvccState state=" + state);
1268
1269        mSrvccState = state;
1270
1271        if (mSrvccState == Call.SrvccState.COMPLETED) {
1272            if (mForegroundCall.getConnections().size() > 0) {
1273                mHandoverCall.switchWith(mForegroundCall);
1274            } else if (mBackgroundCall.getConnections().size() > 0) {
1275                mHandoverCall.switchWith(mBackgroundCall);
1276            }
1277
1278            // release wake lock hold
1279            ImsPhoneConnection con = mHandoverCall.getHandoverConnection();
1280            if (con != null) {
1281                con.releaseWakeLock();
1282            }
1283        }
1284    }
1285
1286    //****** Overridden from Handler
1287
1288    @Override
1289    public void
1290    handleMessage (Message msg) {
1291        AsyncResult ar;
1292        if (DBG) log("handleMessage what=" + msg.what);
1293
1294        switch (msg.what) {
1295            case EVENT_HANGUP_PENDINGMO:
1296                if (mPendingMO != null) {
1297                    mPendingMO.onDisconnect();
1298                    removeConnection(mPendingMO);
1299                    mPendingMO = null;
1300                }
1301
1302                updatePhoneState();
1303                mPhone.notifyPreciseCallStateChanged();
1304                break;
1305            case EVENT_RESUME_BACKGROUND:
1306                try {
1307                    resumeWaitingOrHolding();
1308                } catch (CallStateException e) {
1309                    if (Phone.DEBUG_PHONE) {
1310                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
1311                    }
1312                }
1313                break;
1314            case EVENT_DIAL_PENDINGMO:
1315                dialInternal(mPendingMO, mClirMode, VideoProfile.VideoState.AUDIO_ONLY);
1316                break;
1317
1318            case EVENT_EXIT_ECM_RESPONSE_CDMA:
1319                // no matter the result, we still do the same here
1320                if (pendingCallInEcm) {
1321                    dialInternal(mPendingMO, pendingCallClirMode, pendingCallVideoState);
1322                    pendingCallInEcm = false;
1323                }
1324                mPhone.unsetOnEcbModeExitResponse(this);
1325                break;
1326        }
1327    }
1328
1329    @Override
1330    protected void log(String msg) {
1331        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1332    }
1333
1334    protected void loge(String msg) {
1335        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1336    }
1337
1338    @Override
1339    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1340        pw.println("ImsPhoneCallTracker extends:");
1341        super.dump(fd, pw, args);
1342        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
1343        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
1344        pw.println(" mRingingCall=" + mRingingCall);
1345        pw.println(" mForegroundCall=" + mForegroundCall);
1346        pw.println(" mBackgroundCall=" + mBackgroundCall);
1347        pw.println(" mHandoverCall=" + mHandoverCall);
1348        pw.println(" mPendingMO=" + mPendingMO);
1349        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
1350        pw.println(" mPhone=" + mPhone);
1351        pw.println(" mDesiredMute=" + mDesiredMute);
1352        pw.println(" mState=" + mState);
1353    }
1354
1355    @Override
1356    protected void handlePollCalls(AsyncResult ar) {
1357    }
1358
1359    /* package */
1360    ImsEcbm getEcbmInterface() throws ImsException {
1361        if (mImsManager == null) {
1362            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1363        }
1364
1365        ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
1366        return ecbm;
1367    }
1368
1369    public boolean isInEmergencyCall() {
1370        return mIsInEmergencyCall;
1371    }
1372
1373    public boolean isVolteEnabled() {
1374        return mIsVolteEnabled;
1375    }
1376
1377    public boolean isVtEnabled() {
1378        return mIsVtEnabled;
1379    }
1380}
1381