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