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