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