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