ImsPhoneCallTracker.java revision 0ba5c4a978fba0c15c8539c790a3ae1793bd99e3
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[] mImsFeatureEnabled = {false, false, false, false};
78
79    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
80        @Override
81        public void onReceive(Context context, Intent intent) {
82            if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) {
83                if (DBG) log("onReceive : incoming call intent");
84
85                if (mImsManager == null) return;
86
87                if (mServiceId < 0) return;
88
89                try {
90                    // Network initiated USSD will be treated by mImsUssdListener
91                    boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false);
92                    if (isUssd) {
93                        if (DBG) log("onReceive : USSD");
94                        mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener);
95                        if (mUssdSession != null) {
96                            mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE);
97                        }
98                        return;
99                    }
100
101                    // Normal MT call
102                    ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener);
103                    ImsPhoneConnection conn = new ImsPhoneConnection(mPhone.getContext(), imsCall,
104                            ImsPhoneCallTracker.this, mRingingCall);
105                    addConnection(conn);
106
107                    IImsVideoCallProvider imsVideoCallProvider =
108                            imsCall.getCallSession().getVideoCallProvider();
109                    if (imsVideoCallProvider != null) {
110                        ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
111                                new ImsVideoCallProviderWrapper(imsVideoCallProvider);
112                        conn.setVideoProvider(imsVideoCallProviderWrapper);
113                    }
114
115                    if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) ||
116                            (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) {
117                        conn.update(imsCall, ImsPhoneCall.State.WAITING);
118                    }
119
120                    mPhone.notifyNewRingingConnection(conn);
121                    mPhone.notifyIncomingRing();
122
123                    updatePhoneState();
124                    mPhone.notifyPreciseCallStateChanged();
125                } catch (ImsException e) {
126                    loge("onReceive : exception " + e);
127                } catch (RemoteException e) {
128                }
129            }
130        }
131    };
132
133    //***** Constants
134
135    static final int MAX_CONNECTIONS = 7;
136    static final int MAX_CONNECTIONS_PER_CALL = 5;
137
138    private static final int EVENT_HANGUP_PENDINGMO = 18;
139    private static final int EVENT_RESUME_BACKGROUND = 19;
140    private static final int EVENT_DIAL_PENDINGMO = 20;
141
142    private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
143
144    //***** Instance Variables
145    private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>();
146    private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
147    private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
148
149    ImsPhoneCall mRingingCall = new ImsPhoneCall(this);
150    ImsPhoneCall mForegroundCall = new ImsPhoneCall(this);
151    ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this);
152    ImsPhoneCall mHandoverCall = new ImsPhoneCall(this);
153
154    private ImsPhoneConnection mPendingMO;
155    private int mClirMode = CommandsInterface.CLIR_DEFAULT;
156    private Object mSyncHold = new Object();
157
158    private ImsCall mUssdSession = null;
159    private Message mPendingUssd = null;
160
161    ImsPhone mPhone;
162
163    private boolean mDesiredMute = false;    // false = mute off
164    private boolean mOnHoldToneStarted = false;
165
166    PhoneConstants.State mState = PhoneConstants.State.IDLE;
167
168    private ImsManager mImsManager;
169    private int mServiceId = -1;
170
171    private Call.SrvccState mSrvccState = Call.SrvccState.NONE;
172
173    private boolean mIsInEmergencyCall = false;
174
175    private int pendingCallClirMode;
176    private int pendingCallVideoState;
177    private boolean pendingCallInEcm = false;
178    private boolean mSwitchingFgAndBgCalls = false;
179    private ImsCall mCallExpectedToResume = null;
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.getPhoneId());
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(mPhone.getContext(), 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            // Swap the ImsCalls pointed to by the foreground and background ImsPhoneCalls.
495            // If hold or resume later fails, we will swap them back.
496            mSwitchingFgAndBgCalls = true;
497            mCallExpectedToResume = mBackgroundCall.getImsCall();
498            mForegroundCall.switchWith(mBackgroundCall);
499
500            // Hold the foreground call; once the foreground call is held, the background call will
501            // be resumed.
502            try {
503                imsCall.hold();
504            } catch (ImsException e) {
505                mForegroundCall.switchWith(mBackgroundCall);
506                throw new CallStateException(e.getMessage());
507            }
508        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
509            resumeWaitingOrHolding();
510        }
511    }
512
513    void
514    conference() {
515        if (DBG) log("conference");
516
517        ImsCall fgImsCall = mForegroundCall.getImsCall();
518        if (fgImsCall == null) {
519            log("conference no foreground ims call");
520            return;
521        }
522
523        ImsCall bgImsCall = mBackgroundCall.getImsCall();
524        if (bgImsCall == null) {
525            log("conference no background ims call");
526            return;
527        }
528
529        // Keep track of the connect time of the earliest call so that it can be set on the
530        // {@code ImsConference} when it is created.
531        long conferenceConnectTime = Math.min(mForegroundCall.getEarliestConnectTime(),
532                mBackgroundCall.getEarliestConnectTime());
533        ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection();
534        if (foregroundConnection != null) {
535            foregroundConnection.setConferenceConnectTime(conferenceConnectTime);
536        }
537
538        try {
539            fgImsCall.merge(bgImsCall);
540        } catch (ImsException e) {
541            log("conference " + e.getMessage());
542        }
543    }
544
545    void
546    explicitCallTransfer() {
547        //TODO : implement
548    }
549
550    void
551    clearDisconnected() {
552        if (DBG) log("clearDisconnected");
553
554        internalClearDisconnected();
555
556        updatePhoneState();
557        mPhone.notifyPreciseCallStateChanged();
558    }
559
560    boolean
561    canConference() {
562        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
563            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
564            && !mBackgroundCall.isFull()
565            && !mForegroundCall.isFull();
566    }
567
568    boolean
569    canDial() {
570        boolean ret;
571        int serviceState = mPhone.getServiceState().getState();
572        String disableCall = SystemProperties.get(
573                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
574
575        ret = (serviceState != ServiceState.STATE_POWER_OFF)
576            && mPendingMO == null
577            && !mRingingCall.isRinging()
578            && !disableCall.equals("true")
579            && (!mForegroundCall.getState().isAlive()
580                    || !mBackgroundCall.getState().isAlive());
581
582        return ret;
583    }
584
585    boolean
586    canTransfer() {
587        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
588            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
589    }
590
591    //***** Private Instance Methods
592
593    private void
594    internalClearDisconnected() {
595        mRingingCall.clearDisconnected();
596        mForegroundCall.clearDisconnected();
597        mBackgroundCall.clearDisconnected();
598        mHandoverCall.clearDisconnected();
599    }
600
601    private void
602    updatePhoneState() {
603        PhoneConstants.State oldState = mState;
604
605        if (mRingingCall.isRinging()) {
606            mState = PhoneConstants.State.RINGING;
607        } else if (mPendingMO != null ||
608                !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
609            mState = PhoneConstants.State.OFFHOOK;
610        } else {
611            mState = PhoneConstants.State.IDLE;
612        }
613
614        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
615            mVoiceCallEndedRegistrants.notifyRegistrants(
616                    new AsyncResult(null, null, null));
617        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
618            mVoiceCallStartedRegistrants.notifyRegistrants (
619                    new AsyncResult(null, null, null));
620        }
621
622        if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
623
624        if (mState != oldState) {
625            mPhone.notifyPhoneStateChanged();
626        }
627    }
628
629    private void
630    handleRadioNotAvailable() {
631        // handlePollCalls will clear out its
632        // call list when it gets the CommandException
633        // error result from this
634        pollCallsWhenSafe();
635    }
636
637    private void
638    dumpState() {
639        List l;
640
641        log("Phone State:" + mState);
642
643        log("Ringing call: " + mRingingCall.toString());
644
645        l = mRingingCall.getConnections();
646        for (int i = 0, s = l.size(); i < s; i++) {
647            log(l.get(i).toString());
648        }
649
650        log("Foreground call: " + mForegroundCall.toString());
651
652        l = mForegroundCall.getConnections();
653        for (int i = 0, s = l.size(); i < s; i++) {
654            log(l.get(i).toString());
655        }
656
657        log("Background call: " + mBackgroundCall.toString());
658
659        l = mBackgroundCall.getConnections();
660        for (int i = 0, s = l.size(); i < s; i++) {
661            log(l.get(i).toString());
662        }
663
664    }
665
666    //***** Called from ImsPhone
667
668    void setUiTTYMode(int uiTtyMode, Message onComplete) {
669        try {
670            mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, uiTtyMode, onComplete);
671        } catch (ImsException e) {
672            loge("setTTYMode : " + e);
673            mPhone.sendErrorResponse(onComplete, e);
674        }
675    }
676
677    /*package*/ void setMute(boolean mute) {
678        mDesiredMute = mute;
679        mForegroundCall.setMute(mute);
680    }
681
682    /*package*/ boolean getMute() {
683        return mDesiredMute;
684    }
685
686    /* package */ void sendDtmf(char c, Message result) {
687        if (DBG) log("sendDtmf");
688
689        ImsCall imscall = mForegroundCall.getImsCall();
690        if (imscall != null) {
691            imscall.sendDtmf(c, result);
692        }
693    }
694
695    /*package*/ void
696    startDtmf(char c) {
697        if (DBG) log("startDtmf");
698
699        ImsCall imscall = mForegroundCall.getImsCall();
700        if (imscall != null) {
701            imscall.startDtmf(c);
702        } else {
703            loge("startDtmf : no foreground call");
704        }
705    }
706
707    /*package*/ void
708    stopDtmf() {
709        if (DBG) log("stopDtmf");
710
711        ImsCall imscall = mForegroundCall.getImsCall();
712        if (imscall != null) {
713            imscall.stopDtmf();
714        } else {
715            loge("stopDtmf : no foreground call");
716        }
717    }
718
719    //***** Called from ImsPhoneConnection
720
721    /*package*/ void
722    hangup (ImsPhoneConnection conn) throws CallStateException {
723        if (DBG) log("hangup connection");
724
725        if (conn.getOwner() != this) {
726            throw new CallStateException ("ImsPhoneConnection " + conn
727                    + "does not belong to ImsPhoneCallTracker " + this);
728        }
729
730        hangup(conn.getCall());
731    }
732
733    //***** Called from ImsPhoneCall
734
735    /* package */ void
736    hangup (ImsPhoneCall call) throws CallStateException {
737        if (DBG) log("hangup call");
738
739        if (call.getConnections().size() == 0) {
740            throw new CallStateException("no connections");
741        }
742
743        ImsCall imsCall = call.getImsCall();
744        boolean rejectCall = false;
745
746        if (call == mRingingCall) {
747            if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
748            rejectCall = true;
749        } else if (call == mForegroundCall) {
750            if (call.isDialingOrAlerting()) {
751                if (Phone.DEBUG_PHONE) {
752                    log("(foregnd) hangup dialing or alerting...");
753                }
754            } else {
755                if (Phone.DEBUG_PHONE) {
756                    log("(foregnd) hangup foreground");
757                }
758                //held call will be resumed by onCallTerminated
759            }
760        } else if (call == mBackgroundCall) {
761            if (Phone.DEBUG_PHONE) {
762                log("(backgnd) hangup waiting or background");
763            }
764        } else {
765            throw new CallStateException ("ImsPhoneCall " + call +
766                    "does not belong to ImsPhoneCallTracker " + this);
767        }
768
769        call.onHangupLocal();
770
771        try {
772            if (imsCall != null) {
773                if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
774                else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
775            } else if (mPendingMO != null && call == mForegroundCall) {
776                // is holding a foreground call
777                mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
778                mPendingMO.onDisconnect();
779                removeConnection(mPendingMO);
780                mPendingMO = null;
781                updatePhoneState();
782                removeMessages(EVENT_DIAL_PENDINGMO);
783            }
784        } catch (ImsException e) {
785            throw new CallStateException(e.getMessage());
786        }
787
788        mPhone.notifyPreciseCallStateChanged();
789    }
790
791    void callEndCleanupHandOverCallIfAny() {
792        if (mHandoverCall.mConnections.size() > 0) {
793            if (DBG) log("callEndCleanupHandOverCallIfAny, mHandoverCall.mConnections="
794                    + mHandoverCall.mConnections);
795            mHandoverCall.mConnections.clear();
796            mState = PhoneConstants.State.IDLE;
797        }
798    }
799
800    /* package */
801    void resumeWaitingOrHolding() throws CallStateException {
802        if (DBG) log("resumeWaitingOrHolding");
803
804        try {
805            if (mForegroundCall.getState().isAlive()) {
806                //resume foreground call after holding background call
807                //they were switched before holding
808                ImsCall imsCall = mForegroundCall.getImsCall();
809                if (imsCall != null) imsCall.resume();
810            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
811                //accept waiting call after holding background call
812                ImsCall imsCall = mRingingCall.getImsCall();
813                if (imsCall != null) imsCall.accept(ImsCallProfile.CALL_TYPE_VOICE);
814            } else {
815                //Just resume background call.
816                //To distinguish resuming call with swapping calls
817                //we do not switch calls.here
818                //ImsPhoneConnection.update will chnage the parent when completed
819                ImsCall imsCall = mBackgroundCall.getImsCall();
820                if (imsCall != null) imsCall.resume();
821            }
822        } catch (ImsException e) {
823            throw new CallStateException(e.getMessage());
824        }
825    }
826
827    /* package */
828    void sendUSSD (String ussdString, Message response) {
829        if (DBG) log("sendUSSD");
830
831        try {
832            if (mUssdSession != null) {
833                mUssdSession.sendUssd(ussdString);
834                AsyncResult.forMessage(response, null, null);
835                response.sendToTarget();
836                return;
837            }
838
839            String[] callees = new String[] { ussdString };
840            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
841                    ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
842            profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
843                    ImsCallProfile.DIALSTRING_USSD);
844
845            mUssdSession = mImsManager.makeCall(mServiceId, profile,
846                    callees, mImsUssdListener);
847        } catch (ImsException e) {
848            loge("sendUSSD : " + e);
849            mPhone.sendErrorResponse(response, e);
850        }
851    }
852
853    /* package */
854    void cancelUSSD() {
855        if (mUssdSession == null) return;
856
857        try {
858            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
859        } catch (ImsException e) {
860        }
861
862    }
863
864    private synchronized ImsPhoneConnection findConnection(ImsCall imsCall) {
865        for (ImsPhoneConnection conn : mConnections) {
866            if (conn.getImsCall() == imsCall) {
867                return conn;
868            }
869        }
870        return null;
871    }
872
873    private synchronized void removeConnection(ImsPhoneConnection conn) {
874        mConnections.remove(conn);
875    }
876
877    private synchronized void addConnection(ImsPhoneConnection conn) {
878        mConnections.add(conn);
879    }
880
881    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
882        if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
883
884        if (imsCall == null) return;
885
886        boolean changed = false;
887        ImsPhoneConnection conn = findConnection(imsCall);
888
889        if (conn == null) {
890            // TODO : what should be done?
891            return;
892        }
893
894        changed = conn.update(imsCall, state);
895
896        if (state == ImsPhoneCall.State.DISCONNECTED) {
897            changed = conn.onDisconnect(cause) || changed;
898            //detach the disconnected connections
899            conn.getCall().detach(conn);
900            removeConnection(conn);
901        }
902
903        if (changed) {
904            if (conn.getCall() == mHandoverCall) return;
905            updatePhoneState();
906            mPhone.notifyPreciseCallStateChanged();
907        }
908    }
909
910    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
911        int cause = DisconnectCause.ERROR_UNSPECIFIED;
912
913        //int type = reasonInfo.getReasonType();
914        int code = reasonInfo.getCode();
915        switch (code) {
916            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
917            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
918                return DisconnectCause.NUMBER_UNREACHABLE;
919
920            case ImsReasonInfo.CODE_SIP_BUSY:
921                return DisconnectCause.BUSY;
922
923            case ImsReasonInfo.CODE_USER_TERMINATED:
924                return DisconnectCause.LOCAL;
925
926            case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
927                return DisconnectCause.INCOMING_REJECTED;
928
929            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
930                return DisconnectCause.NORMAL;
931
932            case ImsReasonInfo.CODE_SIP_REDIRECTED:
933            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
934            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
935            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
936            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
937            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
938                return DisconnectCause.SERVER_ERROR;
939
940            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
941            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
942            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
943                return DisconnectCause.SERVER_UNREACHABLE;
944
945            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
946            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
947            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
948            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
949            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
950            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
951            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
952            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
953                return DisconnectCause.OUT_OF_SERVICE;
954
955            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
956            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
957            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
958            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
959                return DisconnectCause.TIMED_OUT;
960
961            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
962            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
963                return DisconnectCause.POWER_OFF;
964
965            default:
966        }
967
968        return cause;
969    }
970
971    /**
972     * Listen to the IMS call state change
973     */
974    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
975        @Override
976        public void onCallProgressing(ImsCall imsCall) {
977            if (DBG) log("onCallProgressing");
978
979            mPendingMO = null;
980            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
981                    DisconnectCause.NOT_DISCONNECTED);
982        }
983
984        @Override
985        public void onCallStarted(ImsCall imsCall) {
986            if (DBG) log("onCallStarted");
987
988            mPendingMO = null;
989            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
990                    DisconnectCause.NOT_DISCONNECTED);
991        }
992
993        /**
994         * onCallStartFailed will be invoked when:
995         * case 1) Dialing fails
996         * case 2) Ringing call is disconnected by local or remote user
997         */
998        @Override
999        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1000            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
1001
1002            if (mPendingMO != null) {
1003                // To initiate dialing circuit-switched call
1004                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
1005                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
1006                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
1007                    mForegroundCall.detach(mPendingMO);
1008                    removeConnection(mPendingMO);
1009                    mPendingMO.finalize();
1010                    mPendingMO = null;
1011                    mPhone.initiateSilentRedial();
1012                    return;
1013                } else {
1014                    int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1015                    processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1016                }
1017                mPendingMO = null;
1018            }
1019        }
1020
1021        @Override
1022        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1023            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
1024
1025            ImsPhoneCall.State oldState = mForegroundCall.getState();
1026            int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1027            ImsPhoneConnection conn = findConnection(imsCall);
1028            if (DBG) log("cause = " + cause + " conn = " + conn);
1029
1030            if (conn != null && conn.isIncoming() && conn.getConnectTime() == 0) {
1031                // Missed
1032                if (cause == DisconnectCause.NORMAL) {
1033                    cause = DisconnectCause.INCOMING_MISSED;
1034                }
1035                if (DBG) log("Incoming connection of 0 connect time detected - translated cause = "
1036                        + cause);
1037
1038            }
1039
1040            if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) {
1041                // Call was terminated while it is merged instead of a remote disconnect.
1042                cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY;
1043            }
1044
1045            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1046        }
1047
1048        @Override
1049        public void onCallHeld(ImsCall imsCall) {
1050            if (DBG) log("onCallHeld");
1051
1052            synchronized (mSyncHold) {
1053                ImsPhoneCall.State oldState = mBackgroundCall.getState();
1054                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
1055                        DisconnectCause.NOT_DISCONNECTED);
1056
1057                // Note: If we're performing a switchWaitingOrHoldingAndActive, the call to
1058                // processCallStateChange above may have caused the mBackgroundCall and
1059                // mForegroundCall references below to change meaning.  Watch out for this if you
1060                // are reading through this code.
1061                if (oldState == ImsPhoneCall.State.ACTIVE) {
1062                    // Note: This case comes up when we have just held a call in response to a
1063                    // switchWaitingOrHoldingAndActive.  We now need to resume the background call.
1064                    // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called.
1065                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
1066                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
1067
1068                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1069                    } else {
1070                        //when multiple connections belong to background call,
1071                        //only the first callback reaches here
1072                        //otherwise the oldState is already HOLDING
1073                        if (mPendingMO != null) {
1074                            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1075                        }
1076
1077                        // In this case there will be no call resumed, so we can assume that we
1078                        // are done switching fg and bg calls now.
1079                        // This may happen if there is no BG call and we are holding a call so that
1080                        // we can dial another one.
1081                        mSwitchingFgAndBgCalls = false;
1082                    }
1083                }
1084            }
1085        }
1086
1087        @Override
1088        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1089            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
1090
1091            synchronized (mSyncHold) {
1092                ImsPhoneCall.State bgState = mBackgroundCall.getState();
1093                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
1094                    // disconnected while processing hold
1095                    if (mPendingMO != null) {
1096                        sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1097                    }
1098                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
1099                    mForegroundCall.switchWith(mBackgroundCall);
1100
1101                    if (mPendingMO != null) {
1102                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
1103                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
1104                    }
1105                }
1106            }
1107        }
1108
1109        @Override
1110        public void onCallResumed(ImsCall imsCall) {
1111            if (DBG) log("onCallResumed");
1112
1113            // If we are the in midst of swapping FG and BG calls and the call we end up resuming
1114            // is not the one we expected, we likely had a resume failure and we need to swap the
1115            // FG and BG calls back.
1116            if (mSwitchingFgAndBgCalls && imsCall != mCallExpectedToResume) {
1117                mForegroundCall.switchWith(mBackgroundCall);
1118                mSwitchingFgAndBgCalls = false;
1119                mCallExpectedToResume = null;
1120            }
1121            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1122                    DisconnectCause.NOT_DISCONNECTED);
1123        }
1124
1125        @Override
1126        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1127            // TODO : What should be done?
1128            // If we are in the midst of swapping the FG and BG calls and we got a resume fail, we
1129            // need to swap back the FG and BG calls.
1130            if (mSwitchingFgAndBgCalls && imsCall == mCallExpectedToResume) {
1131                mForegroundCall.switchWith(mBackgroundCall);
1132                mCallExpectedToResume = null;
1133                mSwitchingFgAndBgCalls = false;
1134            }
1135            mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME);
1136        }
1137
1138        @Override
1139        public void onCallResumeReceived(ImsCall imsCall) {
1140            if (DBG) log("onCallResumeReceived");
1141
1142            if (mOnHoldToneStarted) {
1143                mPhone.stopOnHoldTone();
1144                mOnHoldToneStarted = false;
1145            }
1146        }
1147
1148        @Override
1149        public void onCallHoldReceived(ImsCall imsCall) {
1150            if (DBG) log("onCallHoldReceived");
1151
1152            ImsPhoneConnection conn = findConnection(imsCall);
1153            if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
1154                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
1155                    mPhone.startOnHoldTone();
1156                    mOnHoldToneStarted = true;
1157                }
1158            }
1159        }
1160
1161        @Override
1162        public void onCallMerged(ImsCall call, boolean swapCalls) {
1163            if (DBG) log("onCallMerged");
1164
1165            mForegroundCall.merge(mBackgroundCall, mForegroundCall.getState());
1166            if (swapCalls) {
1167                try {
1168                    switchWaitingOrHoldingAndActive();
1169                } catch (CallStateException e) {
1170                    if (Phone.DEBUG_PHONE) {
1171                        loge("Failed swap fg and bg calls on merge exception=" + e);
1172                    }
1173                }
1174            }
1175            updatePhoneState();
1176            mPhone.notifyPreciseCallStateChanged();
1177        }
1178
1179        @Override
1180        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
1181            if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo);
1182            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
1183        }
1184
1185        /**
1186         * Called when the state of IMS conference participant(s) has changed.
1187         *
1188         * @param call the call object that carries out the IMS call.
1189         * @param participants the participant(s) and their new state information.
1190         */
1191        @Override
1192        public void onConferenceParticipantsStateChanged(ImsCall call,
1193                List<ConferenceParticipant> participants) {
1194            if (DBG) log("onConferenceParticipantsStateChanged");
1195
1196            ImsPhoneConnection conn = findConnection(call);
1197            if (conn != null) {
1198                conn.updateConferenceParticipants(participants);
1199            }
1200        }
1201
1202        @Override
1203        public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
1204            mPhone.onTtyModeReceived(mode);
1205        }
1206
1207        @Override
1208        public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1209            ImsReasonInfo reasonInfo) {
1210            if (DBG) {
1211                log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
1212                    targetAccessTech + ", reasonInfo=" + reasonInfo);
1213            }
1214        }
1215
1216        @Override
1217        public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1218            ImsReasonInfo reasonInfo) {
1219            if (DBG) {
1220                log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech +
1221                    ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo);
1222            }
1223        }
1224    };
1225
1226    /**
1227     * Listen to the IMS call state change
1228     */
1229    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
1230        @Override
1231        public void onCallStarted(ImsCall imsCall) {
1232            if (DBG) log("mImsUssdListener onCallStarted");
1233
1234            if (imsCall == mUssdSession) {
1235                if (mPendingUssd != null) {
1236                    AsyncResult.forMessage(mPendingUssd);
1237                    mPendingUssd.sendToTarget();
1238                    mPendingUssd = null;
1239                }
1240            }
1241        }
1242
1243        @Override
1244        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1245            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
1246
1247            onCallTerminated(imsCall, reasonInfo);
1248        }
1249
1250        @Override
1251        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1252            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
1253
1254            if (imsCall == mUssdSession) {
1255                mUssdSession = null;
1256                if (mPendingUssd != null) {
1257                    CommandException ex =
1258                            new CommandException(CommandException.Error.GENERIC_FAILURE);
1259                    AsyncResult.forMessage(mPendingUssd, null, ex);
1260                    mPendingUssd.sendToTarget();
1261                    mPendingUssd = null;
1262                }
1263            }
1264            imsCall.close();
1265        }
1266
1267        @Override
1268        public void onCallUssdMessageReceived(ImsCall call,
1269                int mode, String ussdMessage) {
1270            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
1271
1272            int ussdMode = -1;
1273
1274            switch(mode) {
1275                case ImsCall.USSD_MODE_REQUEST:
1276                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
1277                    break;
1278
1279                case ImsCall.USSD_MODE_NOTIFY:
1280                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
1281                    break;
1282            }
1283
1284            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
1285        }
1286    };
1287
1288    /**
1289     * Listen to the IMS service state change
1290     *
1291     */
1292    private ImsConnectionStateListener mImsConnectionStateListener =
1293        new ImsConnectionStateListener() {
1294        @Override
1295        public void onImsConnected() {
1296            if (DBG) log("onImsConnected");
1297            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1298            mPhone.setImsRegistered(true);
1299        }
1300
1301        @Override
1302        public void onImsDisconnected(ImsReasonInfo imsReasonInfo) {
1303            if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
1304            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1305            mPhone.setImsRegistered(false);
1306        }
1307
1308        @Override
1309        public void onImsProgressing() {
1310            if (DBG) log("onImsProgressing");
1311        }
1312
1313        @Override
1314        public void onImsResumed() {
1315            if (DBG) log("onImsResumed");
1316            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1317        }
1318
1319        @Override
1320        public void onImsSuspended() {
1321            if (DBG) log("onImsSuspended");
1322            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1323        }
1324
1325        @Override
1326        public void onFeatureCapabilityChanged(int serviceClass,
1327                int[] enabledFeatures, int[] disabledFeatures) {
1328            if (serviceClass == ImsServiceClass.MMTEL) {
1329                // Check enabledFeatures to determine capabilities. We ignore disabledFeatures.
1330                for (int  i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE;
1331                        i <= ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI; i++) {
1332                    if (enabledFeatures[i] == i) {
1333                        // If the feature is set to its own integer value it is enabled.
1334                        if (DBG) log("onFeatureCapabilityChanged: i=" + i + ", value=true");
1335                        mImsFeatureEnabled[i] = true;
1336                    } else if (enabledFeatures[i]
1337                            == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) {
1338                        // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled.
1339                        if (DBG) log("onFeatureCapabilityChanged: i=" + i + ", value=false");
1340                        mImsFeatureEnabled[i] = false;
1341                    } else {
1342                        // Feature has unknown state; it is not its own value or -1.
1343                        if (DBG) {
1344                            loge("onFeatureCapabilityChanged: i=" + i + ", unexpectedValue="
1345                                + enabledFeatures[i]);
1346                        }
1347                    }
1348                }
1349
1350                // TODO: Use the ImsCallSession or ImsCallProfile to tell the initial Wifi state and
1351                // {@link ImsCallSession.Listener#callSessionHandover} to listen for changes to
1352                // wifi capability caused by a handover.
1353                if (DBG) log("onFeatureCapabilityChanged: isVowifiEnabled=" + isVowifiEnabled());
1354                for (ImsPhoneConnection connection : mConnections) {
1355                    connection.updateWifiState();
1356                }
1357            }
1358
1359            if (DBG) log("onFeatureCapabilityChanged: mImsFeatureEnabled=" +  mImsFeatureEnabled);
1360        }
1361    };
1362
1363    /* package */
1364    ImsUtInterface getUtInterface() throws ImsException {
1365        if (mImsManager == null) {
1366            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1367        }
1368
1369        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
1370        return ut;
1371    }
1372
1373    private void transferHandoverConnections(ImsPhoneCall call) {
1374        if (call.mConnections != null) {
1375            for (Connection c : call.mConnections) {
1376                c.mPreHandoverState = call.mState;
1377                log ("Connection state before handover is " + c.getStateBeforeHandover());
1378            }
1379        }
1380        if (mHandoverCall.mConnections == null ) {
1381            mHandoverCall.mConnections = call.mConnections;
1382        } else { // Multi-call SRVCC
1383            mHandoverCall.mConnections.addAll(call.mConnections);
1384        }
1385        if (mHandoverCall.mConnections != null) {
1386            if (call.getImsCall() != null) {
1387                call.getImsCall().close();
1388            }
1389            for (Connection c : mHandoverCall.mConnections) {
1390                ((ImsPhoneConnection)c).changeParent(mHandoverCall);
1391                ((ImsPhoneConnection)c).releaseWakeLock();
1392            }
1393        }
1394        if (call.getState().isAlive()) {
1395            log ("Call is alive and state is " + call.mState);
1396            mHandoverCall.mState = call.mState;
1397        }
1398        call.mConnections.clear();
1399        call.mState = ImsPhoneCall.State.IDLE;
1400    }
1401
1402    /* package */
1403    void notifySrvccState(Call.SrvccState state) {
1404        if (DBG) log("notifySrvccState state=" + state);
1405
1406        mSrvccState = state;
1407
1408        if (mSrvccState == Call.SrvccState.COMPLETED) {
1409            transferHandoverConnections(mForegroundCall);
1410            transferHandoverConnections(mBackgroundCall);
1411            transferHandoverConnections(mRingingCall);
1412        }
1413    }
1414
1415    //****** Overridden from Handler
1416
1417    @Override
1418    public void
1419    handleMessage (Message msg) {
1420        AsyncResult ar;
1421        if (DBG) log("handleMessage what=" + msg.what);
1422
1423        switch (msg.what) {
1424            case EVENT_HANGUP_PENDINGMO:
1425                if (mPendingMO != null) {
1426                    mPendingMO.onDisconnect();
1427                    removeConnection(mPendingMO);
1428                    mPendingMO = null;
1429                }
1430
1431                updatePhoneState();
1432                mPhone.notifyPreciseCallStateChanged();
1433                break;
1434            case EVENT_RESUME_BACKGROUND:
1435                try {
1436                    resumeWaitingOrHolding();
1437                } catch (CallStateException e) {
1438                    if (Phone.DEBUG_PHONE) {
1439                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
1440                    }
1441                }
1442                break;
1443            case EVENT_DIAL_PENDINGMO:
1444                dialInternal(mPendingMO, mClirMode, VideoProfile.VideoState.AUDIO_ONLY);
1445                break;
1446
1447            case EVENT_EXIT_ECM_RESPONSE_CDMA:
1448                // no matter the result, we still do the same here
1449                if (pendingCallInEcm) {
1450                    dialInternal(mPendingMO, pendingCallClirMode, pendingCallVideoState);
1451                    pendingCallInEcm = false;
1452                }
1453                mPhone.unsetOnEcbModeExitResponse(this);
1454                break;
1455        }
1456    }
1457
1458    @Override
1459    protected void log(String msg) {
1460        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1461    }
1462
1463    protected void loge(String msg) {
1464        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1465    }
1466
1467    @Override
1468    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1469        pw.println("ImsPhoneCallTracker extends:");
1470        super.dump(fd, pw, args);
1471        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
1472        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
1473        pw.println(" mRingingCall=" + mRingingCall);
1474        pw.println(" mForegroundCall=" + mForegroundCall);
1475        pw.println(" mBackgroundCall=" + mBackgroundCall);
1476        pw.println(" mHandoverCall=" + mHandoverCall);
1477        pw.println(" mPendingMO=" + mPendingMO);
1478        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
1479        pw.println(" mPhone=" + mPhone);
1480        pw.println(" mDesiredMute=" + mDesiredMute);
1481        pw.println(" mState=" + mState);
1482    }
1483
1484    @Override
1485    protected void handlePollCalls(AsyncResult ar) {
1486    }
1487
1488    /* package */
1489    ImsEcbm getEcbmInterface() throws ImsException {
1490        if (mImsManager == null) {
1491            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1492        }
1493
1494        ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
1495        return ecbm;
1496    }
1497
1498    public boolean isInEmergencyCall() {
1499        return mIsInEmergencyCall;
1500    }
1501
1502    public boolean isVolteEnabled() {
1503        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE];
1504    }
1505
1506    public boolean isVowifiEnabled() {
1507        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI];
1508    }
1509
1510    public boolean isVtEnabled() {
1511        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE]
1512                || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]);
1513    }
1514    @Override
1515    public PhoneConstants.State getState() {
1516        return mState;
1517    }
1518}
1519