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