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