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