ImsPhoneCallTracker.java revision 2e7e09e79c04f34034bebea8f5d86606bb584577
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 && mOnHoldToneStarted) {
1718                mPhone.stopOnHoldTone(conn);
1719                mOnHoldToneStarted = false;
1720            }
1721
1722            SuppServiceNotification supp = new SuppServiceNotification();
1723            // Type of notification: 0 = MO; 1 = MT
1724            // Refer SuppServiceNotification class documentation.
1725            supp.notificationType = 1;
1726            supp.code = SuppServiceNotification.MT_CODE_CALL_RETRIEVED;
1727            mPhone.notifySuppSvcNotification(supp);
1728            mEventLog.writeOnImsCallResumeReceived(imsCall.getCallSession());
1729        }
1730
1731        @Override
1732        public void onCallHoldReceived(ImsCall imsCall) {
1733            if (DBG) log("onCallHoldReceived");
1734
1735            ImsPhoneConnection conn = findConnection(imsCall);
1736            if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
1737                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
1738                    mPhone.startOnHoldTone(conn);
1739                    mOnHoldToneStarted = true;
1740                    mOnHoldToneId = System.identityHashCode(conn);
1741                }
1742            }
1743
1744            SuppServiceNotification supp = new SuppServiceNotification();
1745            // Type of notification: 0 = MO; 1 = MT
1746            // Refer SuppServiceNotification class documentation.
1747            supp.notificationType = 1;
1748            supp.code = SuppServiceNotification.MT_CODE_CALL_ON_HOLD;
1749            mPhone.notifySuppSvcNotification(supp);
1750            mEventLog.writeOnImsCallHoldReceived(imsCall.getCallSession());
1751        }
1752
1753        @Override
1754        public void onCallSuppServiceReceived(ImsCall call,
1755                ImsSuppServiceNotification suppServiceInfo) {
1756            if (DBG) log("onCallSuppServiceReceived: suppServiceInfo=" + suppServiceInfo);
1757
1758            SuppServiceNotification supp = new SuppServiceNotification();
1759            supp.notificationType = suppServiceInfo.notificationType;
1760            supp.code = suppServiceInfo.code;
1761            supp.index = suppServiceInfo.index;
1762            supp.number = suppServiceInfo.number;
1763            supp.history = suppServiceInfo.history;
1764
1765            mPhone.notifySuppSvcNotification(supp);
1766        }
1767
1768        @Override
1769        public void onCallMerged(final ImsCall call, final ImsCall peerCall, boolean swapCalls) {
1770            if (DBG) log("onCallMerged");
1771
1772            ImsPhoneCall foregroundImsPhoneCall = findConnection(call).getCall();
1773            ImsPhoneConnection peerConnection = findConnection(peerCall);
1774            ImsPhoneCall peerImsPhoneCall = peerConnection == null ? null
1775                    : peerConnection.getCall();
1776
1777            if (swapCalls) {
1778                switchAfterConferenceSuccess();
1779            }
1780            foregroundImsPhoneCall.merge(peerImsPhoneCall, ImsPhoneCall.State.ACTIVE);
1781
1782            try {
1783                final ImsPhoneConnection conn = findConnection(call);
1784                log("onCallMerged: ImsPhoneConnection=" + conn);
1785                log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1786                setVideoCallProvider(conn, call);
1787                log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1788            } catch (Exception e) {
1789                loge("onCallMerged: exception " + e);
1790            }
1791
1792            // After merge complete, update foreground as Active
1793            // and background call as Held, if background call exists
1794            processCallStateChange(mForegroundCall.getImsCall(), ImsPhoneCall.State.ACTIVE,
1795                    DisconnectCause.NOT_DISCONNECTED);
1796            if (peerConnection != null) {
1797                processCallStateChange(mBackgroundCall.getImsCall(), ImsPhoneCall.State.HOLDING,
1798                    DisconnectCause.NOT_DISCONNECTED);
1799            }
1800
1801            // Check if the merge was requested by an existing conference call. In that
1802            // case, no further action is required.
1803            if (!call.isMergeRequestedByConf()) {
1804                log("onCallMerged :: calling onMultipartyStateChanged()");
1805                onMultipartyStateChanged(call, true);
1806            } else {
1807                log("onCallMerged :: Merge requested by existing conference.");
1808                // Reset the flag.
1809                call.resetIsMergeRequestedByConf(false);
1810            }
1811            logState();
1812        }
1813
1814        @Override
1815        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
1816            if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo);
1817
1818            // TODO: the call to notifySuppServiceFailed throws up the "merge failed" dialog
1819            // We should move this into the InCallService so that it is handled appropriately
1820            // based on the user facing UI.
1821            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
1822
1823            // Start plumbing this even through Telecom so other components can take
1824            // appropriate action.
1825            ImsPhoneConnection conn = findConnection(call);
1826            if (conn != null) {
1827                conn.onConferenceMergeFailed();
1828            }
1829        }
1830
1831        /**
1832         * Called when the state of IMS conference participant(s) has changed.
1833         *
1834         * @param call the call object that carries out the IMS call.
1835         * @param participants the participant(s) and their new state information.
1836         */
1837        @Override
1838        public void onConferenceParticipantsStateChanged(ImsCall call,
1839                List<ConferenceParticipant> participants) {
1840            if (DBG) log("onConferenceParticipantsStateChanged");
1841
1842            ImsPhoneConnection conn = findConnection(call);
1843            if (conn != null) {
1844                conn.updateConferenceParticipants(participants);
1845            }
1846        }
1847
1848        @Override
1849        public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
1850            mPhone.onTtyModeReceived(mode);
1851        }
1852
1853        @Override
1854        public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1855            ImsReasonInfo reasonInfo) {
1856            if (DBG) {
1857                log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
1858                    targetAccessTech + ", reasonInfo=" + reasonInfo);
1859            }
1860
1861            boolean isHandoverToWifi = srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN &&
1862                    targetAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
1863            if (isHandoverToWifi) {
1864                // If we handed over to wifi successfully, don't check for failure in the future.
1865                removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
1866            }
1867
1868            mEventLog.writeOnImsCallHandover(imsCall.getCallSession(),
1869                    srcAccessTech, targetAccessTech, reasonInfo);
1870        }
1871
1872        @Override
1873        public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1874            ImsReasonInfo reasonInfo) {
1875            if (DBG) {
1876                log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech +
1877                    ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo);
1878            }
1879            mEventLog.writeOnImsCallHandoverFailed(imsCall.getCallSession(),
1880                    srcAccessTech, targetAccessTech, reasonInfo);
1881
1882            boolean isHandoverToWifi = srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN &&
1883                    targetAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
1884            ImsPhoneConnection conn = findConnection(imsCall);
1885            if (conn != null && isHandoverToWifi) {
1886                log("onCallHandoverFailed - handover to WIFI Failed");
1887
1888                // If we know we failed to handover, don't check for failure in the future.
1889                removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
1890
1891                if (mNotifyVtHandoverToWifiFail) {
1892                    // Only notify others if carrier config indicates to do so.
1893                    conn.onHandoverToWifiFailed();
1894                }
1895            }
1896        }
1897
1898        /**
1899         * Handles a change to the multiparty state for an {@code ImsCall}.  Notifies the associated
1900         * {@link ImsPhoneConnection} of the change.
1901         *
1902         * @param imsCall The IMS call.
1903         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
1904         *      otherwise.
1905         */
1906        @Override
1907        public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
1908            if (DBG) log("onMultipartyStateChanged to " + (isMultiParty ? "Y" : "N"));
1909
1910            ImsPhoneConnection conn = findConnection(imsCall);
1911            if (conn != null) {
1912                conn.updateMultipartyState(isMultiParty);
1913            }
1914        }
1915    };
1916
1917    /**
1918     * Listen to the IMS call state change
1919     */
1920    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
1921        @Override
1922        public void onCallStarted(ImsCall imsCall) {
1923            if (DBG) log("mImsUssdListener onCallStarted");
1924
1925            if (imsCall == mUssdSession) {
1926                if (mPendingUssd != null) {
1927                    AsyncResult.forMessage(mPendingUssd);
1928                    mPendingUssd.sendToTarget();
1929                    mPendingUssd = null;
1930                }
1931            }
1932        }
1933
1934        @Override
1935        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1936            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
1937
1938            onCallTerminated(imsCall, reasonInfo);
1939        }
1940
1941        @Override
1942        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1943            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
1944            removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
1945
1946            if (imsCall == mUssdSession) {
1947                mUssdSession = null;
1948                if (mPendingUssd != null) {
1949                    CommandException ex =
1950                            new CommandException(CommandException.Error.GENERIC_FAILURE);
1951                    AsyncResult.forMessage(mPendingUssd, null, ex);
1952                    mPendingUssd.sendToTarget();
1953                    mPendingUssd = null;
1954                }
1955            }
1956            imsCall.close();
1957        }
1958
1959        @Override
1960        public void onCallUssdMessageReceived(ImsCall call,
1961                int mode, String ussdMessage) {
1962            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
1963
1964            int ussdMode = -1;
1965
1966            switch(mode) {
1967                case ImsCall.USSD_MODE_REQUEST:
1968                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
1969                    break;
1970
1971                case ImsCall.USSD_MODE_NOTIFY:
1972                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
1973                    break;
1974            }
1975
1976            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
1977        }
1978    };
1979
1980    /**
1981     * Listen to the IMS service state change
1982     *
1983     */
1984    private ImsConnectionStateListener mImsConnectionStateListener =
1985        new ImsConnectionStateListener() {
1986        @Override
1987        public void onImsConnected() {
1988            if (DBG) log("onImsConnected");
1989            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1990            mPhone.setImsRegistered(true);
1991            mEventLog.writeOnImsConnectionState(
1992                    TelephonyEventLog.IMS_CONNECTION_STATE_CONNECTED, null);
1993        }
1994
1995        @Override
1996        public void onImsDisconnected(ImsReasonInfo imsReasonInfo) {
1997            if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
1998            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1999            mPhone.setImsRegistered(false);
2000            mPhone.processDisconnectReason(imsReasonInfo);
2001            mEventLog.writeOnImsConnectionState(
2002                    TelephonyEventLog.IMS_CONNECTION_STATE_DISCONNECTED, imsReasonInfo);
2003        }
2004
2005        @Override
2006        public void onImsProgressing() {
2007            if (DBG) log("onImsProgressing");
2008            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
2009            mPhone.setImsRegistered(false);
2010            mEventLog.writeOnImsConnectionState(
2011                    TelephonyEventLog.IMS_CONNECTION_STATE_PROGRESSING, null);
2012        }
2013
2014        @Override
2015        public void onImsResumed() {
2016            if (DBG) log("onImsResumed");
2017            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
2018            mEventLog.writeOnImsConnectionState(
2019                    TelephonyEventLog.IMS_CONNECTION_STATE_RESUMED, null);
2020        }
2021
2022        @Override
2023        public void onImsSuspended() {
2024            if (DBG) log("onImsSuspended");
2025            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
2026            mEventLog.writeOnImsConnectionState(
2027                    TelephonyEventLog.IMS_CONNECTION_STATE_SUSPENDED, null);
2028        }
2029
2030        @Override
2031        public void onFeatureCapabilityChanged(int serviceClass,
2032                int[] enabledFeatures, int[] disabledFeatures) {
2033            if (serviceClass == ImsServiceClass.MMTEL) {
2034                boolean tmpIsVideoCallEnabled = isVideoCallEnabled();
2035                // Check enabledFeatures to determine capabilities. We ignore disabledFeatures.
2036                StringBuilder sb;
2037                if (DBG) {
2038                    sb = new StringBuilder(120);
2039                    sb.append("onFeatureCapabilityChanged: ");
2040                }
2041                for (int  i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE;
2042                        i <= ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI &&
2043                        i < enabledFeatures.length; i++) {
2044                    if (enabledFeatures[i] == i) {
2045                        // If the feature is set to its own integer value it is enabled.
2046                        if (DBG) {
2047                            sb.append(mImsFeatureStrings[i]);
2048                            sb.append(":true ");
2049                        }
2050
2051                        mImsFeatureEnabled[i] = true;
2052                    } else if (enabledFeatures[i]
2053                            == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) {
2054                        // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled.
2055                        if (DBG) {
2056                            sb.append(mImsFeatureStrings[i]);
2057                            sb.append(":false ");
2058                        }
2059
2060                        mImsFeatureEnabled[i] = false;
2061                    } else {
2062                        // Feature has unknown state; it is not its own value or -1.
2063                        if (DBG) {
2064                            loge("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i]
2065                                    + "): unexpectedValue=" + enabledFeatures[i]);
2066                        }
2067                    }
2068                }
2069                if (DBG) {
2070                    log(sb.toString());
2071                }
2072                if (tmpIsVideoCallEnabled != isVideoCallEnabled()) {
2073                    mPhone.notifyForVideoCapabilityChanged(isVideoCallEnabled());
2074                }
2075
2076                // TODO: Use the ImsCallSession or ImsCallProfile to tell the initial Wifi state and
2077                // {@link ImsCallSession.Listener#callSessionHandover} to listen for changes to
2078                // wifi capability caused by a handover.
2079                if (DBG) log("onFeatureCapabilityChanged: isVolteEnabled=" + isVolteEnabled()
2080                            + ", isVideoCallEnabled=" + isVideoCallEnabled()
2081                            + ", isVowifiEnabled=" + isVowifiEnabled()
2082                            + ", isUtEnabled=" + isUtEnabled());
2083                for (ImsPhoneConnection connection : mConnections) {
2084                    connection.updateWifiState();
2085                }
2086
2087                mPhone.onFeatureCapabilityChanged();
2088
2089                mEventLog.writeOnImsCapabilities(mImsFeatureEnabled);
2090            }
2091        }
2092
2093        @Override
2094        public void onVoiceMessageCountChanged(int count) {
2095            if (DBG) log("onVoiceMessageCountChanged :: count=" + count);
2096            mPhone.mDefaultPhone.setVoiceMessageCount(count);
2097        }
2098
2099        @Override
2100        public void registrationAssociatedUriChanged(Uri[] uris) {
2101            if (DBG) log("registrationAssociatedUriChanged");
2102            mPhone.setCurrentSubscriberUris(uris);
2103        }
2104    };
2105
2106    private ImsConfigListener.Stub mImsConfigListener = new ImsConfigListener.Stub() {
2107        @Override
2108        public void onGetFeatureResponse(int feature, int network, int value, int status) {}
2109
2110        @Override
2111        public void onSetFeatureResponse(int feature, int network, int value, int status) {
2112            mEventLog.writeImsSetFeatureValue(feature, network, value, status);
2113        }
2114
2115        @Override
2116        public void onGetVideoQuality(int status, int quality) {}
2117
2118        @Override
2119        public void onSetVideoQuality(int status) {}
2120
2121    };
2122
2123    public ImsUtInterface getUtInterface() throws ImsException {
2124        if (mImsManager == null) {
2125            throw getImsManagerIsNullException();
2126        }
2127
2128        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
2129        return ut;
2130    }
2131
2132    private void transferHandoverConnections(ImsPhoneCall call) {
2133        if (call.mConnections != null) {
2134            for (Connection c : call.mConnections) {
2135                c.mPreHandoverState = call.mState;
2136                log ("Connection state before handover is " + c.getStateBeforeHandover());
2137            }
2138        }
2139        if (mHandoverCall.mConnections == null ) {
2140            mHandoverCall.mConnections = call.mConnections;
2141        } else { // Multi-call SRVCC
2142            mHandoverCall.mConnections.addAll(call.mConnections);
2143        }
2144        if (mHandoverCall.mConnections != null) {
2145            if (call.getImsCall() != null) {
2146                call.getImsCall().close();
2147            }
2148            for (Connection c : mHandoverCall.mConnections) {
2149                ((ImsPhoneConnection)c).changeParent(mHandoverCall);
2150                ((ImsPhoneConnection)c).releaseWakeLock();
2151            }
2152        }
2153        if (call.getState().isAlive()) {
2154            log ("Call is alive and state is " + call.mState);
2155            mHandoverCall.mState = call.mState;
2156        }
2157        call.mConnections.clear();
2158        call.mState = ImsPhoneCall.State.IDLE;
2159    }
2160
2161    /* package */
2162    void notifySrvccState(Call.SrvccState state) {
2163        if (DBG) log("notifySrvccState state=" + state);
2164
2165        mSrvccState = state;
2166
2167        if (mSrvccState == Call.SrvccState.COMPLETED) {
2168            transferHandoverConnections(mForegroundCall);
2169            transferHandoverConnections(mBackgroundCall);
2170            transferHandoverConnections(mRingingCall);
2171        }
2172    }
2173
2174    //****** Overridden from Handler
2175
2176    @Override
2177    public void
2178    handleMessage (Message msg) {
2179        AsyncResult ar;
2180        if (DBG) log("handleMessage what=" + msg.what);
2181
2182        switch (msg.what) {
2183            case EVENT_HANGUP_PENDINGMO:
2184                if (mPendingMO != null) {
2185                    mPendingMO.onDisconnect();
2186                    removeConnection(mPendingMO);
2187                    mPendingMO = null;
2188                }
2189                mPendingIntentExtras = null;
2190                updatePhoneState();
2191                mPhone.notifyPreciseCallStateChanged();
2192                break;
2193            case EVENT_RESUME_BACKGROUND:
2194                try {
2195                    resumeWaitingOrHolding();
2196                } catch (CallStateException e) {
2197                    if (Phone.DEBUG_PHONE) {
2198                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
2199                    }
2200                }
2201                break;
2202            case EVENT_DIAL_PENDINGMO:
2203                dialInternal(mPendingMO, mClirMode, mPendingCallVideoState, mPendingIntentExtras);
2204                mPendingIntentExtras = null;
2205                break;
2206
2207            case EVENT_EXIT_ECBM_BEFORE_PENDINGMO:
2208                if (mPendingMO != null) {
2209                    //Send ECBM exit request
2210                    try {
2211                        getEcbmInterface().exitEmergencyCallbackMode();
2212                        mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
2213                        pendingCallClirMode = mClirMode;
2214                        pendingCallInEcm = true;
2215                    } catch (ImsException e) {
2216                        e.printStackTrace();
2217                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
2218                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
2219                    }
2220                }
2221                break;
2222
2223            case EVENT_EXIT_ECM_RESPONSE_CDMA:
2224                // no matter the result, we still do the same here
2225                if (pendingCallInEcm) {
2226                    dialInternal(mPendingMO, pendingCallClirMode,
2227                            mPendingCallVideoState, mPendingIntentExtras);
2228                    mPendingIntentExtras = null;
2229                    pendingCallInEcm = false;
2230                }
2231                mPhone.unsetOnEcbModeExitResponse(this);
2232                break;
2233            case EVENT_VT_DATA_USAGE_UPDATE:
2234                ar = (AsyncResult) msg.obj;
2235                ImsCall call = (ImsCall) ar.userObj;
2236                Long usage = (long) ar.result;
2237                log("VT data usage update. usage = " + usage + ", imsCall = " + call);
2238
2239                Long oldUsage = 0L;
2240                if (mVtDataUsageMap.containsKey(call.uniqueId)) {
2241                    oldUsage = mVtDataUsageMap.get(call.uniqueId);
2242                }
2243                mTotalVtDataUsage += (usage - oldUsage);
2244                mVtDataUsageMap.put(call.uniqueId, usage);
2245                break;
2246            case EVENT_DATA_ENABLED_CHANGED:
2247                ar = (AsyncResult) msg.obj;
2248                if (ar.result instanceof Pair) {
2249                    Pair<Boolean, Integer> p = (Pair<Boolean, Integer>) ar.result;
2250                    onDataEnabledChanged(p.first, p.second);
2251                }
2252                break;
2253            case EVENT_GET_IMS_SERVICE:
2254                try {
2255                    getImsService();
2256                } catch (ImsException e) {
2257                    loge("getImsService: " + e);
2258                    //Leave mImsManager as null, then CallStateException will be thrown when dialing
2259                    mImsManager = null;
2260                    if (mImsServiceRetryCount < NUM_IMS_SERVICE_RETRIES) {
2261                        loge("getImsService: Retrying getting ImsService...");
2262                        sendEmptyMessageDelayed(EVENT_GET_IMS_SERVICE,
2263                                TIME_BETWEEN_IMS_SERVICE_RETRIES_MS);
2264                        mImsServiceRetryCount++;
2265                    } else {
2266                        // We have been unable to connect for
2267                        // NUM_IMS_SERVICE_RETRIES*TIME_BETWEEN_IMS_SERVICE_RETRIES_MS ms. We will
2268                        // probably never be able to connect, so we should just give up.
2269                        loge("getImsService: ImsService retrieval timeout... ImsService is " +
2270                                "unavailable.");
2271                    }
2272                }
2273                break;
2274            case EVENT_CHECK_FOR_WIFI_HANDOVER:
2275                if (msg.obj instanceof ImsCall) {
2276                    ImsCall imsCall = (ImsCall) msg.obj;
2277                    if (!imsCall.isWifiCall()) {
2278                        // Call did not handover to wifi, notify of handover failure.
2279                        ImsPhoneConnection conn = findConnection(imsCall);
2280                        if (conn != null) {
2281                            conn.onHandoverToWifiFailed();
2282                        }
2283                    }
2284                }
2285                break;
2286        }
2287    }
2288
2289    @Override
2290    protected void log(String msg) {
2291        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
2292    }
2293
2294    protected void loge(String msg) {
2295        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
2296    }
2297
2298    /**
2299     * Logs the current state of the ImsPhoneCallTracker.  Useful for debugging issues with
2300     * call tracking.
2301     */
2302    /* package */
2303    void logState() {
2304        if (!VERBOSE_STATE_LOGGING) {
2305            return;
2306        }
2307
2308        StringBuilder sb = new StringBuilder();
2309        sb.append("Current IMS PhoneCall State:\n");
2310        sb.append(" Foreground: ");
2311        sb.append(mForegroundCall);
2312        sb.append("\n");
2313        sb.append(" Background: ");
2314        sb.append(mBackgroundCall);
2315        sb.append("\n");
2316        sb.append(" Ringing: ");
2317        sb.append(mRingingCall);
2318        sb.append("\n");
2319        sb.append(" Handover: ");
2320        sb.append(mHandoverCall);
2321        sb.append("\n");
2322        Rlog.v(LOG_TAG, sb.toString());
2323    }
2324
2325    @Override
2326    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2327        pw.println("ImsPhoneCallTracker extends:");
2328        super.dump(fd, pw, args);
2329        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
2330        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
2331        pw.println(" mRingingCall=" + mRingingCall);
2332        pw.println(" mForegroundCall=" + mForegroundCall);
2333        pw.println(" mBackgroundCall=" + mBackgroundCall);
2334        pw.println(" mHandoverCall=" + mHandoverCall);
2335        pw.println(" mPendingMO=" + mPendingMO);
2336        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
2337        pw.println(" mPhone=" + mPhone);
2338        pw.println(" mDesiredMute=" + mDesiredMute);
2339        pw.println(" mState=" + mState);
2340        for (int i = 0; i < mImsFeatureEnabled.length; i++) {
2341            pw.println(" " + mImsFeatureStrings[i] + ": "
2342                    + ((mImsFeatureEnabled[i]) ? "enabled" : "disabled"));
2343        }
2344        pw.println(" mTotalVtDataUsage=" + mTotalVtDataUsage);
2345        for (Map.Entry<Integer, Long> entry : mVtDataUsageMap.entrySet()) {
2346            pw.println("    id=" + entry.getKey() + " ,usage=" + entry.getValue());
2347        }
2348
2349        pw.flush();
2350        pw.println("++++++++++++++++++++++++++++++++");
2351
2352        try {
2353            if (mImsManager != null) {
2354                mImsManager.dump(fd, pw, args);
2355            }
2356        } catch (Exception e) {
2357            e.printStackTrace();
2358        }
2359
2360        if (mConnections != null && mConnections.size() > 0) {
2361            pw.println("mConnections:");
2362            for (int i = 0; i < mConnections.size(); i++) {
2363                pw.println("  [" + i + "]: " + mConnections.get(i));
2364            }
2365        }
2366    }
2367
2368    @Override
2369    protected void handlePollCalls(AsyncResult ar) {
2370    }
2371
2372    /* package */
2373    ImsEcbm getEcbmInterface() throws ImsException {
2374        if (mImsManager == null) {
2375            throw getImsManagerIsNullException();
2376        }
2377
2378        ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
2379        return ecbm;
2380    }
2381
2382    /* package */
2383    ImsMultiEndpoint getMultiEndpointInterface() throws ImsException {
2384        if (mImsManager == null) {
2385            throw getImsManagerIsNullException();
2386        }
2387
2388        try {
2389            return mImsManager.getMultiEndpointInterface(mServiceId);
2390        } catch (ImsException e) {
2391            if (e.getCode() == ImsReasonInfo.CODE_MULTIENDPOINT_NOT_SUPPORTED) {
2392                return null;
2393            } else {
2394                throw e;
2395            }
2396
2397        }
2398    }
2399
2400    public boolean isInEmergencyCall() {
2401        return mIsInEmergencyCall;
2402    }
2403
2404    public boolean isVolteEnabled() {
2405        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE];
2406    }
2407
2408    public boolean isVowifiEnabled() {
2409        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI];
2410    }
2411
2412    public boolean isVideoCallEnabled() {
2413        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE]
2414                || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]);
2415    }
2416
2417    @Override
2418    public PhoneConstants.State getState() {
2419        return mState;
2420    }
2421
2422    private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
2423            throws RemoteException {
2424        IImsVideoCallProvider imsVideoCallProvider =
2425                imsCall.getCallSession().getVideoCallProvider();
2426        if (imsVideoCallProvider != null) {
2427            ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
2428                    new ImsVideoCallProviderWrapper(imsVideoCallProvider);
2429            conn.setVideoProvider(imsVideoCallProviderWrapper);
2430            imsVideoCallProviderWrapper.registerForDataUsageUpdate
2431                    (this, EVENT_VT_DATA_USAGE_UPDATE, imsCall);
2432            imsVideoCallProviderWrapper.addImsVideoProviderCallback(conn);
2433        }
2434    }
2435
2436    public boolean isUtEnabled() {
2437        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_LTE]
2438            || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI]);
2439    }
2440
2441    /**
2442     * Given a call subject, removes any characters considered by the current carrier to be
2443     * invalid, as well as escaping (using \) any characters which the carrier requires to be
2444     * escaped.
2445     *
2446     * @param callSubject The call subject.
2447     * @return The call subject with invalid characters removed and escaping applied as required.
2448     */
2449    private String cleanseInstantLetteringMessage(String callSubject) {
2450        if (TextUtils.isEmpty(callSubject)) {
2451            return callSubject;
2452        }
2453
2454        // Get the carrier config for the current sub.
2455        CarrierConfigManager configMgr = (CarrierConfigManager)
2456                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
2457        // Bail if we can't find the carrier config service.
2458        if (configMgr == null) {
2459            return callSubject;
2460        }
2461
2462        PersistableBundle carrierConfig = configMgr.getConfigForSubId(mPhone.getSubId());
2463        // Bail if no carrier config found.
2464        if (carrierConfig == null) {
2465            return callSubject;
2466        }
2467
2468        // Try to replace invalid characters
2469        String invalidCharacters = carrierConfig.getString(
2470                CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING);
2471        if (!TextUtils.isEmpty(invalidCharacters)) {
2472            callSubject = callSubject.replaceAll(invalidCharacters, "");
2473        }
2474
2475        // Try to escape characters which need to be escaped.
2476        String escapedCharacters = carrierConfig.getString(
2477                CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING);
2478        if (!TextUtils.isEmpty(escapedCharacters)) {
2479            callSubject = escapeChars(escapedCharacters, callSubject);
2480        }
2481        return callSubject;
2482    }
2483
2484    /**
2485     * Given a source string, return a string where a set of characters are escaped using the
2486     * backslash character.
2487     *
2488     * @param toEscape The characters to escape with a backslash.
2489     * @param source The source string.
2490     * @return The source string with characters escaped.
2491     */
2492    private String escapeChars(String toEscape, String source) {
2493        StringBuilder escaped = new StringBuilder();
2494        for (char c : source.toCharArray()) {
2495            if (toEscape.contains(Character.toString(c))) {
2496                escaped.append("\\");
2497            }
2498            escaped.append(c);
2499        }
2500
2501        return escaped.toString();
2502    }
2503
2504    /**
2505     * Initiates a pull of an external call.
2506     *
2507     * Initiates a pull by making a dial request with the {@link ImsCallProfile#EXTRA_IS_CALL_PULL}
2508     * extra specified.  We call {@link ImsPhone#notifyUnknownConnection(Connection)} which notifies
2509     * Telecom of the new dialed connection.  The
2510     * {@code PstnIncomingCallNotifier#maybeSwapWithUnknownConnection} logic ensures that the new
2511     * {@link ImsPhoneConnection} resulting from the dial gets swapped with the
2512     * {@link ImsExternalConnection}, which effectively makes the external call become a regular
2513     * call.  Magic!
2514     *
2515     * @param number The phone number of the call to be pulled.
2516     * @param videoState The desired video state of the pulled call.
2517     * @param dialogId The {@link ImsExternalConnection#getCallId()} dialog id associated with the
2518     *                 call which is being pulled.
2519     */
2520    @Override
2521    public void pullExternalCall(String number, int videoState, int dialogId) {
2522        Bundle extras = new Bundle();
2523        extras.putBoolean(ImsCallProfile.EXTRA_IS_CALL_PULL, true);
2524        extras.putInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, dialogId);
2525        try {
2526            Connection connection = dial(number, videoState, extras);
2527            mPhone.notifyUnknownConnection(connection);
2528        } catch (CallStateException e) {
2529            loge("pullExternalCall failed - " + e);
2530        }
2531    }
2532
2533    private ImsException getImsManagerIsNullException() {
2534        return new ImsException("no ims manager", ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
2535    }
2536
2537    /**
2538     * Determines if answering an incoming call will cause the active call to be disconnected.
2539     * <p>
2540     * This will be the case if
2541     * {@link CarrierConfigManager#KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is
2542     * {@code true} for the carrier, the active call is a video call over WIFI, and the incoming
2543     * call is an audio call.
2544     *
2545     * @param activeCall The active call.
2546     * @param incomingCall The incoming call.
2547     * @return {@code true} if answering the incoming call will cause the active call to be
2548     *      disconnected, {@code false} otherwise.
2549     */
2550    private boolean shouldDisconnectActiveCallOnAnswer(ImsCall activeCall,
2551            ImsCall incomingCall) {
2552
2553        if (!mDropVideoCallWhenAnsweringAudioCall) {
2554            return false;
2555        }
2556
2557        boolean isActiveCallVideo = activeCall.isVideoCall() ||
2558                (mTreatDowngradedVideoCallsAsVideoCalls && activeCall.wasVideoCall());
2559        boolean isActiveCallOnWifi = activeCall.isWifiCall();
2560        boolean isVoWifiEnabled = mImsManager.isWfcEnabledByPlatform(mPhone.getContext()) &&
2561                mImsManager.isWfcEnabledByUser(mPhone.getContext());
2562        boolean isIncomingCallAudio = !incomingCall.isVideoCall();
2563        log("shouldDisconnectActiveCallOnAnswer : isActiveCallVideo=" + isActiveCallVideo +
2564                " isActiveCallOnWifi=" + isActiveCallOnWifi + " isIncomingCallAudio=" +
2565                isIncomingCallAudio + " isVowifiEnabled=" + isVoWifiEnabled);
2566
2567        return isActiveCallVideo && isActiveCallOnWifi && isIncomingCallAudio && !isVoWifiEnabled;
2568    }
2569
2570    /** Get aggregated video call data usage since boot.
2571     *
2572     * @return data usage in bytes
2573     */
2574    public long getVtDataUsage() {
2575
2576        // If there is an ongoing VT call, request the latest VT usage from the modem. The latest
2577        // usage will return asynchronously so it won't be counted in this round, but it will be
2578        // eventually counted when next getVtDataUsage is called.
2579        if (mState != PhoneConstants.State.IDLE) {
2580            for (ImsPhoneConnection conn : mConnections) {
2581                android.telecom.Connection.VideoProvider videoProvider = conn.getVideoProvider();
2582                if (videoProvider != null) {
2583                    videoProvider.onRequestConnectionDataUsage();
2584                }
2585            }
2586        }
2587
2588        return mTotalVtDataUsage;
2589    }
2590
2591    public void registerPhoneStateListener(PhoneStateListener listener) {
2592        mPhoneStateListeners.add(listener);
2593    }
2594
2595    public void unregisterPhoneStateListener(PhoneStateListener listener) {
2596        mPhoneStateListeners.remove(listener);
2597    }
2598
2599    /**
2600     * Notifies local telephony listeners of changes to the IMS phone state.
2601     *
2602     * @param oldState The old state.
2603     * @param newState The new state.
2604     */
2605    private void notifyPhoneStateChanged(PhoneConstants.State oldState,
2606            PhoneConstants.State newState) {
2607
2608        for (PhoneStateListener listener : mPhoneStateListeners) {
2609            listener.onPhoneStateChanged(oldState, newState);
2610        }
2611    }
2612
2613    /** Modify video call to a new video state.
2614     *
2615     * @param imsCall IMS call to be modified
2616     * @param newVideoState New video state. (Refer to VideoProfile)
2617     */
2618    private void modifyVideoCall(ImsCall imsCall, int newVideoState) {
2619        ImsPhoneConnection conn = findConnection(imsCall);
2620        if (conn != null) {
2621            int oldVideoState = conn.getVideoState();
2622            if (conn.getVideoProvider() != null) {
2623                conn.getVideoProvider().onSendSessionModifyRequest(
2624                        new VideoProfile(oldVideoState), new VideoProfile(newVideoState));
2625            }
2626        }
2627    }
2628
2629    /**
2630     * Handler of data enabled changed event
2631     * @param enabled True if data is enabled, otherwise disabled.
2632     * @param reason Reason for data enabled/disabled (see {@code REASON_*} in
2633     *      {@link DataEnabledSettings}.
2634     */
2635    private void onDataEnabledChanged(boolean enabled, int reason) {
2636
2637        log("onDataEnabledChanged: enabled=" + enabled + ", reason=" + reason);
2638        ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId()).setDataEnabled(enabled);
2639
2640        if (!enabled) {
2641            int reasonCode;
2642            if (reason == DataEnabledSettings.REASON_POLICY_DATA_ENABLED) {
2643                reasonCode = ImsReasonInfo.CODE_DATA_LIMIT_REACHED;
2644            } else if (reason == DataEnabledSettings.REASON_USER_DATA_ENABLED) {
2645                reasonCode = ImsReasonInfo.CODE_DATA_DISABLED;
2646            } else {
2647                // Unexpected code, default to data disabled.
2648                reasonCode = ImsReasonInfo.CODE_DATA_DISABLED;
2649            }
2650
2651            // If data is disabled while there are ongoing VT calls which are not taking place over
2652            // wifi, then they should be disconnected to prevent the user from incurring further
2653            // data charges.
2654            for (ImsPhoneConnection conn : mConnections) {
2655                ImsCall imsCall = conn.getImsCall();
2656                if (imsCall != null && imsCall.isVideoCall() && !imsCall.isWifiCall()) {
2657                    if (conn.hasCapabilities(
2658                            Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL |
2659                                    Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE)) {
2660
2661                        // If the carrier supports downgrading to voice, then we can simply issue a
2662                        // downgrade to voice instead of terminating the call.
2663                        if (reasonCode == ImsReasonInfo.CODE_DATA_DISABLED) {
2664                            conn.onConnectionEvent(TelephonyManager.EVENT_DOWNGRADE_DATA_DISABLED,
2665                                    null);
2666                        } else if (reasonCode == ImsReasonInfo.CODE_DATA_LIMIT_REACHED) {
2667                            conn.onConnectionEvent(
2668                                    TelephonyManager.EVENT_DOWNGRADE_DATA_LIMIT_REACHED, null);
2669                        }
2670                        modifyVideoCall(imsCall, VideoProfile.STATE_AUDIO_ONLY);
2671                    } else {
2672                        // If the carrier does not support downgrading to voice, the only choice we
2673                        // have is to terminate the call.
2674                        try {
2675                            imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED, reasonCode);
2676                        } catch (ImsException ie) {
2677                            loge("Couldn't terminate call " + imsCall);
2678                        }
2679                    }
2680                }
2681            }
2682        }
2683
2684        // This will call into updateVideoCallFeatureValue and eventually all clients will be
2685        // asynchronously notified that the availability of VT over LTE has changed.
2686        ImsManager.updateImsServiceConfig(mPhone.getContext(), mPhone.getPhoneId(), true);
2687    }
2688
2689    /**
2690     * @return {@code true} if the device is connected to a WIFI network, {@code false} otherwise.
2691     */
2692    private boolean isWifiConnected() {
2693        ConnectivityManager cm = (ConnectivityManager) mPhone.getContext()
2694                .getSystemService(Context.CONNECTIVITY_SERVICE);
2695        if (cm != null) {
2696            NetworkInfo ni = cm.getActiveNetworkInfo();
2697            if (ni != null && ni.isConnected()) {
2698                return ni.getType() == ConnectivityManager.TYPE_WIFI;
2699            }
2700        }
2701        return false;
2702    }
2703
2704    /**
2705     * @return {@code true} if downgrading of a video call to audio is supported.
2706     */
2707    public boolean isCarrierDowngradeOfVtCallSupported() {
2708        return mSupportDowngradeVtToAudio;
2709    }
2710}
2711