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