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