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