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