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