ImsPhoneCallTracker.java revision 8762e33ba987774e43aa9f4e216ebdb084d3a03e
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony.imsphone;
18
19import java.io.FileDescriptor;
20import java.io.PrintWriter;
21import java.util.ArrayList;
22import java.util.List;
23
24import android.app.PendingIntent;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.SharedPreferences;
30import android.os.AsyncResult;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.Message;
34import android.os.Registrant;
35import android.os.RegistrantList;
36import android.os.RemoteException;
37import android.os.SystemProperties;
38import android.provider.Settings;
39import android.widget.Toast;
40import android.preference.PreferenceManager;
41import android.telecom.ConferenceParticipant;
42import android.telecom.VideoProfile;
43import android.telephony.DisconnectCause;
44import android.telephony.PhoneNumberUtils;
45import android.telephony.Rlog;
46import android.telephony.ServiceState;
47
48import com.android.ims.ImsCall;
49import com.android.ims.ImsCallProfile;
50import com.android.ims.ImsConfig;
51import com.android.ims.ImsConnectionStateListener;
52import com.android.ims.ImsEcbm;
53import com.android.ims.ImsException;
54import com.android.ims.ImsManager;
55import com.android.ims.ImsReasonInfo;
56import com.android.ims.ImsServiceClass;
57import com.android.ims.ImsSuppServiceNotification;
58import com.android.ims.ImsUtInterface;
59import com.android.ims.internal.IImsVideoCallProvider;
60import com.android.ims.internal.ImsVideoCallProviderWrapper;
61import com.android.internal.telephony.Call;
62import com.android.internal.telephony.CallStateException;
63import com.android.internal.telephony.CallTracker;
64import com.android.internal.telephony.CommandException;
65import com.android.internal.telephony.CommandsInterface;
66import com.android.internal.telephony.Connection;
67import com.android.internal.telephony.Phone;
68import com.android.internal.telephony.PhoneBase;
69import com.android.internal.telephony.PhoneConstants;
70import com.android.internal.telephony.TelephonyProperties;
71import com.android.internal.telephony.gsm.SuppServiceNotification;
72
73/**
74 * {@hide}
75 */
76public final class ImsPhoneCallTracker extends CallTracker {
77    static final String LOG_TAG = "ImsPhoneCallTracker";
78
79    private static final boolean DBG = true;
80
81    private boolean[] mImsFeatureEnabled = {false, false, false, false};
82
83    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
84        @Override
85        public void onReceive(Context context, Intent intent) {
86            if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) {
87                if (DBG) log("onReceive : incoming call intent");
88
89                if (mImsManager == null) return;
90
91                if (mServiceId < 0) return;
92
93                try {
94                    // Network initiated USSD will be treated by mImsUssdListener
95                    boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false);
96                    if (isUssd) {
97                        if (DBG) log("onReceive : USSD");
98                        mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener);
99                        if (mUssdSession != null) {
100                            mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE);
101                        }
102                        return;
103                    }
104
105                    boolean isUnknown = intent.getBooleanExtra(ImsManager.EXTRA_IS_UNKNOWN_CALL,
106                            false);
107                    if (DBG) {
108                        log("onReceive : isUnknown = " + isUnknown +
109                                " fg = " + mForegroundCall.getState() +
110                                " bg = " + mBackgroundCall.getState());
111                    }
112
113                    // Normal MT/Unknown call
114                    ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener);
115                    ImsPhoneConnection conn = new ImsPhoneConnection(mPhone.getContext(), imsCall,
116                            ImsPhoneCallTracker.this,
117                            (isUnknown? mForegroundCall: mRingingCall), isUnknown);
118                    addConnection(conn);
119
120                    setVideoCallProvider(conn, imsCall);
121
122                    if (isUnknown) {
123                        mPhone.notifyUnknownConnection(conn);
124                    } else {
125                        if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) ||
126                                (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) {
127                            conn.update(imsCall, ImsPhoneCall.State.WAITING);
128                        }
129
130                        mPhone.notifyNewRingingConnection(conn);
131                        mPhone.notifyIncomingRing();
132                    }
133
134                    updatePhoneState();
135                    mPhone.notifyPreciseCallStateChanged();
136                } catch (ImsException e) {
137                    loge("onReceive : exception " + e);
138                } catch (RemoteException e) {
139                }
140            }
141        }
142    };
143
144    //***** Constants
145
146    static final int MAX_CONNECTIONS = 7;
147    static final int MAX_CONNECTIONS_PER_CALL = 5;
148
149    private static final int EVENT_HANGUP_PENDINGMO = 18;
150    private static final int EVENT_RESUME_BACKGROUND = 19;
151    private static final int EVENT_DIAL_PENDINGMO = 20;
152
153    private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
154
155    //***** Instance Variables
156    private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>();
157    private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
158    private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
159
160    ImsPhoneCall mRingingCall = new ImsPhoneCall(this);
161    ImsPhoneCall mForegroundCall = new ImsPhoneCall(this);
162    ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this);
163    ImsPhoneCall mHandoverCall = new ImsPhoneCall(this);
164
165    private ImsPhoneConnection mPendingMO;
166    private int mClirMode = CommandsInterface.CLIR_DEFAULT;
167    private Object mSyncHold = new Object();
168
169    private ImsCall mUssdSession = null;
170    private Message mPendingUssd = null;
171
172    ImsPhone mPhone;
173
174    private boolean mDesiredMute = false;    // false = mute off
175    private boolean mOnHoldToneStarted = false;
176
177    PhoneConstants.State mState = PhoneConstants.State.IDLE;
178
179    private ImsManager mImsManager;
180    private int mServiceId = -1;
181
182    private Call.SrvccState mSrvccState = Call.SrvccState.NONE;
183
184    private boolean mIsInEmergencyCall = false;
185
186    private int pendingCallClirMode;
187    private int mPendingCallVideoState;
188    private boolean pendingCallInEcm = false;
189    private boolean mSwitchingFgAndBgCalls = false;
190    private ImsCall mCallExpectedToResume = null;
191
192    //***** Events
193
194
195    //***** Constructors
196
197    ImsPhoneCallTracker(ImsPhone phone) {
198        this.mPhone = phone;
199
200        IntentFilter intentfilter = new IntentFilter();
201        intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL);
202        mPhone.getContext().registerReceiver(mReceiver, intentfilter);
203
204        Thread t = new Thread() {
205            public void run() {
206                getImsService();
207            }
208        };
209        t.start();
210    }
211
212    private PendingIntent createIncomingCallPendingIntent() {
213        Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
214        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
215        return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
216                PendingIntent.FLAG_UPDATE_CURRENT);
217    }
218
219    private void getImsService() {
220        if (DBG) log("getImsService");
221        mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId());
222        try {
223            mServiceId = mImsManager.open(ImsServiceClass.MMTEL,
224                    createIncomingCallPendingIntent(),
225                    mImsConnectionStateListener);
226
227            // Get the ECBM interface and set IMSPhone's listener object for notifications
228            getEcbmInterface().setEcbmStateListener(mPhone.mImsEcbmStateListener);
229            if (mPhone.isInEcm()) {
230                // Call exit ECBM which will invoke onECBMExited
231                mPhone.exitEmergencyCallbackMode();
232            }
233            int mPreferredTtyMode = Settings.Secure.getInt(
234                mPhone.getContext().getContentResolver(),
235                Settings.Secure.PREFERRED_TTY_MODE,
236                Phone.TTY_MODE_OFF);
237           mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, mPreferredTtyMode, null);
238
239        } catch (ImsException e) {
240            loge("getImsService: " + e);
241            //Leave mImsManager as null, then CallStateException will be thrown when dialing
242            mImsManager = null;
243        }
244    }
245
246    public void dispose() {
247        if (DBG) log("dispose");
248        mRingingCall.dispose();
249        mBackgroundCall.dispose();
250        mForegroundCall.dispose();
251        mHandoverCall.dispose();
252
253        clearDisconnected();
254        mPhone.getContext().unregisterReceiver(mReceiver);
255    }
256
257    @Override
258    protected void finalize() {
259        log("ImsPhoneCallTracker finalized");
260    }
261
262    //***** Instance Methods
263
264    //***** Public Methods
265    @Override
266    public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
267        Registrant r = new Registrant(h, what, obj);
268        mVoiceCallStartedRegistrants.add(r);
269    }
270
271    @Override
272    public void unregisterForVoiceCallStarted(Handler h) {
273        mVoiceCallStartedRegistrants.remove(h);
274    }
275
276    @Override
277    public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
278        Registrant r = new Registrant(h, what, obj);
279        mVoiceCallEndedRegistrants.add(r);
280    }
281
282    @Override
283    public void unregisterForVoiceCallEnded(Handler h) {
284        mVoiceCallEndedRegistrants.remove(h);
285    }
286
287    Connection
288    dial(String dialString, int videoState, Bundle intentExtras) throws CallStateException {
289        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
290        int oirMode = sp.getInt(PhoneBase.CLIR_KEY, CommandsInterface.CLIR_DEFAULT);
291        return dial(dialString, oirMode, videoState, intentExtras);
292    }
293
294    /**
295     * oirMode is one of the CLIR_ constants
296     */
297    synchronized Connection
298    dial(String dialString, int clirMode, int videoState, Bundle intentExtras)
299            throws CallStateException {
300        boolean isPhoneInEcmMode = SystemProperties.getBoolean(
301                TelephonyProperties.PROPERTY_INECM_MODE, false);
302        boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString);
303
304        if (DBG) log("dial clirMode=" + clirMode);
305
306        // note that this triggers call state changed notif
307        clearDisconnected();
308
309        if (mImsManager == null) {
310            throw new CallStateException("service not available");
311        }
312
313        if (!canDial()) {
314            throw new CallStateException("cannot dial in current state");
315        }
316
317        if (isPhoneInEcmMode && isEmergencyNumber) {
318            handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER);
319        }
320
321        boolean holdBeforeDial = false;
322
323        // The new call must be assigned to the foreground call.
324        // That call must be idle, so place anything that's
325        // there on hold
326        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
327            if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
328                //we should have failed in !canDial() above before we get here
329                throw new CallStateException("cannot dial in current state");
330            }
331            // foreground call is empty for the newly dialed connection
332            holdBeforeDial = true;
333            // Cache the video state for pending MO call.
334            mPendingCallVideoState = videoState;
335            switchWaitingOrHoldingAndActive();
336        }
337
338        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
339        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
340
341        mClirMode = clirMode;
342
343        synchronized (mSyncHold) {
344            if (holdBeforeDial) {
345                fgState = mForegroundCall.getState();
346                bgState = mBackgroundCall.getState();
347
348                //holding foreground call failed
349                if (fgState == ImsPhoneCall.State.ACTIVE) {
350                    throw new CallStateException("cannot dial in current state");
351                }
352
353                //holding foreground call succeeded
354                if (bgState == ImsPhoneCall.State.HOLDING) {
355                    holdBeforeDial = false;
356                }
357            }
358
359            mPendingMO = new ImsPhoneConnection(mPhone.getContext(),
360                    checkForTestEmergencyNumber(dialString), this, mForegroundCall);
361        }
362        addConnection(mPendingMO);
363
364        if (!holdBeforeDial) {
365            if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
366                dialInternal(mPendingMO, clirMode, videoState);
367            } else {
368                try {
369                    getEcbmInterface().exitEmergencyCallbackMode();
370                } catch (ImsException e) {
371                    e.printStackTrace();
372                    throw new CallStateException("service not available");
373                }
374                mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
375                pendingCallClirMode = clirMode;
376                mPendingCallVideoState = videoState;
377                pendingCallInEcm = true;
378            }
379        }
380
381        updatePhoneState();
382        mPhone.notifyPreciseCallStateChanged();
383
384        return mPendingMO;
385    }
386
387    private void handleEcmTimer(int action) {
388        mPhone.handleTimerInEmergencyCallbackMode(action);
389        switch (action) {
390            case ImsPhone.CANCEL_ECM_TIMER:
391                break;
392            case ImsPhone.RESTART_ECM_TIMER:
393                break;
394            default:
395                log("handleEcmTimer, unsupported action " + action);
396        }
397    }
398
399    private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState) {
400        if (conn == null) {
401            return;
402        }
403
404        if (conn.getAddress()== null || conn.getAddress().length() == 0
405                || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
406            // Phone number is invalid
407            conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER);
408            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
409            return;
410        }
411
412        // Always unmute when initiating a new call
413        setMute(false);
414        int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ?
415                ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
416        int callType = ImsCallProfile.getCallTypeFromVideoState(videoState);
417        //TODO(vt): Is this sufficient?  At what point do we know the video state of the call?
418        conn.setVideoState(videoState);
419
420        try {
421            String[] callees = new String[] { conn.getAddress() };
422            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
423                    serviceType, callType);
424            profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
425
426            ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
427                    callees, mImsCallListener);
428            conn.setImsCall(imsCall);
429
430            setVideoCallProvider(conn, imsCall);
431        } catch (ImsException e) {
432            loge("dialInternal : " + e);
433            conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
434            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
435        } catch (RemoteException e) {
436        }
437    }
438
439    /**
440     * Accepts a call with the specified video state.  The video state is the video state that the
441     * user has agreed upon in the InCall UI.
442     *
443     * @param videoState The video State
444     * @throws CallStateException
445     */
446    void acceptCall (int videoState) throws CallStateException {
447        if (DBG) log("acceptCall");
448
449        if (mForegroundCall.getState().isAlive()
450                && mBackgroundCall.getState().isAlive()) {
451            throw new CallStateException("cannot accept call");
452        }
453
454        if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING)
455                && mForegroundCall.getState().isAlive()) {
456            setMute(false);
457            // Cache video state for pending MT call.
458            mPendingCallVideoState = videoState;
459            switchWaitingOrHoldingAndActive();
460        } else if (mRingingCall.getState().isRinging()) {
461            if (DBG) log("acceptCall: incoming...");
462            // Always unmute when answering a new call
463            setMute(false);
464            try {
465                ImsCall imsCall = mRingingCall.getImsCall();
466                if (imsCall != null) {
467                    imsCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
468                } else {
469                    throw new CallStateException("no valid ims call");
470                }
471            } catch (ImsException e) {
472                throw new CallStateException("cannot accept call");
473            }
474        } else {
475            throw new CallStateException("phone not ringing");
476        }
477    }
478
479    void
480    rejectCall () throws CallStateException {
481        if (DBG) log("rejectCall");
482
483        if (mRingingCall.getState().isRinging()) {
484            hangup(mRingingCall);
485        } else {
486            throw new CallStateException("phone not ringing");
487        }
488    }
489
490
491    private void switchAfterConferenceSuccess() {
492        if (DBG) log("switchAfterConferenceSuccess fg =" + mForegroundCall.getState() +
493                ", bg = " + mBackgroundCall.getState());
494
495        if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
496            log("switchAfterConferenceSuccess");
497            mForegroundCall.switchWith(mBackgroundCall);
498        }
499    }
500
501    void
502    switchWaitingOrHoldingAndActive() throws CallStateException {
503        if (DBG) log("switchWaitingOrHoldingAndActive");
504
505        if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
506            throw new CallStateException("cannot be in the incoming state");
507        }
508
509        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
510            ImsCall imsCall = mForegroundCall.getImsCall();
511            if (imsCall == null) {
512                throw new CallStateException("no ims call");
513            }
514
515            // Swap the ImsCalls pointed to by the foreground and background ImsPhoneCalls.
516            // If hold or resume later fails, we will swap them back.
517            mSwitchingFgAndBgCalls = true;
518            mCallExpectedToResume = mBackgroundCall.getImsCall();
519            mForegroundCall.switchWith(mBackgroundCall);
520
521            // Hold the foreground call; once the foreground call is held, the background call will
522            // be resumed.
523            try {
524                imsCall.hold();
525            } catch (ImsException e) {
526                mForegroundCall.switchWith(mBackgroundCall);
527                throw new CallStateException(e.getMessage());
528            }
529        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
530            resumeWaitingOrHolding();
531        }
532    }
533
534    void
535    conference() {
536        if (DBG) log("conference");
537
538        ImsCall fgImsCall = mForegroundCall.getImsCall();
539        if (fgImsCall == null) {
540            log("conference no foreground ims call");
541            return;
542        }
543
544        ImsCall bgImsCall = mBackgroundCall.getImsCall();
545        if (bgImsCall == null) {
546            log("conference no background ims call");
547            return;
548        }
549
550        // Keep track of the connect time of the earliest call so that it can be set on the
551        // {@code ImsConference} when it is created.
552        long conferenceConnectTime = Math.min(mForegroundCall.getEarliestConnectTime(),
553                mBackgroundCall.getEarliestConnectTime());
554        ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection();
555        if (foregroundConnection != null) {
556            foregroundConnection.setConferenceConnectTime(conferenceConnectTime);
557        }
558
559        try {
560            fgImsCall.merge(bgImsCall);
561        } catch (ImsException e) {
562            log("conference " + e.getMessage());
563        }
564    }
565
566    void
567    explicitCallTransfer() {
568        //TODO : implement
569    }
570
571    void
572    clearDisconnected() {
573        if (DBG) log("clearDisconnected");
574
575        internalClearDisconnected();
576
577        updatePhoneState();
578        mPhone.notifyPreciseCallStateChanged();
579    }
580
581    boolean
582    canConference() {
583        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
584            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
585            && !mBackgroundCall.isFull()
586            && !mForegroundCall.isFull();
587    }
588
589    boolean
590    canDial() {
591        boolean ret;
592        int serviceState = mPhone.getServiceState().getState();
593        String disableCall = SystemProperties.get(
594                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
595
596        ret = (serviceState != ServiceState.STATE_POWER_OFF)
597            && mPendingMO == null
598            && !mRingingCall.isRinging()
599            && !disableCall.equals("true")
600            && (!mForegroundCall.getState().isAlive()
601                    || !mBackgroundCall.getState().isAlive());
602
603        return ret;
604    }
605
606    boolean
607    canTransfer() {
608        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
609            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
610    }
611
612    //***** Private Instance Methods
613
614    private void
615    internalClearDisconnected() {
616        mRingingCall.clearDisconnected();
617        mForegroundCall.clearDisconnected();
618        mBackgroundCall.clearDisconnected();
619        mHandoverCall.clearDisconnected();
620    }
621
622    private void
623    updatePhoneState() {
624        PhoneConstants.State oldState = mState;
625
626        if (mRingingCall.isRinging()) {
627            mState = PhoneConstants.State.RINGING;
628        } else if (mPendingMO != null ||
629                !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
630            mState = PhoneConstants.State.OFFHOOK;
631        } else {
632            mState = PhoneConstants.State.IDLE;
633        }
634
635        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
636            mVoiceCallEndedRegistrants.notifyRegistrants(
637                    new AsyncResult(null, null, null));
638        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
639            mVoiceCallStartedRegistrants.notifyRegistrants (
640                    new AsyncResult(null, null, null));
641        }
642
643        if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
644
645        if (mState != oldState) {
646            mPhone.notifyPhoneStateChanged();
647        }
648    }
649
650    private void
651    handleRadioNotAvailable() {
652        // handlePollCalls will clear out its
653        // call list when it gets the CommandException
654        // error result from this
655        pollCallsWhenSafe();
656    }
657
658    private void
659    dumpState() {
660        List l;
661
662        log("Phone State:" + mState);
663
664        log("Ringing call: " + mRingingCall.toString());
665
666        l = mRingingCall.getConnections();
667        for (int i = 0, s = l.size(); i < s; i++) {
668            log(l.get(i).toString());
669        }
670
671        log("Foreground call: " + mForegroundCall.toString());
672
673        l = mForegroundCall.getConnections();
674        for (int i = 0, s = l.size(); i < s; i++) {
675            log(l.get(i).toString());
676        }
677
678        log("Background call: " + mBackgroundCall.toString());
679
680        l = mBackgroundCall.getConnections();
681        for (int i = 0, s = l.size(); i < s; i++) {
682            log(l.get(i).toString());
683        }
684
685    }
686
687    //***** Called from ImsPhone
688
689    void setUiTTYMode(int uiTtyMode, Message onComplete) {
690        try {
691            mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, uiTtyMode, onComplete);
692        } catch (ImsException e) {
693            loge("setTTYMode : " + e);
694            mPhone.sendErrorResponse(onComplete, e);
695        }
696    }
697
698    /*package*/ void setMute(boolean mute) {
699        mDesiredMute = mute;
700        mForegroundCall.setMute(mute);
701    }
702
703    /*package*/ boolean getMute() {
704        return mDesiredMute;
705    }
706
707    /* package */ void sendDtmf(char c, Message result) {
708        if (DBG) log("sendDtmf");
709
710        ImsCall imscall = mForegroundCall.getImsCall();
711        if (imscall != null) {
712            imscall.sendDtmf(c, result);
713        }
714    }
715
716    /*package*/ void
717    startDtmf(char c) {
718        if (DBG) log("startDtmf");
719
720        ImsCall imscall = mForegroundCall.getImsCall();
721        if (imscall != null) {
722            imscall.startDtmf(c);
723        } else {
724            loge("startDtmf : no foreground call");
725        }
726    }
727
728    /*package*/ void
729    stopDtmf() {
730        if (DBG) log("stopDtmf");
731
732        ImsCall imscall = mForegroundCall.getImsCall();
733        if (imscall != null) {
734            imscall.stopDtmf();
735        } else {
736            loge("stopDtmf : no foreground call");
737        }
738    }
739
740    //***** Called from ImsPhoneConnection
741
742    /*package*/ void
743    hangup (ImsPhoneConnection conn) throws CallStateException {
744        if (DBG) log("hangup connection");
745
746        if (conn.getOwner() != this) {
747            throw new CallStateException ("ImsPhoneConnection " + conn
748                    + "does not belong to ImsPhoneCallTracker " + this);
749        }
750
751        hangup(conn.getCall());
752    }
753
754    //***** Called from ImsPhoneCall
755
756    /* package */ void
757    hangup (ImsPhoneCall call) throws CallStateException {
758        if (DBG) log("hangup call");
759
760        if (call.getConnections().size() == 0) {
761            throw new CallStateException("no connections");
762        }
763
764        ImsCall imsCall = call.getImsCall();
765        boolean rejectCall = false;
766
767        if (call == mRingingCall) {
768            if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
769            rejectCall = true;
770        } else if (call == mForegroundCall) {
771            if (call.isDialingOrAlerting()) {
772                if (Phone.DEBUG_PHONE) {
773                    log("(foregnd) hangup dialing or alerting...");
774                }
775            } else {
776                if (Phone.DEBUG_PHONE) {
777                    log("(foregnd) hangup foreground");
778                }
779                //held call will be resumed by onCallTerminated
780            }
781        } else if (call == mBackgroundCall) {
782            if (Phone.DEBUG_PHONE) {
783                log("(backgnd) hangup waiting or background");
784            }
785        } else {
786            throw new CallStateException ("ImsPhoneCall " + call +
787                    "does not belong to ImsPhoneCallTracker " + this);
788        }
789
790        call.onHangupLocal();
791
792        try {
793            if (imsCall != null) {
794                if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
795                else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
796            } else if (mPendingMO != null && call == mForegroundCall) {
797                // is holding a foreground call
798                mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
799                mPendingMO.onDisconnect();
800                removeConnection(mPendingMO);
801                mPendingMO = null;
802                updatePhoneState();
803                removeMessages(EVENT_DIAL_PENDINGMO);
804            }
805        } catch (ImsException e) {
806            throw new CallStateException(e.getMessage());
807        }
808
809        mPhone.notifyPreciseCallStateChanged();
810    }
811
812    void callEndCleanupHandOverCallIfAny() {
813        if (mHandoverCall.mConnections.size() > 0) {
814            if (DBG) log("callEndCleanupHandOverCallIfAny, mHandoverCall.mConnections="
815                    + mHandoverCall.mConnections);
816            mHandoverCall.mConnections.clear();
817            mState = PhoneConstants.State.IDLE;
818        }
819    }
820
821    /* package */
822    void resumeWaitingOrHolding() throws CallStateException {
823        if (DBG) log("resumeWaitingOrHolding");
824
825        try {
826            if (mForegroundCall.getState().isAlive()) {
827                //resume foreground call after holding background call
828                //they were switched before holding
829                ImsCall imsCall = mForegroundCall.getImsCall();
830                if (imsCall != null) imsCall.resume();
831            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
832                //accept waiting call after holding background call
833                ImsCall imsCall = mRingingCall.getImsCall();
834                if (imsCall != null) {
835                    imsCall.accept(
836                        ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState));
837                }
838            } else {
839                //Just resume background call.
840                //To distinguish resuming call with swapping calls
841                //we do not switch calls.here
842                //ImsPhoneConnection.update will chnage the parent when completed
843                ImsCall imsCall = mBackgroundCall.getImsCall();
844                if (imsCall != null) imsCall.resume();
845            }
846        } catch (ImsException e) {
847            throw new CallStateException(e.getMessage());
848        }
849    }
850
851    /* package */
852    void sendUSSD (String ussdString, Message response) {
853        if (DBG) log("sendUSSD");
854
855        try {
856            if (mUssdSession != null) {
857                mUssdSession.sendUssd(ussdString);
858                AsyncResult.forMessage(response, null, null);
859                response.sendToTarget();
860                return;
861            }
862
863            String[] callees = new String[] { ussdString };
864            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
865                    ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
866            profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
867                    ImsCallProfile.DIALSTRING_USSD);
868
869            mUssdSession = mImsManager.makeCall(mServiceId, profile,
870                    callees, mImsUssdListener);
871        } catch (ImsException e) {
872            loge("sendUSSD : " + e);
873            mPhone.sendErrorResponse(response, e);
874        }
875    }
876
877    /* package */
878    void cancelUSSD() {
879        if (mUssdSession == null) return;
880
881        try {
882            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
883        } catch (ImsException e) {
884        }
885
886    }
887
888    private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) {
889        for (ImsPhoneConnection conn : mConnections) {
890            if (conn.getImsCall() == imsCall) {
891                return conn;
892            }
893        }
894        return null;
895    }
896
897    private synchronized void removeConnection(ImsPhoneConnection conn) {
898        mConnections.remove(conn);
899    }
900
901    private synchronized void addConnection(ImsPhoneConnection conn) {
902        mConnections.add(conn);
903    }
904
905    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
906        if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
907
908        if (imsCall == null) return;
909
910        boolean changed = false;
911        ImsPhoneConnection conn = findConnection(imsCall);
912
913        if (conn == null) {
914            // TODO : what should be done?
915            return;
916        }
917
918        changed = conn.update(imsCall, state);
919
920        if (state == ImsPhoneCall.State.DISCONNECTED) {
921            changed = conn.onDisconnect(cause) || changed;
922            //detach the disconnected connections
923            conn.getCall().detach(conn);
924            removeConnection(conn);
925        }
926
927        if (changed) {
928            if (conn.getCall() == mHandoverCall) return;
929            updatePhoneState();
930            mPhone.notifyPreciseCallStateChanged();
931        }
932    }
933
934    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
935        int cause = DisconnectCause.ERROR_UNSPECIFIED;
936
937        //int type = reasonInfo.getReasonType();
938        int code = reasonInfo.getCode();
939        switch (code) {
940            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
941            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
942                return DisconnectCause.NUMBER_UNREACHABLE;
943
944            case ImsReasonInfo.CODE_SIP_BUSY:
945                return DisconnectCause.BUSY;
946
947            case ImsReasonInfo.CODE_USER_TERMINATED:
948                return DisconnectCause.LOCAL;
949
950            case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
951                return DisconnectCause.INCOMING_REJECTED;
952
953            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
954                return DisconnectCause.NORMAL;
955
956            case ImsReasonInfo.CODE_SIP_REDIRECTED:
957            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
958            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
959            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
960            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
961            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
962                return DisconnectCause.SERVER_ERROR;
963
964            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
965            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
966            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
967                return DisconnectCause.SERVER_UNREACHABLE;
968
969            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
970            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
971            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
972            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
973            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
974            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
975            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
976            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
977                return DisconnectCause.OUT_OF_SERVICE;
978
979            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
980            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
981            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
982            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
983                return DisconnectCause.TIMED_OUT;
984
985            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
986            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
987                return DisconnectCause.POWER_OFF;
988
989            case ImsReasonInfo.CODE_FDN_BLOCKED:
990                return DisconnectCause.FDN_BLOCKED;
991            default:
992        }
993
994        return cause;
995    }
996
997    /**
998     * Listen to the IMS call state change
999     */
1000    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
1001        @Override
1002        public void onCallProgressing(ImsCall imsCall) {
1003            if (DBG) log("onCallProgressing");
1004
1005            mPendingMO = null;
1006            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
1007                    DisconnectCause.NOT_DISCONNECTED);
1008        }
1009
1010        @Override
1011        public void onCallStarted(ImsCall imsCall) {
1012            if (DBG) log("onCallStarted");
1013
1014            mPendingMO = null;
1015            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1016                    DisconnectCause.NOT_DISCONNECTED);
1017        }
1018
1019        @Override
1020        public void onCallUpdated(ImsCall imsCall) {
1021            if (DBG) log("onCallUpdated");
1022            if (imsCall == null) {
1023                return;
1024            }
1025            ImsPhoneConnection conn = findConnection(imsCall);
1026            if (conn != null) {
1027                processCallStateChange(imsCall, conn.getCall().mState,
1028                        DisconnectCause.NOT_DISCONNECTED);
1029            }
1030        }
1031
1032        /**
1033         * onCallStartFailed will be invoked when:
1034         * case 1) Dialing fails
1035         * case 2) Ringing call is disconnected by local or remote user
1036         */
1037        @Override
1038        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1039            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
1040
1041            if (mPendingMO != null) {
1042                // To initiate dialing circuit-switched call
1043                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
1044                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
1045                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
1046                    mForegroundCall.detach(mPendingMO);
1047                    removeConnection(mPendingMO);
1048                    mPendingMO.finalize();
1049                    mPendingMO = null;
1050                    mPhone.initiateSilentRedial();
1051                    return;
1052                } else {
1053                    int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1054                    processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1055                }
1056                mPendingMO = null;
1057            }
1058        }
1059
1060        @Override
1061        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1062            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
1063
1064            ImsPhoneCall.State oldState = mForegroundCall.getState();
1065            int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1066            ImsPhoneConnection conn = findConnection(imsCall);
1067            if (DBG) log("cause = " + cause + " conn = " + conn);
1068
1069            if (conn != null && conn.isIncoming() && conn.getConnectTime() == 0) {
1070                // Missed
1071                if (cause == DisconnectCause.NORMAL) {
1072                    cause = DisconnectCause.INCOMING_MISSED;
1073                } else {
1074                    cause = DisconnectCause.INCOMING_REJECTED;
1075                }
1076                if (DBG) log("Incoming connection of 0 connect time detected - translated cause = "
1077                        + cause);
1078
1079            }
1080
1081            if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) {
1082                // Call was terminated while it is merged instead of a remote disconnect.
1083                cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY;
1084            }
1085
1086            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1087            if (mForegroundCall.getState() != ImsPhoneCall.State.ACTIVE) {
1088                if (mRingingCall.getState().isRinging()) {
1089                    // Drop pending MO. We should address incoming call first
1090                    mPendingMO = null;
1091                } else if (mPendingMO != null) {
1092                    sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1093                }
1094            }
1095        }
1096
1097        @Override
1098        public void onCallHeld(ImsCall imsCall) {
1099            if (DBG) log("onCallHeld");
1100
1101            synchronized (mSyncHold) {
1102                ImsPhoneCall.State oldState = mBackgroundCall.getState();
1103                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
1104                        DisconnectCause.NOT_DISCONNECTED);
1105
1106                // Note: If we're performing a switchWaitingOrHoldingAndActive, the call to
1107                // processCallStateChange above may have caused the mBackgroundCall and
1108                // mForegroundCall references below to change meaning.  Watch out for this if you
1109                // are reading through this code.
1110                if (oldState == ImsPhoneCall.State.ACTIVE) {
1111                    // Note: This case comes up when we have just held a call in response to a
1112                    // switchWaitingOrHoldingAndActive.  We now need to resume the background call.
1113                    // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called.
1114                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
1115                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
1116
1117                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1118                    } else {
1119                        //when multiple connections belong to background call,
1120                        //only the first callback reaches here
1121                        //otherwise the oldState is already HOLDING
1122                        if (mPendingMO != null) {
1123                            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1124                        }
1125
1126                        // In this case there will be no call resumed, so we can assume that we
1127                        // are done switching fg and bg calls now.
1128                        // This may happen if there is no BG call and we are holding a call so that
1129                        // we can dial another one.
1130                        mSwitchingFgAndBgCalls = false;
1131                    }
1132                }
1133            }
1134        }
1135
1136        @Override
1137        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1138            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
1139
1140            synchronized (mSyncHold) {
1141                ImsPhoneCall.State bgState = mBackgroundCall.getState();
1142                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
1143                    // disconnected while processing hold
1144                    if (mPendingMO != null) {
1145                        sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1146                    }
1147                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
1148                    mForegroundCall.switchWith(mBackgroundCall);
1149
1150                    if (mPendingMO != null) {
1151                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
1152                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
1153                    }
1154                }
1155            }
1156        }
1157
1158        @Override
1159        public void onCallResumed(ImsCall imsCall) {
1160            if (DBG) log("onCallResumed");
1161
1162            // If we are the in midst of swapping FG and BG calls and the call we end up resuming
1163            // is not the one we expected, we likely had a resume failure and we need to swap the
1164            // FG and BG calls back.
1165            if (mSwitchingFgAndBgCalls && imsCall != mCallExpectedToResume) {
1166                mForegroundCall.switchWith(mBackgroundCall);
1167                mSwitchingFgAndBgCalls = false;
1168                mCallExpectedToResume = null;
1169            }
1170            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1171                    DisconnectCause.NOT_DISCONNECTED);
1172        }
1173
1174        @Override
1175        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1176            // TODO : What should be done?
1177            // If we are in the midst of swapping the FG and BG calls and we got a resume fail, we
1178            // need to swap back the FG and BG calls.
1179            if (mSwitchingFgAndBgCalls && imsCall == mCallExpectedToResume) {
1180                mForegroundCall.switchWith(mBackgroundCall);
1181                mCallExpectedToResume = null;
1182                mSwitchingFgAndBgCalls = false;
1183            }
1184            mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME);
1185        }
1186
1187        @Override
1188        public void onCallResumeReceived(ImsCall imsCall) {
1189            if (DBG) log("onCallResumeReceived");
1190
1191            if (mOnHoldToneStarted) {
1192                mPhone.stopOnHoldTone();
1193                mOnHoldToneStarted = false;
1194            }
1195
1196            SuppServiceNotification supp = new SuppServiceNotification();
1197            // Type of notification: 0 = MO; 1 = MT
1198            // Refer SuppServiceNotification class documentation.
1199            supp.notificationType = 1;
1200            supp.code = SuppServiceNotification.MT_CODE_CALL_RETRIEVED;
1201            mPhone.notifySuppSvcNotification(supp);
1202        }
1203
1204        @Override
1205        public void onCallHoldReceived(ImsCall imsCall) {
1206            if (DBG) log("onCallHoldReceived");
1207
1208            ImsPhoneConnection conn = findConnection(imsCall);
1209            if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
1210                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
1211                    mPhone.startOnHoldTone();
1212                    mOnHoldToneStarted = true;
1213                }
1214            }
1215
1216            SuppServiceNotification supp = new SuppServiceNotification();
1217            // Type of notification: 0 = MO; 1 = MT
1218            // Refer SuppServiceNotification class documentation.
1219            supp.notificationType = 1;
1220            supp.code = SuppServiceNotification.MT_CODE_CALL_ON_HOLD;
1221            mPhone.notifySuppSvcNotification(supp);
1222        }
1223
1224        @Override
1225        public void onCallSuppServiceReceived(ImsCall call,
1226                ImsSuppServiceNotification suppServiceInfo) {
1227            if (DBG) log("onCallSuppServiceReceived: suppServiceInfo=" + suppServiceInfo);
1228
1229            SuppServiceNotification supp = new SuppServiceNotification();
1230            supp.notificationType = suppServiceInfo.notificationType;
1231            supp.code = suppServiceInfo.code;
1232            supp.index = suppServiceInfo.index;
1233            supp.number = suppServiceInfo.number;
1234            supp.history = suppServiceInfo.history;
1235
1236            mPhone.notifySuppSvcNotification(supp);
1237        }
1238
1239        @Override
1240        public void onCallMerged(final ImsCall call, final ImsCall peerCall, boolean swapCalls) {
1241            if (DBG) log("onCallMerged");
1242
1243            ImsPhoneConnection peerConnection = findConnection(peerCall);
1244            mForegroundCall = findConnection(call).getCall();
1245            if (peerConnection != null) {
1246                mBackgroundCall = peerConnection.getCall();
1247            } else {
1248                log("onCallMerged :: Null connection for background call.");
1249            }
1250
1251            log("onCallMerged :: call.mSession=" + call.getSession());
1252            if (peerCall.getSession() != null) {
1253                log("onCallMerged :: b/g call session=" + peerCall.getSession());
1254            } else {
1255                log("onCallMerged :: b/g call session is null");
1256            }
1257
1258            if (swapCalls) {
1259                switchAfterConferenceSuccess();
1260            }
1261            mForegroundCall.merge(mBackgroundCall, ImsPhoneCall.State.ACTIVE);
1262
1263            // TODO Temporary code. Remove the try-catch block from the runnable once thread
1264            // synchronization is fixed.
1265            Runnable r = new Runnable() {
1266                @Override
1267                public void run() {
1268                    try {
1269                        final ImsPhoneConnection conn = findConnection(call);
1270                        log("onCallMerged: ImsPhoneConnection=" + conn);
1271                        log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1272                        setVideoCallProvider(conn, call);
1273                        log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1274                    } catch (Exception e) {
1275                        loge("onCallMerged: exception " + e);
1276                    }
1277                }
1278            };
1279
1280            ImsPhoneCallTracker.this.post(r);
1281
1282            // After merge complete, update foreground as Active
1283            // and background call as Held, if background call exists
1284            processCallStateChange(mForegroundCall.getImsCall(), ImsPhoneCall.State.ACTIVE,
1285                    DisconnectCause.NOT_DISCONNECTED);
1286            if (peerConnection != null) {
1287                processCallStateChange(mBackgroundCall.getImsCall(), ImsPhoneCall.State.HOLDING,
1288                    DisconnectCause.NOT_DISCONNECTED);
1289            }
1290
1291            // Check if the merge was requested by an existing conference call. In that
1292            // case, no further action is required.
1293            if (!call.isMergeRequestedByConf()) {
1294                log("onCallMerged :: calling onMultipartyStateChanged()");
1295                onMultipartyStateChanged(call, true);
1296            } else {
1297                log("onCallMerged :: Merge requested by existing conference.");
1298                // Reset the flag.
1299                call.resetIsMergeRequestedByConf(false);
1300            }
1301
1302        }
1303
1304        @Override
1305        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
1306            if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo);
1307
1308            // TODO: the call to notifySuppServiceFailed throws up the "merge failed" dialog
1309            // We should move this into the InCallService so that it is handled appropriately
1310            // based on the user facing UI.
1311            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
1312
1313            // Start plumbing this even through Telecom so other components can take
1314            // appropriate action.
1315            ImsPhoneConnection conn = findConnection(call);
1316            if (conn != null) {
1317                conn.onConferenceMergeFailed();
1318            }
1319        }
1320
1321        /**
1322         * Called when the state of IMS conference participant(s) has changed.
1323         *
1324         * @param call the call object that carries out the IMS call.
1325         * @param participants the participant(s) and their new state information.
1326         */
1327        @Override
1328        public void onConferenceParticipantsStateChanged(ImsCall call,
1329                List<ConferenceParticipant> participants) {
1330            if (DBG) log("onConferenceParticipantsStateChanged");
1331
1332            ImsPhoneConnection conn = findConnection(call);
1333            if (conn != null) {
1334                conn.updateConferenceParticipants(participants);
1335            }
1336        }
1337
1338        @Override
1339        public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
1340            mPhone.onTtyModeReceived(mode);
1341        }
1342
1343        @Override
1344        public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1345            ImsReasonInfo reasonInfo) {
1346            if (DBG) {
1347                log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
1348                    targetAccessTech + ", reasonInfo=" + reasonInfo);
1349            }
1350        }
1351
1352        @Override
1353        public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1354            ImsReasonInfo reasonInfo) {
1355            if (DBG) {
1356                log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech +
1357                    ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo);
1358            }
1359        }
1360
1361        /**
1362         * Handles a change to the multiparty state for an {@code ImsCall}.  Notifies the associated
1363         * {@link ImsPhoneConnection} of the change.
1364         *
1365         * @param imsCall The IMS call.
1366         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
1367         *      otherwise.
1368         */
1369        @Override
1370        public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
1371            if (DBG) log("onMultipartyStateChanged to " + (isMultiParty ? "Y" : "N"));
1372
1373            ImsPhoneConnection conn = findConnection(imsCall);
1374            if (conn != null) {
1375                conn.updateMultipartyState(isMultiParty);
1376            }
1377        }
1378    };
1379
1380    /**
1381     * Listen to the IMS call state change
1382     */
1383    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
1384        @Override
1385        public void onCallStarted(ImsCall imsCall) {
1386            if (DBG) log("mImsUssdListener onCallStarted");
1387
1388            if (imsCall == mUssdSession) {
1389                if (mPendingUssd != null) {
1390                    AsyncResult.forMessage(mPendingUssd);
1391                    mPendingUssd.sendToTarget();
1392                    mPendingUssd = null;
1393                }
1394            }
1395        }
1396
1397        @Override
1398        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1399            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
1400
1401            onCallTerminated(imsCall, reasonInfo);
1402        }
1403
1404        @Override
1405        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1406            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
1407
1408            if (imsCall == mUssdSession) {
1409                mUssdSession = null;
1410                if (mPendingUssd != null) {
1411                    CommandException ex =
1412                            new CommandException(CommandException.Error.GENERIC_FAILURE);
1413                    AsyncResult.forMessage(mPendingUssd, null, ex);
1414                    mPendingUssd.sendToTarget();
1415                    mPendingUssd = null;
1416                }
1417            }
1418            imsCall.close();
1419        }
1420
1421        @Override
1422        public void onCallUssdMessageReceived(ImsCall call,
1423                int mode, String ussdMessage) {
1424            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
1425
1426            int ussdMode = -1;
1427
1428            switch(mode) {
1429                case ImsCall.USSD_MODE_REQUEST:
1430                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
1431                    break;
1432
1433                case ImsCall.USSD_MODE_NOTIFY:
1434                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
1435                    break;
1436            }
1437
1438            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
1439        }
1440    };
1441
1442    /**
1443     * Listen to the IMS service state change
1444     *
1445     */
1446    private ImsConnectionStateListener mImsConnectionStateListener =
1447        new ImsConnectionStateListener() {
1448        @Override
1449        public void onImsConnected() {
1450            if (DBG) log("onImsConnected");
1451            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1452            mPhone.setImsRegistered(true);
1453        }
1454
1455        @Override
1456        public void onImsDisconnected(ImsReasonInfo imsReasonInfo) {
1457            if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
1458            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1459            mPhone.setImsRegistered(false);
1460            mPhone.processDisconnectReason(imsReasonInfo);
1461        }
1462
1463        @Override
1464        public void onImsProgressing() {
1465            if (DBG) log("onImsProgressing");
1466            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1467            mPhone.setImsRegistered(false);
1468        }
1469
1470        @Override
1471        public void onImsResumed() {
1472            if (DBG) log("onImsResumed");
1473            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1474        }
1475
1476        @Override
1477        public void onImsSuspended() {
1478            if (DBG) log("onImsSuspended");
1479            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1480        }
1481
1482        @Override
1483        public void onFeatureCapabilityChanged(int serviceClass,
1484                int[] enabledFeatures, int[] disabledFeatures) {
1485            if (serviceClass == ImsServiceClass.MMTEL) {
1486                boolean tmpIsVideoCallEnabled = isVideoCallEnabled();
1487                // Check enabledFeatures to determine capabilities. We ignore disabledFeatures.
1488                for (int  i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE;
1489                        i <= ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI; i++) {
1490                    if (enabledFeatures[i] == i) {
1491                        // If the feature is set to its own integer value it is enabled.
1492                        if (DBG) log("onFeatureCapabilityChanged: i=" + i + ", value=true");
1493                        mImsFeatureEnabled[i] = true;
1494                    } else if (enabledFeatures[i]
1495                            == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) {
1496                        // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled.
1497                        if (DBG) log("onFeatureCapabilityChanged: i=" + i + ", value=false");
1498                        mImsFeatureEnabled[i] = false;
1499                    } else {
1500                        // Feature has unknown state; it is not its own value or -1.
1501                        if (DBG) {
1502                            loge("onFeatureCapabilityChanged: i=" + i + ", unexpectedValue="
1503                                + enabledFeatures[i]);
1504                        }
1505                    }
1506                }
1507                if (tmpIsVideoCallEnabled != isVideoCallEnabled()) {
1508                    mPhone.notifyForVideoCapabilityChanged(isVideoCallEnabled());
1509                }
1510
1511                // TODO: Use the ImsCallSession or ImsCallProfile to tell the initial Wifi state and
1512                // {@link ImsCallSession.Listener#callSessionHandover} to listen for changes to
1513                // wifi capability caused by a handover.
1514                if (DBG) log("onFeatureCapabilityChanged: isVowifiEnabled=" + isVowifiEnabled());
1515                for (ImsPhoneConnection connection : mConnections) {
1516                    connection.updateWifiState();
1517                }
1518
1519                mPhone.onFeatureCapabilityChanged();
1520            }
1521
1522            if (DBG) log("onFeatureCapabilityChanged: mImsFeatureEnabled=" +  mImsFeatureEnabled);
1523        }
1524
1525        @Override
1526        public void onVoiceMessageCountChanged(int count) {
1527            if (DBG) log("onVoiceMessageCountChanged :: count=" + count);
1528            mPhone.mDefaultPhone.setVoiceMessageCount(count);
1529        }
1530    };
1531
1532    /* package */
1533    ImsUtInterface getUtInterface() throws ImsException {
1534        if (mImsManager == null) {
1535            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1536        }
1537
1538        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
1539        return ut;
1540    }
1541
1542    private void transferHandoverConnections(ImsPhoneCall call) {
1543        if (call.mConnections != null) {
1544            for (Connection c : call.mConnections) {
1545                c.mPreHandoverState = call.mState;
1546                log ("Connection state before handover is " + c.getStateBeforeHandover());
1547            }
1548        }
1549        if (mHandoverCall.mConnections == null ) {
1550            mHandoverCall.mConnections = call.mConnections;
1551        } else { // Multi-call SRVCC
1552            mHandoverCall.mConnections.addAll(call.mConnections);
1553        }
1554        if (mHandoverCall.mConnections != null) {
1555            if (call.getImsCall() != null) {
1556                call.getImsCall().close();
1557            }
1558            for (Connection c : mHandoverCall.mConnections) {
1559                ((ImsPhoneConnection)c).changeParent(mHandoverCall);
1560                ((ImsPhoneConnection)c).releaseWakeLock();
1561            }
1562        }
1563        if (call.getState().isAlive()) {
1564            log ("Call is alive and state is " + call.mState);
1565            mHandoverCall.mState = call.mState;
1566        }
1567        call.mConnections.clear();
1568        call.mState = ImsPhoneCall.State.IDLE;
1569    }
1570
1571    /* package */
1572    void notifySrvccState(Call.SrvccState state) {
1573        if (DBG) log("notifySrvccState state=" + state);
1574
1575        mSrvccState = state;
1576
1577        if (mSrvccState == Call.SrvccState.COMPLETED) {
1578            transferHandoverConnections(mForegroundCall);
1579            transferHandoverConnections(mBackgroundCall);
1580            transferHandoverConnections(mRingingCall);
1581        }
1582    }
1583
1584    //****** Overridden from Handler
1585
1586    @Override
1587    public void
1588    handleMessage (Message msg) {
1589        AsyncResult ar;
1590        if (DBG) log("handleMessage what=" + msg.what);
1591
1592        switch (msg.what) {
1593            case EVENT_HANGUP_PENDINGMO:
1594                if (mPendingMO != null) {
1595                    mPendingMO.onDisconnect();
1596                    removeConnection(mPendingMO);
1597                    mPendingMO = null;
1598                }
1599
1600                updatePhoneState();
1601                mPhone.notifyPreciseCallStateChanged();
1602                break;
1603            case EVENT_RESUME_BACKGROUND:
1604                try {
1605                    resumeWaitingOrHolding();
1606                } catch (CallStateException e) {
1607                    if (Phone.DEBUG_PHONE) {
1608                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
1609                    }
1610                }
1611                break;
1612            case EVENT_DIAL_PENDINGMO:
1613                dialInternal(mPendingMO, mClirMode, mPendingCallVideoState);
1614                break;
1615
1616            case EVENT_EXIT_ECM_RESPONSE_CDMA:
1617                // no matter the result, we still do the same here
1618                if (pendingCallInEcm) {
1619                    dialInternal(mPendingMO, pendingCallClirMode,
1620                            mPendingCallVideoState);
1621                    pendingCallInEcm = false;
1622                }
1623                mPhone.unsetOnEcbModeExitResponse(this);
1624                break;
1625        }
1626    }
1627
1628    @Override
1629    protected void log(String msg) {
1630        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1631    }
1632
1633    protected void loge(String msg) {
1634        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1635    }
1636
1637    @Override
1638    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1639        pw.println("ImsPhoneCallTracker extends:");
1640        super.dump(fd, pw, args);
1641        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
1642        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
1643        pw.println(" mRingingCall=" + mRingingCall);
1644        pw.println(" mForegroundCall=" + mForegroundCall);
1645        pw.println(" mBackgroundCall=" + mBackgroundCall);
1646        pw.println(" mHandoverCall=" + mHandoverCall);
1647        pw.println(" mPendingMO=" + mPendingMO);
1648        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
1649        pw.println(" mPhone=" + mPhone);
1650        pw.println(" mDesiredMute=" + mDesiredMute);
1651        pw.println(" mState=" + mState);
1652    }
1653
1654    @Override
1655    protected void handlePollCalls(AsyncResult ar) {
1656    }
1657
1658    /* package */
1659    ImsEcbm getEcbmInterface() throws ImsException {
1660        if (mImsManager == null) {
1661            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1662        }
1663
1664        ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
1665        return ecbm;
1666    }
1667
1668    public boolean isInEmergencyCall() {
1669        return mIsInEmergencyCall;
1670    }
1671
1672    public boolean isVolteEnabled() {
1673        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE];
1674    }
1675
1676    public boolean isVowifiEnabled() {
1677        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI];
1678    }
1679
1680    public boolean isVideoCallEnabled() {
1681        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE]
1682                || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]);
1683    }
1684
1685    @Override
1686    public PhoneConstants.State getState() {
1687        return mState;
1688    }
1689
1690    private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
1691            throws RemoteException {
1692        IImsVideoCallProvider imsVideoCallProvider =
1693                imsCall.getCallSession().getVideoCallProvider();
1694        if (imsVideoCallProvider != null) {
1695            ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
1696                    new ImsVideoCallProviderWrapper(imsVideoCallProvider);
1697            conn.setVideoProvider(imsVideoCallProviderWrapper);
1698        }
1699    }
1700}
1701