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