ImsPhoneCallTracker.java revision 75f96a40e73bbf262287b64f7ba79f058adac472
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            boolean switchingWithWaitingCall = mBackgroundCall.getImsCall() == null &&
786                    mRingingCall != null &&
787                    mRingingCall.getState() == ImsPhoneCall.State.WAITING;
788
789            mSwitchingFgAndBgCalls = true;
790            if (switchingWithWaitingCall) {
791                mCallExpectedToResume = mRingingCall.getImsCall();
792            } else {
793                mCallExpectedToResume = mBackgroundCall.getImsCall();
794            }
795            mForegroundCall.switchWith(mBackgroundCall);
796
797            // Hold the foreground call; once the foreground call is held, the background call will
798            // be resumed.
799            try {
800                imsCall.hold();
801                mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
802                        ImsCommand.IMS_CMD_HOLD);
803
804                // If there is no background call to resume, then don't expect there to be a switch.
805                if (mCallExpectedToResume == null) {
806                    log("mCallExpectedToResume is null");
807                    mSwitchingFgAndBgCalls = false;
808                }
809            } catch (ImsException e) {
810                mForegroundCall.switchWith(mBackgroundCall);
811                throw new CallStateException(e.getMessage());
812            }
813        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
814            resumeWaitingOrHolding();
815        }
816    }
817
818    public void
819    conference() {
820        if (DBG) log("conference");
821
822        ImsCall fgImsCall = mForegroundCall.getImsCall();
823        if (fgImsCall == null) {
824            log("conference no foreground ims call");
825            return;
826        }
827
828        ImsCall bgImsCall = mBackgroundCall.getImsCall();
829        if (bgImsCall == null) {
830            log("conference no background ims call");
831            return;
832        }
833
834        // Keep track of the connect time of the earliest call so that it can be set on the
835        // {@code ImsConference} when it is created.
836        long foregroundConnectTime = mForegroundCall.getEarliestConnectTime();
837        long backgroundConnectTime = mBackgroundCall.getEarliestConnectTime();
838        long conferenceConnectTime;
839        if (foregroundConnectTime > 0 && backgroundConnectTime > 0) {
840            conferenceConnectTime = Math.min(mForegroundCall.getEarliestConnectTime(),
841                    mBackgroundCall.getEarliestConnectTime());
842            log("conference - using connect time = " + conferenceConnectTime);
843        } else if (foregroundConnectTime > 0) {
844            log("conference - bg call connect time is 0; using fg = " + foregroundConnectTime);
845            conferenceConnectTime = foregroundConnectTime;
846        } else {
847            log("conference - fg call connect time is 0; using bg = " + backgroundConnectTime);
848            conferenceConnectTime = backgroundConnectTime;
849        }
850
851        ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection();
852        if (foregroundConnection != null) {
853            foregroundConnection.setConferenceConnectTime(conferenceConnectTime);
854        }
855
856        try {
857            fgImsCall.merge(bgImsCall);
858        } catch (ImsException e) {
859            log("conference " + e.getMessage());
860        }
861    }
862
863    public void
864    explicitCallTransfer() {
865        //TODO : implement
866    }
867
868    public void
869    clearDisconnected() {
870        if (DBG) log("clearDisconnected");
871
872        internalClearDisconnected();
873
874        updatePhoneState();
875        mPhone.notifyPreciseCallStateChanged();
876    }
877
878    public boolean
879    canConference() {
880        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
881            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
882            && !mBackgroundCall.isFull()
883            && !mForegroundCall.isFull();
884    }
885
886    public boolean
887    canDial() {
888        boolean ret;
889        int serviceState = mPhone.getServiceState().getState();
890        String disableCall = SystemProperties.get(
891                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
892
893        ret = (serviceState != ServiceState.STATE_POWER_OFF)
894            && mPendingMO == null
895            && !mRingingCall.isRinging()
896            && !disableCall.equals("true")
897            && (!mForegroundCall.getState().isAlive()
898                    || !mBackgroundCall.getState().isAlive());
899
900        return ret;
901    }
902
903    public boolean
904    canTransfer() {
905        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
906            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
907    }
908
909    //***** Private Instance Methods
910
911    private void
912    internalClearDisconnected() {
913        mRingingCall.clearDisconnected();
914        mForegroundCall.clearDisconnected();
915        mBackgroundCall.clearDisconnected();
916        mHandoverCall.clearDisconnected();
917    }
918
919    private void
920    updatePhoneState() {
921        PhoneConstants.State oldState = mState;
922
923        boolean isPendingMOIdle = mPendingMO == null || !mPendingMO.getState().isAlive();
924
925        if (mRingingCall.isRinging()) {
926            mState = PhoneConstants.State.RINGING;
927        } else if (!isPendingMOIdle || !mForegroundCall.isIdle() || !mBackgroundCall.isIdle()) {
928            // There is a non-idle call, so we're off the hook.
929            mState = PhoneConstants.State.OFFHOOK;
930        } else {
931            mState = PhoneConstants.State.IDLE;
932        }
933
934        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
935            mVoiceCallEndedRegistrants.notifyRegistrants(
936                    new AsyncResult(null, null, null));
937        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
938            mVoiceCallStartedRegistrants.notifyRegistrants (
939                    new AsyncResult(null, null, null));
940        }
941
942        if (DBG) {
943            log("updatePhoneState pendingMo = " + (mPendingMO == null ? "null"
944                    : mPendingMO.getState()) + ", fg= " + mForegroundCall.getState() + "("
945                    + mForegroundCall.getConnections().size() + "), bg= " + mBackgroundCall
946                    .getState() + "(" + mBackgroundCall.getConnections().size() + ")");
947            log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
948        }
949
950        if (mState != oldState) {
951            mPhone.notifyPhoneStateChanged();
952            mMetrics.writePhoneState(mPhone.getPhoneId(), mState);
953            notifyPhoneStateChanged(oldState, mState);
954        }
955    }
956
957    private void
958    handleRadioNotAvailable() {
959        // handlePollCalls will clear out its
960        // call list when it gets the CommandException
961        // error result from this
962        pollCallsWhenSafe();
963    }
964
965    private void
966    dumpState() {
967        List l;
968
969        log("Phone State:" + mState);
970
971        log("Ringing call: " + mRingingCall.toString());
972
973        l = mRingingCall.getConnections();
974        for (int i = 0, s = l.size(); i < s; i++) {
975            log(l.get(i).toString());
976        }
977
978        log("Foreground call: " + mForegroundCall.toString());
979
980        l = mForegroundCall.getConnections();
981        for (int i = 0, s = l.size(); i < s; i++) {
982            log(l.get(i).toString());
983        }
984
985        log("Background call: " + mBackgroundCall.toString());
986
987        l = mBackgroundCall.getConnections();
988        for (int i = 0, s = l.size(); i < s; i++) {
989            log(l.get(i).toString());
990        }
991
992    }
993
994    //***** Called from ImsPhone
995
996    public void setUiTTYMode(int uiTtyMode, Message onComplete) {
997        if (mImsManager == null) {
998            mPhone.sendErrorResponse(onComplete, getImsManagerIsNullException());
999            return;
1000        }
1001
1002        try {
1003            mImsManager.setUiTTYMode(mPhone.getContext(), mServiceId, uiTtyMode, onComplete);
1004        } catch (ImsException e) {
1005            loge("setTTYMode : " + e);
1006            mPhone.sendErrorResponse(onComplete, e);
1007        }
1008    }
1009
1010    public void setMute(boolean mute) {
1011        mDesiredMute = mute;
1012        mForegroundCall.setMute(mute);
1013    }
1014
1015    public boolean getMute() {
1016        return mDesiredMute;
1017    }
1018
1019    public void sendDtmf(char c, Message result) {
1020        if (DBG) log("sendDtmf");
1021
1022        ImsCall imscall = mForegroundCall.getImsCall();
1023        if (imscall != null) {
1024            imscall.sendDtmf(c, result);
1025        }
1026    }
1027
1028    public void
1029    startDtmf(char c) {
1030        if (DBG) log("startDtmf");
1031
1032        ImsCall imscall = mForegroundCall.getImsCall();
1033        if (imscall != null) {
1034            imscall.startDtmf(c);
1035        } else {
1036            loge("startDtmf : no foreground call");
1037        }
1038    }
1039
1040    public void
1041    stopDtmf() {
1042        if (DBG) log("stopDtmf");
1043
1044        ImsCall imscall = mForegroundCall.getImsCall();
1045        if (imscall != null) {
1046            imscall.stopDtmf();
1047        } else {
1048            loge("stopDtmf : no foreground call");
1049        }
1050    }
1051
1052    //***** Called from ImsPhoneConnection
1053
1054    public void hangup (ImsPhoneConnection conn) throws CallStateException {
1055        if (DBG) log("hangup connection");
1056
1057        if (conn.getOwner() != this) {
1058            throw new CallStateException ("ImsPhoneConnection " + conn
1059                    + "does not belong to ImsPhoneCallTracker " + this);
1060        }
1061
1062        hangup(conn.getCall());
1063    }
1064
1065    //***** Called from ImsPhoneCall
1066
1067    public void hangup (ImsPhoneCall call) throws CallStateException {
1068        if (DBG) log("hangup call");
1069
1070        if (call.getConnections().size() == 0) {
1071            throw new CallStateException("no connections");
1072        }
1073
1074        ImsCall imsCall = call.getImsCall();
1075        boolean rejectCall = false;
1076
1077        if (call == mRingingCall) {
1078            if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
1079            rejectCall = true;
1080        } else if (call == mForegroundCall) {
1081            if (call.isDialingOrAlerting()) {
1082                if (Phone.DEBUG_PHONE) {
1083                    log("(foregnd) hangup dialing or alerting...");
1084                }
1085            } else {
1086                if (Phone.DEBUG_PHONE) {
1087                    log("(foregnd) hangup foreground");
1088                }
1089                //held call will be resumed by onCallTerminated
1090            }
1091        } else if (call == mBackgroundCall) {
1092            if (Phone.DEBUG_PHONE) {
1093                log("(backgnd) hangup waiting or background");
1094            }
1095        } else {
1096            throw new CallStateException ("ImsPhoneCall " + call +
1097                    "does not belong to ImsPhoneCallTracker " + this);
1098        }
1099
1100        call.onHangupLocal();
1101
1102        try {
1103            if (imsCall != null) {
1104                if (rejectCall) {
1105                    imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
1106                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1107                            ImsCommand.IMS_CMD_REJECT);
1108                } else {
1109                    imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
1110                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1111                            ImsCommand.IMS_CMD_TERMINATE);
1112                }
1113            } else if (mPendingMO != null && call == mForegroundCall) {
1114                // is holding a foreground call
1115                mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
1116                mPendingMO.onDisconnect();
1117                removeConnection(mPendingMO);
1118                mPendingMO = null;
1119                updatePhoneState();
1120                removeMessages(EVENT_DIAL_PENDINGMO);
1121            }
1122        } catch (ImsException e) {
1123            throw new CallStateException(e.getMessage());
1124        }
1125
1126        mPhone.notifyPreciseCallStateChanged();
1127    }
1128
1129    void callEndCleanupHandOverCallIfAny() {
1130        if (mHandoverCall.mConnections.size() > 0) {
1131            if (DBG) log("callEndCleanupHandOverCallIfAny, mHandoverCall.mConnections="
1132                    + mHandoverCall.mConnections);
1133            mHandoverCall.mConnections.clear();
1134            mConnections.clear();
1135            mState = PhoneConstants.State.IDLE;
1136        }
1137    }
1138
1139    /* package */
1140    void resumeWaitingOrHolding() throws CallStateException {
1141        if (DBG) log("resumeWaitingOrHolding");
1142
1143        try {
1144            if (mForegroundCall.getState().isAlive()) {
1145                //resume foreground call after holding background call
1146                //they were switched before holding
1147                ImsCall imsCall = mForegroundCall.getImsCall();
1148                if (imsCall != null) {
1149                    imsCall.resume();
1150                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1151                            ImsCommand.IMS_CMD_RESUME);
1152                }
1153            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
1154                //accept waiting call after holding background call
1155                ImsCall imsCall = mRingingCall.getImsCall();
1156                if (imsCall != null) {
1157                    imsCall.accept(
1158                        ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState));
1159                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1160                            ImsCommand.IMS_CMD_ACCEPT);
1161                }
1162            } else {
1163                //Just resume background call.
1164                //To distinguish resuming call with swapping calls
1165                //we do not switch calls.here
1166                //ImsPhoneConnection.update will chnage the parent when completed
1167                ImsCall imsCall = mBackgroundCall.getImsCall();
1168                if (imsCall != null) {
1169                    imsCall.resume();
1170                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
1171                            ImsCommand.IMS_CMD_RESUME);
1172                }
1173            }
1174        } catch (ImsException e) {
1175            throw new CallStateException(e.getMessage());
1176        }
1177    }
1178
1179    public void sendUSSD (String ussdString, Message response) {
1180        if (DBG) log("sendUSSD");
1181
1182        try {
1183            if (mUssdSession != null) {
1184                mUssdSession.sendUssd(ussdString);
1185                AsyncResult.forMessage(response, null, null);
1186                response.sendToTarget();
1187                return;
1188            }
1189
1190            if (mImsManager == null) {
1191                mPhone.sendErrorResponse(response, getImsManagerIsNullException());
1192                return;
1193            }
1194
1195            String[] callees = new String[] { ussdString };
1196            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
1197                    ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
1198            profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
1199                    ImsCallProfile.DIALSTRING_USSD);
1200
1201            mUssdSession = mImsManager.makeCall(mServiceId, profile,
1202                    callees, mImsUssdListener);
1203        } catch (ImsException e) {
1204            loge("sendUSSD : " + e);
1205            mPhone.sendErrorResponse(response, e);
1206        }
1207    }
1208
1209    public void cancelUSSD() {
1210        if (mUssdSession == null) return;
1211
1212        try {
1213            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
1214        } catch (ImsException e) {
1215        }
1216
1217    }
1218
1219    private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) {
1220        for (ImsPhoneConnection conn : mConnections) {
1221            if (conn.getImsCall() == imsCall) {
1222                return conn;
1223            }
1224        }
1225        return null;
1226    }
1227
1228    private synchronized void removeConnection(ImsPhoneConnection conn) {
1229        mConnections.remove(conn);
1230        // If not emergency call is remaining, notify emergency call registrants
1231        if (mIsInEmergencyCall) {
1232            boolean isEmergencyCallInList = false;
1233            // if no emergency calls pending, set this to false
1234            for (ImsPhoneConnection imsPhoneConnection : mConnections) {
1235                if (imsPhoneConnection != null && imsPhoneConnection.isEmergency() == true) {
1236                    isEmergencyCallInList = true;
1237                    break;
1238                }
1239            }
1240
1241            if (!isEmergencyCallInList) {
1242                mIsInEmergencyCall = false;
1243                mPhone.sendEmergencyCallStateChange(false);
1244            }
1245        }
1246    }
1247
1248    private synchronized void addConnection(ImsPhoneConnection conn) {
1249        mConnections.add(conn);
1250        if (conn.isEmergency()) {
1251            mIsInEmergencyCall = true;
1252            mPhone.sendEmergencyCallStateChange(true);
1253        }
1254    }
1255
1256    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
1257        if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
1258        // This method is called on onCallUpdate() where there is not necessarily a call state
1259        // change. In these situations, we'll ignore the state related updates and only process
1260        // the change in media capabilities (as expected).  The default is to not ignore state
1261        // changes so we do not change existing behavior.
1262        processCallStateChange(imsCall, state, cause, false /* do not ignore state update */);
1263    }
1264
1265    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause,
1266            boolean ignoreState) {
1267        if (DBG) {
1268            log("processCallStateChange state=" + state + " cause=" + cause
1269                    + " ignoreState=" + ignoreState);
1270        }
1271
1272        if (imsCall == null) return;
1273
1274        boolean changed = false;
1275        ImsPhoneConnection conn = findConnection(imsCall);
1276
1277        if (conn == null) {
1278            // TODO : what should be done?
1279            return;
1280        }
1281
1282        // processCallStateChange is triggered for onCallUpdated as well.
1283        // onCallUpdated should not modify the state of the call
1284        // It should modify only other capabilities of call through updateMediaCapabilities
1285        // State updates will be triggered through individual callbacks
1286        // i.e. onCallHeld, onCallResume, etc and conn.update will be responsible for the update
1287        conn.updateMediaCapabilities(imsCall);
1288        if (ignoreState) {
1289            conn.updateAddressDisplay(imsCall);
1290            conn.updateExtras(imsCall);
1291
1292            maybeSetVideoCallProvider(conn, imsCall);
1293            return;
1294        }
1295
1296        changed = conn.update(imsCall, state);
1297        if (state == ImsPhoneCall.State.DISCONNECTED) {
1298            changed = conn.onDisconnect(cause) || changed;
1299            //detach the disconnected connections
1300            conn.getCall().detach(conn);
1301            removeConnection(conn);
1302        }
1303
1304        if (changed) {
1305            if (conn.getCall() == mHandoverCall) return;
1306            updatePhoneState();
1307            mPhone.notifyPreciseCallStateChanged();
1308        }
1309    }
1310
1311    private void maybeSetVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall) {
1312        android.telecom.Connection.VideoProvider connVideoProvider = conn.getVideoProvider();
1313        if (connVideoProvider != null || imsCall.getCallSession().getVideoCallProvider() == null) {
1314            return;
1315        }
1316
1317        try {
1318            setVideoCallProvider(conn, imsCall);
1319        } catch (RemoteException e) {
1320            loge("maybeSetVideoCallProvider: exception " + e);
1321        }
1322    }
1323
1324    /**
1325     * Returns the {@link ImsReasonInfo#getCode()}, potentially remapping to a new value based on
1326     * the {@link ImsReasonInfo#getCode()} and {@link ImsReasonInfo#getExtraMessage()}.
1327     *
1328     * See {@link #mImsReasonCodeMap}.
1329     *
1330     * @param reasonInfo The {@link ImsReasonInfo}.
1331     * @return The remapped code.
1332     */
1333    private int maybeRemapReasonCode(ImsReasonInfo reasonInfo) {
1334        int code = reasonInfo.getCode();
1335
1336        Pair<Integer, String> toCheck = new Pair<>(code, reasonInfo.getExtraMessage());
1337
1338        if (mImsReasonCodeMap.containsKey(toCheck)) {
1339            int toCode = mImsReasonCodeMap.get(toCheck);
1340
1341            log("maybeRemapReasonCode : fromCode = " + reasonInfo.getCode() + " ; message = "
1342                    + reasonInfo.getExtraMessage() + " ; toCode = " + toCode);
1343            return toCode;
1344        }
1345        return code;
1346    }
1347
1348    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
1349        int cause = DisconnectCause.ERROR_UNSPECIFIED;
1350
1351        //int type = reasonInfo.getReasonType();
1352        int code = maybeRemapReasonCode(reasonInfo);
1353        switch (code) {
1354            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
1355            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
1356                return DisconnectCause.NUMBER_UNREACHABLE;
1357
1358            case ImsReasonInfo.CODE_SIP_BUSY:
1359                return DisconnectCause.BUSY;
1360
1361            case ImsReasonInfo.CODE_USER_TERMINATED:
1362                return DisconnectCause.LOCAL;
1363
1364            case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
1365            case ImsReasonInfo.CODE_REMOTE_CALL_DECLINE:
1366                // If the call has been declined locally (on this device), or on remotely (on
1367                // another device using multiendpoint functionality), mark it as rejected.
1368                return DisconnectCause.INCOMING_REJECTED;
1369
1370            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
1371                return DisconnectCause.NORMAL;
1372
1373            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
1374                return DisconnectCause.SERVER_ERROR;
1375
1376            case ImsReasonInfo.CODE_SIP_REDIRECTED:
1377            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
1378            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
1379            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
1380            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
1381                return DisconnectCause.SERVER_ERROR;
1382
1383            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
1384            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
1385            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
1386                return DisconnectCause.SERVER_UNREACHABLE;
1387
1388            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
1389            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
1390            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
1391            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
1392            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
1393            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
1394            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
1395            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
1396                return DisconnectCause.OUT_OF_SERVICE;
1397
1398            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
1399            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
1400            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
1401            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
1402                return DisconnectCause.TIMED_OUT;
1403
1404            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
1405            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
1406                return DisconnectCause.POWER_OFF;
1407
1408            case ImsReasonInfo.CODE_FDN_BLOCKED:
1409                return DisconnectCause.FDN_BLOCKED;
1410
1411            case ImsReasonInfo.CODE_ANSWERED_ELSEWHERE:
1412                return DisconnectCause.ANSWERED_ELSEWHERE;
1413
1414            case ImsReasonInfo.CODE_CALL_END_CAUSE_CALL_PULL:
1415                return DisconnectCause.CALL_PULLED;
1416
1417            case ImsReasonInfo.CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED:
1418                return DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED;
1419
1420            case ImsReasonInfo.CODE_DATA_DISABLED:
1421                return DisconnectCause.DATA_DISABLED;
1422
1423            case ImsReasonInfo.CODE_DATA_LIMIT_REACHED:
1424                return DisconnectCause.DATA_LIMIT_REACHED;
1425            default:
1426        }
1427
1428        return cause;
1429    }
1430
1431    /**
1432     * @return true if the phone is in Emergency Callback mode, otherwise false
1433     */
1434    private boolean isPhoneInEcbMode() {
1435        return SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false);
1436    }
1437
1438    /**
1439     * Before dialing pending MO request, check for the Emergency Callback mode.
1440     * If device is in Emergency callback mode, then exit the mode before dialing pending MO.
1441     */
1442    private void dialPendingMO() {
1443        boolean isPhoneInEcmMode = isPhoneInEcbMode();
1444        boolean isEmergencyNumber = mPendingMO.isEmergency();
1445        if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
1446            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1447        } else {
1448            sendEmptyMessage(EVENT_EXIT_ECBM_BEFORE_PENDINGMO);
1449        }
1450    }
1451
1452    /**
1453     * Listen to the IMS call state change
1454     */
1455    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
1456        @Override
1457        public void onCallProgressing(ImsCall imsCall) {
1458            if (DBG) log("onCallProgressing");
1459
1460            mPendingMO = null;
1461            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
1462                    DisconnectCause.NOT_DISCONNECTED);
1463            mMetrics.writeOnImsCallProgressing(mPhone.getPhoneId(), imsCall.getCallSession());
1464        }
1465
1466        @Override
1467        public void onCallStarted(ImsCall imsCall) {
1468            if (DBG) log("onCallStarted");
1469
1470            if (mSwitchingFgAndBgCalls) {
1471                // If we put a call on hold to answer an incoming call, we should reset the
1472                // variables that keep track of the switch here.
1473                if (mCallExpectedToResume != null && mCallExpectedToResume == imsCall) {
1474                    if (DBG) log("onCallStarted: starting a call as a result of a switch.");
1475                    mSwitchingFgAndBgCalls = false;
1476                    mCallExpectedToResume = null;
1477                }
1478            }
1479
1480            mPendingMO = null;
1481            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1482                    DisconnectCause.NOT_DISCONNECTED);
1483
1484            if (mNotifyVtHandoverToWifiFail &&
1485                    !imsCall.isWifiCall() && imsCall.isVideoCall() && isWifiConnected()) {
1486                // Schedule check to see if handover succeeded.
1487                sendMessageDelayed(obtainMessage(EVENT_CHECK_FOR_WIFI_HANDOVER, imsCall),
1488                        HANDOVER_TO_WIFI_TIMEOUT_MS);
1489            }
1490
1491            mMetrics.writeOnImsCallStarted(mPhone.getPhoneId(), imsCall.getCallSession());
1492        }
1493
1494        @Override
1495        public void onCallUpdated(ImsCall imsCall) {
1496            if (DBG) log("onCallUpdated");
1497            if (imsCall == null) {
1498                return;
1499            }
1500            ImsPhoneConnection conn = findConnection(imsCall);
1501            if (conn != null) {
1502                processCallStateChange(imsCall, conn.getCall().mState,
1503                        DisconnectCause.NOT_DISCONNECTED, true /*ignore state update*/);
1504                mMetrics.writeImsCallState(mPhone.getPhoneId(),
1505                        imsCall.getCallSession(), conn.getCall().mState);
1506            }
1507        }
1508
1509        /**
1510         * onCallStartFailed will be invoked when:
1511         * case 1) Dialing fails
1512         * case 2) Ringing call is disconnected by local or remote user
1513         */
1514        @Override
1515        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1516            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
1517
1518            if (mSwitchingFgAndBgCalls) {
1519                // If we put a call on hold to answer an incoming call, we should reset the
1520                // variables that keep track of the switch here.
1521                if (mCallExpectedToResume != null && mCallExpectedToResume == imsCall) {
1522                    if (DBG) log("onCallStarted: starting a call as a result of a switch.");
1523                    mSwitchingFgAndBgCalls = false;
1524                    mCallExpectedToResume = null;
1525                }
1526            }
1527
1528            if (mPendingMO != null) {
1529                // To initiate dialing circuit-switched call
1530                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
1531                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
1532                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
1533                    mForegroundCall.detach(mPendingMO);
1534                    removeConnection(mPendingMO);
1535                    mPendingMO.finalize();
1536                    mPendingMO = null;
1537                    mPhone.initiateSilentRedial();
1538                    return;
1539                } else {
1540                    mPendingMO = null;
1541                    int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1542                    processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1543                }
1544                mMetrics.writeOnImsCallStartFailed(mPhone.getPhoneId(), imsCall.getCallSession(),
1545                        reasonInfo);
1546            }
1547        }
1548
1549        @Override
1550        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1551            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
1552
1553            int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1554            ImsPhoneConnection conn = findConnection(imsCall);
1555            if (DBG) log("cause = " + cause + " conn = " + conn);
1556
1557            if (conn != null) {
1558                android.telecom.Connection.VideoProvider videoProvider = conn.getVideoProvider();
1559                if (videoProvider instanceof ImsVideoCallProviderWrapper) {
1560                    ImsVideoCallProviderWrapper wrapper = (ImsVideoCallProviderWrapper)
1561                            videoProvider;
1562
1563                    wrapper.removeImsVideoProviderCallback(conn);
1564                }
1565            }
1566            if (mOnHoldToneId == System.identityHashCode(conn)) {
1567                if (conn != null && mOnHoldToneStarted) {
1568                    mPhone.stopOnHoldTone(conn);
1569                }
1570                mOnHoldToneStarted = false;
1571                mOnHoldToneId = -1;
1572            }
1573            if (conn != null) {
1574                if (conn.isPulledCall() && (
1575                        reasonInfo.getCode() == ImsReasonInfo.CODE_CALL_PULL_OUT_OF_SYNC ||
1576                        reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_TEMPRARILY_UNAVAILABLE ||
1577                        reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_FORBIDDEN) &&
1578                        mPhone != null && mPhone.getExternalCallTracker() != null) {
1579
1580                    log("Call pull failed.");
1581                    // Call was being pulled, but the call pull has failed -- inform the associated
1582                    // TelephonyConnection that the pull failed, and provide it with the original
1583                    // external connection which was pulled so that it can be swapped back.
1584                    conn.onCallPullFailed(mPhone.getExternalCallTracker()
1585                            .getConnectionById(conn.getPulledDialogId()));
1586                    // Do not mark as disconnected; the call will just change from being a regular
1587                    // call to being an external call again.
1588                    cause = DisconnectCause.NOT_DISCONNECTED;
1589
1590                } else if (conn.isIncoming() && conn.getConnectTime() == 0
1591                        && cause != DisconnectCause.ANSWERED_ELSEWHERE) {
1592                    // Missed
1593                    if (cause == DisconnectCause.NORMAL) {
1594                        cause = DisconnectCause.INCOMING_MISSED;
1595                    } else {
1596                        cause = DisconnectCause.INCOMING_REJECTED;
1597                    }
1598                    if (DBG) log("Incoming connection of 0 connect time detected - translated " +
1599                            "cause = " + cause);
1600                }
1601            }
1602
1603            if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) {
1604                // Call was terminated while it is merged instead of a remote disconnect.
1605                cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY;
1606            }
1607
1608            mMetrics.writeOnImsCallTerminated(mPhone.getPhoneId(), imsCall.getCallSession(),
1609                    reasonInfo);
1610
1611            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1612            if (mForegroundCall.getState() != ImsPhoneCall.State.ACTIVE) {
1613                if (mRingingCall.getState().isRinging()) {
1614                    // Drop pending MO. We should address incoming call first
1615                    mPendingMO = null;
1616                } else if (mPendingMO != null) {
1617                    sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1618                }
1619            }
1620
1621            if (mSwitchingFgAndBgCalls) {
1622                if (DBG) {
1623                    log("onCallTerminated: Call terminated in the midst of Switching " +
1624                            "Fg and Bg calls.");
1625                }
1626                // If we are the in midst of swapping FG and BG calls and the call that was
1627                // terminated was the one that we expected to resume, we need to swap the FG and
1628                // BG calls back.
1629                if (imsCall == mCallExpectedToResume) {
1630                    if (DBG) {
1631                        log("onCallTerminated: switching " + mForegroundCall + " with "
1632                                + mBackgroundCall);
1633                    }
1634                    mForegroundCall.switchWith(mBackgroundCall);
1635                }
1636                // This call terminated in the midst of a switch after the other call was held, so
1637                // resume it back to ACTIVE state since the switch failed.
1638                log("onCallTerminated: foreground call in state " + mForegroundCall.getState() +
1639                        " and ringing call in state " + (mRingingCall == null ? "null" :
1640                        mRingingCall.getState().toString()));
1641
1642                if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING ||
1643                        mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
1644                    sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1645                    mSwitchingFgAndBgCalls = false;
1646                    mCallExpectedToResume = null;
1647                }
1648            }
1649        }
1650
1651        @Override
1652        public void onCallHeld(ImsCall imsCall) {
1653            if (DBG) {
1654                if (mForegroundCall.getImsCall() == imsCall) {
1655                    log("onCallHeld (fg) " + imsCall);
1656                } else if (mBackgroundCall.getImsCall() == imsCall) {
1657                    log("onCallHeld (bg) " + imsCall);
1658                }
1659            }
1660
1661            synchronized (mSyncHold) {
1662                ImsPhoneCall.State oldState = mBackgroundCall.getState();
1663                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
1664                        DisconnectCause.NOT_DISCONNECTED);
1665
1666                // Note: If we're performing a switchWaitingOrHoldingAndActive, the call to
1667                // processCallStateChange above may have caused the mBackgroundCall and
1668                // mForegroundCall references below to change meaning.  Watch out for this if you
1669                // are reading through this code.
1670                if (oldState == ImsPhoneCall.State.ACTIVE) {
1671                    // Note: This case comes up when we have just held a call in response to a
1672                    // switchWaitingOrHoldingAndActive.  We now need to resume the background call.
1673                    // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called.
1674                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
1675                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
1676                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1677                    } else {
1678                        //when multiple connections belong to background call,
1679                        //only the first callback reaches here
1680                        //otherwise the oldState is already HOLDING
1681                        if (mPendingMO != null) {
1682                            dialPendingMO();
1683                        }
1684
1685                        // In this case there will be no call resumed, so we can assume that we
1686                        // are done switching fg and bg calls now.
1687                        // This may happen if there is no BG call and we are holding a call so that
1688                        // we can dial another one.
1689                        mSwitchingFgAndBgCalls = false;
1690                    }
1691                } else if (oldState == ImsPhoneCall.State.IDLE && mSwitchingFgAndBgCalls) {
1692                    // The other call terminated in the midst of a switch before this call was held,
1693                    // so resume the foreground call back to ACTIVE state since the switch failed.
1694                    if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
1695                        sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1696                        mSwitchingFgAndBgCalls = false;
1697                        mCallExpectedToResume = null;
1698                    }
1699                }
1700            }
1701            mMetrics.writeOnImsCallHeld(mPhone.getPhoneId(), imsCall.getCallSession());
1702        }
1703
1704        @Override
1705        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1706            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
1707
1708            synchronized (mSyncHold) {
1709                ImsPhoneCall.State bgState = mBackgroundCall.getState();
1710                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
1711                    // disconnected while processing hold
1712                    if (mPendingMO != null) {
1713                        dialPendingMO();
1714                    }
1715                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
1716                    mForegroundCall.switchWith(mBackgroundCall);
1717
1718                    if (mPendingMO != null) {
1719                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
1720                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
1721                    }
1722                }
1723                mPhone.notifySuppServiceFailed(Phone.SuppService.HOLD);
1724            }
1725            mMetrics.writeOnImsCallHoldFailed(mPhone.getPhoneId(), imsCall.getCallSession(),
1726                    reasonInfo);
1727        }
1728
1729        @Override
1730        public void onCallResumed(ImsCall imsCall) {
1731            if (DBG) log("onCallResumed");
1732
1733            // If we are the in midst of swapping FG and BG calls and the call we end up resuming
1734            // is not the one we expected, we likely had a resume failure and we need to swap the
1735            // FG and BG calls back.
1736            if (mSwitchingFgAndBgCalls) {
1737                if (imsCall != mCallExpectedToResume) {
1738                    // If the call which resumed isn't as expected, we need to swap back to the
1739                    // previous configuration; the swap has failed.
1740                    if (DBG) {
1741                        log("onCallResumed : switching " + mForegroundCall + " with "
1742                                + mBackgroundCall);
1743                    }
1744                    mForegroundCall.switchWith(mBackgroundCall);
1745                } else {
1746                    // The call which resumed is the one we expected to resume, so we can clear out
1747                    // the mSwitchingFgAndBgCalls flag.
1748                    if (DBG) {
1749                        log("onCallResumed : expected call resumed.");
1750                    }
1751                }
1752                mSwitchingFgAndBgCalls = false;
1753                mCallExpectedToResume = null;
1754            }
1755            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1756                    DisconnectCause.NOT_DISCONNECTED);
1757            mMetrics.writeOnImsCallResumed(mPhone.getPhoneId(), imsCall.getCallSession());
1758        }
1759
1760        @Override
1761        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1762            if (mSwitchingFgAndBgCalls) {
1763                // If we are in the midst of swapping the FG and BG calls and
1764                // we got a resume fail, we need to swap back the FG and BG calls.
1765                // Since the FG call was held, will also try to resume the same.
1766                if (imsCall == mCallExpectedToResume) {
1767                    if (DBG) {
1768                        log("onCallResumeFailed : switching " + mForegroundCall + " with "
1769                                + mBackgroundCall);
1770                    }
1771                    mForegroundCall.switchWith(mBackgroundCall);
1772                    if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
1773                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1774                    }
1775                }
1776
1777                //Call swap is done, reset the relevant variables
1778                mCallExpectedToResume = null;
1779                mSwitchingFgAndBgCalls = false;
1780            }
1781            mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME);
1782            mMetrics.writeOnImsCallResumeFailed(mPhone.getPhoneId(), imsCall.getCallSession(),
1783                    reasonInfo);
1784        }
1785
1786        @Override
1787        public void onCallResumeReceived(ImsCall imsCall) {
1788            if (DBG) log("onCallResumeReceived");
1789            ImsPhoneConnection conn = findConnection(imsCall);
1790            if (conn != null) {
1791                if (mOnHoldToneStarted) {
1792                    mPhone.stopOnHoldTone(conn);
1793                    mOnHoldToneStarted = false;
1794                }
1795
1796                conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_UNHELD, null);
1797            }
1798
1799            SuppServiceNotification supp = new SuppServiceNotification();
1800            // Type of notification: 0 = MO; 1 = MT
1801            // Refer SuppServiceNotification class documentation.
1802            supp.notificationType = 1;
1803            supp.code = SuppServiceNotification.MT_CODE_CALL_RETRIEVED;
1804            mPhone.notifySuppSvcNotification(supp);
1805            mMetrics.writeOnImsCallResumeReceived(mPhone.getPhoneId(), imsCall.getCallSession());
1806        }
1807
1808        @Override
1809        public void onCallHoldReceived(ImsCall imsCall) {
1810            if (DBG) log("onCallHoldReceived");
1811
1812            ImsPhoneConnection conn = findConnection(imsCall);
1813            if (conn != null) {
1814                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall) &&
1815                        conn.getState() == ImsPhoneCall.State.ACTIVE) {
1816                    mPhone.startOnHoldTone(conn);
1817                    mOnHoldToneStarted = true;
1818                    mOnHoldToneId = System.identityHashCode(conn);
1819                }
1820
1821                conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_HELD, null);
1822            }
1823
1824            SuppServiceNotification supp = new SuppServiceNotification();
1825            // Type of notification: 0 = MO; 1 = MT
1826            // Refer SuppServiceNotification class documentation.
1827            supp.notificationType = 1;
1828            supp.code = SuppServiceNotification.MT_CODE_CALL_ON_HOLD;
1829            mPhone.notifySuppSvcNotification(supp);
1830            mMetrics.writeOnImsCallHoldReceived(mPhone.getPhoneId(), imsCall.getCallSession());
1831        }
1832
1833        @Override
1834        public void onCallSuppServiceReceived(ImsCall call,
1835                ImsSuppServiceNotification suppServiceInfo) {
1836            if (DBG) log("onCallSuppServiceReceived: suppServiceInfo=" + suppServiceInfo);
1837
1838            SuppServiceNotification supp = new SuppServiceNotification();
1839            supp.notificationType = suppServiceInfo.notificationType;
1840            supp.code = suppServiceInfo.code;
1841            supp.index = suppServiceInfo.index;
1842            supp.number = suppServiceInfo.number;
1843            supp.history = suppServiceInfo.history;
1844
1845            mPhone.notifySuppSvcNotification(supp);
1846        }
1847
1848        @Override
1849        public void onCallMerged(final ImsCall call, final ImsCall peerCall, boolean swapCalls) {
1850            if (DBG) log("onCallMerged");
1851
1852            ImsPhoneCall foregroundImsPhoneCall = findConnection(call).getCall();
1853            ImsPhoneConnection peerConnection = findConnection(peerCall);
1854            ImsPhoneCall peerImsPhoneCall = peerConnection == null ? null
1855                    : peerConnection.getCall();
1856
1857            if (swapCalls) {
1858                switchAfterConferenceSuccess();
1859            }
1860            foregroundImsPhoneCall.merge(peerImsPhoneCall, ImsPhoneCall.State.ACTIVE);
1861
1862            try {
1863                final ImsPhoneConnection conn = findConnection(call);
1864                log("onCallMerged: ImsPhoneConnection=" + conn);
1865                log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1866                setVideoCallProvider(conn, call);
1867                log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1868            } catch (Exception e) {
1869                loge("onCallMerged: exception " + e);
1870            }
1871
1872            // After merge complete, update foreground as Active
1873            // and background call as Held, if background call exists
1874            processCallStateChange(mForegroundCall.getImsCall(), ImsPhoneCall.State.ACTIVE,
1875                    DisconnectCause.NOT_DISCONNECTED);
1876            if (peerConnection != null) {
1877                processCallStateChange(mBackgroundCall.getImsCall(), ImsPhoneCall.State.HOLDING,
1878                    DisconnectCause.NOT_DISCONNECTED);
1879            }
1880
1881            // Check if the merge was requested by an existing conference call. In that
1882            // case, no further action is required.
1883            if (!call.isMergeRequestedByConf()) {
1884                log("onCallMerged :: calling onMultipartyStateChanged()");
1885                onMultipartyStateChanged(call, true);
1886            } else {
1887                log("onCallMerged :: Merge requested by existing conference.");
1888                // Reset the flag.
1889                call.resetIsMergeRequestedByConf(false);
1890            }
1891            logState();
1892        }
1893
1894        @Override
1895        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
1896            if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo);
1897
1898            // TODO: the call to notifySuppServiceFailed throws up the "merge failed" dialog
1899            // We should move this into the InCallService so that it is handled appropriately
1900            // based on the user facing UI.
1901            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
1902
1903            // Start plumbing this even through Telecom so other components can take
1904            // appropriate action.
1905            ImsPhoneConnection conn = findConnection(call);
1906            if (conn != null) {
1907                conn.onConferenceMergeFailed();
1908            }
1909        }
1910
1911        /**
1912         * Called when the state of IMS conference participant(s) has changed.
1913         *
1914         * @param call the call object that carries out the IMS call.
1915         * @param participants the participant(s) and their new state information.
1916         */
1917        @Override
1918        public void onConferenceParticipantsStateChanged(ImsCall call,
1919                List<ConferenceParticipant> participants) {
1920            if (DBG) log("onConferenceParticipantsStateChanged");
1921
1922            ImsPhoneConnection conn = findConnection(call);
1923            if (conn != null) {
1924                conn.updateConferenceParticipants(participants);
1925            }
1926        }
1927
1928        @Override
1929        public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
1930            mPhone.onTtyModeReceived(mode);
1931        }
1932
1933        @Override
1934        public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1935            ImsReasonInfo reasonInfo) {
1936            if (DBG) {
1937                log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
1938                    targetAccessTech + ", reasonInfo=" + reasonInfo);
1939            }
1940
1941            boolean isHandoverToWifi = srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN &&
1942                    targetAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
1943            if (isHandoverToWifi) {
1944                // If we handed over to wifi successfully, don't check for failure in the future.
1945                removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
1946            }
1947
1948            boolean isHandoverFromWifi =
1949                    srcAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN &&
1950                            targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
1951            if (mNotifyHandoverVideoFromWifiToLTE && isHandoverFromWifi && imsCall.isVideoCall()) {
1952                log("onCallHandover :: notifying of WIFI to LTE handover.");
1953                ImsPhoneConnection conn = findConnection(imsCall);
1954                if (conn != null) {
1955                    conn.onConnectionEvent(
1956                            TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE, null);
1957                } else {
1958                    loge("onCallHandover :: failed to notify of handover; connection is null.");
1959                }
1960            }
1961
1962            mMetrics.writeOnImsCallHandoverEvent(mPhone.getPhoneId(),
1963                    TelephonyCallSession.Event.Type.IMS_CALL_HANDOVER, imsCall.getCallSession(),
1964                    srcAccessTech, targetAccessTech, reasonInfo);
1965        }
1966
1967        @Override
1968        public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1969            ImsReasonInfo reasonInfo) {
1970            if (DBG) {
1971                log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech +
1972                    ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo);
1973            }
1974            mMetrics.writeOnImsCallHandoverEvent(mPhone.getPhoneId(),
1975                    TelephonyCallSession.Event.Type.IMS_CALL_HANDOVER_FAILED,
1976                    imsCall.getCallSession(), srcAccessTech, targetAccessTech, reasonInfo);
1977
1978            boolean isHandoverToWifi = srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN &&
1979                    targetAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
1980            ImsPhoneConnection conn = findConnection(imsCall);
1981            if (conn != null && isHandoverToWifi) {
1982                log("onCallHandoverFailed - handover to WIFI Failed");
1983
1984                // If we know we failed to handover, don't check for failure in the future.
1985                removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
1986
1987                if (mNotifyVtHandoverToWifiFail) {
1988                    // Only notify others if carrier config indicates to do so.
1989                    conn.onHandoverToWifiFailed();
1990                }
1991            }
1992        }
1993
1994        /**
1995         * Handles a change to the multiparty state for an {@code ImsCall}.  Notifies the associated
1996         * {@link ImsPhoneConnection} of the change.
1997         *
1998         * @param imsCall The IMS call.
1999         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
2000         *      otherwise.
2001         */
2002        @Override
2003        public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
2004            if (DBG) log("onMultipartyStateChanged to " + (isMultiParty ? "Y" : "N"));
2005
2006            ImsPhoneConnection conn = findConnection(imsCall);
2007            if (conn != null) {
2008                conn.updateMultipartyState(isMultiParty);
2009            }
2010        }
2011    };
2012
2013    /**
2014     * Listen to the IMS call state change
2015     */
2016    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
2017        @Override
2018        public void onCallStarted(ImsCall imsCall) {
2019            if (DBG) log("mImsUssdListener onCallStarted");
2020
2021            if (imsCall == mUssdSession) {
2022                if (mPendingUssd != null) {
2023                    AsyncResult.forMessage(mPendingUssd);
2024                    mPendingUssd.sendToTarget();
2025                    mPendingUssd = null;
2026                }
2027            }
2028        }
2029
2030        @Override
2031        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
2032            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
2033
2034            onCallTerminated(imsCall, reasonInfo);
2035        }
2036
2037        @Override
2038        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
2039            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
2040            removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
2041
2042            if (imsCall == mUssdSession) {
2043                mUssdSession = null;
2044                if (mPendingUssd != null) {
2045                    CommandException ex =
2046                            new CommandException(CommandException.Error.GENERIC_FAILURE);
2047                    AsyncResult.forMessage(mPendingUssd, null, ex);
2048                    mPendingUssd.sendToTarget();
2049                    mPendingUssd = null;
2050                }
2051            }
2052            imsCall.close();
2053        }
2054
2055        @Override
2056        public void onCallUssdMessageReceived(ImsCall call,
2057                int mode, String ussdMessage) {
2058            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
2059
2060            int ussdMode = -1;
2061
2062            switch(mode) {
2063                case ImsCall.USSD_MODE_REQUEST:
2064                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
2065                    break;
2066
2067                case ImsCall.USSD_MODE_NOTIFY:
2068                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
2069                    break;
2070            }
2071
2072            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
2073        }
2074    };
2075
2076    /**
2077     * Listen to the IMS service state change
2078     *
2079     */
2080    private ImsConnectionStateListener mImsConnectionStateListener =
2081        new ImsConnectionStateListener() {
2082        @Override
2083        public void onImsConnected() {
2084            if (DBG) log("onImsConnected");
2085            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
2086            mPhone.setImsRegistered(true);
2087            mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
2088                    ImsConnectionState.State.CONNECTED, null);
2089        }
2090
2091        @Override
2092        public void onImsDisconnected(ImsReasonInfo imsReasonInfo) {
2093            if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
2094            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
2095            mPhone.setImsRegistered(false);
2096            mPhone.processDisconnectReason(imsReasonInfo);
2097            mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
2098                    ImsConnectionState.State.DISCONNECTED, imsReasonInfo);
2099        }
2100
2101        @Override
2102        public void onImsProgressing() {
2103            if (DBG) log("onImsProgressing");
2104            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
2105            mPhone.setImsRegistered(false);
2106            mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
2107                    ImsConnectionState.State.PROGRESSING, null);
2108        }
2109
2110        @Override
2111        public void onImsResumed() {
2112            if (DBG) log("onImsResumed");
2113            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
2114            mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
2115                    ImsConnectionState.State.RESUMED, null);
2116        }
2117
2118        @Override
2119        public void onImsSuspended() {
2120            if (DBG) log("onImsSuspended");
2121            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
2122            mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
2123                    ImsConnectionState.State.SUSPENDED, null);
2124
2125        }
2126
2127        @Override
2128        public void onFeatureCapabilityChanged(int serviceClass,
2129                int[] enabledFeatures, int[] disabledFeatures) {
2130            if (serviceClass == ImsServiceClass.MMTEL) {
2131                boolean tmpIsVideoCallEnabled = isVideoCallEnabled();
2132                // Check enabledFeatures to determine capabilities. We ignore disabledFeatures.
2133                StringBuilder sb;
2134                if (DBG) {
2135                    sb = new StringBuilder(120);
2136                    sb.append("onFeatureCapabilityChanged: ");
2137                }
2138                for (int  i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE;
2139                        i <= ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI &&
2140                        i < enabledFeatures.length; i++) {
2141                    if (enabledFeatures[i] == i) {
2142                        // If the feature is set to its own integer value it is enabled.
2143                        if (DBG) {
2144                            sb.append(mImsFeatureStrings[i]);
2145                            sb.append(":true ");
2146                        }
2147
2148                        mImsFeatureEnabled[i] = true;
2149                    } else if (enabledFeatures[i]
2150                            == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) {
2151                        // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled.
2152                        if (DBG) {
2153                            sb.append(mImsFeatureStrings[i]);
2154                            sb.append(":false ");
2155                        }
2156
2157                        mImsFeatureEnabled[i] = false;
2158                    } else {
2159                        // Feature has unknown state; it is not its own value or -1.
2160                        if (DBG) {
2161                            loge("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i]
2162                                    + "): unexpectedValue=" + enabledFeatures[i]);
2163                        }
2164                    }
2165                }
2166                if (DBG) {
2167                    log(sb.toString());
2168                }
2169                if (tmpIsVideoCallEnabled != isVideoCallEnabled()) {
2170                    mPhone.notifyForVideoCapabilityChanged(isVideoCallEnabled());
2171                }
2172
2173                if (DBG) log("onFeatureCapabilityChanged: isVolteEnabled=" + isVolteEnabled()
2174                            + ", isVideoCallEnabled=" + isVideoCallEnabled()
2175                            + ", isVowifiEnabled=" + isVowifiEnabled()
2176                            + ", isUtEnabled=" + isUtEnabled());
2177
2178                mPhone.onFeatureCapabilityChanged();
2179
2180                mMetrics.writeOnImsCapabilities(
2181                        mPhone.getPhoneId(), mImsFeatureEnabled);
2182            }
2183        }
2184
2185        @Override
2186        public void onVoiceMessageCountChanged(int count) {
2187            if (DBG) log("onVoiceMessageCountChanged :: count=" + count);
2188            mPhone.mDefaultPhone.setVoiceMessageCount(count);
2189        }
2190
2191        @Override
2192        public void registrationAssociatedUriChanged(Uri[] uris) {
2193            if (DBG) log("registrationAssociatedUriChanged");
2194            mPhone.setCurrentSubscriberUris(uris);
2195        }
2196    };
2197
2198    private ImsConfigListener.Stub mImsConfigListener = new ImsConfigListener.Stub() {
2199        @Override
2200        public void onGetFeatureResponse(int feature, int network, int value, int status) {}
2201
2202        @Override
2203        public void onSetFeatureResponse(int feature, int network, int value, int status) {
2204            mMetrics.writeImsSetFeatureValue(
2205                    mPhone.getPhoneId(), feature, network, value, status);
2206        }
2207
2208        @Override
2209        public void onGetVideoQuality(int status, int quality) {}
2210
2211        @Override
2212        public void onSetVideoQuality(int status) {}
2213
2214    };
2215
2216    public ImsUtInterface getUtInterface() throws ImsException {
2217        if (mImsManager == null) {
2218            throw getImsManagerIsNullException();
2219        }
2220
2221        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
2222        return ut;
2223    }
2224
2225    private void transferHandoverConnections(ImsPhoneCall call) {
2226        if (call.mConnections != null) {
2227            for (Connection c : call.mConnections) {
2228                c.mPreHandoverState = call.mState;
2229                log ("Connection state before handover is " + c.getStateBeforeHandover());
2230            }
2231        }
2232        if (mHandoverCall.mConnections == null ) {
2233            mHandoverCall.mConnections = call.mConnections;
2234        } else { // Multi-call SRVCC
2235            mHandoverCall.mConnections.addAll(call.mConnections);
2236        }
2237        if (mHandoverCall.mConnections != null) {
2238            if (call.getImsCall() != null) {
2239                call.getImsCall().close();
2240            }
2241            for (Connection c : mHandoverCall.mConnections) {
2242                ((ImsPhoneConnection)c).changeParent(mHandoverCall);
2243                ((ImsPhoneConnection)c).releaseWakeLock();
2244            }
2245        }
2246        if (call.getState().isAlive()) {
2247            log ("Call is alive and state is " + call.mState);
2248            mHandoverCall.mState = call.mState;
2249        }
2250        call.mConnections.clear();
2251        call.mState = ImsPhoneCall.State.IDLE;
2252    }
2253
2254    /* package */
2255    void notifySrvccState(Call.SrvccState state) {
2256        if (DBG) log("notifySrvccState state=" + state);
2257
2258        mSrvccState = state;
2259
2260        if (mSrvccState == Call.SrvccState.COMPLETED) {
2261            transferHandoverConnections(mForegroundCall);
2262            transferHandoverConnections(mBackgroundCall);
2263            transferHandoverConnections(mRingingCall);
2264        }
2265    }
2266
2267    //****** Overridden from Handler
2268
2269    @Override
2270    public void
2271    handleMessage (Message msg) {
2272        AsyncResult ar;
2273        if (DBG) log("handleMessage what=" + msg.what);
2274
2275        switch (msg.what) {
2276            case EVENT_HANGUP_PENDINGMO:
2277                if (mPendingMO != null) {
2278                    mPendingMO.onDisconnect();
2279                    removeConnection(mPendingMO);
2280                    mPendingMO = null;
2281                }
2282                mPendingIntentExtras = null;
2283                updatePhoneState();
2284                mPhone.notifyPreciseCallStateChanged();
2285                break;
2286            case EVENT_RESUME_BACKGROUND:
2287                try {
2288                    resumeWaitingOrHolding();
2289                } catch (CallStateException e) {
2290                    if (Phone.DEBUG_PHONE) {
2291                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
2292                    }
2293                }
2294                break;
2295            case EVENT_DIAL_PENDINGMO:
2296                dialInternal(mPendingMO, mClirMode, mPendingCallVideoState, mPendingIntentExtras);
2297                mPendingIntentExtras = null;
2298                break;
2299
2300            case EVENT_EXIT_ECBM_BEFORE_PENDINGMO:
2301                if (mPendingMO != null) {
2302                    //Send ECBM exit request
2303                    try {
2304                        getEcbmInterface().exitEmergencyCallbackMode();
2305                        mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
2306                        pendingCallClirMode = mClirMode;
2307                        pendingCallInEcm = true;
2308                    } catch (ImsException e) {
2309                        e.printStackTrace();
2310                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
2311                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
2312                    }
2313                }
2314                break;
2315
2316            case EVENT_EXIT_ECM_RESPONSE_CDMA:
2317                // no matter the result, we still do the same here
2318                if (pendingCallInEcm) {
2319                    dialInternal(mPendingMO, pendingCallClirMode,
2320                            mPendingCallVideoState, mPendingIntentExtras);
2321                    mPendingIntentExtras = null;
2322                    pendingCallInEcm = false;
2323                }
2324                mPhone.unsetOnEcbModeExitResponse(this);
2325                break;
2326            case EVENT_VT_DATA_USAGE_UPDATE:
2327                ar = (AsyncResult) msg.obj;
2328                ImsCall call = (ImsCall) ar.userObj;
2329                Long usage = (long) ar.result;
2330                log("VT data usage update. usage = " + usage + ", imsCall = " + call);
2331
2332                Long oldUsage = 0L;
2333                if (mVtDataUsageMap.containsKey(call.uniqueId)) {
2334                    oldUsage = mVtDataUsageMap.get(call.uniqueId);
2335                }
2336                mTotalVtDataUsage += (usage - oldUsage);
2337                mVtDataUsageMap.put(call.uniqueId, usage);
2338                break;
2339            case EVENT_DATA_ENABLED_CHANGED:
2340                ar = (AsyncResult) msg.obj;
2341                if (ar.result instanceof Pair) {
2342                    Pair<Boolean, Integer> p = (Pair<Boolean, Integer>) ar.result;
2343                    onDataEnabledChanged(p.first, p.second);
2344                }
2345                break;
2346            case EVENT_GET_IMS_SERVICE:
2347                try {
2348                    getImsService();
2349                } catch (ImsException e) {
2350                    loge("getImsService: " + e);
2351                    //Leave mImsManager as null, then CallStateException will be thrown when dialing
2352                    mImsManager = null;
2353                    if (mImsServiceRetryCount < NUM_IMS_SERVICE_RETRIES) {
2354                        loge("getImsService: Retrying getting ImsService...");
2355                        sendEmptyMessageDelayed(EVENT_GET_IMS_SERVICE,
2356                                TIME_BETWEEN_IMS_SERVICE_RETRIES_MS);
2357                        mImsServiceRetryCount++;
2358                    } else {
2359                        // We have been unable to connect for
2360                        // NUM_IMS_SERVICE_RETRIES*TIME_BETWEEN_IMS_SERVICE_RETRIES_MS ms. We will
2361                        // probably never be able to connect, so we should just give up.
2362                        loge("getImsService: ImsService retrieval timeout... ImsService is " +
2363                                "unavailable.");
2364                    }
2365                }
2366                break;
2367            case EVENT_CHECK_FOR_WIFI_HANDOVER:
2368                if (msg.obj instanceof ImsCall) {
2369                    ImsCall imsCall = (ImsCall) msg.obj;
2370                    if (!imsCall.isWifiCall()) {
2371                        // Call did not handover to wifi, notify of handover failure.
2372                        ImsPhoneConnection conn = findConnection(imsCall);
2373                        if (conn != null) {
2374                            conn.onHandoverToWifiFailed();
2375                        }
2376                    }
2377                }
2378                break;
2379        }
2380    }
2381
2382    @Override
2383    protected void log(String msg) {
2384        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
2385    }
2386
2387    protected void loge(String msg) {
2388        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
2389    }
2390
2391    /**
2392     * Logs the current state of the ImsPhoneCallTracker.  Useful for debugging issues with
2393     * call tracking.
2394     */
2395    /* package */
2396    void logState() {
2397        if (!VERBOSE_STATE_LOGGING) {
2398            return;
2399        }
2400
2401        StringBuilder sb = new StringBuilder();
2402        sb.append("Current IMS PhoneCall State:\n");
2403        sb.append(" Foreground: ");
2404        sb.append(mForegroundCall);
2405        sb.append("\n");
2406        sb.append(" Background: ");
2407        sb.append(mBackgroundCall);
2408        sb.append("\n");
2409        sb.append(" Ringing: ");
2410        sb.append(mRingingCall);
2411        sb.append("\n");
2412        sb.append(" Handover: ");
2413        sb.append(mHandoverCall);
2414        sb.append("\n");
2415        Rlog.v(LOG_TAG, sb.toString());
2416    }
2417
2418    @Override
2419    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2420        pw.println("ImsPhoneCallTracker extends:");
2421        super.dump(fd, pw, args);
2422        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
2423        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
2424        pw.println(" mRingingCall=" + mRingingCall);
2425        pw.println(" mForegroundCall=" + mForegroundCall);
2426        pw.println(" mBackgroundCall=" + mBackgroundCall);
2427        pw.println(" mHandoverCall=" + mHandoverCall);
2428        pw.println(" mPendingMO=" + mPendingMO);
2429        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
2430        pw.println(" mPhone=" + mPhone);
2431        pw.println(" mDesiredMute=" + mDesiredMute);
2432        pw.println(" mState=" + mState);
2433        for (int i = 0; i < mImsFeatureEnabled.length; i++) {
2434            pw.println(" " + mImsFeatureStrings[i] + ": "
2435                    + ((mImsFeatureEnabled[i]) ? "enabled" : "disabled"));
2436        }
2437        pw.println(" mTotalVtDataUsage=" + mTotalVtDataUsage);
2438        for (Map.Entry<Integer, Long> entry : mVtDataUsageMap.entrySet()) {
2439            pw.println("    id=" + entry.getKey() + " ,usage=" + entry.getValue());
2440        }
2441
2442        pw.flush();
2443        pw.println("++++++++++++++++++++++++++++++++");
2444
2445        try {
2446            if (mImsManager != null) {
2447                mImsManager.dump(fd, pw, args);
2448            }
2449        } catch (Exception e) {
2450            e.printStackTrace();
2451        }
2452
2453        if (mConnections != null && mConnections.size() > 0) {
2454            pw.println("mConnections:");
2455            for (int i = 0; i < mConnections.size(); i++) {
2456                pw.println("  [" + i + "]: " + mConnections.get(i));
2457            }
2458        }
2459    }
2460
2461    @Override
2462    protected void handlePollCalls(AsyncResult ar) {
2463    }
2464
2465    /* package */
2466    ImsEcbm getEcbmInterface() throws ImsException {
2467        if (mImsManager == null) {
2468            throw getImsManagerIsNullException();
2469        }
2470
2471        ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
2472        return ecbm;
2473    }
2474
2475    /* package */
2476    ImsMultiEndpoint getMultiEndpointInterface() throws ImsException {
2477        if (mImsManager == null) {
2478            throw getImsManagerIsNullException();
2479        }
2480
2481        try {
2482            return mImsManager.getMultiEndpointInterface(mServiceId);
2483        } catch (ImsException e) {
2484            if (e.getCode() == ImsReasonInfo.CODE_MULTIENDPOINT_NOT_SUPPORTED) {
2485                return null;
2486            } else {
2487                throw e;
2488            }
2489
2490        }
2491    }
2492
2493    public boolean isInEmergencyCall() {
2494        return mIsInEmergencyCall;
2495    }
2496
2497    public boolean isVolteEnabled() {
2498        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE];
2499    }
2500
2501    public boolean isVowifiEnabled() {
2502        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI];
2503    }
2504
2505    public boolean isVideoCallEnabled() {
2506        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE]
2507                || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]);
2508    }
2509
2510    @Override
2511    public PhoneConstants.State getState() {
2512        return mState;
2513    }
2514
2515    private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
2516            throws RemoteException {
2517        IImsVideoCallProvider imsVideoCallProvider =
2518                imsCall.getCallSession().getVideoCallProvider();
2519        if (imsVideoCallProvider != null) {
2520            ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
2521                    new ImsVideoCallProviderWrapper(imsVideoCallProvider);
2522            conn.setVideoProvider(imsVideoCallProviderWrapper);
2523            imsVideoCallProviderWrapper.registerForDataUsageUpdate
2524                    (this, EVENT_VT_DATA_USAGE_UPDATE, imsCall);
2525            imsVideoCallProviderWrapper.addImsVideoProviderCallback(conn);
2526        }
2527    }
2528
2529    public boolean isUtEnabled() {
2530        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_LTE]
2531            || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI]);
2532    }
2533
2534    /**
2535     * Given a call subject, removes any characters considered by the current carrier to be
2536     * invalid, as well as escaping (using \) any characters which the carrier requires to be
2537     * escaped.
2538     *
2539     * @param callSubject The call subject.
2540     * @return The call subject with invalid characters removed and escaping applied as required.
2541     */
2542    private String cleanseInstantLetteringMessage(String callSubject) {
2543        if (TextUtils.isEmpty(callSubject)) {
2544            return callSubject;
2545        }
2546
2547        // Get the carrier config for the current sub.
2548        CarrierConfigManager configMgr = (CarrierConfigManager)
2549                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
2550        // Bail if we can't find the carrier config service.
2551        if (configMgr == null) {
2552            return callSubject;
2553        }
2554
2555        PersistableBundle carrierConfig = configMgr.getConfigForSubId(mPhone.getSubId());
2556        // Bail if no carrier config found.
2557        if (carrierConfig == null) {
2558            return callSubject;
2559        }
2560
2561        // Try to replace invalid characters
2562        String invalidCharacters = carrierConfig.getString(
2563                CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING);
2564        if (!TextUtils.isEmpty(invalidCharacters)) {
2565            callSubject = callSubject.replaceAll(invalidCharacters, "");
2566        }
2567
2568        // Try to escape characters which need to be escaped.
2569        String escapedCharacters = carrierConfig.getString(
2570                CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING);
2571        if (!TextUtils.isEmpty(escapedCharacters)) {
2572            callSubject = escapeChars(escapedCharacters, callSubject);
2573        }
2574        return callSubject;
2575    }
2576
2577    /**
2578     * Given a source string, return a string where a set of characters are escaped using the
2579     * backslash character.
2580     *
2581     * @param toEscape The characters to escape with a backslash.
2582     * @param source The source string.
2583     * @return The source string with characters escaped.
2584     */
2585    private String escapeChars(String toEscape, String source) {
2586        StringBuilder escaped = new StringBuilder();
2587        for (char c : source.toCharArray()) {
2588            if (toEscape.contains(Character.toString(c))) {
2589                escaped.append("\\");
2590            }
2591            escaped.append(c);
2592        }
2593
2594        return escaped.toString();
2595    }
2596
2597    /**
2598     * Initiates a pull of an external call.
2599     *
2600     * Initiates a pull by making a dial request with the {@link ImsCallProfile#EXTRA_IS_CALL_PULL}
2601     * extra specified.  We call {@link ImsPhone#notifyUnknownConnection(Connection)} which notifies
2602     * Telecom of the new dialed connection.  The
2603     * {@code PstnIncomingCallNotifier#maybeSwapWithUnknownConnection} logic ensures that the new
2604     * {@link ImsPhoneConnection} resulting from the dial gets swapped with the
2605     * {@link ImsExternalConnection}, which effectively makes the external call become a regular
2606     * call.  Magic!
2607     *
2608     * @param number The phone number of the call to be pulled.
2609     * @param videoState The desired video state of the pulled call.
2610     * @param dialogId The {@link ImsExternalConnection#getCallId()} dialog id associated with the
2611     *                 call which is being pulled.
2612     */
2613    @Override
2614    public void pullExternalCall(String number, int videoState, int dialogId) {
2615        Bundle extras = new Bundle();
2616        extras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL, true);
2617        extras.putInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, dialogId);
2618        try {
2619            Connection connection = dial(number, videoState, extras);
2620            mPhone.notifyUnknownConnection(connection);
2621        } catch (CallStateException e) {
2622            loge("pullExternalCall failed - " + e);
2623        }
2624    }
2625
2626    private ImsException getImsManagerIsNullException() {
2627        return new ImsException("no ims manager", ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
2628    }
2629
2630    /**
2631     * Determines if answering an incoming call will cause the active call to be disconnected.
2632     * <p>
2633     * This will be the case if
2634     * {@link CarrierConfigManager#KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is
2635     * {@code true} for the carrier, the active call is a video call over WIFI, and the incoming
2636     * call is an audio call.
2637     *
2638     * @param activeCall The active call.
2639     * @param incomingCall The incoming call.
2640     * @return {@code true} if answering the incoming call will cause the active call to be
2641     *      disconnected, {@code false} otherwise.
2642     */
2643    private boolean shouldDisconnectActiveCallOnAnswer(ImsCall activeCall,
2644            ImsCall incomingCall) {
2645
2646        if (!mDropVideoCallWhenAnsweringAudioCall) {
2647            return false;
2648        }
2649
2650        boolean isActiveCallVideo = activeCall.isVideoCall() ||
2651                (mTreatDowngradedVideoCallsAsVideoCalls && activeCall.wasVideoCall());
2652        boolean isActiveCallOnWifi = activeCall.isWifiCall();
2653        boolean isVoWifiEnabled = mImsManager.isWfcEnabledByPlatform(mPhone.getContext()) &&
2654                mImsManager.isWfcEnabledByUser(mPhone.getContext());
2655        boolean isIncomingCallAudio = !incomingCall.isVideoCall();
2656        log("shouldDisconnectActiveCallOnAnswer : isActiveCallVideo=" + isActiveCallVideo +
2657                " isActiveCallOnWifi=" + isActiveCallOnWifi + " isIncomingCallAudio=" +
2658                isIncomingCallAudio + " isVowifiEnabled=" + isVoWifiEnabled);
2659
2660        return isActiveCallVideo && isActiveCallOnWifi && isIncomingCallAudio && !isVoWifiEnabled;
2661    }
2662
2663    /** Get aggregated video call data usage since boot.
2664     *
2665     * @return data usage in bytes
2666     */
2667    public long getVtDataUsage() {
2668
2669        // If there is an ongoing VT call, request the latest VT usage from the modem. The latest
2670        // usage will return asynchronously so it won't be counted in this round, but it will be
2671        // eventually counted when next getVtDataUsage is called.
2672        if (mState != PhoneConstants.State.IDLE) {
2673            for (ImsPhoneConnection conn : mConnections) {
2674                android.telecom.Connection.VideoProvider videoProvider = conn.getVideoProvider();
2675                if (videoProvider != null) {
2676                    videoProvider.onRequestConnectionDataUsage();
2677                }
2678            }
2679        }
2680
2681        return mTotalVtDataUsage;
2682    }
2683
2684    public void registerPhoneStateListener(PhoneStateListener listener) {
2685        mPhoneStateListeners.add(listener);
2686    }
2687
2688    public void unregisterPhoneStateListener(PhoneStateListener listener) {
2689        mPhoneStateListeners.remove(listener);
2690    }
2691
2692    /**
2693     * Notifies local telephony listeners of changes to the IMS phone state.
2694     *
2695     * @param oldState The old state.
2696     * @param newState The new state.
2697     */
2698    private void notifyPhoneStateChanged(PhoneConstants.State oldState,
2699            PhoneConstants.State newState) {
2700
2701        for (PhoneStateListener listener : mPhoneStateListeners) {
2702            listener.onPhoneStateChanged(oldState, newState);
2703        }
2704    }
2705
2706    /** Modify video call to a new video state.
2707     *
2708     * @param imsCall IMS call to be modified
2709     * @param newVideoState New video state. (Refer to VideoProfile)
2710     */
2711    private void modifyVideoCall(ImsCall imsCall, int newVideoState) {
2712        ImsPhoneConnection conn = findConnection(imsCall);
2713        if (conn != null) {
2714            int oldVideoState = conn.getVideoState();
2715            if (conn.getVideoProvider() != null) {
2716                conn.getVideoProvider().onSendSessionModifyRequest(
2717                        new VideoProfile(oldVideoState), new VideoProfile(newVideoState));
2718            }
2719        }
2720    }
2721
2722    /**
2723     * Handler of data enabled changed event
2724     * @param enabled True if data is enabled, otherwise disabled.
2725     * @param reason Reason for data enabled/disabled (see {@code REASON_*} in
2726     *      {@link DataEnabledSettings}.
2727     */
2728    private void onDataEnabledChanged(boolean enabled, int reason) {
2729
2730        log("onDataEnabledChanged: enabled=" + enabled + ", reason=" + reason);
2731        ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId()).setDataEnabled(enabled);
2732
2733        if (!enabled) {
2734            int reasonCode;
2735            if (reason == DataEnabledSettings.REASON_POLICY_DATA_ENABLED) {
2736                reasonCode = ImsReasonInfo.CODE_DATA_LIMIT_REACHED;
2737            } else if (reason == DataEnabledSettings.REASON_USER_DATA_ENABLED) {
2738                reasonCode = ImsReasonInfo.CODE_DATA_DISABLED;
2739            } else {
2740                // Unexpected code, default to data disabled.
2741                reasonCode = ImsReasonInfo.CODE_DATA_DISABLED;
2742            }
2743
2744            // If data is disabled while there are ongoing VT calls which are not taking place over
2745            // wifi, then they should be disconnected to prevent the user from incurring further
2746            // data charges.
2747            for (ImsPhoneConnection conn : mConnections) {
2748                ImsCall imsCall = conn.getImsCall();
2749                if (imsCall != null && imsCall.isVideoCall() && !imsCall.isWifiCall()) {
2750                    if (conn.hasCapabilities(
2751                            Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL |
2752                                    Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE)) {
2753
2754                        // If the carrier supports downgrading to voice, then we can simply issue a
2755                        // downgrade to voice instead of terminating the call.
2756                        if (reasonCode == ImsReasonInfo.CODE_DATA_DISABLED) {
2757                            conn.onConnectionEvent(TelephonyManager.EVENT_DOWNGRADE_DATA_DISABLED,
2758                                    null);
2759                        } else if (reasonCode == ImsReasonInfo.CODE_DATA_LIMIT_REACHED) {
2760                            conn.onConnectionEvent(
2761                                    TelephonyManager.EVENT_DOWNGRADE_DATA_LIMIT_REACHED, null);
2762                        }
2763                        modifyVideoCall(imsCall, VideoProfile.STATE_AUDIO_ONLY);
2764                    } else {
2765                        // If the carrier does not support downgrading to voice, the only choice we
2766                        // have is to terminate the call.
2767                        try {
2768                            imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED, reasonCode);
2769                        } catch (ImsException ie) {
2770                            loge("Couldn't terminate call " + imsCall);
2771                        }
2772                    }
2773                }
2774            }
2775        }
2776
2777        // This will call into updateVideoCallFeatureValue and eventually all clients will be
2778        // asynchronously notified that the availability of VT over LTE has changed.
2779        ImsManager.updateImsServiceConfig(mPhone.getContext(), mPhone.getPhoneId(), true);
2780    }
2781
2782    /**
2783     * @return {@code true} if the device is connected to a WIFI network, {@code false} otherwise.
2784     */
2785    private boolean isWifiConnected() {
2786        ConnectivityManager cm = (ConnectivityManager) mPhone.getContext()
2787                .getSystemService(Context.CONNECTIVITY_SERVICE);
2788        if (cm != null) {
2789            NetworkInfo ni = cm.getActiveNetworkInfo();
2790            if (ni != null && ni.isConnected()) {
2791                return ni.getType() == ConnectivityManager.TYPE_WIFI;
2792            }
2793        }
2794        return false;
2795    }
2796
2797    /**
2798     * @return {@code true} if downgrading of a video call to audio is supported.
2799     */
2800    public boolean isCarrierDowngradeOfVtCallSupported() {
2801        return mSupportDowngradeVtToAudio;
2802    }
2803}
2804