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