ImsPhoneCallTracker.java revision c02d6b7f2f48a291b42892424e6858f71c3d84a9
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.getContext(), 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.getContext(),
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            // Translate call subject intent-extra from Telecom-specific extra key to the
438            // ImsCallProfile key.
439            if (intentExtras != null) {
440                if (intentExtras.containsKey(android.telecom.TelecomManager.EXTRA_CALL_SUBJECT)) {
441                    intentExtras.putString(ImsCallProfile.EXTRA_DISPLAY_TEXT,
442                            intentExtras.getString(
443                                    android.telecom.TelecomManager.EXTRA_CALL_SUBJECT));
444                }
445
446                // Pack the OEM-specific call extras.
447                profile.mCallExtras.putBundle(ImsCallProfile.EXTRA_OEM_EXTRAS, intentExtras);
448
449                // NOTE: Extras to be sent over the network are packed into the
450                // intentExtras individually, with uniquely defined keys.
451                // These key-value pairs are processed by IMS Service before
452                // being sent to the lower layers/to the network.
453            }
454
455            ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
456                    callees, mImsCallListener);
457            conn.setImsCall(imsCall);
458
459            setVideoCallProvider(conn, imsCall);
460        } catch (ImsException e) {
461            loge("dialInternal : " + e);
462            conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
463            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
464        } catch (RemoteException e) {
465        }
466    }
467
468    /**
469     * Accepts a call with the specified video state.  The video state is the video state that the
470     * user has agreed upon in the InCall UI.
471     *
472     * @param videoState The video State
473     * @throws CallStateException
474     */
475    void acceptCall (int videoState) throws CallStateException {
476        if (DBG) log("acceptCall");
477
478        if (mForegroundCall.getState().isAlive()
479                && mBackgroundCall.getState().isAlive()) {
480            throw new CallStateException("cannot accept call");
481        }
482
483        if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING)
484                && mForegroundCall.getState().isAlive()) {
485            setMute(false);
486            // Cache video state for pending MT call.
487            mPendingCallVideoState = videoState;
488            switchWaitingOrHoldingAndActive();
489        } else if (mRingingCall.getState().isRinging()) {
490            if (DBG) log("acceptCall: incoming...");
491            // Always unmute when answering a new call
492            setMute(false);
493            try {
494                ImsCall imsCall = mRingingCall.getImsCall();
495                if (imsCall != null) {
496                    imsCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
497                } else {
498                    throw new CallStateException("no valid ims call");
499                }
500            } catch (ImsException e) {
501                throw new CallStateException("cannot accept call");
502            }
503        } else {
504            throw new CallStateException("phone not ringing");
505        }
506    }
507
508    void
509    rejectCall () throws CallStateException {
510        if (DBG) log("rejectCall");
511
512        if (mRingingCall.getState().isRinging()) {
513            hangup(mRingingCall);
514        } else {
515            throw new CallStateException("phone not ringing");
516        }
517    }
518
519
520    private void switchAfterConferenceSuccess() {
521        if (DBG) log("switchAfterConferenceSuccess fg =" + mForegroundCall.getState() +
522                ", bg = " + mBackgroundCall.getState());
523
524        if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
525            log("switchAfterConferenceSuccess");
526            mForegroundCall.switchWith(mBackgroundCall);
527        }
528    }
529
530    void
531    switchWaitingOrHoldingAndActive() throws CallStateException {
532        if (DBG) log("switchWaitingOrHoldingAndActive");
533
534        if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
535            throw new CallStateException("cannot be in the incoming state");
536        }
537
538        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
539            ImsCall imsCall = mForegroundCall.getImsCall();
540            if (imsCall == null) {
541                throw new CallStateException("no ims call");
542            }
543
544            // Swap the ImsCalls pointed to by the foreground and background ImsPhoneCalls.
545            // If hold or resume later fails, we will swap them back.
546            mSwitchingFgAndBgCalls = true;
547            mCallExpectedToResume = mBackgroundCall.getImsCall();
548            mForegroundCall.switchWith(mBackgroundCall);
549
550            // Hold the foreground call; once the foreground call is held, the background call will
551            // be resumed.
552            try {
553                imsCall.hold();
554
555                // If there is no background call to resume, then don't expect there to be a switch.
556                if (mCallExpectedToResume == null) {
557                    mSwitchingFgAndBgCalls = false;
558                }
559            } catch (ImsException e) {
560                mForegroundCall.switchWith(mBackgroundCall);
561                throw new CallStateException(e.getMessage());
562            }
563        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
564            resumeWaitingOrHolding();
565        }
566    }
567
568    void
569    conference() {
570        if (DBG) log("conference");
571
572        ImsCall fgImsCall = mForegroundCall.getImsCall();
573        if (fgImsCall == null) {
574            log("conference no foreground ims call");
575            return;
576        }
577
578        ImsCall bgImsCall = mBackgroundCall.getImsCall();
579        if (bgImsCall == null) {
580            log("conference no background ims call");
581            return;
582        }
583
584        // Keep track of the connect time of the earliest call so that it can be set on the
585        // {@code ImsConference} when it is created.
586        long foregroundConnectTime = mForegroundCall.getEarliestConnectTime();
587        long backgroundConnectTime = mBackgroundCall.getEarliestConnectTime();
588        long conferenceConnectTime;
589        if (foregroundConnectTime > 0 && backgroundConnectTime > 0) {
590            conferenceConnectTime = Math.min(mForegroundCall.getEarliestConnectTime(),
591                    mBackgroundCall.getEarliestConnectTime());
592            log("conference - using connect time = " + conferenceConnectTime);
593        } else if (foregroundConnectTime > 0) {
594            log("conference - bg call connect time is 0; using fg = " + foregroundConnectTime);
595            conferenceConnectTime = foregroundConnectTime;
596        } else {
597            log("conference - fg call connect time is 0; using bg = " + backgroundConnectTime);
598            conferenceConnectTime = backgroundConnectTime;
599        }
600
601        ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection();
602        if (foregroundConnection != null) {
603            foregroundConnection.setConferenceConnectTime(conferenceConnectTime);
604        }
605
606        try {
607            fgImsCall.merge(bgImsCall);
608        } catch (ImsException e) {
609            log("conference " + e.getMessage());
610        }
611    }
612
613    void
614    explicitCallTransfer() {
615        //TODO : implement
616    }
617
618    void
619    clearDisconnected() {
620        if (DBG) log("clearDisconnected");
621
622        internalClearDisconnected();
623
624        updatePhoneState();
625        mPhone.notifyPreciseCallStateChanged();
626    }
627
628    boolean
629    canConference() {
630        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
631            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
632            && !mBackgroundCall.isFull()
633            && !mForegroundCall.isFull();
634    }
635
636    boolean
637    canDial() {
638        boolean ret;
639        int serviceState = mPhone.getServiceState().getState();
640        String disableCall = SystemProperties.get(
641                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
642
643        ret = (serviceState != ServiceState.STATE_POWER_OFF)
644            && mPendingMO == null
645            && !mRingingCall.isRinging()
646            && !disableCall.equals("true")
647            && (!mForegroundCall.getState().isAlive()
648                    || !mBackgroundCall.getState().isAlive());
649
650        return ret;
651    }
652
653    boolean
654    canTransfer() {
655        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
656            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
657    }
658
659    //***** Private Instance Methods
660
661    private void
662    internalClearDisconnected() {
663        mRingingCall.clearDisconnected();
664        mForegroundCall.clearDisconnected();
665        mBackgroundCall.clearDisconnected();
666        mHandoverCall.clearDisconnected();
667    }
668
669    private void
670    updatePhoneState() {
671        PhoneConstants.State oldState = mState;
672
673        if (mRingingCall.isRinging()) {
674            mState = PhoneConstants.State.RINGING;
675        } else if (mPendingMO != null ||
676                !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
677            mState = PhoneConstants.State.OFFHOOK;
678        } else {
679            mState = PhoneConstants.State.IDLE;
680        }
681
682        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
683            mVoiceCallEndedRegistrants.notifyRegistrants(
684                    new AsyncResult(null, null, null));
685        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
686            mVoiceCallStartedRegistrants.notifyRegistrants (
687                    new AsyncResult(null, null, null));
688        }
689
690        if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
691
692        if (mState != oldState) {
693            mPhone.notifyPhoneStateChanged();
694        }
695    }
696
697    private void
698    handleRadioNotAvailable() {
699        // handlePollCalls will clear out its
700        // call list when it gets the CommandException
701        // error result from this
702        pollCallsWhenSafe();
703    }
704
705    private void
706    dumpState() {
707        List l;
708
709        log("Phone State:" + mState);
710
711        log("Ringing call: " + mRingingCall.toString());
712
713        l = mRingingCall.getConnections();
714        for (int i = 0, s = l.size(); i < s; i++) {
715            log(l.get(i).toString());
716        }
717
718        log("Foreground call: " + mForegroundCall.toString());
719
720        l = mForegroundCall.getConnections();
721        for (int i = 0, s = l.size(); i < s; i++) {
722            log(l.get(i).toString());
723        }
724
725        log("Background call: " + mBackgroundCall.toString());
726
727        l = mBackgroundCall.getConnections();
728        for (int i = 0, s = l.size(); i < s; i++) {
729            log(l.get(i).toString());
730        }
731
732    }
733
734    //***** Called from ImsPhone
735
736    void setUiTTYMode(int uiTtyMode, Message onComplete) {
737        try {
738            mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, uiTtyMode, onComplete);
739        } catch (ImsException e) {
740            loge("setTTYMode : " + e);
741            mPhone.sendErrorResponse(onComplete, e);
742        }
743    }
744
745    /*package*/ void setMute(boolean mute) {
746        mDesiredMute = mute;
747        mForegroundCall.setMute(mute);
748    }
749
750    /*package*/ boolean getMute() {
751        return mDesiredMute;
752    }
753
754    /* package */ void sendDtmf(char c, Message result) {
755        if (DBG) log("sendDtmf");
756
757        ImsCall imscall = mForegroundCall.getImsCall();
758        if (imscall != null) {
759            imscall.sendDtmf(c, result);
760        }
761    }
762
763    /*package*/ void
764    startDtmf(char c) {
765        if (DBG) log("startDtmf");
766
767        ImsCall imscall = mForegroundCall.getImsCall();
768        if (imscall != null) {
769            imscall.startDtmf(c);
770        } else {
771            loge("startDtmf : no foreground call");
772        }
773    }
774
775    /*package*/ void
776    stopDtmf() {
777        if (DBG) log("stopDtmf");
778
779        ImsCall imscall = mForegroundCall.getImsCall();
780        if (imscall != null) {
781            imscall.stopDtmf();
782        } else {
783            loge("stopDtmf : no foreground call");
784        }
785    }
786
787    //***** Called from ImsPhoneConnection
788
789    /*package*/ void
790    hangup (ImsPhoneConnection conn) throws CallStateException {
791        if (DBG) log("hangup connection");
792
793        if (conn.getOwner() != this) {
794            throw new CallStateException ("ImsPhoneConnection " + conn
795                    + "does not belong to ImsPhoneCallTracker " + this);
796        }
797
798        hangup(conn.getCall());
799    }
800
801    //***** Called from ImsPhoneCall
802
803    /* package */ void
804    hangup (ImsPhoneCall call) throws CallStateException {
805        if (DBG) log("hangup call");
806
807        if (call.getConnections().size() == 0) {
808            throw new CallStateException("no connections");
809        }
810
811        ImsCall imsCall = call.getImsCall();
812        boolean rejectCall = false;
813
814        if (call == mRingingCall) {
815            if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
816            rejectCall = true;
817        } else if (call == mForegroundCall) {
818            if (call.isDialingOrAlerting()) {
819                if (Phone.DEBUG_PHONE) {
820                    log("(foregnd) hangup dialing or alerting...");
821                }
822            } else {
823                if (Phone.DEBUG_PHONE) {
824                    log("(foregnd) hangup foreground");
825                }
826                //held call will be resumed by onCallTerminated
827            }
828        } else if (call == mBackgroundCall) {
829            if (Phone.DEBUG_PHONE) {
830                log("(backgnd) hangup waiting or background");
831            }
832        } else {
833            throw new CallStateException ("ImsPhoneCall " + call +
834                    "does not belong to ImsPhoneCallTracker " + this);
835        }
836
837        call.onHangupLocal();
838
839        try {
840            if (imsCall != null) {
841                if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
842                else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
843            } else if (mPendingMO != null && call == mForegroundCall) {
844                // is holding a foreground call
845                mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
846                mPendingMO.onDisconnect();
847                removeConnection(mPendingMO);
848                mPendingMO = null;
849                updatePhoneState();
850                removeMessages(EVENT_DIAL_PENDINGMO);
851            }
852        } catch (ImsException e) {
853            throw new CallStateException(e.getMessage());
854        }
855
856        mPhone.notifyPreciseCallStateChanged();
857    }
858
859    void callEndCleanupHandOverCallIfAny() {
860        if (mHandoverCall.mConnections.size() > 0) {
861            if (DBG) log("callEndCleanupHandOverCallIfAny, mHandoverCall.mConnections="
862                    + mHandoverCall.mConnections);
863            mHandoverCall.mConnections.clear();
864            mState = PhoneConstants.State.IDLE;
865        }
866    }
867
868    /* package */
869    void resumeWaitingOrHolding() throws CallStateException {
870        if (DBG) log("resumeWaitingOrHolding");
871
872        try {
873            if (mForegroundCall.getState().isAlive()) {
874                //resume foreground call after holding background call
875                //they were switched before holding
876                ImsCall imsCall = mForegroundCall.getImsCall();
877                if (imsCall != null) imsCall.resume();
878            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
879                //accept waiting call after holding background call
880                ImsCall imsCall = mRingingCall.getImsCall();
881                if (imsCall != null) {
882                    imsCall.accept(
883                        ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState));
884                }
885            } else {
886                //Just resume background call.
887                //To distinguish resuming call with swapping calls
888                //we do not switch calls.here
889                //ImsPhoneConnection.update will chnage the parent when completed
890                ImsCall imsCall = mBackgroundCall.getImsCall();
891                if (imsCall != null) imsCall.resume();
892            }
893        } catch (ImsException e) {
894            throw new CallStateException(e.getMessage());
895        }
896    }
897
898    /* package */
899    void sendUSSD (String ussdString, Message response) {
900        if (DBG) log("sendUSSD");
901
902        try {
903            if (mUssdSession != null) {
904                mUssdSession.sendUssd(ussdString);
905                AsyncResult.forMessage(response, null, null);
906                response.sendToTarget();
907                return;
908            }
909
910            String[] callees = new String[] { ussdString };
911            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
912                    ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
913            profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
914                    ImsCallProfile.DIALSTRING_USSD);
915
916            mUssdSession = mImsManager.makeCall(mServiceId, profile,
917                    callees, mImsUssdListener);
918        } catch (ImsException e) {
919            loge("sendUSSD : " + e);
920            mPhone.sendErrorResponse(response, e);
921        }
922    }
923
924    /* package */
925    void cancelUSSD() {
926        if (mUssdSession == null) return;
927
928        try {
929            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
930        } catch (ImsException e) {
931        }
932
933    }
934
935    private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) {
936        for (ImsPhoneConnection conn : mConnections) {
937            if (conn.getImsCall() == imsCall) {
938                return conn;
939            }
940        }
941        return null;
942    }
943
944    private synchronized void removeConnection(ImsPhoneConnection conn) {
945        mConnections.remove(conn);
946    }
947
948    private synchronized void addConnection(ImsPhoneConnection conn) {
949        mConnections.add(conn);
950    }
951
952    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
953        if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
954        // This method is called on onCallUpdate() where there is not necessarily a call state
955        // change. In these situations, we'll ignore the state related updates and only process
956        // the change in media capabilities (as expected).  The default is to not ignore state
957        // changes so we do not change existing behavior.
958        processCallStateChange(imsCall, state, cause, false /* do not ignore state update */);
959    }
960
961    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause,
962            boolean ignoreState) {
963        if (DBG) {
964            log("processCallStateChange state=" + state + " cause=" + cause
965                    + " ignoreState=" + ignoreState);
966        }
967
968        if (imsCall == null) return;
969
970        boolean changed = false;
971        ImsPhoneConnection conn = findConnection(imsCall);
972
973        if (conn == null) {
974            // TODO : what should be done?
975            return;
976        }
977
978        // processCallStateChange is triggered for onCallUpdated as well.
979        // onCallUpdated should not modify the state of the call
980        // It should modify only other capabilities of call through updateMediaCapabilities
981        // State updates will be triggered through individual callbacks
982        // i.e. onCallHeld, onCallResume, etc and conn.update will be responsible for the update
983        if (ignoreState) {
984            conn.updateMediaCapabilities(imsCall);
985            return;
986        }
987
988        changed = conn.update(imsCall, state);
989        if (state == ImsPhoneCall.State.DISCONNECTED) {
990            changed = conn.onDisconnect(cause) || changed;
991            //detach the disconnected connections
992            conn.getCall().detach(conn);
993            removeConnection(conn);
994        }
995
996        if (changed) {
997            if (conn.getCall() == mHandoverCall) return;
998            updatePhoneState();
999            mPhone.notifyPreciseCallStateChanged();
1000        }
1001    }
1002
1003    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
1004        int cause = DisconnectCause.ERROR_UNSPECIFIED;
1005
1006        //int type = reasonInfo.getReasonType();
1007        int code = reasonInfo.getCode();
1008        switch (code) {
1009            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
1010            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
1011                return DisconnectCause.NUMBER_UNREACHABLE;
1012
1013            case ImsReasonInfo.CODE_SIP_BUSY:
1014                return DisconnectCause.BUSY;
1015
1016            case ImsReasonInfo.CODE_USER_TERMINATED:
1017                return DisconnectCause.LOCAL;
1018
1019            case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
1020                return DisconnectCause.INCOMING_REJECTED;
1021
1022            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
1023                return DisconnectCause.NORMAL;
1024
1025            case ImsReasonInfo.CODE_SIP_REDIRECTED:
1026            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
1027            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
1028            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
1029            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
1030            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
1031                return DisconnectCause.SERVER_ERROR;
1032
1033            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
1034            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
1035            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
1036                return DisconnectCause.SERVER_UNREACHABLE;
1037
1038            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
1039            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
1040            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
1041            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
1042            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
1043            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
1044            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
1045            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
1046                return DisconnectCause.OUT_OF_SERVICE;
1047
1048            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
1049            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
1050            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
1051            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
1052                return DisconnectCause.TIMED_OUT;
1053
1054            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
1055            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
1056                return DisconnectCause.POWER_OFF;
1057
1058            case ImsReasonInfo.CODE_FDN_BLOCKED:
1059                return DisconnectCause.FDN_BLOCKED;
1060            default:
1061        }
1062
1063        return cause;
1064    }
1065
1066    /**
1067     * Listen to the IMS call state change
1068     */
1069    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
1070        @Override
1071        public void onCallProgressing(ImsCall imsCall) {
1072            if (DBG) log("onCallProgressing");
1073
1074            mPendingMO = null;
1075            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
1076                    DisconnectCause.NOT_DISCONNECTED);
1077        }
1078
1079        @Override
1080        public void onCallStarted(ImsCall imsCall) {
1081            if (DBG) log("onCallStarted");
1082
1083            mPendingMO = null;
1084            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1085                    DisconnectCause.NOT_DISCONNECTED);
1086        }
1087
1088        @Override
1089        public void onCallUpdated(ImsCall imsCall) {
1090            if (DBG) log("onCallUpdated");
1091            if (imsCall == null) {
1092                return;
1093            }
1094            ImsPhoneConnection conn = findConnection(imsCall);
1095            if (conn != null) {
1096                processCallStateChange(imsCall, conn.getCall().mState,
1097                        DisconnectCause.NOT_DISCONNECTED, true /*ignore state update*/);
1098            }
1099        }
1100
1101        /**
1102         * onCallStartFailed will be invoked when:
1103         * case 1) Dialing fails
1104         * case 2) Ringing call is disconnected by local or remote user
1105         */
1106        @Override
1107        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1108            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
1109
1110            if (mPendingMO != null) {
1111                // To initiate dialing circuit-switched call
1112                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
1113                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
1114                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
1115                    mForegroundCall.detach(mPendingMO);
1116                    removeConnection(mPendingMO);
1117                    mPendingMO.finalize();
1118                    mPendingMO = null;
1119                    mPhone.initiateSilentRedial();
1120                    return;
1121                } else {
1122                    int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1123                    processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1124                }
1125                mPendingMO = null;
1126            }
1127        }
1128
1129        @Override
1130        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1131            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
1132
1133            ImsPhoneCall.State oldState = mForegroundCall.getState();
1134            int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1135            ImsPhoneConnection conn = findConnection(imsCall);
1136            if (DBG) log("cause = " + cause + " conn = " + conn);
1137
1138            if (conn != null && conn.isIncoming() && conn.getConnectTime() == 0) {
1139                // Missed
1140                if (cause == DisconnectCause.NORMAL) {
1141                    cause = DisconnectCause.INCOMING_MISSED;
1142                } else {
1143                    cause = DisconnectCause.INCOMING_REJECTED;
1144                }
1145                if (DBG) log("Incoming connection of 0 connect time detected - translated cause = "
1146                        + cause);
1147
1148            }
1149
1150            if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) {
1151                // Call was terminated while it is merged instead of a remote disconnect.
1152                cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY;
1153            }
1154
1155            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1156            if (mForegroundCall.getState() != ImsPhoneCall.State.ACTIVE) {
1157                if (mRingingCall.getState().isRinging()) {
1158                    // Drop pending MO. We should address incoming call first
1159                    mPendingMO = null;
1160                } else if (mPendingMO != null) {
1161                    sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1162                }
1163            }
1164        }
1165
1166        @Override
1167        public void onCallHeld(ImsCall imsCall) {
1168            if (DBG) {
1169                if (mForegroundCall.getImsCall() == imsCall) {
1170                    log("onCallHeld (fg) " + imsCall);
1171                } else if (mBackgroundCall.getImsCall() == imsCall) {
1172                    log("onCallHeld (bg) " + imsCall);
1173                }
1174            }
1175
1176            synchronized (mSyncHold) {
1177                ImsPhoneCall.State oldState = mBackgroundCall.getState();
1178                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
1179                        DisconnectCause.NOT_DISCONNECTED);
1180
1181                // Note: If we're performing a switchWaitingOrHoldingAndActive, the call to
1182                // processCallStateChange above may have caused the mBackgroundCall and
1183                // mForegroundCall references below to change meaning.  Watch out for this if you
1184                // are reading through this code.
1185                if (oldState == ImsPhoneCall.State.ACTIVE) {
1186                    // Note: This case comes up when we have just held a call in response to a
1187                    // switchWaitingOrHoldingAndActive.  We now need to resume the background call.
1188                    // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called.
1189                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
1190                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
1191
1192                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1193                    } else {
1194                        //when multiple connections belong to background call,
1195                        //only the first callback reaches here
1196                        //otherwise the oldState is already HOLDING
1197                        if (mPendingMO != null) {
1198                            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1199                        }
1200
1201                        // In this case there will be no call resumed, so we can assume that we
1202                        // are done switching fg and bg calls now.
1203                        // This may happen if there is no BG call and we are holding a call so that
1204                        // we can dial another one.
1205                        mSwitchingFgAndBgCalls = false;
1206                    }
1207                }
1208            }
1209        }
1210
1211        @Override
1212        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1213            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
1214
1215            synchronized (mSyncHold) {
1216                ImsPhoneCall.State bgState = mBackgroundCall.getState();
1217                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
1218                    // disconnected while processing hold
1219                    if (mPendingMO != null) {
1220                        sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1221                    }
1222                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
1223                    mForegroundCall.switchWith(mBackgroundCall);
1224
1225                    if (mPendingMO != null) {
1226                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
1227                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
1228                    }
1229                }
1230            }
1231        }
1232
1233        @Override
1234        public void onCallResumed(ImsCall imsCall) {
1235            if (DBG) log("onCallResumed");
1236
1237            // If we are the in midst of swapping FG and BG calls and the call we end up resuming
1238            // is not the one we expected, we likely had a resume failure and we need to swap the
1239            // FG and BG calls back.
1240            if (mSwitchingFgAndBgCalls && imsCall != mCallExpectedToResume) {
1241                if (DBG) {
1242                    log("onCallResumed : switching " + mForegroundCall + " with "
1243                            + mBackgroundCall);
1244                }
1245                mForegroundCall.switchWith(mBackgroundCall);
1246                mSwitchingFgAndBgCalls = false;
1247                mCallExpectedToResume = null;
1248            }
1249            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1250                    DisconnectCause.NOT_DISCONNECTED);
1251        }
1252
1253        @Override
1254        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1255            // If we are in the midst of swapping the FG and BG calls and we got a resume fail, we
1256            // need to swap back the FG and BG calls.
1257            if (mSwitchingFgAndBgCalls && imsCall == mCallExpectedToResume) {
1258                if (DBG) {
1259                    log("onCallResumeFailed : switching " + mForegroundCall + " with "
1260                            + mBackgroundCall);
1261                }
1262                mForegroundCall.switchWith(mBackgroundCall);
1263                mCallExpectedToResume = null;
1264                mSwitchingFgAndBgCalls = false;
1265            }
1266            mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME);
1267        }
1268
1269        @Override
1270        public void onCallResumeReceived(ImsCall imsCall) {
1271            if (DBG) log("onCallResumeReceived");
1272
1273            if (mOnHoldToneStarted) {
1274                mPhone.stopOnHoldTone();
1275                mOnHoldToneStarted = false;
1276            }
1277
1278            SuppServiceNotification supp = new SuppServiceNotification();
1279            // Type of notification: 0 = MO; 1 = MT
1280            // Refer SuppServiceNotification class documentation.
1281            supp.notificationType = 1;
1282            supp.code = SuppServiceNotification.MT_CODE_CALL_RETRIEVED;
1283            mPhone.notifySuppSvcNotification(supp);
1284        }
1285
1286        @Override
1287        public void onCallHoldReceived(ImsCall imsCall) {
1288            if (DBG) log("onCallHoldReceived");
1289
1290            ImsPhoneConnection conn = findConnection(imsCall);
1291            if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
1292                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
1293                    mPhone.startOnHoldTone();
1294                    mOnHoldToneStarted = true;
1295                }
1296            }
1297
1298            SuppServiceNotification supp = new SuppServiceNotification();
1299            // Type of notification: 0 = MO; 1 = MT
1300            // Refer SuppServiceNotification class documentation.
1301            supp.notificationType = 1;
1302            supp.code = SuppServiceNotification.MT_CODE_CALL_ON_HOLD;
1303            mPhone.notifySuppSvcNotification(supp);
1304        }
1305
1306        @Override
1307        public void onCallSuppServiceReceived(ImsCall call,
1308                ImsSuppServiceNotification suppServiceInfo) {
1309            if (DBG) log("onCallSuppServiceReceived: suppServiceInfo=" + suppServiceInfo);
1310
1311            SuppServiceNotification supp = new SuppServiceNotification();
1312            supp.notificationType = suppServiceInfo.notificationType;
1313            supp.code = suppServiceInfo.code;
1314            supp.index = suppServiceInfo.index;
1315            supp.number = suppServiceInfo.number;
1316            supp.history = suppServiceInfo.history;
1317
1318            mPhone.notifySuppSvcNotification(supp);
1319        }
1320
1321        @Override
1322        public void onCallMerged(final ImsCall call, final ImsCall peerCall, boolean swapCalls) {
1323            if (DBG) log("onCallMerged");
1324
1325            ImsPhoneCall foregroundImsPhoneCall = findConnection(call).getCall();
1326            ImsPhoneConnection peerConnection = findConnection(peerCall);
1327            ImsPhoneCall peerImsPhoneCall = peerConnection == null ? null
1328                    : peerConnection.getCall();
1329
1330            if (swapCalls) {
1331                switchAfterConferenceSuccess();
1332            }
1333            foregroundImsPhoneCall.merge(peerImsPhoneCall, ImsPhoneCall.State.ACTIVE);
1334
1335            // TODO Temporary code. Remove the try-catch block from the runnable once thread
1336            // synchronization is fixed.
1337            Runnable r = new Runnable() {
1338                @Override
1339                public void run() {
1340                    try {
1341                        final ImsPhoneConnection conn = findConnection(call);
1342                        log("onCallMerged: ImsPhoneConnection=" + conn);
1343                        log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1344                        setVideoCallProvider(conn, call);
1345                        log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1346                    } catch (Exception e) {
1347                        loge("onCallMerged: exception " + e);
1348                    }
1349                }
1350            };
1351
1352            ImsPhoneCallTracker.this.post(r);
1353
1354            // After merge complete, update foreground as Active
1355            // and background call as Held, if background call exists
1356            processCallStateChange(mForegroundCall.getImsCall(), ImsPhoneCall.State.ACTIVE,
1357                    DisconnectCause.NOT_DISCONNECTED);
1358            if (peerConnection != null) {
1359                processCallStateChange(mBackgroundCall.getImsCall(), ImsPhoneCall.State.HOLDING,
1360                    DisconnectCause.NOT_DISCONNECTED);
1361            }
1362
1363            // Check if the merge was requested by an existing conference call. In that
1364            // case, no further action is required.
1365            if (!call.isMergeRequestedByConf()) {
1366                log("onCallMerged :: calling onMultipartyStateChanged()");
1367                onMultipartyStateChanged(call, true);
1368            } else {
1369                log("onCallMerged :: Merge requested by existing conference.");
1370                // Reset the flag.
1371                call.resetIsMergeRequestedByConf(false);
1372            }
1373            logState();
1374        }
1375
1376        @Override
1377        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
1378            if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo);
1379
1380            // TODO: the call to notifySuppServiceFailed throws up the "merge failed" dialog
1381            // We should move this into the InCallService so that it is handled appropriately
1382            // based on the user facing UI.
1383            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
1384
1385            // Start plumbing this even through Telecom so other components can take
1386            // appropriate action.
1387            ImsPhoneConnection conn = findConnection(call);
1388            if (conn != null) {
1389                conn.onConferenceMergeFailed();
1390            }
1391        }
1392
1393        /**
1394         * Called when the state of IMS conference participant(s) has changed.
1395         *
1396         * @param call the call object that carries out the IMS call.
1397         * @param participants the participant(s) and their new state information.
1398         */
1399        @Override
1400        public void onConferenceParticipantsStateChanged(ImsCall call,
1401                List<ConferenceParticipant> participants) {
1402            if (DBG) log("onConferenceParticipantsStateChanged");
1403
1404            ImsPhoneConnection conn = findConnection(call);
1405            if (conn != null) {
1406                conn.updateConferenceParticipants(participants);
1407            }
1408        }
1409
1410        @Override
1411        public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
1412            mPhone.onTtyModeReceived(mode);
1413        }
1414
1415        @Override
1416        public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1417            ImsReasonInfo reasonInfo) {
1418            if (DBG) {
1419                log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
1420                    targetAccessTech + ", reasonInfo=" + reasonInfo);
1421            }
1422        }
1423
1424        @Override
1425        public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1426            ImsReasonInfo reasonInfo) {
1427            if (DBG) {
1428                log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech +
1429                    ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo);
1430            }
1431        }
1432
1433        /**
1434         * Handles a change to the multiparty state for an {@code ImsCall}.  Notifies the associated
1435         * {@link ImsPhoneConnection} of the change.
1436         *
1437         * @param imsCall The IMS call.
1438         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
1439         *      otherwise.
1440         */
1441        @Override
1442        public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
1443            if (DBG) log("onMultipartyStateChanged to " + (isMultiParty ? "Y" : "N"));
1444
1445            ImsPhoneConnection conn = findConnection(imsCall);
1446            if (conn != null) {
1447                conn.updateMultipartyState(isMultiParty);
1448            }
1449        }
1450    };
1451
1452    /**
1453     * Listen to the IMS call state change
1454     */
1455    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
1456        @Override
1457        public void onCallStarted(ImsCall imsCall) {
1458            if (DBG) log("mImsUssdListener onCallStarted");
1459
1460            if (imsCall == mUssdSession) {
1461                if (mPendingUssd != null) {
1462                    AsyncResult.forMessage(mPendingUssd);
1463                    mPendingUssd.sendToTarget();
1464                    mPendingUssd = null;
1465                }
1466            }
1467        }
1468
1469        @Override
1470        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1471            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
1472
1473            onCallTerminated(imsCall, reasonInfo);
1474        }
1475
1476        @Override
1477        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1478            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
1479
1480            if (imsCall == mUssdSession) {
1481                mUssdSession = null;
1482                if (mPendingUssd != null) {
1483                    CommandException ex =
1484                            new CommandException(CommandException.Error.GENERIC_FAILURE);
1485                    AsyncResult.forMessage(mPendingUssd, null, ex);
1486                    mPendingUssd.sendToTarget();
1487                    mPendingUssd = null;
1488                }
1489            }
1490            imsCall.close();
1491        }
1492
1493        @Override
1494        public void onCallUssdMessageReceived(ImsCall call,
1495                int mode, String ussdMessage) {
1496            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
1497
1498            int ussdMode = -1;
1499
1500            switch(mode) {
1501                case ImsCall.USSD_MODE_REQUEST:
1502                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
1503                    break;
1504
1505                case ImsCall.USSD_MODE_NOTIFY:
1506                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
1507                    break;
1508            }
1509
1510            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
1511        }
1512    };
1513
1514    /**
1515     * Listen to the IMS service state change
1516     *
1517     */
1518    private ImsConnectionStateListener mImsConnectionStateListener =
1519        new ImsConnectionStateListener() {
1520        @Override
1521        public void onImsConnected() {
1522            if (DBG) log("onImsConnected");
1523            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1524            mPhone.setImsRegistered(true);
1525        }
1526
1527        @Override
1528        public void onImsDisconnected(ImsReasonInfo imsReasonInfo) {
1529            if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
1530            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1531            mPhone.setImsRegistered(false);
1532            mPhone.processDisconnectReason(imsReasonInfo);
1533        }
1534
1535        @Override
1536        public void onImsProgressing() {
1537            if (DBG) log("onImsProgressing");
1538            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1539            mPhone.setImsRegistered(false);
1540        }
1541
1542        @Override
1543        public void onImsResumed() {
1544            if (DBG) log("onImsResumed");
1545            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1546        }
1547
1548        @Override
1549        public void onImsSuspended() {
1550            if (DBG) log("onImsSuspended");
1551            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1552        }
1553
1554        @Override
1555        public void onFeatureCapabilityChanged(int serviceClass,
1556                int[] enabledFeatures, int[] disabledFeatures) {
1557            if (serviceClass == ImsServiceClass.MMTEL) {
1558                boolean tmpIsVideoCallEnabled = isVideoCallEnabled();
1559                // Check enabledFeatures to determine capabilities. We ignore disabledFeatures.
1560                for (int  i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE;
1561                        i <= ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI; i++) {
1562                    if (enabledFeatures[i] == i) {
1563                        // If the feature is set to its own integer value it is enabled.
1564                        if (DBG) log("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i] + "): value=true");
1565                        mImsFeatureEnabled[i] = true;
1566                    } else if (enabledFeatures[i]
1567                            == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) {
1568                        // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled.
1569                        if (DBG) log("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i] + "): value=false");
1570                        mImsFeatureEnabled[i] = false;
1571                    } else {
1572                        // Feature has unknown state; it is not its own value or -1.
1573                        if (DBG) {
1574                            loge("onFeatureCapabilityChanged(" + i + ", " +mImsFeatureStrings[i] + "): unexpectedValue="
1575                                + enabledFeatures[i]);
1576                        }
1577                    }
1578                }
1579                if (tmpIsVideoCallEnabled != isVideoCallEnabled()) {
1580                    mPhone.notifyForVideoCapabilityChanged(isVideoCallEnabled());
1581                }
1582
1583                // TODO: Use the ImsCallSession or ImsCallProfile to tell the initial Wifi state and
1584                // {@link ImsCallSession.Listener#callSessionHandover} to listen for changes to
1585                // wifi capability caused by a handover.
1586                if (DBG) log("onFeatureCapabilityChanged: isVolteEnabled=" + isVolteEnabled()
1587                            + ", isVideoCallEnabled=" + isVideoCallEnabled()
1588                            + ", isVowifiEnabled=" + isVowifiEnabled()
1589                            + ", isUtEnabled=" + isUtEnabled());
1590                for (ImsPhoneConnection connection : mConnections) {
1591                    connection.updateWifiState();
1592                }
1593
1594                mPhone.onFeatureCapabilityChanged();
1595            }
1596        }
1597
1598        @Override
1599        public void onVoiceMessageCountChanged(int count) {
1600            if (DBG) log("onVoiceMessageCountChanged :: count=" + count);
1601            mPhone.mDefaultPhone.setVoiceMessageCount(count);
1602        }
1603    };
1604
1605    /* package */
1606    ImsUtInterface getUtInterface() throws ImsException {
1607        if (mImsManager == null) {
1608            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1609        }
1610
1611        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
1612        return ut;
1613    }
1614
1615    private void transferHandoverConnections(ImsPhoneCall call) {
1616        if (call.mConnections != null) {
1617            for (Connection c : call.mConnections) {
1618                c.mPreHandoverState = call.mState;
1619                log ("Connection state before handover is " + c.getStateBeforeHandover());
1620            }
1621        }
1622        if (mHandoverCall.mConnections == null ) {
1623            mHandoverCall.mConnections = call.mConnections;
1624        } else { // Multi-call SRVCC
1625            mHandoverCall.mConnections.addAll(call.mConnections);
1626        }
1627        if (mHandoverCall.mConnections != null) {
1628            if (call.getImsCall() != null) {
1629                call.getImsCall().close();
1630            }
1631            for (Connection c : mHandoverCall.mConnections) {
1632                ((ImsPhoneConnection)c).changeParent(mHandoverCall);
1633                ((ImsPhoneConnection)c).releaseWakeLock();
1634            }
1635        }
1636        if (call.getState().isAlive()) {
1637            log ("Call is alive and state is " + call.mState);
1638            mHandoverCall.mState = call.mState;
1639        }
1640        call.mConnections.clear();
1641        call.mState = ImsPhoneCall.State.IDLE;
1642    }
1643
1644    /* package */
1645    void notifySrvccState(Call.SrvccState state) {
1646        if (DBG) log("notifySrvccState state=" + state);
1647
1648        mSrvccState = state;
1649
1650        if (mSrvccState == Call.SrvccState.COMPLETED) {
1651            transferHandoverConnections(mForegroundCall);
1652            transferHandoverConnections(mBackgroundCall);
1653            transferHandoverConnections(mRingingCall);
1654        }
1655    }
1656
1657    //****** Overridden from Handler
1658
1659    @Override
1660    public void
1661    handleMessage (Message msg) {
1662        AsyncResult ar;
1663        if (DBG) log("handleMessage what=" + msg.what);
1664
1665        switch (msg.what) {
1666            case EVENT_HANGUP_PENDINGMO:
1667                if (mPendingMO != null) {
1668                    mPendingMO.onDisconnect();
1669                    removeConnection(mPendingMO);
1670                    mPendingMO = null;
1671                }
1672                mPendingIntentExtras = null;
1673                updatePhoneState();
1674                mPhone.notifyPreciseCallStateChanged();
1675                break;
1676            case EVENT_RESUME_BACKGROUND:
1677                try {
1678                    resumeWaitingOrHolding();
1679                } catch (CallStateException e) {
1680                    if (Phone.DEBUG_PHONE) {
1681                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
1682                    }
1683                }
1684                break;
1685            case EVENT_DIAL_PENDINGMO:
1686                dialInternal(mPendingMO, mClirMode, mPendingCallVideoState, mPendingIntentExtras);
1687                mPendingIntentExtras = null;
1688                break;
1689
1690            case EVENT_EXIT_ECM_RESPONSE_CDMA:
1691                // no matter the result, we still do the same here
1692                if (pendingCallInEcm) {
1693                    dialInternal(mPendingMO, pendingCallClirMode,
1694                            mPendingCallVideoState, mPendingIntentExtras);
1695                    mPendingIntentExtras = null;
1696                    pendingCallInEcm = false;
1697                }
1698                mPhone.unsetOnEcbModeExitResponse(this);
1699                break;
1700        }
1701    }
1702
1703    @Override
1704    protected void log(String msg) {
1705        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1706    }
1707
1708    protected void loge(String msg) {
1709        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1710    }
1711
1712    /**
1713     * Logs the current state of the ImsPhoneCallTracker.  Useful for debugging issues with
1714     * call tracking.
1715     */
1716    /* package */
1717    void logState() {
1718        if (!VERBOSE_STATE_LOGGING) {
1719            return;
1720        }
1721
1722        StringBuilder sb = new StringBuilder();
1723        sb.append("Current IMS PhoneCall State:\n");
1724        sb.append(" Foreground: ");
1725        sb.append(mForegroundCall);
1726        sb.append("\n");
1727        sb.append(" Background: ");
1728        sb.append(mBackgroundCall);
1729        sb.append("\n");
1730        sb.append(" Ringing: ");
1731        sb.append(mRingingCall);
1732        sb.append("\n");
1733        sb.append(" Handover: ");
1734        sb.append(mHandoverCall);
1735        sb.append("\n");
1736        Rlog.v(LOG_TAG, sb.toString());
1737    }
1738
1739    @Override
1740    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1741        pw.println("ImsPhoneCallTracker extends:");
1742        super.dump(fd, pw, args);
1743        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
1744        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
1745        pw.println(" mRingingCall=" + mRingingCall);
1746        pw.println(" mForegroundCall=" + mForegroundCall);
1747        pw.println(" mBackgroundCall=" + mBackgroundCall);
1748        pw.println(" mHandoverCall=" + mHandoverCall);
1749        pw.println(" mPendingMO=" + mPendingMO);
1750        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
1751        pw.println(" mPhone=" + mPhone);
1752        pw.println(" mDesiredMute=" + mDesiredMute);
1753        pw.println(" mState=" + mState);
1754    }
1755
1756    @Override
1757    protected void handlePollCalls(AsyncResult ar) {
1758    }
1759
1760    /* package */
1761    ImsEcbm getEcbmInterface() throws ImsException {
1762        if (mImsManager == null) {
1763            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1764        }
1765
1766        ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
1767        return ecbm;
1768    }
1769
1770    public boolean isInEmergencyCall() {
1771        return mIsInEmergencyCall;
1772    }
1773
1774    public boolean isVolteEnabled() {
1775        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE];
1776    }
1777
1778    public boolean isVowifiEnabled() {
1779        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI];
1780    }
1781
1782    public boolean isVideoCallEnabled() {
1783        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE]
1784                || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]);
1785    }
1786
1787    @Override
1788    public PhoneConstants.State getState() {
1789        return mState;
1790    }
1791
1792    private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
1793            throws RemoteException {
1794        IImsVideoCallProvider imsVideoCallProvider =
1795                imsCall.getCallSession().getVideoCallProvider();
1796        if (imsVideoCallProvider != null) {
1797            ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
1798                    new ImsVideoCallProviderWrapper(imsVideoCallProvider);
1799            conn.setVideoProvider(imsVideoCallProviderWrapper);
1800        }
1801    }
1802
1803    public boolean isUtEnabled() {
1804        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_LTE]
1805            || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI]);
1806    }
1807}
1808