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