ImsPhoneCallTracker.java revision c3ec7d484569eff5005ee31fe3c96dfc00b86096
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.HashMap;
23import java.util.List;
24import java.util.Map;
25
26import android.app.PendingIntent;
27import android.content.BroadcastReceiver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.SharedPreferences;
32import android.os.AsyncResult;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.Message;
36import android.os.PersistableBundle;
37import android.os.Registrant;
38import android.os.RegistrantList;
39import android.os.RemoteException;
40import android.os.SystemProperties;
41import android.provider.Settings;
42import android.telephony.CarrierConfigManager;
43import android.text.TextUtils;
44import android.preference.PreferenceManager;
45import android.telecom.ConferenceParticipant;
46import android.telecom.VideoProfile;
47import android.telephony.DisconnectCause;
48import android.telephony.PhoneNumberUtils;
49import android.telephony.Rlog;
50import android.telephony.ServiceState;
51import android.telephony.SubscriptionManager;
52
53import com.android.ims.ImsCall;
54import com.android.ims.ImsCallProfile;
55import com.android.ims.ImsConfig;
56import com.android.ims.ImsConfigListener;
57import com.android.ims.ImsConnectionStateListener;
58import com.android.ims.ImsEcbm;
59import com.android.ims.ImsException;
60import com.android.ims.ImsManager;
61import com.android.ims.ImsMultiEndpoint;
62import com.android.ims.ImsReasonInfo;
63import com.android.ims.ImsServiceClass;
64import com.android.ims.ImsSuppServiceNotification;
65import com.android.ims.ImsUtInterface;
66import com.android.ims.internal.IImsVideoCallProvider;
67import com.android.ims.internal.ImsVideoCallProviderWrapper;
68import com.android.internal.telephony.Call;
69import com.android.internal.telephony.CallStateException;
70import com.android.internal.telephony.CallTracker;
71import com.android.internal.telephony.CommandException;
72import com.android.internal.telephony.CommandsInterface;
73import com.android.internal.telephony.Connection;
74import com.android.internal.telephony.Phone;
75import com.android.internal.telephony.PhoneConstants;
76import com.android.internal.telephony.TelephonyEventLog;
77import com.android.internal.telephony.TelephonyProperties;
78import com.android.internal.telephony.gsm.SuppServiceNotification;
79
80/**
81 * {@hide}
82 */
83public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
84    static final String LOG_TAG = "ImsPhoneCallTracker";
85
86    private static final boolean DBG = true;
87
88    // When true, dumps the state of ImsPhoneCallTracker after changes to foreground and background
89    // calls.  This is helpful for debugging.
90    private static final boolean VERBOSE_STATE_LOGGING = false; /* stopship if true */
91
92    //Indices map to ImsConfig.FeatureConstants
93    private boolean[] mImsFeatureEnabled = {false, false, false, false, false, false};
94    private final String[] mImsFeatureStrings = {"VoLTE", "ViLTE", "VoWiFi", "ViWiFi",
95            "UTLTE", "UTWiFi"};
96
97    private TelephonyEventLog mEventLog;
98
99    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
100        @Override
101        public void onReceive(Context context, Intent intent) {
102            if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) {
103                if (DBG) log("onReceive : incoming call intent");
104
105                if (mImsManager == null) return;
106
107                if (mServiceId < 0) return;
108
109                try {
110                    // Network initiated USSD will be treated by mImsUssdListener
111                    boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false);
112                    if (isUssd) {
113                        if (DBG) log("onReceive : USSD");
114                        mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener);
115                        if (mUssdSession != null) {
116                            mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE);
117                        }
118                        return;
119                    }
120
121                    boolean isUnknown = intent.getBooleanExtra(ImsManager.EXTRA_IS_UNKNOWN_CALL,
122                            false);
123                    if (DBG) {
124                        log("onReceive : isUnknown = " + isUnknown +
125                                " fg = " + mForegroundCall.getState() +
126                                " bg = " + mBackgroundCall.getState());
127                    }
128
129                    // Normal MT/Unknown call
130                    ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener);
131                    ImsPhoneConnection conn = new ImsPhoneConnection(mPhone, imsCall,
132                            ImsPhoneCallTracker.this,
133                            (isUnknown? mForegroundCall: mRingingCall), isUnknown);
134
135                    // If there is an active call.
136                    if (mForegroundCall.hasConnections()) {
137                        ImsCall activeCall = mForegroundCall.getFirstConnection().getImsCall();
138                        boolean answeringWillDisconnect =
139                                shouldDisconnectActiveCallOnAnswer(activeCall, imsCall);
140                        conn.setActiveCallDisconnectedOnAnswer(answeringWillDisconnect);
141                    }
142                    conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall);
143                    addConnection(conn);
144
145                    setVideoCallProvider(conn, imsCall);
146
147                    mEventLog.writeOnImsCallReceive(imsCall.getSession());
148
149                    if (isUnknown) {
150                        mPhone.notifyUnknownConnection(conn);
151                    } else {
152                        if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) ||
153                                (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) {
154                            conn.update(imsCall, ImsPhoneCall.State.WAITING);
155                        }
156
157                        mPhone.notifyNewRingingConnection(conn);
158                        mPhone.notifyIncomingRing();
159                    }
160
161                    updatePhoneState();
162                    mPhone.notifyPreciseCallStateChanged();
163                } catch (ImsException e) {
164                    loge("onReceive : exception " + e);
165                } catch (RemoteException e) {
166                }
167            } else if (intent.getAction().equals(
168                    CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
169                int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
170                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
171                if (subId == mPhone.getSubId()) {
172                    cacheCarrierConfiguration(subId);
173                    log("onReceive : Updating mAllowEmergencyVideoCalls = " +
174                            mAllowEmergencyVideoCalls);
175                }
176            }
177        }
178    };
179
180    //***** Constants
181
182    static final int MAX_CONNECTIONS = 7;
183    static final int MAX_CONNECTIONS_PER_CALL = 5;
184
185    private static final int EVENT_HANGUP_PENDINGMO = 18;
186    private static final int EVENT_RESUME_BACKGROUND = 19;
187    private static final int EVENT_DIAL_PENDINGMO = 20;
188    private static final int EVENT_EXIT_ECBM_BEFORE_PENDINGMO = 21;
189    private static final int EVENT_VT_DATA_USAGE_UPDATE = 22;
190
191    private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
192
193    //***** Instance Variables
194    private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>();
195    private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
196    private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
197
198    public ImsPhoneCall mRingingCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_RINGING);
199    public ImsPhoneCall mForegroundCall = new ImsPhoneCall(this,
200            ImsPhoneCall.CONTEXT_FOREGROUND);
201    public ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this,
202            ImsPhoneCall.CONTEXT_BACKGROUND);
203    public ImsPhoneCall mHandoverCall = new ImsPhoneCall(this, ImsPhoneCall.CONTEXT_HANDOVER);
204
205    // Hold aggregated video call data usage for each video call since boot.
206    // The ImsCall's call id is the key of the map.
207    private final HashMap<Integer, Long> mVtDataUsageMap = new HashMap<>();
208    private volatile long mTotalVtDataUsage = 0;
209
210    private ImsPhoneConnection mPendingMO;
211    private int mClirMode = CommandsInterface.CLIR_DEFAULT;
212    private Object mSyncHold = new Object();
213
214    private ImsCall mUssdSession = null;
215    private Message mPendingUssd = null;
216
217    ImsPhone mPhone;
218
219    private boolean mDesiredMute = false;    // false = mute off
220    private boolean mOnHoldToneStarted = false;
221    private int mOnHoldToneId = -1;
222
223    private PhoneConstants.State mState = PhoneConstants.State.IDLE;
224
225    private ImsManager mImsManager;
226    private int mServiceId = -1;
227
228    private Call.SrvccState mSrvccState = Call.SrvccState.NONE;
229
230    private boolean mIsInEmergencyCall = false;
231
232    private int pendingCallClirMode;
233    private int mPendingCallVideoState;
234    private Bundle mPendingIntentExtras;
235    private boolean pendingCallInEcm = false;
236    private boolean mSwitchingFgAndBgCalls = false;
237    private ImsCall mCallExpectedToResume = null;
238    private boolean mAllowEmergencyVideoCalls = false;
239
240    /**
241     * Carrier configuration option which determines if video calls which have been downgraded to an
242     * audio call should be treated as if they are still video calls.
243     */
244    private boolean mTreatDowngradedVideoCallsAsVideoCalls = false;
245
246    /**
247     * Carrier configuration option which determines if an ongoing video call over wifi should be
248     * dropped when an audio call is answered.
249     */
250    private boolean mDropVideoCallWhenAnsweringAudioCall = false;
251
252    /**
253     * Carrier configuration option which determines whether adding a call during a video call
254     * should be allowed.
255     */
256    private boolean mAllowAddCallDuringVideoCall = true;
257
258    //***** Events
259
260
261    //***** Constructors
262
263    public ImsPhoneCallTracker(ImsPhone phone) {
264        this.mPhone = phone;
265
266        mEventLog = new TelephonyEventLog(mPhone.getPhoneId());
267
268        IntentFilter intentfilter = new IntentFilter();
269        intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL);
270        intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
271        mPhone.getContext().registerReceiver(mReceiver, intentfilter);
272        cacheCarrierConfiguration(mPhone.getSubId());
273
274        Thread t = new Thread() {
275            public void run() {
276                getImsService();
277            }
278        };
279        t.start();
280    }
281
282    private PendingIntent createIncomingCallPendingIntent() {
283        Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
284        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
285        return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
286                PendingIntent.FLAG_UPDATE_CURRENT);
287    }
288
289    private void getImsService() {
290        if (DBG) log("getImsService");
291        mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId());
292        try {
293            mServiceId = mImsManager.open(ImsServiceClass.MMTEL,
294                    createIncomingCallPendingIntent(),
295                    mImsConnectionStateListener);
296
297            mImsManager.setImsConfigListener(mImsConfigListener);
298
299            // Get the ECBM interface and set IMSPhone's listener object for notifications
300            getEcbmInterface().setEcbmStateListener(mPhone.getImsEcbmStateListener());
301            if (mPhone.isInEcm()) {
302                // Call exit ECBM which will invoke onECBMExited
303                mPhone.exitEmergencyCallbackMode();
304            }
305            int mPreferredTtyMode = Settings.Secure.getInt(
306                mPhone.getContext().getContentResolver(),
307                Settings.Secure.PREFERRED_TTY_MODE,
308                Phone.TTY_MODE_OFF);
309            mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, mPreferredTtyMode, null);
310
311            ImsMultiEndpoint multiEndpoint = getMultiEndpointInterface();
312            if (multiEndpoint != null) {
313                multiEndpoint.setExternalCallStateListener(
314                        mPhone.getExternalCallTracker().getExternalCallStateListener());
315            }
316        } catch (ImsException e) {
317            loge("getImsService: " + e);
318            //Leave mImsManager as null, then CallStateException will be thrown when dialing
319            mImsManager = null;
320        }
321    }
322
323    public void dispose() {
324        if (DBG) log("dispose");
325        mRingingCall.dispose();
326        mBackgroundCall.dispose();
327        mForegroundCall.dispose();
328        mHandoverCall.dispose();
329
330        clearDisconnected();
331        mPhone.getContext().unregisterReceiver(mReceiver);
332    }
333
334    @Override
335    protected void finalize() {
336        log("ImsPhoneCallTracker finalized");
337    }
338
339    //***** Instance Methods
340
341    //***** Public Methods
342    @Override
343    public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
344        Registrant r = new Registrant(h, what, obj);
345        mVoiceCallStartedRegistrants.add(r);
346    }
347
348    @Override
349    public void unregisterForVoiceCallStarted(Handler h) {
350        mVoiceCallStartedRegistrants.remove(h);
351    }
352
353    @Override
354    public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
355        Registrant r = new Registrant(h, what, obj);
356        mVoiceCallEndedRegistrants.add(r);
357    }
358
359    @Override
360    public void unregisterForVoiceCallEnded(Handler h) {
361        mVoiceCallEndedRegistrants.remove(h);
362    }
363
364    public Connection dial(String dialString, int videoState, Bundle intentExtras) throws
365            CallStateException {
366        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
367        int oirMode = sp.getInt(Phone.CLIR_KEY, CommandsInterface.CLIR_DEFAULT);
368        return dial(dialString, oirMode, videoState, intentExtras);
369    }
370
371    /**
372     * oirMode is one of the CLIR_ constants
373     */
374    synchronized Connection
375    dial(String dialString, int clirMode, int videoState, Bundle intentExtras)
376            throws CallStateException {
377        boolean isPhoneInEcmMode = isPhoneInEcbMode();
378        boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString);
379
380        if (DBG) log("dial clirMode=" + clirMode);
381
382        // note that this triggers call state changed notif
383        clearDisconnected();
384
385        if (mImsManager == null) {
386            throw new CallStateException("service not available");
387        }
388
389        if (!canDial()) {
390            throw new CallStateException("cannot dial in current state");
391        }
392
393        if (isPhoneInEcmMode && isEmergencyNumber) {
394            handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER);
395        }
396
397        // If the call is to an emergency number and the carrier does not support video emergency
398        // calls, dial as an audio-only call.
399        if (isEmergencyNumber && VideoProfile.isVideo(videoState) &&
400                !mAllowEmergencyVideoCalls) {
401            loge("dial: carrier does not support video emergency calls; downgrade to audio-only");
402            videoState = VideoProfile.STATE_AUDIO_ONLY;
403        }
404
405        boolean holdBeforeDial = false;
406
407        // The new call must be assigned to the foreground call.
408        // That call must be idle, so place anything that's
409        // there on hold
410        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
411            if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
412                //we should have failed in !canDial() above before we get here
413                throw new CallStateException("cannot dial in current state");
414            }
415            // foreground call is empty for the newly dialed connection
416            holdBeforeDial = true;
417            // Cache the video state for pending MO call.
418            mPendingCallVideoState = videoState;
419            mPendingIntentExtras = intentExtras;
420            switchWaitingOrHoldingAndActive();
421        }
422
423        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
424        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
425
426        mClirMode = clirMode;
427
428        synchronized (mSyncHold) {
429            if (holdBeforeDial) {
430                fgState = mForegroundCall.getState();
431                bgState = mBackgroundCall.getState();
432
433                //holding foreground call failed
434                if (fgState == ImsPhoneCall.State.ACTIVE) {
435                    throw new CallStateException("cannot dial in current state");
436                }
437
438                //holding foreground call succeeded
439                if (bgState == ImsPhoneCall.State.HOLDING) {
440                    holdBeforeDial = false;
441                }
442            }
443
444            mPendingMO = new ImsPhoneConnection(mPhone,
445                    checkForTestEmergencyNumber(dialString), this, mForegroundCall,
446                    isEmergencyNumber);
447            mPendingMO.setVideoState(videoState);
448        }
449        addConnection(mPendingMO);
450
451        if (!holdBeforeDial) {
452            if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
453                dialInternal(mPendingMO, clirMode, videoState, intentExtras);
454            } else {
455                try {
456                    getEcbmInterface().exitEmergencyCallbackMode();
457                } catch (ImsException e) {
458                    e.printStackTrace();
459                    throw new CallStateException("service not available");
460                }
461                mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
462                pendingCallClirMode = clirMode;
463                mPendingCallVideoState = videoState;
464                pendingCallInEcm = true;
465            }
466        }
467
468        updatePhoneState();
469        mPhone.notifyPreciseCallStateChanged();
470
471        return mPendingMO;
472    }
473
474    /**
475     * Caches frequently used carrier configuration items locally.
476     *
477     * @param subId The sub id.
478     */
479    private void cacheCarrierConfiguration(int subId) {
480        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
481                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
482        if (carrierConfigManager == null) {
483            loge("cacheCarrierConfiguration: No carrier config service found.");
484            return;
485        }
486
487        PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId);
488        if (carrierConfig == null) {
489            loge("cacheCarrierConfiguration: Empty carrier config.");
490            return;
491        }
492
493        mAllowEmergencyVideoCalls =
494                carrierConfig.getBoolean(CarrierConfigManager.KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL);
495        mTreatDowngradedVideoCallsAsVideoCalls =
496                carrierConfig.getBoolean(
497                        CarrierConfigManager.KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL);
498        mDropVideoCallWhenAnsweringAudioCall =
499                carrierConfig.getBoolean(
500                        CarrierConfigManager.KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL);
501        mAllowAddCallDuringVideoCall =
502                carrierConfig.getBoolean(
503                        CarrierConfigManager.KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL);
504    }
505
506    private void handleEcmTimer(int action) {
507        mPhone.handleTimerInEmergencyCallbackMode(action);
508        switch (action) {
509            case ImsPhone.CANCEL_ECM_TIMER:
510                break;
511            case ImsPhone.RESTART_ECM_TIMER:
512                break;
513            default:
514                log("handleEcmTimer, unsupported action " + action);
515        }
516    }
517
518    private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState,
519            Bundle intentExtras) {
520
521        if (conn == null) {
522            return;
523        }
524
525        if (conn.getAddress()== null || conn.getAddress().length() == 0
526                || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
527            // Phone number is invalid
528            conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER);
529            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
530            return;
531        }
532
533        // Always unmute when initiating a new call
534        setMute(false);
535        int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ?
536                ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
537        int callType = ImsCallProfile.getCallTypeFromVideoState(videoState);
538        //TODO(vt): Is this sufficient?  At what point do we know the video state of the call?
539        conn.setVideoState(videoState);
540
541        try {
542            String[] callees = new String[] { conn.getAddress() };
543            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
544                    serviceType, callType);
545            profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
546
547            // Translate call subject intent-extra from Telecom-specific extra key to the
548            // ImsCallProfile key.
549            if (intentExtras != null) {
550                if (intentExtras.containsKey(android.telecom.TelecomManager.EXTRA_CALL_SUBJECT)) {
551                    intentExtras.putString(ImsCallProfile.EXTRA_DISPLAY_TEXT,
552                            cleanseInstantLetteringMessage(intentExtras.getString(
553                                    android.telecom.TelecomManager.EXTRA_CALL_SUBJECT))
554                    );
555                }
556
557                if (intentExtras.containsKey(ImsCallProfile.EXTRA_IS_CALL_PULL)) {
558                    profile.mCallExtras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL,
559                            intentExtras.getBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL));
560                }
561
562                // Pack the OEM-specific call extras.
563                profile.mCallExtras.putBundle(ImsCallProfile.EXTRA_OEM_EXTRAS, intentExtras);
564
565                // NOTE: Extras to be sent over the network are packed into the
566                // intentExtras individually, with uniquely defined keys.
567                // These key-value pairs are processed by IMS Service before
568                // being sent to the lower layers/to the network.
569            }
570
571            ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
572                    callees, mImsCallListener);
573            conn.setImsCall(imsCall);
574
575            mEventLog.writeOnImsCallStart(imsCall.getSession());
576
577            setVideoCallProvider(conn, imsCall);
578            conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall);
579        } catch (ImsException e) {
580            loge("dialInternal : " + e);
581            conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
582            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
583        } catch (RemoteException e) {
584        }
585    }
586
587    /**
588     * Accepts a call with the specified video state.  The video state is the video state that the
589     * user has agreed upon in the InCall UI.
590     *
591     * @param videoState The video State
592     * @throws CallStateException
593     */
594    public void acceptCall (int videoState) throws CallStateException {
595        if (DBG) log("acceptCall");
596
597        if (mForegroundCall.getState().isAlive()
598                && mBackgroundCall.getState().isAlive()) {
599            throw new CallStateException("cannot accept call");
600        }
601
602        if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING)
603                && mForegroundCall.getState().isAlive()) {
604            setMute(false);
605
606            boolean answeringWillDisconnect = false;
607            ImsCall activeCall = mForegroundCall.getImsCall();
608            ImsCall ringingCall = mRingingCall.getImsCall();
609            if (mForegroundCall.hasConnections() && mRingingCall.hasConnections()) {
610                answeringWillDisconnect =
611                        shouldDisconnectActiveCallOnAnswer(activeCall, ringingCall);
612            }
613
614            // Cache video state for pending MT call.
615            mPendingCallVideoState = videoState;
616
617            if (answeringWillDisconnect) {
618                // We need to disconnect the foreground call before answering the background call.
619                mForegroundCall.hangup();
620                try {
621                    ringingCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
622                } catch (ImsException e) {
623                    throw new CallStateException("cannot accept call");
624                }
625            } else {
626                switchWaitingOrHoldingAndActive();
627            }
628        } else if (mRingingCall.getState().isRinging()) {
629            if (DBG) log("acceptCall: incoming...");
630            // Always unmute when answering a new call
631            setMute(false);
632            try {
633                ImsCall imsCall = mRingingCall.getImsCall();
634                if (imsCall != null) {
635                    imsCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
636                    mEventLog.writeOnImsCallAccept(imsCall.getSession());
637                } else {
638                    throw new CallStateException("no valid ims call");
639                }
640            } catch (ImsException e) {
641                throw new CallStateException("cannot accept call");
642            }
643        } else {
644            throw new CallStateException("phone not ringing");
645        }
646    }
647
648    public void rejectCall () throws CallStateException {
649        if (DBG) log("rejectCall");
650
651        if (mRingingCall.getState().isRinging()) {
652            hangup(mRingingCall);
653        } else {
654            throw new CallStateException("phone not ringing");
655        }
656    }
657
658
659    private void switchAfterConferenceSuccess() {
660        if (DBG) log("switchAfterConferenceSuccess fg =" + mForegroundCall.getState() +
661                ", bg = " + mBackgroundCall.getState());
662
663        if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
664            log("switchAfterConferenceSuccess");
665            mForegroundCall.switchWith(mBackgroundCall);
666        }
667    }
668
669    public void switchWaitingOrHoldingAndActive() throws CallStateException {
670        if (DBG) log("switchWaitingOrHoldingAndActive");
671
672        if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
673            throw new CallStateException("cannot be in the incoming state");
674        }
675
676        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
677            ImsCall imsCall = mForegroundCall.getImsCall();
678            if (imsCall == null) {
679                throw new CallStateException("no ims call");
680            }
681
682            // Swap the ImsCalls pointed to by the foreground and background ImsPhoneCalls.
683            // If hold or resume later fails, we will swap them back.
684            mSwitchingFgAndBgCalls = true;
685            mCallExpectedToResume = mBackgroundCall.getImsCall();
686            mForegroundCall.switchWith(mBackgroundCall);
687
688            // Hold the foreground call; once the foreground call is held, the background call will
689            // be resumed.
690            try {
691                imsCall.hold();
692                mEventLog.writeOnImsCallHold(imsCall.getSession());
693
694                // If there is no background call to resume, then don't expect there to be a switch.
695                if (mCallExpectedToResume == null) {
696                    mSwitchingFgAndBgCalls = false;
697                }
698            } catch (ImsException e) {
699                mForegroundCall.switchWith(mBackgroundCall);
700                throw new CallStateException(e.getMessage());
701            }
702        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
703            resumeWaitingOrHolding();
704        }
705    }
706
707    public void
708    conference() {
709        if (DBG) log("conference");
710
711        ImsCall fgImsCall = mForegroundCall.getImsCall();
712        if (fgImsCall == null) {
713            log("conference no foreground ims call");
714            return;
715        }
716
717        ImsCall bgImsCall = mBackgroundCall.getImsCall();
718        if (bgImsCall == null) {
719            log("conference no background ims call");
720            return;
721        }
722
723        // Keep track of the connect time of the earliest call so that it can be set on the
724        // {@code ImsConference} when it is created.
725        long foregroundConnectTime = mForegroundCall.getEarliestConnectTime();
726        long backgroundConnectTime = mBackgroundCall.getEarliestConnectTime();
727        long conferenceConnectTime;
728        if (foregroundConnectTime > 0 && backgroundConnectTime > 0) {
729            conferenceConnectTime = Math.min(mForegroundCall.getEarliestConnectTime(),
730                    mBackgroundCall.getEarliestConnectTime());
731            log("conference - using connect time = " + conferenceConnectTime);
732        } else if (foregroundConnectTime > 0) {
733            log("conference - bg call connect time is 0; using fg = " + foregroundConnectTime);
734            conferenceConnectTime = foregroundConnectTime;
735        } else {
736            log("conference - fg call connect time is 0; using bg = " + backgroundConnectTime);
737            conferenceConnectTime = backgroundConnectTime;
738        }
739
740        ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection();
741        if (foregroundConnection != null) {
742            foregroundConnection.setConferenceConnectTime(conferenceConnectTime);
743        }
744
745        try {
746            fgImsCall.merge(bgImsCall);
747        } catch (ImsException e) {
748            log("conference " + e.getMessage());
749        }
750    }
751
752    public void
753    explicitCallTransfer() {
754        //TODO : implement
755    }
756
757    public void
758    clearDisconnected() {
759        if (DBG) log("clearDisconnected");
760
761        internalClearDisconnected();
762
763        updatePhoneState();
764        mPhone.notifyPreciseCallStateChanged();
765    }
766
767    public boolean
768    canConference() {
769        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
770            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
771            && !mBackgroundCall.isFull()
772            && !mForegroundCall.isFull();
773    }
774
775    public boolean
776    canDial() {
777        boolean ret;
778        int serviceState = mPhone.getServiceState().getState();
779        String disableCall = SystemProperties.get(
780                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
781
782        ret = (serviceState != ServiceState.STATE_POWER_OFF)
783            && mPendingMO == null
784            && !mRingingCall.isRinging()
785            && !disableCall.equals("true")
786            && (!mForegroundCall.getState().isAlive()
787                    || !mBackgroundCall.getState().isAlive());
788
789        return ret;
790    }
791
792    public boolean
793    canTransfer() {
794        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
795            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
796    }
797
798    //***** Private Instance Methods
799
800    private void
801    internalClearDisconnected() {
802        mRingingCall.clearDisconnected();
803        mForegroundCall.clearDisconnected();
804        mBackgroundCall.clearDisconnected();
805        mHandoverCall.clearDisconnected();
806    }
807
808    private void
809    updatePhoneState() {
810        PhoneConstants.State oldState = mState;
811
812        if (mRingingCall.isRinging()) {
813            mState = PhoneConstants.State.RINGING;
814        } else if (mPendingMO != null ||
815                !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
816            mState = PhoneConstants.State.OFFHOOK;
817        } else {
818            mState = PhoneConstants.State.IDLE;
819        }
820
821        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
822            mVoiceCallEndedRegistrants.notifyRegistrants(
823                    new AsyncResult(null, null, null));
824        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
825            mVoiceCallStartedRegistrants.notifyRegistrants (
826                    new AsyncResult(null, null, null));
827        }
828
829        if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
830
831        if (mState != oldState) {
832            mPhone.notifyPhoneStateChanged();
833            mEventLog.writePhoneState(mState);
834        }
835    }
836
837    private void
838    handleRadioNotAvailable() {
839        // handlePollCalls will clear out its
840        // call list when it gets the CommandException
841        // error result from this
842        pollCallsWhenSafe();
843    }
844
845    private void
846    dumpState() {
847        List l;
848
849        log("Phone State:" + mState);
850
851        log("Ringing call: " + mRingingCall.toString());
852
853        l = mRingingCall.getConnections();
854        for (int i = 0, s = l.size(); i < s; i++) {
855            log(l.get(i).toString());
856        }
857
858        log("Foreground call: " + mForegroundCall.toString());
859
860        l = mForegroundCall.getConnections();
861        for (int i = 0, s = l.size(); i < s; i++) {
862            log(l.get(i).toString());
863        }
864
865        log("Background call: " + mBackgroundCall.toString());
866
867        l = mBackgroundCall.getConnections();
868        for (int i = 0, s = l.size(); i < s; i++) {
869            log(l.get(i).toString());
870        }
871
872    }
873
874    //***** Called from ImsPhone
875
876    public void setUiTTYMode(int uiTtyMode, Message onComplete) {
877        if (mImsManager == null) {
878            mPhone.sendErrorResponse(onComplete, getImsManagerIsNullException());
879            return;
880        }
881
882        try {
883            mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, uiTtyMode, onComplete);
884        } catch (ImsException e) {
885            loge("setTTYMode : " + e);
886            mPhone.sendErrorResponse(onComplete, e);
887        }
888    }
889
890    public void setMute(boolean mute) {
891        mDesiredMute = mute;
892        mForegroundCall.setMute(mute);
893    }
894
895    public boolean getMute() {
896        return mDesiredMute;
897    }
898
899    public void sendDtmf(char c, Message result) {
900        if (DBG) log("sendDtmf");
901
902        ImsCall imscall = mForegroundCall.getImsCall();
903        if (imscall != null) {
904            imscall.sendDtmf(c, result);
905        }
906    }
907
908    public void
909    startDtmf(char c) {
910        if (DBG) log("startDtmf");
911
912        ImsCall imscall = mForegroundCall.getImsCall();
913        if (imscall != null) {
914            imscall.startDtmf(c);
915        } else {
916            loge("startDtmf : no foreground call");
917        }
918    }
919
920    public void
921    stopDtmf() {
922        if (DBG) log("stopDtmf");
923
924        ImsCall imscall = mForegroundCall.getImsCall();
925        if (imscall != null) {
926            imscall.stopDtmf();
927        } else {
928            loge("stopDtmf : no foreground call");
929        }
930    }
931
932    //***** Called from ImsPhoneConnection
933
934    public void hangup (ImsPhoneConnection conn) throws CallStateException {
935        if (DBG) log("hangup connection");
936
937        if (conn.getOwner() != this) {
938            throw new CallStateException ("ImsPhoneConnection " + conn
939                    + "does not belong to ImsPhoneCallTracker " + this);
940        }
941
942        hangup(conn.getCall());
943    }
944
945    //***** Called from ImsPhoneCall
946
947    public void hangup (ImsPhoneCall call) throws CallStateException {
948        if (DBG) log("hangup call");
949
950        if (call.getConnections().size() == 0) {
951            throw new CallStateException("no connections");
952        }
953
954        ImsCall imsCall = call.getImsCall();
955        boolean rejectCall = false;
956
957        if (call == mRingingCall) {
958            if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
959            rejectCall = true;
960        } else if (call == mForegroundCall) {
961            if (call.isDialingOrAlerting()) {
962                if (Phone.DEBUG_PHONE) {
963                    log("(foregnd) hangup dialing or alerting...");
964                }
965            } else {
966                if (Phone.DEBUG_PHONE) {
967                    log("(foregnd) hangup foreground");
968                }
969                //held call will be resumed by onCallTerminated
970            }
971        } else if (call == mBackgroundCall) {
972            if (Phone.DEBUG_PHONE) {
973                log("(backgnd) hangup waiting or background");
974            }
975        } else {
976            throw new CallStateException ("ImsPhoneCall " + call +
977                    "does not belong to ImsPhoneCallTracker " + this);
978        }
979
980        call.onHangupLocal();
981
982        try {
983            if (imsCall != null) {
984                if (rejectCall) {
985                    imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
986                    mEventLog.writeOnImsCallReject(imsCall.getSession());
987                } else {
988                    imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
989                    mEventLog.writeOnImsCallTerminate(imsCall.getSession());
990                }
991            } else if (mPendingMO != null && call == mForegroundCall) {
992                // is holding a foreground call
993                mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
994                mPendingMO.onDisconnect();
995                removeConnection(mPendingMO);
996                mPendingMO = null;
997                updatePhoneState();
998                removeMessages(EVENT_DIAL_PENDINGMO);
999            }
1000        } catch (ImsException e) {
1001            throw new CallStateException(e.getMessage());
1002        }
1003
1004        mPhone.notifyPreciseCallStateChanged();
1005    }
1006
1007    void callEndCleanupHandOverCallIfAny() {
1008        if (mHandoverCall.mConnections.size() > 0) {
1009            if (DBG) log("callEndCleanupHandOverCallIfAny, mHandoverCall.mConnections="
1010                    + mHandoverCall.mConnections);
1011            mHandoverCall.mConnections.clear();
1012            mState = PhoneConstants.State.IDLE;
1013        }
1014    }
1015
1016    /* package */
1017    void resumeWaitingOrHolding() throws CallStateException {
1018        if (DBG) log("resumeWaitingOrHolding");
1019
1020        try {
1021            if (mForegroundCall.getState().isAlive()) {
1022                //resume foreground call after holding background call
1023                //they were switched before holding
1024                ImsCall imsCall = mForegroundCall.getImsCall();
1025                if (imsCall != null) {
1026                    imsCall.resume();
1027                    mEventLog.writeOnImsCallResume(imsCall.getSession());
1028                }
1029            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
1030                //accept waiting call after holding background call
1031                ImsCall imsCall = mRingingCall.getImsCall();
1032                if (imsCall != null) {
1033                    imsCall.accept(
1034                        ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState));
1035                    mEventLog.writeOnImsCallAccept(imsCall.getSession());
1036                }
1037            } else {
1038                //Just resume background call.
1039                //To distinguish resuming call with swapping calls
1040                //we do not switch calls.here
1041                //ImsPhoneConnection.update will chnage the parent when completed
1042                ImsCall imsCall = mBackgroundCall.getImsCall();
1043                if (imsCall != null) {
1044                    imsCall.resume();
1045                    mEventLog.writeOnImsCallResume(imsCall.getSession());
1046                }
1047            }
1048        } catch (ImsException e) {
1049            throw new CallStateException(e.getMessage());
1050        }
1051    }
1052
1053    public void sendUSSD (String ussdString, Message response) {
1054        if (DBG) log("sendUSSD");
1055
1056        try {
1057            if (mUssdSession != null) {
1058                mUssdSession.sendUssd(ussdString);
1059                AsyncResult.forMessage(response, null, null);
1060                response.sendToTarget();
1061                return;
1062            }
1063
1064            if (mImsManager == null) {
1065                mPhone.sendErrorResponse(response, getImsManagerIsNullException());
1066                return;
1067            }
1068
1069            String[] callees = new String[] { ussdString };
1070            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
1071                    ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
1072            profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
1073                    ImsCallProfile.DIALSTRING_USSD);
1074
1075            mUssdSession = mImsManager.makeCall(mServiceId, profile,
1076                    callees, mImsUssdListener);
1077        } catch (ImsException e) {
1078            loge("sendUSSD : " + e);
1079            mPhone.sendErrorResponse(response, e);
1080        }
1081    }
1082
1083    public void cancelUSSD() {
1084        if (mUssdSession == null) return;
1085
1086        try {
1087            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
1088        } catch (ImsException e) {
1089        }
1090
1091    }
1092
1093    private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) {
1094        for (ImsPhoneConnection conn : mConnections) {
1095            if (conn.getImsCall() == imsCall) {
1096                return conn;
1097            }
1098        }
1099        return null;
1100    }
1101
1102    private synchronized void removeConnection(ImsPhoneConnection conn) {
1103        mConnections.remove(conn);
1104        // If not emergency call is remaining, notify emergency call registrants
1105        if (mIsInEmergencyCall) {
1106            boolean isEmergencyCallInList = false;
1107            // if no emergency calls pending, set this to false
1108            for (ImsPhoneConnection imsPhoneConnection : mConnections) {
1109                if (imsPhoneConnection != null && imsPhoneConnection.isEmergency() == true) {
1110                    isEmergencyCallInList = true;
1111                    break;
1112                }
1113            }
1114
1115            if (!isEmergencyCallInList) {
1116                mIsInEmergencyCall = false;
1117                mPhone.sendEmergencyCallStateChange(false);
1118            }
1119        }
1120    }
1121
1122    private synchronized void addConnection(ImsPhoneConnection conn) {
1123        mConnections.add(conn);
1124        if (conn.isEmergency()) {
1125            mIsInEmergencyCall = true;
1126            mPhone.sendEmergencyCallStateChange(true);
1127        }
1128    }
1129
1130    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
1131        if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
1132        // This method is called on onCallUpdate() where there is not necessarily a call state
1133        // change. In these situations, we'll ignore the state related updates and only process
1134        // the change in media capabilities (as expected).  The default is to not ignore state
1135        // changes so we do not change existing behavior.
1136        processCallStateChange(imsCall, state, cause, false /* do not ignore state update */);
1137    }
1138
1139    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause,
1140            boolean ignoreState) {
1141        if (DBG) {
1142            log("processCallStateChange state=" + state + " cause=" + cause
1143                    + " ignoreState=" + ignoreState);
1144        }
1145
1146        if (imsCall == null) return;
1147
1148        boolean changed = false;
1149        ImsPhoneConnection conn = findConnection(imsCall);
1150
1151        if (conn == null) {
1152            // TODO : what should be done?
1153            return;
1154        }
1155
1156        // processCallStateChange is triggered for onCallUpdated as well.
1157        // onCallUpdated should not modify the state of the call
1158        // It should modify only other capabilities of call through updateMediaCapabilities
1159        // State updates will be triggered through individual callbacks
1160        // i.e. onCallHeld, onCallResume, etc and conn.update will be responsible for the update
1161        conn.updateMediaCapabilities(imsCall);
1162        if (ignoreState) {
1163            conn.updateAddressDisplay(imsCall);
1164            conn.updateExtras(imsCall);
1165
1166            maybeSetVideoCallProvider(conn, imsCall);
1167            return;
1168        }
1169
1170        changed = conn.update(imsCall, state);
1171        if (state == ImsPhoneCall.State.DISCONNECTED) {
1172            changed = conn.onDisconnect(cause) || changed;
1173            //detach the disconnected connections
1174            conn.getCall().detach(conn);
1175            removeConnection(conn);
1176        }
1177
1178        if (changed) {
1179            if (conn.getCall() == mHandoverCall) return;
1180            updatePhoneState();
1181            mPhone.notifyPreciseCallStateChanged();
1182        }
1183    }
1184
1185    private void maybeSetVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall) {
1186        android.telecom.Connection.VideoProvider connVideoProvider = conn.getVideoProvider();
1187        if (connVideoProvider != null || imsCall.getCallSession().getVideoCallProvider() == null) {
1188            return;
1189        }
1190
1191        try {
1192            setVideoCallProvider(conn, imsCall);
1193        } catch (RemoteException e) {
1194            loge("maybeSetVideoCallProvider: exception " + e);
1195        }
1196    }
1197
1198    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
1199        int cause = DisconnectCause.ERROR_UNSPECIFIED;
1200
1201        //int type = reasonInfo.getReasonType();
1202        int code = reasonInfo.getCode();
1203        switch (code) {
1204            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
1205            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
1206                return DisconnectCause.NUMBER_UNREACHABLE;
1207
1208            case ImsReasonInfo.CODE_SIP_BUSY:
1209                return DisconnectCause.BUSY;
1210
1211            case ImsReasonInfo.CODE_USER_TERMINATED:
1212                return DisconnectCause.LOCAL;
1213
1214            case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
1215                return DisconnectCause.INCOMING_REJECTED;
1216
1217            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
1218                return DisconnectCause.NORMAL;
1219
1220            case ImsReasonInfo.CODE_SIP_REDIRECTED:
1221            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
1222            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
1223            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
1224            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
1225            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
1226                return DisconnectCause.SERVER_ERROR;
1227
1228            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
1229            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
1230            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
1231                return DisconnectCause.SERVER_UNREACHABLE;
1232
1233            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
1234            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
1235            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
1236            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
1237            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
1238            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
1239            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
1240            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
1241                return DisconnectCause.OUT_OF_SERVICE;
1242
1243            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
1244            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
1245            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
1246            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
1247                return DisconnectCause.TIMED_OUT;
1248
1249            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
1250            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
1251                return DisconnectCause.POWER_OFF;
1252
1253            case ImsReasonInfo.CODE_FDN_BLOCKED:
1254                return DisconnectCause.FDN_BLOCKED;
1255
1256            case ImsReasonInfo.CODE_ANSWERED_ELSEWHERE:
1257            case ImsReasonInfo.CODE_CALL_END_CAUSE_CALL_PULL:
1258                return DisconnectCause.CALL_PULLED;
1259            default:
1260        }
1261
1262        return cause;
1263    }
1264
1265    /**
1266     * @return true if the phone is in Emergency Callback mode, otherwise false
1267     */
1268    private boolean isPhoneInEcbMode() {
1269        return SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false);
1270    }
1271
1272    /**
1273     * Before dialing pending MO request, check for the Emergency Callback mode.
1274     * If device is in Emergency callback mode, then exit the mode before dialing pending MO.
1275     */
1276    private void dialPendingMO() {
1277        boolean isPhoneInEcmMode = isPhoneInEcbMode();
1278        boolean isEmergencyNumber = mPendingMO.isEmergency();
1279        if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
1280            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1281        } else {
1282            sendEmptyMessage(EVENT_EXIT_ECBM_BEFORE_PENDINGMO);
1283        }
1284    }
1285
1286    /**
1287     * Listen to the IMS call state change
1288     */
1289    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
1290        @Override
1291        public void onCallProgressing(ImsCall imsCall) {
1292            if (DBG) log("onCallProgressing");
1293
1294            mPendingMO = null;
1295            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
1296                    DisconnectCause.NOT_DISCONNECTED);
1297            mEventLog.writeOnImsCallProgressing(imsCall.getCallSession());
1298        }
1299
1300        @Override
1301        public void onCallStarted(ImsCall imsCall) {
1302            if (DBG) log("onCallStarted");
1303
1304            mPendingMO = null;
1305            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1306                    DisconnectCause.NOT_DISCONNECTED);
1307            mEventLog.writeOnImsCallStarted(imsCall.getCallSession());
1308        }
1309
1310        @Override
1311        public void onCallUpdated(ImsCall imsCall) {
1312            if (DBG) log("onCallUpdated");
1313            if (imsCall == null) {
1314                return;
1315            }
1316            ImsPhoneConnection conn = findConnection(imsCall);
1317            if (conn != null) {
1318                processCallStateChange(imsCall, conn.getCall().mState,
1319                        DisconnectCause.NOT_DISCONNECTED, true /*ignore state update*/);
1320                mEventLog.writeImsCallState(imsCall.getCallSession(), conn.getCall().mState);
1321            }
1322        }
1323
1324        /**
1325         * onCallStartFailed will be invoked when:
1326         * case 1) Dialing fails
1327         * case 2) Ringing call is disconnected by local or remote user
1328         */
1329        @Override
1330        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1331            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
1332
1333            if (mPendingMO != null) {
1334                // To initiate dialing circuit-switched call
1335                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
1336                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
1337                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
1338                    mForegroundCall.detach(mPendingMO);
1339                    removeConnection(mPendingMO);
1340                    mPendingMO.finalize();
1341                    mPendingMO = null;
1342                    mPhone.initiateSilentRedial();
1343                    return;
1344                } else {
1345                    mPendingMO = null;
1346                    int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1347                    processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1348                }
1349                mEventLog.writeOnImsCallStartFailed(imsCall.getCallSession(), reasonInfo);
1350            }
1351        }
1352
1353        @Override
1354        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1355            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
1356
1357            ImsPhoneCall.State oldState = mForegroundCall.getState();
1358            int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1359            ImsPhoneConnection conn = findConnection(imsCall);
1360            if (DBG) log("cause = " + cause + " conn = " + conn);
1361
1362            if (mOnHoldToneId == System.identityHashCode(conn)) {
1363                if (conn != null && mOnHoldToneStarted) {
1364                    mPhone.stopOnHoldTone(conn);
1365                }
1366                mOnHoldToneStarted = false;
1367                mOnHoldToneId = -1;
1368            }
1369            if (conn != null && conn.isIncoming() && conn.getConnectTime() == 0) {
1370                // Missed
1371                if (cause == DisconnectCause.NORMAL) {
1372                    cause = DisconnectCause.INCOMING_MISSED;
1373                } else {
1374                    cause = DisconnectCause.INCOMING_REJECTED;
1375                }
1376                if (DBG) log("Incoming connection of 0 connect time detected - translated cause = "
1377                        + cause);
1378
1379            }
1380
1381            if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) {
1382                // Call was terminated while it is merged instead of a remote disconnect.
1383                cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY;
1384            }
1385
1386            mEventLog.writeOnImsCallTerminated(imsCall.getCallSession(), reasonInfo);
1387
1388            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1389            if (mForegroundCall.getState() != ImsPhoneCall.State.ACTIVE) {
1390                if (mRingingCall.getState().isRinging()) {
1391                    // Drop pending MO. We should address incoming call first
1392                    mPendingMO = null;
1393                } else if (mPendingMO != null) {
1394                    sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1395                }
1396            }
1397
1398            if (mSwitchingFgAndBgCalls) {
1399                if (DBG) {
1400                    log("onCallTerminated: Call terminated in the midst of Switching " +
1401                            "Fg and Bg calls.");
1402                }
1403                // If we are the in midst of swapping FG and BG calls and the call that was
1404                // terminated was the one that we expected to resume, we need to swap the FG and
1405                // BG calls back.
1406                if (imsCall == mCallExpectedToResume) {
1407                    if (DBG) {
1408                        log("onCallTerminated: switching " + mForegroundCall + " with "
1409                                + mBackgroundCall);
1410                    }
1411                    mForegroundCall.switchWith(mBackgroundCall);
1412                }
1413                // This call terminated in the midst of a switch after the other call was held, so
1414                // resume it back to ACTIVE state since the switch failed.
1415                if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
1416                    sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1417                    mSwitchingFgAndBgCalls = false;
1418                    mCallExpectedToResume = null;
1419                }
1420            }
1421        }
1422
1423        @Override
1424        public void onCallHeld(ImsCall imsCall) {
1425            if (DBG) {
1426                if (mForegroundCall.getImsCall() == imsCall) {
1427                    log("onCallHeld (fg) " + imsCall);
1428                } else if (mBackgroundCall.getImsCall() == imsCall) {
1429                    log("onCallHeld (bg) " + imsCall);
1430                }
1431            }
1432
1433            synchronized (mSyncHold) {
1434                ImsPhoneCall.State oldState = mBackgroundCall.getState();
1435                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
1436                        DisconnectCause.NOT_DISCONNECTED);
1437
1438                // Note: If we're performing a switchWaitingOrHoldingAndActive, the call to
1439                // processCallStateChange above may have caused the mBackgroundCall and
1440                // mForegroundCall references below to change meaning.  Watch out for this if you
1441                // are reading through this code.
1442                if (oldState == ImsPhoneCall.State.ACTIVE) {
1443                    // Note: This case comes up when we have just held a call in response to a
1444                    // switchWaitingOrHoldingAndActive.  We now need to resume the background call.
1445                    // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called.
1446                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
1447                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
1448                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1449                    } else {
1450                        //when multiple connections belong to background call,
1451                        //only the first callback reaches here
1452                        //otherwise the oldState is already HOLDING
1453                        if (mPendingMO != null) {
1454                            dialPendingMO();
1455                        }
1456
1457                        // In this case there will be no call resumed, so we can assume that we
1458                        // are done switching fg and bg calls now.
1459                        // This may happen if there is no BG call and we are holding a call so that
1460                        // we can dial another one.
1461                        mSwitchingFgAndBgCalls = false;
1462                    }
1463                } else if (oldState == ImsPhoneCall.State.IDLE && mSwitchingFgAndBgCalls) {
1464                    // The other call terminated in the midst of a switch before this call was held,
1465                    // so resume the foreground call back to ACTIVE state since the switch failed.
1466                    if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
1467                        sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1468                        mSwitchingFgAndBgCalls = false;
1469                        mCallExpectedToResume = null;
1470                    }
1471                }
1472            }
1473            mEventLog.writeOnImsCallHeld(imsCall.getCallSession());
1474        }
1475
1476        @Override
1477        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1478            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
1479
1480            synchronized (mSyncHold) {
1481                ImsPhoneCall.State bgState = mBackgroundCall.getState();
1482                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
1483                    // disconnected while processing hold
1484                    if (mPendingMO != null) {
1485                        dialPendingMO();
1486                    }
1487                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
1488                    mForegroundCall.switchWith(mBackgroundCall);
1489
1490                    if (mPendingMO != null) {
1491                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
1492                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
1493                    }
1494                }
1495                mPhone.notifySuppServiceFailed(Phone.SuppService.HOLD);
1496            }
1497            mEventLog.writeOnImsCallHoldFailed(imsCall.getCallSession(), reasonInfo);
1498        }
1499
1500        @Override
1501        public void onCallResumed(ImsCall imsCall) {
1502            if (DBG) log("onCallResumed");
1503
1504            // If we are the in midst of swapping FG and BG calls and the call we end up resuming
1505            // is not the one we expected, we likely had a resume failure and we need to swap the
1506            // FG and BG calls back.
1507            if (mSwitchingFgAndBgCalls) {
1508                if (imsCall != mCallExpectedToResume) {
1509                    // If the call which resumed isn't as expected, we need to swap back to the
1510                    // previous configuration; the swap has failed.
1511                    if (DBG) {
1512                        log("onCallResumed : switching " + mForegroundCall + " with "
1513                                + mBackgroundCall);
1514                    }
1515                    mForegroundCall.switchWith(mBackgroundCall);
1516                } else {
1517                    // The call which resumed is the one we expected to resume, so we can clear out
1518                    // the mSwitchingFgAndBgCalls flag.
1519                    if (DBG) {
1520                        log("onCallResumed : expected call resumed.");
1521                    }
1522                }
1523                mSwitchingFgAndBgCalls = false;
1524                mCallExpectedToResume = null;
1525            }
1526            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1527                    DisconnectCause.NOT_DISCONNECTED);
1528            mEventLog.writeOnImsCallResumed(imsCall.getCallSession());
1529        }
1530
1531        @Override
1532        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1533            if (mSwitchingFgAndBgCalls) {
1534                // If we are in the midst of swapping the FG and BG calls and
1535                // we got a resume fail, we need to swap back the FG and BG calls.
1536                // Since the FG call was held, will also try to resume the same.
1537                if (imsCall == mCallExpectedToResume) {
1538                    if (DBG) {
1539                        log("onCallResumeFailed : switching " + mForegroundCall + " with "
1540                                + mBackgroundCall);
1541                    }
1542                    mForegroundCall.switchWith(mBackgroundCall);
1543                    if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
1544                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1545                    }
1546                }
1547
1548                //Call swap is done, reset the relevant variables
1549                mCallExpectedToResume = null;
1550                mSwitchingFgAndBgCalls = false;
1551            }
1552            mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME);
1553            mEventLog.writeOnImsCallResumeFailed(imsCall.getCallSession(), reasonInfo);
1554        }
1555
1556        @Override
1557        public void onCallResumeReceived(ImsCall imsCall) {
1558            if (DBG) log("onCallResumeReceived");
1559            ImsPhoneConnection conn = findConnection(imsCall);
1560            if (conn != null && mOnHoldToneStarted) {
1561                mPhone.stopOnHoldTone(conn);
1562                mOnHoldToneStarted = false;
1563            }
1564
1565            SuppServiceNotification supp = new SuppServiceNotification();
1566            // Type of notification: 0 = MO; 1 = MT
1567            // Refer SuppServiceNotification class documentation.
1568            supp.notificationType = 1;
1569            supp.code = SuppServiceNotification.MT_CODE_CALL_RETRIEVED;
1570            mPhone.notifySuppSvcNotification(supp);
1571            mEventLog.writeOnImsCallResumeReceived(imsCall.getCallSession());
1572        }
1573
1574        @Override
1575        public void onCallHoldReceived(ImsCall imsCall) {
1576            if (DBG) log("onCallHoldReceived");
1577
1578            ImsPhoneConnection conn = findConnection(imsCall);
1579            if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
1580                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
1581                    mPhone.startOnHoldTone(conn);
1582                    mOnHoldToneStarted = true;
1583                    mOnHoldToneId = System.identityHashCode(conn);
1584                }
1585            }
1586
1587            SuppServiceNotification supp = new SuppServiceNotification();
1588            // Type of notification: 0 = MO; 1 = MT
1589            // Refer SuppServiceNotification class documentation.
1590            supp.notificationType = 1;
1591            supp.code = SuppServiceNotification.MT_CODE_CALL_ON_HOLD;
1592            mPhone.notifySuppSvcNotification(supp);
1593            mEventLog.writeOnImsCallHoldReceived(imsCall.getCallSession());
1594        }
1595
1596        @Override
1597        public void onCallSuppServiceReceived(ImsCall call,
1598                ImsSuppServiceNotification suppServiceInfo) {
1599            if (DBG) log("onCallSuppServiceReceived: suppServiceInfo=" + suppServiceInfo);
1600
1601            SuppServiceNotification supp = new SuppServiceNotification();
1602            supp.notificationType = suppServiceInfo.notificationType;
1603            supp.code = suppServiceInfo.code;
1604            supp.index = suppServiceInfo.index;
1605            supp.number = suppServiceInfo.number;
1606            supp.history = suppServiceInfo.history;
1607
1608            mPhone.notifySuppSvcNotification(supp);
1609        }
1610
1611        @Override
1612        public void onCallMerged(final ImsCall call, final ImsCall peerCall, boolean swapCalls) {
1613            if (DBG) log("onCallMerged");
1614
1615            ImsPhoneCall foregroundImsPhoneCall = findConnection(call).getCall();
1616            ImsPhoneConnection peerConnection = findConnection(peerCall);
1617            ImsPhoneCall peerImsPhoneCall = peerConnection == null ? null
1618                    : peerConnection.getCall();
1619
1620            if (swapCalls) {
1621                switchAfterConferenceSuccess();
1622            }
1623            foregroundImsPhoneCall.merge(peerImsPhoneCall, ImsPhoneCall.State.ACTIVE);
1624
1625            try {
1626                final ImsPhoneConnection conn = findConnection(call);
1627                log("onCallMerged: ImsPhoneConnection=" + conn);
1628                log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1629                setVideoCallProvider(conn, call);
1630                log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1631            } catch (Exception e) {
1632                loge("onCallMerged: exception " + e);
1633            }
1634
1635            // After merge complete, update foreground as Active
1636            // and background call as Held, if background call exists
1637            processCallStateChange(mForegroundCall.getImsCall(), ImsPhoneCall.State.ACTIVE,
1638                    DisconnectCause.NOT_DISCONNECTED);
1639            if (peerConnection != null) {
1640                processCallStateChange(mBackgroundCall.getImsCall(), ImsPhoneCall.State.HOLDING,
1641                    DisconnectCause.NOT_DISCONNECTED);
1642            }
1643
1644            // Check if the merge was requested by an existing conference call. In that
1645            // case, no further action is required.
1646            if (!call.isMergeRequestedByConf()) {
1647                log("onCallMerged :: calling onMultipartyStateChanged()");
1648                onMultipartyStateChanged(call, true);
1649            } else {
1650                log("onCallMerged :: Merge requested by existing conference.");
1651                // Reset the flag.
1652                call.resetIsMergeRequestedByConf(false);
1653            }
1654            logState();
1655        }
1656
1657        @Override
1658        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
1659            if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo);
1660
1661            // TODO: the call to notifySuppServiceFailed throws up the "merge failed" dialog
1662            // We should move this into the InCallService so that it is handled appropriately
1663            // based on the user facing UI.
1664            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
1665
1666            // Start plumbing this even through Telecom so other components can take
1667            // appropriate action.
1668            ImsPhoneConnection conn = findConnection(call);
1669            if (conn != null) {
1670                conn.onConferenceMergeFailed();
1671            }
1672        }
1673
1674        /**
1675         * Called when the state of IMS conference participant(s) has changed.
1676         *
1677         * @param call the call object that carries out the IMS call.
1678         * @param participants the participant(s) and their new state information.
1679         */
1680        @Override
1681        public void onConferenceParticipantsStateChanged(ImsCall call,
1682                List<ConferenceParticipant> participants) {
1683            if (DBG) log("onConferenceParticipantsStateChanged");
1684
1685            ImsPhoneConnection conn = findConnection(call);
1686            if (conn != null) {
1687                conn.updateConferenceParticipants(participants);
1688            }
1689        }
1690
1691        @Override
1692        public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
1693            mPhone.onTtyModeReceived(mode);
1694        }
1695
1696        @Override
1697        public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1698            ImsReasonInfo reasonInfo) {
1699            if (DBG) {
1700                log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
1701                    targetAccessTech + ", reasonInfo=" + reasonInfo);
1702            }
1703            mEventLog.writeOnImsCallHandover(imsCall.getCallSession(),
1704                    srcAccessTech, targetAccessTech, reasonInfo);
1705        }
1706
1707        @Override
1708        public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1709            ImsReasonInfo reasonInfo) {
1710            if (DBG) {
1711                log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech +
1712                    ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo);
1713            }
1714            mEventLog.writeOnImsCallHandoverFailed(imsCall.getCallSession(),
1715                    srcAccessTech, targetAccessTech, reasonInfo);
1716        }
1717
1718        /**
1719         * Handles a change to the multiparty state for an {@code ImsCall}.  Notifies the associated
1720         * {@link ImsPhoneConnection} of the change.
1721         *
1722         * @param imsCall The IMS call.
1723         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
1724         *      otherwise.
1725         */
1726        @Override
1727        public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
1728            if (DBG) log("onMultipartyStateChanged to " + (isMultiParty ? "Y" : "N"));
1729
1730            ImsPhoneConnection conn = findConnection(imsCall);
1731            if (conn != null) {
1732                conn.updateMultipartyState(isMultiParty);
1733            }
1734        }
1735    };
1736
1737    /**
1738     * Listen to the IMS call state change
1739     */
1740    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
1741        @Override
1742        public void onCallStarted(ImsCall imsCall) {
1743            if (DBG) log("mImsUssdListener onCallStarted");
1744
1745            if (imsCall == mUssdSession) {
1746                if (mPendingUssd != null) {
1747                    AsyncResult.forMessage(mPendingUssd);
1748                    mPendingUssd.sendToTarget();
1749                    mPendingUssd = null;
1750                }
1751            }
1752        }
1753
1754        @Override
1755        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1756            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
1757
1758            onCallTerminated(imsCall, reasonInfo);
1759        }
1760
1761        @Override
1762        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1763            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
1764
1765            if (imsCall == mUssdSession) {
1766                mUssdSession = null;
1767                if (mPendingUssd != null) {
1768                    CommandException ex =
1769                            new CommandException(CommandException.Error.GENERIC_FAILURE);
1770                    AsyncResult.forMessage(mPendingUssd, null, ex);
1771                    mPendingUssd.sendToTarget();
1772                    mPendingUssd = null;
1773                }
1774            }
1775            imsCall.close();
1776        }
1777
1778        @Override
1779        public void onCallUssdMessageReceived(ImsCall call,
1780                int mode, String ussdMessage) {
1781            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
1782
1783            int ussdMode = -1;
1784
1785            switch(mode) {
1786                case ImsCall.USSD_MODE_REQUEST:
1787                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
1788                    break;
1789
1790                case ImsCall.USSD_MODE_NOTIFY:
1791                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
1792                    break;
1793            }
1794
1795            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
1796        }
1797    };
1798
1799    /**
1800     * Listen to the IMS service state change
1801     *
1802     */
1803    private ImsConnectionStateListener mImsConnectionStateListener =
1804        new ImsConnectionStateListener() {
1805        @Override
1806        public void onImsConnected() {
1807            if (DBG) log("onImsConnected");
1808            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1809            mPhone.setImsRegistered(true);
1810            mEventLog.writeOnImsConnectionState(
1811                    TelephonyEventLog.IMS_CONNECTION_STATE_CONNECTED, null);
1812        }
1813
1814        @Override
1815        public void onImsDisconnected(ImsReasonInfo imsReasonInfo) {
1816            if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
1817            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1818            mPhone.setImsRegistered(false);
1819            mPhone.processDisconnectReason(imsReasonInfo);
1820            mEventLog.writeOnImsConnectionState(
1821                    TelephonyEventLog.IMS_CONNECTION_STATE_DISCONNECTED, imsReasonInfo);
1822        }
1823
1824        @Override
1825        public void onImsProgressing() {
1826            if (DBG) log("onImsProgressing");
1827            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1828            mPhone.setImsRegistered(false);
1829            mEventLog.writeOnImsConnectionState(
1830                    TelephonyEventLog.IMS_CONNECTION_STATE_PROGRESSING, null);
1831        }
1832
1833        @Override
1834        public void onImsResumed() {
1835            if (DBG) log("onImsResumed");
1836            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1837            mEventLog.writeOnImsConnectionState(
1838                    TelephonyEventLog.IMS_CONNECTION_STATE_RESUMED, null);
1839        }
1840
1841        @Override
1842        public void onImsSuspended() {
1843            if (DBG) log("onImsSuspended");
1844            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1845            mEventLog.writeOnImsConnectionState(
1846                    TelephonyEventLog.IMS_CONNECTION_STATE_SUSPENDED, null);
1847        }
1848
1849        @Override
1850        public void onFeatureCapabilityChanged(int serviceClass,
1851                int[] enabledFeatures, int[] disabledFeatures) {
1852            if (serviceClass == ImsServiceClass.MMTEL) {
1853                boolean tmpIsVideoCallEnabled = isVideoCallEnabled();
1854                // Check enabledFeatures to determine capabilities. We ignore disabledFeatures.
1855                StringBuilder sb;
1856                if (DBG) {
1857                    sb = new StringBuilder(120);
1858                    sb.append("onFeatureCapabilityChanged: ");
1859                }
1860                for (int  i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE;
1861                        i <= ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI &&
1862                        i < enabledFeatures.length; i++) {
1863                    if (enabledFeatures[i] == i) {
1864                        // If the feature is set to its own integer value it is enabled.
1865                        if (DBG) {
1866                            sb.append(mImsFeatureStrings[i]);
1867                            sb.append(":true ");
1868                        }
1869
1870                        mImsFeatureEnabled[i] = true;
1871                    } else if (enabledFeatures[i]
1872                            == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) {
1873                        // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled.
1874                        if (DBG) {
1875                            sb.append(mImsFeatureStrings[i]);
1876                            sb.append(":false ");
1877                        }
1878
1879                        mImsFeatureEnabled[i] = false;
1880                    } else {
1881                        // Feature has unknown state; it is not its own value or -1.
1882                        if (DBG) {
1883                            loge("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i]
1884                                    + "): unexpectedValue=" + enabledFeatures[i]);
1885                        }
1886                    }
1887                }
1888                if (DBG) {
1889                    log(sb.toString());
1890                }
1891                if (tmpIsVideoCallEnabled != isVideoCallEnabled()) {
1892                    mPhone.notifyForVideoCapabilityChanged(isVideoCallEnabled());
1893                }
1894
1895                // TODO: Use the ImsCallSession or ImsCallProfile to tell the initial Wifi state and
1896                // {@link ImsCallSession.Listener#callSessionHandover} to listen for changes to
1897                // wifi capability caused by a handover.
1898                if (DBG) log("onFeatureCapabilityChanged: isVolteEnabled=" + isVolteEnabled()
1899                            + ", isVideoCallEnabled=" + isVideoCallEnabled()
1900                            + ", isVowifiEnabled=" + isVowifiEnabled()
1901                            + ", isUtEnabled=" + isUtEnabled());
1902                for (ImsPhoneConnection connection : mConnections) {
1903                    connection.updateWifiState();
1904                }
1905
1906                mPhone.onFeatureCapabilityChanged();
1907
1908                mEventLog.writeOnImsCapabilities(mImsFeatureEnabled);
1909            }
1910        }
1911
1912        @Override
1913        public void onVoiceMessageCountChanged(int count) {
1914            if (DBG) log("onVoiceMessageCountChanged :: count=" + count);
1915            mPhone.mDefaultPhone.setVoiceMessageCount(count);
1916        }
1917    };
1918
1919    private ImsConfigListener.Stub mImsConfigListener = new ImsConfigListener.Stub() {
1920        @Override
1921        public void onGetFeatureResponse(int feature, int network, int value, int status) {}
1922
1923        @Override
1924        public void onSetFeatureResponse(int feature, int network, int value, int status) {
1925            mEventLog.writeImsSetFeatureValue(feature, network, value, status);
1926        }
1927
1928        @Override
1929        public void onGetVideoQuality(int status, int quality) {}
1930
1931        @Override
1932        public void onSetVideoQuality(int status) {}
1933
1934    };
1935
1936    public ImsUtInterface getUtInterface() throws ImsException {
1937        if (mImsManager == null) {
1938            throw getImsManagerIsNullException();
1939        }
1940
1941        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
1942        return ut;
1943    }
1944
1945    private void transferHandoverConnections(ImsPhoneCall call) {
1946        if (call.mConnections != null) {
1947            for (Connection c : call.mConnections) {
1948                c.mPreHandoverState = call.mState;
1949                log ("Connection state before handover is " + c.getStateBeforeHandover());
1950            }
1951        }
1952        if (mHandoverCall.mConnections == null ) {
1953            mHandoverCall.mConnections = call.mConnections;
1954        } else { // Multi-call SRVCC
1955            mHandoverCall.mConnections.addAll(call.mConnections);
1956        }
1957        if (mHandoverCall.mConnections != null) {
1958            if (call.getImsCall() != null) {
1959                call.getImsCall().close();
1960            }
1961            for (Connection c : mHandoverCall.mConnections) {
1962                ((ImsPhoneConnection)c).changeParent(mHandoverCall);
1963                ((ImsPhoneConnection)c).releaseWakeLock();
1964            }
1965        }
1966        if (call.getState().isAlive()) {
1967            log ("Call is alive and state is " + call.mState);
1968            mHandoverCall.mState = call.mState;
1969        }
1970        call.mConnections.clear();
1971        call.mState = ImsPhoneCall.State.IDLE;
1972    }
1973
1974    /* package */
1975    void notifySrvccState(Call.SrvccState state) {
1976        if (DBG) log("notifySrvccState state=" + state);
1977
1978        mSrvccState = state;
1979
1980        if (mSrvccState == Call.SrvccState.COMPLETED) {
1981            transferHandoverConnections(mForegroundCall);
1982            transferHandoverConnections(mBackgroundCall);
1983            transferHandoverConnections(mRingingCall);
1984        }
1985    }
1986
1987    //****** Overridden from Handler
1988
1989    @Override
1990    public void
1991    handleMessage (Message msg) {
1992        AsyncResult ar;
1993        if (DBG) log("handleMessage what=" + msg.what);
1994
1995        switch (msg.what) {
1996            case EVENT_HANGUP_PENDINGMO:
1997                if (mPendingMO != null) {
1998                    mPendingMO.onDisconnect();
1999                    removeConnection(mPendingMO);
2000                    mPendingMO = null;
2001                }
2002                mPendingIntentExtras = null;
2003                updatePhoneState();
2004                mPhone.notifyPreciseCallStateChanged();
2005                break;
2006            case EVENT_RESUME_BACKGROUND:
2007                try {
2008                    resumeWaitingOrHolding();
2009                } catch (CallStateException e) {
2010                    if (Phone.DEBUG_PHONE) {
2011                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
2012                    }
2013                }
2014                break;
2015            case EVENT_DIAL_PENDINGMO:
2016                dialInternal(mPendingMO, mClirMode, mPendingCallVideoState, mPendingIntentExtras);
2017                mPendingIntentExtras = null;
2018                break;
2019
2020            case EVENT_EXIT_ECBM_BEFORE_PENDINGMO:
2021                if (mPendingMO != null) {
2022                    //Send ECBM exit request
2023                    try {
2024                        getEcbmInterface().exitEmergencyCallbackMode();
2025                        mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
2026                        pendingCallClirMode = mClirMode;
2027                        pendingCallInEcm = true;
2028                    } catch (ImsException e) {
2029                        e.printStackTrace();
2030                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
2031                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
2032                    }
2033                }
2034                break;
2035
2036            case EVENT_EXIT_ECM_RESPONSE_CDMA:
2037                // no matter the result, we still do the same here
2038                if (pendingCallInEcm) {
2039                    dialInternal(mPendingMO, pendingCallClirMode,
2040                            mPendingCallVideoState, mPendingIntentExtras);
2041                    mPendingIntentExtras = null;
2042                    pendingCallInEcm = false;
2043                }
2044                mPhone.unsetOnEcbModeExitResponse(this);
2045                break;
2046            case EVENT_VT_DATA_USAGE_UPDATE:
2047                ar = (AsyncResult) msg.obj;
2048                ImsCall call = (ImsCall) ar.userObj;
2049                Long usage = (long) ar.result;
2050                log("VT data usage update. usage = " + usage + ", imsCall = " + call);
2051
2052                Long oldUsage = 0L;
2053                if (mVtDataUsageMap.containsKey(call.uniqueId)) {
2054                    oldUsage = mVtDataUsageMap.get(call.uniqueId);
2055                }
2056                mTotalVtDataUsage += (usage - oldUsage);
2057                mVtDataUsageMap.put(call.uniqueId, usage);
2058                break;
2059        }
2060    }
2061
2062    @Override
2063    protected void log(String msg) {
2064        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
2065    }
2066
2067    protected void loge(String msg) {
2068        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
2069    }
2070
2071    /**
2072     * Logs the current state of the ImsPhoneCallTracker.  Useful for debugging issues with
2073     * call tracking.
2074     */
2075    /* package */
2076    void logState() {
2077        if (!VERBOSE_STATE_LOGGING) {
2078            return;
2079        }
2080
2081        StringBuilder sb = new StringBuilder();
2082        sb.append("Current IMS PhoneCall State:\n");
2083        sb.append(" Foreground: ");
2084        sb.append(mForegroundCall);
2085        sb.append("\n");
2086        sb.append(" Background: ");
2087        sb.append(mBackgroundCall);
2088        sb.append("\n");
2089        sb.append(" Ringing: ");
2090        sb.append(mRingingCall);
2091        sb.append("\n");
2092        sb.append(" Handover: ");
2093        sb.append(mHandoverCall);
2094        sb.append("\n");
2095        Rlog.v(LOG_TAG, sb.toString());
2096    }
2097
2098    @Override
2099    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2100        pw.println("ImsPhoneCallTracker extends:");
2101        super.dump(fd, pw, args);
2102        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
2103        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
2104        pw.println(" mRingingCall=" + mRingingCall);
2105        pw.println(" mForegroundCall=" + mForegroundCall);
2106        pw.println(" mBackgroundCall=" + mBackgroundCall);
2107        pw.println(" mHandoverCall=" + mHandoverCall);
2108        pw.println(" mPendingMO=" + mPendingMO);
2109        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
2110        pw.println(" mPhone=" + mPhone);
2111        pw.println(" mDesiredMute=" + mDesiredMute);
2112        pw.println(" mState=" + mState);
2113        for (int i = 0; i < mImsFeatureEnabled.length; i++) {
2114            pw.println(" " + mImsFeatureStrings[i] + ": "
2115                    + ((mImsFeatureEnabled[i]) ? "enabled" : "disabled"));
2116        }
2117        pw.println(" mTotalVtDataUsage=" + mTotalVtDataUsage);
2118        for (Map.Entry<Integer, Long> entry : mVtDataUsageMap.entrySet()) {
2119            pw.println("    id=" + entry.getKey() + " ,usage=" + entry.getValue());
2120        }
2121
2122        pw.flush();
2123        pw.println("++++++++++++++++++++++++++++++++");
2124
2125        try {
2126            if (mImsManager != null) {
2127                mImsManager.dump(fd, pw, args);
2128            }
2129        } catch (Exception e) {
2130            e.printStackTrace();
2131        }
2132
2133        if (mConnections != null && mConnections.size() > 0) {
2134            pw.println("mConnections:");
2135            for (int i = 0; i < mConnections.size(); i++) {
2136                pw.println("  [" + i + "]: " + mConnections.get(i));
2137            }
2138        }
2139    }
2140
2141    @Override
2142    protected void handlePollCalls(AsyncResult ar) {
2143    }
2144
2145    /* package */
2146    ImsEcbm getEcbmInterface() throws ImsException {
2147        if (mImsManager == null) {
2148            throw getImsManagerIsNullException();
2149        }
2150
2151        ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
2152        return ecbm;
2153    }
2154
2155    /* package */
2156    ImsMultiEndpoint getMultiEndpointInterface() throws ImsException {
2157        if (mImsManager == null) {
2158            throw getImsManagerIsNullException();
2159        }
2160
2161        try {
2162            return mImsManager.getMultiEndpointInterface(mServiceId);
2163        } catch (ImsException e) {
2164            if (e.getCode() == ImsReasonInfo.CODE_MULTIENDPOINT_NOT_SUPPORTED) {
2165                return null;
2166            } else {
2167                throw e;
2168            }
2169
2170        }
2171    }
2172
2173    public boolean isInEmergencyCall() {
2174        return mIsInEmergencyCall;
2175    }
2176
2177    public boolean isVolteEnabled() {
2178        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE];
2179    }
2180
2181    public boolean isVowifiEnabled() {
2182        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI];
2183    }
2184
2185    public boolean isVideoCallEnabled() {
2186        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE]
2187                || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]);
2188    }
2189
2190    @Override
2191    public PhoneConstants.State getState() {
2192        return mState;
2193    }
2194
2195    private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
2196            throws RemoteException {
2197        IImsVideoCallProvider imsVideoCallProvider =
2198                imsCall.getCallSession().getVideoCallProvider();
2199        if (imsVideoCallProvider != null) {
2200            ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
2201                    new ImsVideoCallProviderWrapper(imsVideoCallProvider);
2202            conn.setVideoProvider(imsVideoCallProviderWrapper);
2203            imsVideoCallProviderWrapper.registerForDataUsageUpdate
2204                    (this, EVENT_VT_DATA_USAGE_UPDATE, imsCall);
2205        }
2206    }
2207
2208    public boolean isUtEnabled() {
2209        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_LTE]
2210            || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI]);
2211    }
2212
2213    /**
2214     * Given a call subject, removes any characters considered by the current carrier to be
2215     * invalid, as well as escaping (using \) any characters which the carrier requires to be
2216     * escaped.
2217     *
2218     * @param callSubject The call subject.
2219     * @return The call subject with invalid characters removed and escaping applied as required.
2220     */
2221    private String cleanseInstantLetteringMessage(String callSubject) {
2222        if (TextUtils.isEmpty(callSubject)) {
2223            return callSubject;
2224        }
2225
2226        // Get the carrier config for the current sub.
2227        CarrierConfigManager configMgr = (CarrierConfigManager)
2228                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
2229        // Bail if we can't find the carrier config service.
2230        if (configMgr == null) {
2231            return callSubject;
2232        }
2233
2234        PersistableBundle carrierConfig = configMgr.getConfigForSubId(mPhone.getSubId());
2235        // Bail if no carrier config found.
2236        if (carrierConfig == null) {
2237            return callSubject;
2238        }
2239
2240        // Try to replace invalid characters
2241        String invalidCharacters = carrierConfig.getString(
2242                CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING);
2243        if (!TextUtils.isEmpty(invalidCharacters)) {
2244            callSubject = callSubject.replaceAll(invalidCharacters, "");
2245        }
2246
2247        // Try to escape characters which need to be escaped.
2248        String escapedCharacters = carrierConfig.getString(
2249                CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING);
2250        if (!TextUtils.isEmpty(escapedCharacters)) {
2251            callSubject = escapeChars(escapedCharacters, callSubject);
2252        }
2253        return callSubject;
2254    }
2255
2256    /**
2257     * Given a source string, return a string where a set of characters are escaped using the
2258     * backslash character.
2259     *
2260     * @param toEscape The characters to escape with a backslash.
2261     * @param source The source string.
2262     * @return The source string with characters escaped.
2263     */
2264    private String escapeChars(String toEscape, String source) {
2265        StringBuilder escaped = new StringBuilder();
2266        for (char c : source.toCharArray()) {
2267            if (toEscape.contains(Character.toString(c))) {
2268                escaped.append("\\");
2269            }
2270            escaped.append(c);
2271        }
2272
2273        return escaped.toString();
2274    }
2275
2276    /**
2277     * Initiates a pull of an external call.
2278     *
2279     * Initiates a pull by making a dial request with the {@link ImsCallProfile#EXTRA_IS_CALL_PULL}
2280     * extra specified.  We call {@link ImsPhone#notifyUnknownConnection(Connection)} which notifies
2281     * Telecom of the new dialed connection.  The
2282     * {@code PstnIncomingCallNotifier#maybeSwapWithUnknownConnection} logic ensures that the new
2283     * {@link ImsPhoneConnection} resulting from the dial gets swapped with the
2284     * {@link ImsExternalConnection}, which effectively makes the external call become a regular
2285     * call.  Magic!
2286     *
2287     * @param number The phone number of the call to be pulled.
2288     * @param videoState The desired video state of the pulled call.
2289     */
2290    @Override
2291    public void pullExternalCall(String number, int videoState) {
2292        Bundle extras = new Bundle();
2293        extras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL, true);
2294        try {
2295            Connection connection = dial(number, videoState, extras);
2296            mPhone.notifyUnknownConnection(connection);
2297        } catch (CallStateException e) {
2298            loge("pullExternalCall failed - " + e);
2299        }
2300    }
2301
2302    private ImsException getImsManagerIsNullException() {
2303        return new ImsException("no ims manager", ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
2304    }
2305
2306    /**
2307     * Determines if answering an incoming call will cause the active call to be disconnected.
2308     * <p>
2309     * This will be the case if
2310     * {@link CarrierConfigManager#KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is
2311     * {@code true} for the carrier, the active call is a video call over WIFI, and the incoming
2312     * call is an audio call.
2313     *
2314     * @param activeCall The active call.
2315     * @param incomingCall The incoming call.
2316     * @return {@code true} if answering the incoming call will cause the active call to be
2317     *      disconnected, {@code false} otherwise.
2318     */
2319    private boolean shouldDisconnectActiveCallOnAnswer(ImsCall activeCall,
2320            ImsCall incomingCall) {
2321
2322        if (!mDropVideoCallWhenAnsweringAudioCall) {
2323            return false;
2324        }
2325
2326        boolean isActiveCallVideo = activeCall.isVideoCall() ||
2327                (mTreatDowngradedVideoCallsAsVideoCalls && activeCall.wasVideoCall());
2328        boolean isActiveCallOnWifi = activeCall.isWifiCall();
2329        boolean isIncomingCallAudio = !incomingCall.isVideoCall();
2330
2331        return isActiveCallVideo && isActiveCallOnWifi && isIncomingCallAudio;
2332    }
2333
2334    /** Get aggregated video call data usage since boot.
2335     *
2336     * @return data usage in bytes
2337     */
2338    public long getVtDataUsage() {
2339
2340        // If there is an ongoing VT call, request the latest VT usage from the modem. The latest
2341        // usage will return asynchronously so it won't be counted in this round, but it will be
2342        // eventually counted when next getVtDataUsage is called.
2343        if (mState != PhoneConstants.State.IDLE) {
2344            for (ImsPhoneConnection conn : mConnections) {
2345                conn.getVideoProvider().onRequestConnectionDataUsage();
2346            }
2347        }
2348
2349        return mTotalVtDataUsage;
2350    }
2351}
2352