ImsPhoneCallTracker.java revision 93c62c8a71821f46194e16ca3e84f95e101edb90
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    public 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    /* package */
999    void cancelUSSD() {
1000        if (mUssdSession == null) return;
1001
1002        try {
1003            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
1004        } catch (ImsException e) {
1005        }
1006
1007    }
1008
1009    private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) {
1010        for (ImsPhoneConnection conn : mConnections) {
1011            if (conn.getImsCall() == imsCall) {
1012                return conn;
1013            }
1014        }
1015        return null;
1016    }
1017
1018    private synchronized void removeConnection(ImsPhoneConnection conn) {
1019        mConnections.remove(conn);
1020        // If not emergency call is remaining, notify emergency call registrants
1021        if (mIsInEmergencyCall) {
1022            boolean isEmergencyCallInList = false;
1023            // if no emergency calls pending, set this to false
1024            for (ImsPhoneConnection imsPhoneConnection : mConnections) {
1025                if (imsPhoneConnection != null && imsPhoneConnection.isEmergency() == true) {
1026                    isEmergencyCallInList = true;
1027                    break;
1028                }
1029            }
1030
1031            if (!isEmergencyCallInList) {
1032                mIsInEmergencyCall = false;
1033                mPhone.sendEmergencyCallStateChange(false);
1034            }
1035        }
1036    }
1037
1038    private synchronized void addConnection(ImsPhoneConnection conn) {
1039        mConnections.add(conn);
1040        if (conn.isEmergency()) {
1041            mIsInEmergencyCall = true;
1042            mPhone.sendEmergencyCallStateChange(true);
1043        }
1044    }
1045
1046    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
1047        if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
1048        // This method is called on onCallUpdate() where there is not necessarily a call state
1049        // change. In these situations, we'll ignore the state related updates and only process
1050        // the change in media capabilities (as expected).  The default is to not ignore state
1051        // changes so we do not change existing behavior.
1052        processCallStateChange(imsCall, state, cause, false /* do not ignore state update */);
1053    }
1054
1055    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause,
1056            boolean ignoreState) {
1057        if (DBG) {
1058            log("processCallStateChange state=" + state + " cause=" + cause
1059                    + " ignoreState=" + ignoreState);
1060        }
1061
1062        if (imsCall == null) return;
1063
1064        boolean changed = false;
1065        ImsPhoneConnection conn = findConnection(imsCall);
1066
1067        if (conn == null) {
1068            // TODO : what should be done?
1069            return;
1070        }
1071
1072        // processCallStateChange is triggered for onCallUpdated as well.
1073        // onCallUpdated should not modify the state of the call
1074        // It should modify only other capabilities of call through updateMediaCapabilities
1075        // State updates will be triggered through individual callbacks
1076        // i.e. onCallHeld, onCallResume, etc and conn.update will be responsible for the update
1077        conn.updateMediaCapabilities(imsCall);
1078        if (ignoreState) {
1079            conn.updateAddressDisplay(imsCall);
1080            conn.updateExtras(imsCall);
1081            return;
1082        }
1083
1084        changed = conn.update(imsCall, state);
1085        if (state == ImsPhoneCall.State.DISCONNECTED) {
1086            changed = conn.onDisconnect(cause) || changed;
1087            //detach the disconnected connections
1088            conn.getCall().detach(conn);
1089            removeConnection(conn);
1090        }
1091
1092        if (changed) {
1093            if (conn.getCall() == mHandoverCall) return;
1094            updatePhoneState();
1095            mPhone.notifyPreciseCallStateChanged();
1096        }
1097    }
1098
1099    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
1100        int cause = DisconnectCause.ERROR_UNSPECIFIED;
1101
1102        //int type = reasonInfo.getReasonType();
1103        int code = reasonInfo.getCode();
1104        switch (code) {
1105            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
1106            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
1107                return DisconnectCause.NUMBER_UNREACHABLE;
1108
1109            case ImsReasonInfo.CODE_SIP_BUSY:
1110                return DisconnectCause.BUSY;
1111
1112            case ImsReasonInfo.CODE_USER_TERMINATED:
1113                return DisconnectCause.LOCAL;
1114
1115            case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
1116                return DisconnectCause.INCOMING_REJECTED;
1117
1118            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
1119                return DisconnectCause.NORMAL;
1120
1121            case ImsReasonInfo.CODE_SIP_REDIRECTED:
1122            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
1123            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
1124            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
1125            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
1126            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
1127                return DisconnectCause.SERVER_ERROR;
1128
1129            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
1130            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
1131            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
1132                return DisconnectCause.SERVER_UNREACHABLE;
1133
1134            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
1135            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
1136            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
1137            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
1138            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
1139            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
1140            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
1141            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
1142                return DisconnectCause.OUT_OF_SERVICE;
1143
1144            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
1145            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
1146            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
1147            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
1148                return DisconnectCause.TIMED_OUT;
1149
1150            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
1151            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
1152                return DisconnectCause.POWER_OFF;
1153
1154            case ImsReasonInfo.CODE_FDN_BLOCKED:
1155                return DisconnectCause.FDN_BLOCKED;
1156            default:
1157        }
1158
1159        return cause;
1160    }
1161
1162    /**
1163     * @return true if the phone is in Emergency Callback mode, otherwise false
1164     */
1165    private boolean isPhoneInEcbMode() {
1166        return SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false);
1167    }
1168
1169    /**
1170     * Before dialing pending MO request, check for the Emergency Callback mode.
1171     * If device is in Emergency callback mode, then exit the mode before dialing pending MO.
1172     */
1173    private void dialPendingMO() {
1174        boolean isPhoneInEcmMode = isPhoneInEcbMode();
1175        boolean isEmergencyNumber = mPendingMO.isEmergency();
1176        if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
1177            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1178        } else {
1179            sendEmptyMessage(EVENT_EXIT_ECBM_BEFORE_PENDINGMO);
1180        }
1181    }
1182
1183    /**
1184     * Listen to the IMS call state change
1185     */
1186    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
1187        @Override
1188        public void onCallProgressing(ImsCall imsCall) {
1189            if (DBG) log("onCallProgressing");
1190
1191            mPendingMO = null;
1192            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
1193                    DisconnectCause.NOT_DISCONNECTED);
1194            mEventLog.writeOnImsCallProgressing(imsCall.getCallSession());
1195        }
1196
1197        @Override
1198        public void onCallStarted(ImsCall imsCall) {
1199            if (DBG) log("onCallStarted");
1200
1201            mPendingMO = null;
1202            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1203                    DisconnectCause.NOT_DISCONNECTED);
1204            mEventLog.writeOnImsCallStarted(imsCall.getCallSession());
1205        }
1206
1207        @Override
1208        public void onCallUpdated(ImsCall imsCall) {
1209            if (DBG) log("onCallUpdated");
1210            if (imsCall == null) {
1211                return;
1212            }
1213            ImsPhoneConnection conn = findConnection(imsCall);
1214            if (conn != null) {
1215                processCallStateChange(imsCall, conn.getCall().mState,
1216                        DisconnectCause.NOT_DISCONNECTED, true /*ignore state update*/);
1217                mEventLog.writeImsCallState(imsCall.getCallSession(), conn.getCall().mState);
1218            }
1219        }
1220
1221        /**
1222         * onCallStartFailed will be invoked when:
1223         * case 1) Dialing fails
1224         * case 2) Ringing call is disconnected by local or remote user
1225         */
1226        @Override
1227        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1228            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
1229
1230            if (mPendingMO != null) {
1231                // To initiate dialing circuit-switched call
1232                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
1233                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
1234                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
1235                    mForegroundCall.detach(mPendingMO);
1236                    removeConnection(mPendingMO);
1237                    mPendingMO.finalize();
1238                    mPendingMO = null;
1239                    mPhone.initiateSilentRedial();
1240                    return;
1241                } else {
1242                    mPendingMO = null;
1243                    int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1244                    processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1245                }
1246                mEventLog.writeOnImsCallStartFailed(imsCall.getCallSession(), reasonInfo);
1247            }
1248        }
1249
1250        @Override
1251        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1252            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
1253
1254            ImsPhoneCall.State oldState = mForegroundCall.getState();
1255            int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
1256            ImsPhoneConnection conn = findConnection(imsCall);
1257            if (DBG) log("cause = " + cause + " conn = " + conn);
1258
1259            if (mOnHoldToneId == System.identityHashCode(conn)) {
1260                if (conn != null && mOnHoldToneStarted) {
1261                    mPhone.stopOnHoldTone(conn);
1262                }
1263                mOnHoldToneStarted = false;
1264                mOnHoldToneId = -1;
1265            }
1266            if (conn != null && conn.isIncoming() && conn.getConnectTime() == 0) {
1267                // Missed
1268                if (cause == DisconnectCause.NORMAL) {
1269                    cause = DisconnectCause.INCOMING_MISSED;
1270                } else {
1271                    cause = DisconnectCause.INCOMING_REJECTED;
1272                }
1273                if (DBG) log("Incoming connection of 0 connect time detected - translated cause = "
1274                        + cause);
1275
1276            }
1277
1278            if (cause == DisconnectCause.NORMAL && conn != null && conn.getImsCall().isMerged()) {
1279                // Call was terminated while it is merged instead of a remote disconnect.
1280                cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY;
1281            }
1282
1283            mEventLog.writeOnImsCallTerminated(imsCall.getCallSession(), reasonInfo);
1284
1285            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
1286            if (mForegroundCall.getState() != ImsPhoneCall.State.ACTIVE) {
1287                if (mRingingCall.getState().isRinging()) {
1288                    // Drop pending MO. We should address incoming call first
1289                    mPendingMO = null;
1290                } else if (mPendingMO != null) {
1291                    sendEmptyMessage(EVENT_DIAL_PENDINGMO);
1292                }
1293            }
1294
1295            if (mSwitchingFgAndBgCalls) {
1296                if (DBG) {
1297                    log("onCallTerminated: Call terminated in the midst of Switching " +
1298                            "Fg and Bg calls.");
1299                }
1300                // If we are the in midst of swapping FG and BG calls and the call that was
1301                // terminated was the one that we expected to resume, we need to swap the FG and
1302                // BG calls back.
1303                if (imsCall == mCallExpectedToResume) {
1304                    if (DBG) {
1305                        log("onCallTerminated: switching " + mForegroundCall + " with "
1306                                + mBackgroundCall);
1307                    }
1308                    mForegroundCall.switchWith(mBackgroundCall);
1309                }
1310                // This call terminated in the midst of a switch after the other call was held, so
1311                // resume it back to ACTIVE state since the switch failed.
1312                if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
1313                    sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1314                    mSwitchingFgAndBgCalls = false;
1315                    mCallExpectedToResume = null;
1316                }
1317            }
1318        }
1319
1320        @Override
1321        public void onCallHeld(ImsCall imsCall) {
1322            if (DBG) {
1323                if (mForegroundCall.getImsCall() == imsCall) {
1324                    log("onCallHeld (fg) " + imsCall);
1325                } else if (mBackgroundCall.getImsCall() == imsCall) {
1326                    log("onCallHeld (bg) " + imsCall);
1327                }
1328            }
1329
1330            synchronized (mSyncHold) {
1331                ImsPhoneCall.State oldState = mBackgroundCall.getState();
1332                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
1333                        DisconnectCause.NOT_DISCONNECTED);
1334
1335                // Note: If we're performing a switchWaitingOrHoldingAndActive, the call to
1336                // processCallStateChange above may have caused the mBackgroundCall and
1337                // mForegroundCall references below to change meaning.  Watch out for this if you
1338                // are reading through this code.
1339                if (oldState == ImsPhoneCall.State.ACTIVE) {
1340                    // Note: This case comes up when we have just held a call in response to a
1341                    // switchWaitingOrHoldingAndActive.  We now need to resume the background call.
1342                    // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called.
1343                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
1344                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
1345                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1346                    } else {
1347                        //when multiple connections belong to background call,
1348                        //only the first callback reaches here
1349                        //otherwise the oldState is already HOLDING
1350                        if (mPendingMO != null) {
1351                            dialPendingMO();
1352                        }
1353
1354                        // In this case there will be no call resumed, so we can assume that we
1355                        // are done switching fg and bg calls now.
1356                        // This may happen if there is no BG call and we are holding a call so that
1357                        // we can dial another one.
1358                        mSwitchingFgAndBgCalls = false;
1359                    }
1360                } else if (oldState == ImsPhoneCall.State.IDLE && mSwitchingFgAndBgCalls) {
1361                    // The other call terminated in the midst of a switch before this call was held,
1362                    // so resume the foreground call back to ACTIVE state since the switch failed.
1363                    if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
1364                        sendEmptyMessage(EVENT_RESUME_BACKGROUND);
1365                        mSwitchingFgAndBgCalls = false;
1366                        mCallExpectedToResume = null;
1367                    }
1368                }
1369            }
1370            mEventLog.writeOnImsCallHeld(imsCall.getCallSession());
1371        }
1372
1373        @Override
1374        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1375            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
1376
1377            synchronized (mSyncHold) {
1378                ImsPhoneCall.State bgState = mBackgroundCall.getState();
1379                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
1380                    // disconnected while processing hold
1381                    if (mPendingMO != null) {
1382                        dialPendingMO();
1383                    }
1384                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
1385                    mForegroundCall.switchWith(mBackgroundCall);
1386
1387                    if (mPendingMO != null) {
1388                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
1389                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
1390                    }
1391                }
1392                mPhone.notifySuppServiceFailed(Phone.SuppService.HOLD);
1393            }
1394            mEventLog.writeOnImsCallHoldFailed(imsCall.getCallSession(), reasonInfo);
1395        }
1396
1397        @Override
1398        public void onCallResumed(ImsCall imsCall) {
1399            if (DBG) log("onCallResumed");
1400
1401            // If we are the in midst of swapping FG and BG calls and the call we end up resuming
1402            // is not the one we expected, we likely had a resume failure and we need to swap the
1403            // FG and BG calls back.
1404            if (mSwitchingFgAndBgCalls) {
1405                if (imsCall != mCallExpectedToResume) {
1406                    // If the call which resumed isn't as expected, we need to swap back to the
1407                    // previous configuration; the swap has failed.
1408                    if (DBG) {
1409                        log("onCallResumed : switching " + mForegroundCall + " with "
1410                                + mBackgroundCall);
1411                    }
1412                    mForegroundCall.switchWith(mBackgroundCall);
1413                } else {
1414                    // The call which resumed is the one we expected to resume, so we can clear out
1415                    // the mSwitchingFgAndBgCalls flag.
1416                    if (DBG) {
1417                        log("onCallResumed : expected call resumed.");
1418                    }
1419                }
1420                mSwitchingFgAndBgCalls = false;
1421                mCallExpectedToResume = null;
1422            }
1423            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1424                    DisconnectCause.NOT_DISCONNECTED);
1425            mEventLog.writeOnImsCallResumed(imsCall.getCallSession());
1426        }
1427
1428        @Override
1429        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1430            // If we are in the midst of swapping the FG and BG calls and we got a resume fail, we
1431            // need to swap back the FG and BG calls.
1432            if (mSwitchingFgAndBgCalls && imsCall == mCallExpectedToResume) {
1433                if (DBG) {
1434                    log("onCallResumeFailed : switching " + mForegroundCall + " with "
1435                            + mBackgroundCall);
1436                }
1437                mForegroundCall.switchWith(mBackgroundCall);
1438                mCallExpectedToResume = null;
1439                mSwitchingFgAndBgCalls = false;
1440            }
1441            mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME);
1442            mEventLog.writeOnImsCallResumeFailed(imsCall.getCallSession(), reasonInfo);
1443        }
1444
1445        @Override
1446        public void onCallResumeReceived(ImsCall imsCall) {
1447            if (DBG) log("onCallResumeReceived");
1448            ImsPhoneConnection conn = findConnection(imsCall);
1449            if (conn != null && mOnHoldToneStarted) {
1450                mPhone.stopOnHoldTone(conn);
1451                mOnHoldToneStarted = false;
1452            }
1453
1454            SuppServiceNotification supp = new SuppServiceNotification();
1455            // Type of notification: 0 = MO; 1 = MT
1456            // Refer SuppServiceNotification class documentation.
1457            supp.notificationType = 1;
1458            supp.code = SuppServiceNotification.MT_CODE_CALL_RETRIEVED;
1459            mPhone.notifySuppSvcNotification(supp);
1460            mEventLog.writeOnImsCallResumeReceived(imsCall.getCallSession());
1461        }
1462
1463        @Override
1464        public void onCallHoldReceived(ImsCall imsCall) {
1465            if (DBG) log("onCallHoldReceived");
1466
1467            ImsPhoneConnection conn = findConnection(imsCall);
1468            if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
1469                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
1470                    mPhone.startOnHoldTone(conn);
1471                    mOnHoldToneStarted = true;
1472                    mOnHoldToneId = System.identityHashCode(conn);
1473                }
1474            }
1475
1476            SuppServiceNotification supp = new SuppServiceNotification();
1477            // Type of notification: 0 = MO; 1 = MT
1478            // Refer SuppServiceNotification class documentation.
1479            supp.notificationType = 1;
1480            supp.code = SuppServiceNotification.MT_CODE_CALL_ON_HOLD;
1481            mPhone.notifySuppSvcNotification(supp);
1482            mEventLog.writeOnImsCallHoldReceived(imsCall.getCallSession());
1483        }
1484
1485        @Override
1486        public void onCallSuppServiceReceived(ImsCall call,
1487                ImsSuppServiceNotification suppServiceInfo) {
1488            if (DBG) log("onCallSuppServiceReceived: suppServiceInfo=" + suppServiceInfo);
1489
1490            SuppServiceNotification supp = new SuppServiceNotification();
1491            supp.notificationType = suppServiceInfo.notificationType;
1492            supp.code = suppServiceInfo.code;
1493            supp.index = suppServiceInfo.index;
1494            supp.number = suppServiceInfo.number;
1495            supp.history = suppServiceInfo.history;
1496
1497            mPhone.notifySuppSvcNotification(supp);
1498        }
1499
1500        @Override
1501        public void onCallMerged(final ImsCall call, final ImsCall peerCall, boolean swapCalls) {
1502            if (DBG) log("onCallMerged");
1503
1504            ImsPhoneCall foregroundImsPhoneCall = findConnection(call).getCall();
1505            ImsPhoneConnection peerConnection = findConnection(peerCall);
1506            ImsPhoneCall peerImsPhoneCall = peerConnection == null ? null
1507                    : peerConnection.getCall();
1508
1509            if (swapCalls) {
1510                switchAfterConferenceSuccess();
1511            }
1512            foregroundImsPhoneCall.merge(peerImsPhoneCall, ImsPhoneCall.State.ACTIVE);
1513
1514            try {
1515                final ImsPhoneConnection conn = findConnection(call);
1516                log("onCallMerged: ImsPhoneConnection=" + conn);
1517                log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1518                setVideoCallProvider(conn, call);
1519                log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
1520            } catch (Exception e) {
1521                loge("onCallMerged: exception " + e);
1522            }
1523
1524            // After merge complete, update foreground as Active
1525            // and background call as Held, if background call exists
1526            processCallStateChange(mForegroundCall.getImsCall(), ImsPhoneCall.State.ACTIVE,
1527                    DisconnectCause.NOT_DISCONNECTED);
1528            if (peerConnection != null) {
1529                processCallStateChange(mBackgroundCall.getImsCall(), ImsPhoneCall.State.HOLDING,
1530                    DisconnectCause.NOT_DISCONNECTED);
1531            }
1532
1533            // Check if the merge was requested by an existing conference call. In that
1534            // case, no further action is required.
1535            if (!call.isMergeRequestedByConf()) {
1536                log("onCallMerged :: calling onMultipartyStateChanged()");
1537                onMultipartyStateChanged(call, true);
1538            } else {
1539                log("onCallMerged :: Merge requested by existing conference.");
1540                // Reset the flag.
1541                call.resetIsMergeRequestedByConf(false);
1542            }
1543            logState();
1544        }
1545
1546        @Override
1547        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
1548            if (DBG) log("onCallMergeFailed reasonInfo=" + reasonInfo);
1549
1550            // TODO: the call to notifySuppServiceFailed throws up the "merge failed" dialog
1551            // We should move this into the InCallService so that it is handled appropriately
1552            // based on the user facing UI.
1553            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
1554
1555            // Start plumbing this even through Telecom so other components can take
1556            // appropriate action.
1557            ImsPhoneConnection conn = findConnection(call);
1558            if (conn != null) {
1559                conn.onConferenceMergeFailed();
1560            }
1561        }
1562
1563        /**
1564         * Called when the state of IMS conference participant(s) has changed.
1565         *
1566         * @param call the call object that carries out the IMS call.
1567         * @param participants the participant(s) and their new state information.
1568         */
1569        @Override
1570        public void onConferenceParticipantsStateChanged(ImsCall call,
1571                List<ConferenceParticipant> participants) {
1572            if (DBG) log("onConferenceParticipantsStateChanged");
1573
1574            ImsPhoneConnection conn = findConnection(call);
1575            if (conn != null) {
1576                conn.updateConferenceParticipants(participants);
1577            }
1578        }
1579
1580        @Override
1581        public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
1582            mPhone.onTtyModeReceived(mode);
1583        }
1584
1585        @Override
1586        public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1587            ImsReasonInfo reasonInfo) {
1588            if (DBG) {
1589                log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
1590                    targetAccessTech + ", reasonInfo=" + reasonInfo);
1591            }
1592            mEventLog.writeOnImsCallHandover(imsCall.getCallSession(),
1593                    srcAccessTech, targetAccessTech, reasonInfo);
1594        }
1595
1596        @Override
1597        public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
1598            ImsReasonInfo reasonInfo) {
1599            if (DBG) {
1600                log("onCallHandoverFailed :: srcAccessTech=" + srcAccessTech +
1601                    ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" + reasonInfo);
1602            }
1603            mEventLog.writeOnImsCallHandoverFailed(imsCall.getCallSession(),
1604                    srcAccessTech, targetAccessTech, reasonInfo);
1605        }
1606
1607        /**
1608         * Handles a change to the multiparty state for an {@code ImsCall}.  Notifies the associated
1609         * {@link ImsPhoneConnection} of the change.
1610         *
1611         * @param imsCall The IMS call.
1612         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
1613         *      otherwise.
1614         */
1615        @Override
1616        public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
1617            if (DBG) log("onMultipartyStateChanged to " + (isMultiParty ? "Y" : "N"));
1618
1619            ImsPhoneConnection conn = findConnection(imsCall);
1620            if (conn != null) {
1621                conn.updateMultipartyState(isMultiParty);
1622            }
1623        }
1624    };
1625
1626    /**
1627     * Listen to the IMS call state change
1628     */
1629    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
1630        @Override
1631        public void onCallStarted(ImsCall imsCall) {
1632            if (DBG) log("mImsUssdListener onCallStarted");
1633
1634            if (imsCall == mUssdSession) {
1635                if (mPendingUssd != null) {
1636                    AsyncResult.forMessage(mPendingUssd);
1637                    mPendingUssd.sendToTarget();
1638                    mPendingUssd = null;
1639                }
1640            }
1641        }
1642
1643        @Override
1644        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1645            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
1646
1647            onCallTerminated(imsCall, reasonInfo);
1648        }
1649
1650        @Override
1651        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1652            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
1653
1654            if (imsCall == mUssdSession) {
1655                mUssdSession = null;
1656                if (mPendingUssd != null) {
1657                    CommandException ex =
1658                            new CommandException(CommandException.Error.GENERIC_FAILURE);
1659                    AsyncResult.forMessage(mPendingUssd, null, ex);
1660                    mPendingUssd.sendToTarget();
1661                    mPendingUssd = null;
1662                }
1663            }
1664            imsCall.close();
1665        }
1666
1667        @Override
1668        public void onCallUssdMessageReceived(ImsCall call,
1669                int mode, String ussdMessage) {
1670            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
1671
1672            int ussdMode = -1;
1673
1674            switch(mode) {
1675                case ImsCall.USSD_MODE_REQUEST:
1676                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
1677                    break;
1678
1679                case ImsCall.USSD_MODE_NOTIFY:
1680                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
1681                    break;
1682            }
1683
1684            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
1685        }
1686    };
1687
1688    /**
1689     * Listen to the IMS service state change
1690     *
1691     */
1692    private ImsConnectionStateListener mImsConnectionStateListener =
1693        new ImsConnectionStateListener() {
1694        @Override
1695        public void onImsConnected() {
1696            if (DBG) log("onImsConnected");
1697            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1698            mPhone.setImsRegistered(true);
1699            mEventLog.writeOnImsConnectionState(
1700                    TelephonyEventLog.IMS_CONNECTION_STATE_CONNECTED, null);
1701        }
1702
1703        @Override
1704        public void onImsDisconnected(ImsReasonInfo imsReasonInfo) {
1705            if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
1706            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1707            mPhone.setImsRegistered(false);
1708            mPhone.processDisconnectReason(imsReasonInfo);
1709            mEventLog.writeOnImsConnectionState(
1710                    TelephonyEventLog.IMS_CONNECTION_STATE_DISCONNECTED, imsReasonInfo);
1711        }
1712
1713        @Override
1714        public void onImsProgressing() {
1715            if (DBG) log("onImsProgressing");
1716            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1717            mPhone.setImsRegistered(false);
1718            mEventLog.writeOnImsConnectionState(
1719                    TelephonyEventLog.IMS_CONNECTION_STATE_PROGRESSING, null);
1720        }
1721
1722        @Override
1723        public void onImsResumed() {
1724            if (DBG) log("onImsResumed");
1725            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1726            mEventLog.writeOnImsConnectionState(
1727                    TelephonyEventLog.IMS_CONNECTION_STATE_RESUMED, null);
1728        }
1729
1730        @Override
1731        public void onImsSuspended() {
1732            if (DBG) log("onImsSuspended");
1733            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1734            mEventLog.writeOnImsConnectionState(
1735                    TelephonyEventLog.IMS_CONNECTION_STATE_SUSPENDED, null);
1736        }
1737
1738        @Override
1739        public void onFeatureCapabilityChanged(int serviceClass,
1740                int[] enabledFeatures, int[] disabledFeatures) {
1741            if (serviceClass == ImsServiceClass.MMTEL) {
1742                boolean tmpIsVideoCallEnabled = isVideoCallEnabled();
1743                // Check enabledFeatures to determine capabilities. We ignore disabledFeatures.
1744                StringBuilder sb;
1745                if (DBG) {
1746                    sb = new StringBuilder(120);
1747                    sb.append("onFeatureCapabilityChanged: ");
1748                }
1749                for (int  i = ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE;
1750                        i <= ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI &&
1751                        i < enabledFeatures.length; i++) {
1752                    if (enabledFeatures[i] == i) {
1753                        // If the feature is set to its own integer value it is enabled.
1754                        if (DBG) {
1755                            sb.append(mImsFeatureStrings[i]);
1756                            sb.append(":true ");
1757                        }
1758
1759                        mImsFeatureEnabled[i] = true;
1760                    } else if (enabledFeatures[i]
1761                            == ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) {
1762                        // FEATURE_TYPE_UNKNOWN indicates that a feature is disabled.
1763                        if (DBG) {
1764                            sb.append(mImsFeatureStrings[i]);
1765                            sb.append(":false ");
1766                        }
1767
1768                        mImsFeatureEnabled[i] = false;
1769                    } else {
1770                        // Feature has unknown state; it is not its own value or -1.
1771                        if (DBG) {
1772                            loge("onFeatureCapabilityChanged(" + i + ", " + mImsFeatureStrings[i]
1773                                    + "): unexpectedValue=" + enabledFeatures[i]);
1774                        }
1775                    }
1776                }
1777                if (DBG) {
1778                    log(sb.toString());
1779                }
1780                if (tmpIsVideoCallEnabled != isVideoCallEnabled()) {
1781                    mPhone.notifyForVideoCapabilityChanged(isVideoCallEnabled());
1782                }
1783
1784                // TODO: Use the ImsCallSession or ImsCallProfile to tell the initial Wifi state and
1785                // {@link ImsCallSession.Listener#callSessionHandover} to listen for changes to
1786                // wifi capability caused by a handover.
1787                if (DBG) log("onFeatureCapabilityChanged: isVolteEnabled=" + isVolteEnabled()
1788                            + ", isVideoCallEnabled=" + isVideoCallEnabled()
1789                            + ", isVowifiEnabled=" + isVowifiEnabled()
1790                            + ", isUtEnabled=" + isUtEnabled());
1791                for (ImsPhoneConnection connection : mConnections) {
1792                    connection.updateWifiState();
1793                }
1794
1795                mPhone.onFeatureCapabilityChanged();
1796
1797                mEventLog.writeOnImsCapabilities(mImsFeatureEnabled);
1798            }
1799        }
1800
1801        @Override
1802        public void onVoiceMessageCountChanged(int count) {
1803            if (DBG) log("onVoiceMessageCountChanged :: count=" + count);
1804            mPhone.mDefaultPhone.setVoiceMessageCount(count);
1805        }
1806    };
1807
1808    private ImsConfigListener.Stub mImsConfigListener = new ImsConfigListener.Stub() {
1809        @Override
1810        public void onGetFeatureResponse(int feature, int network, int value, int status) {}
1811
1812        @Override
1813        public void onSetFeatureResponse(int feature, int network, int value, int status) {
1814            mEventLog.writeImsSetFeatureValue(feature, network, value, status);
1815        }
1816
1817        @Override
1818        public void onGetVideoQuality(int status, int quality) {}
1819
1820        @Override
1821        public void onSetVideoQuality(int status) {}
1822
1823    };
1824
1825    public ImsUtInterface getUtInterface() throws ImsException {
1826        if (mImsManager == null) {
1827            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1828        }
1829
1830        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
1831        return ut;
1832    }
1833
1834    private void transferHandoverConnections(ImsPhoneCall call) {
1835        if (call.mConnections != null) {
1836            for (Connection c : call.mConnections) {
1837                c.mPreHandoverState = call.mState;
1838                log ("Connection state before handover is " + c.getStateBeforeHandover());
1839            }
1840        }
1841        if (mHandoverCall.mConnections == null ) {
1842            mHandoverCall.mConnections = call.mConnections;
1843        } else { // Multi-call SRVCC
1844            mHandoverCall.mConnections.addAll(call.mConnections);
1845        }
1846        if (mHandoverCall.mConnections != null) {
1847            if (call.getImsCall() != null) {
1848                call.getImsCall().close();
1849            }
1850            for (Connection c : mHandoverCall.mConnections) {
1851                ((ImsPhoneConnection)c).changeParent(mHandoverCall);
1852                ((ImsPhoneConnection)c).releaseWakeLock();
1853            }
1854        }
1855        if (call.getState().isAlive()) {
1856            log ("Call is alive and state is " + call.mState);
1857            mHandoverCall.mState = call.mState;
1858        }
1859        call.mConnections.clear();
1860        call.mState = ImsPhoneCall.State.IDLE;
1861    }
1862
1863    /* package */
1864    void notifySrvccState(Call.SrvccState state) {
1865        if (DBG) log("notifySrvccState state=" + state);
1866
1867        mSrvccState = state;
1868
1869        if (mSrvccState == Call.SrvccState.COMPLETED) {
1870            transferHandoverConnections(mForegroundCall);
1871            transferHandoverConnections(mBackgroundCall);
1872            transferHandoverConnections(mRingingCall);
1873        }
1874    }
1875
1876    //****** Overridden from Handler
1877
1878    @Override
1879    public void
1880    handleMessage (Message msg) {
1881        AsyncResult ar;
1882        if (DBG) log("handleMessage what=" + msg.what);
1883
1884        switch (msg.what) {
1885            case EVENT_HANGUP_PENDINGMO:
1886                if (mPendingMO != null) {
1887                    mPendingMO.onDisconnect();
1888                    removeConnection(mPendingMO);
1889                    mPendingMO = null;
1890                }
1891                mPendingIntentExtras = null;
1892                updatePhoneState();
1893                mPhone.notifyPreciseCallStateChanged();
1894                break;
1895            case EVENT_RESUME_BACKGROUND:
1896                try {
1897                    resumeWaitingOrHolding();
1898                } catch (CallStateException e) {
1899                    if (Phone.DEBUG_PHONE) {
1900                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
1901                    }
1902                }
1903                break;
1904            case EVENT_DIAL_PENDINGMO:
1905                dialInternal(mPendingMO, mClirMode, mPendingCallVideoState, mPendingIntentExtras);
1906                mPendingIntentExtras = null;
1907                break;
1908
1909            case EVENT_EXIT_ECBM_BEFORE_PENDINGMO:
1910                if (mPendingMO != null) {
1911                    //Send ECBM exit request
1912                    try {
1913                        getEcbmInterface().exitEmergencyCallbackMode();
1914                        mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
1915                        pendingCallClirMode = mClirMode;
1916                        pendingCallInEcm = true;
1917                    } catch (ImsException e) {
1918                        e.printStackTrace();
1919                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
1920                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
1921                    }
1922                }
1923                break;
1924
1925            case EVENT_EXIT_ECM_RESPONSE_CDMA:
1926                // no matter the result, we still do the same here
1927                if (pendingCallInEcm) {
1928                    dialInternal(mPendingMO, pendingCallClirMode,
1929                            mPendingCallVideoState, mPendingIntentExtras);
1930                    mPendingIntentExtras = null;
1931                    pendingCallInEcm = false;
1932                }
1933                mPhone.unsetOnEcbModeExitResponse(this);
1934                break;
1935        }
1936    }
1937
1938    @Override
1939    protected void log(String msg) {
1940        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1941    }
1942
1943    protected void loge(String msg) {
1944        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1945    }
1946
1947    /**
1948     * Logs the current state of the ImsPhoneCallTracker.  Useful for debugging issues with
1949     * call tracking.
1950     */
1951    /* package */
1952    void logState() {
1953        if (!VERBOSE_STATE_LOGGING) {
1954            return;
1955        }
1956
1957        StringBuilder sb = new StringBuilder();
1958        sb.append("Current IMS PhoneCall State:\n");
1959        sb.append(" Foreground: ");
1960        sb.append(mForegroundCall);
1961        sb.append("\n");
1962        sb.append(" Background: ");
1963        sb.append(mBackgroundCall);
1964        sb.append("\n");
1965        sb.append(" Ringing: ");
1966        sb.append(mRingingCall);
1967        sb.append("\n");
1968        sb.append(" Handover: ");
1969        sb.append(mHandoverCall);
1970        sb.append("\n");
1971        Rlog.v(LOG_TAG, sb.toString());
1972    }
1973
1974    @Override
1975    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1976        pw.println("ImsPhoneCallTracker extends:");
1977        super.dump(fd, pw, args);
1978        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
1979        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
1980        pw.println(" mRingingCall=" + mRingingCall);
1981        pw.println(" mForegroundCall=" + mForegroundCall);
1982        pw.println(" mBackgroundCall=" + mBackgroundCall);
1983        pw.println(" mHandoverCall=" + mHandoverCall);
1984        pw.println(" mPendingMO=" + mPendingMO);
1985        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
1986        pw.println(" mPhone=" + mPhone);
1987        pw.println(" mDesiredMute=" + mDesiredMute);
1988        pw.println(" mState=" + mState);
1989        for (int i = 0; i < mImsFeatureEnabled.length; i++) {
1990            pw.println(" " + mImsFeatureStrings[i] + ": "
1991                    + ((mImsFeatureEnabled[i]) ? "enabled" : "disabled"));
1992        }
1993        pw.flush();
1994        pw.println("++++++++++++++++++++++++++++++++");
1995
1996        try {
1997            if (mImsManager != null) {
1998                mImsManager.dump(fd, pw, args);
1999            }
2000        } catch (Exception e) {
2001            e.printStackTrace();
2002        }
2003
2004        if (mConnections != null && mConnections.size() > 0) {
2005            pw.println("mConnections:");
2006            for (int i = 0; i < mConnections.size(); i++) {
2007                pw.println("  [" + i + "]: " + mConnections.get(i));
2008            }
2009        }
2010    }
2011
2012    @Override
2013    protected void handlePollCalls(AsyncResult ar) {
2014    }
2015
2016    /* package */
2017    ImsEcbm getEcbmInterface() throws ImsException {
2018        if (mImsManager == null) {
2019            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
2020        }
2021
2022        ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
2023        return ecbm;
2024    }
2025
2026    public boolean isInEmergencyCall() {
2027        return mIsInEmergencyCall;
2028    }
2029
2030    public boolean isVolteEnabled() {
2031        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE];
2032    }
2033
2034    public boolean isVowifiEnabled() {
2035        return mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI];
2036    }
2037
2038    public boolean isVideoCallEnabled() {
2039        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE]
2040                || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI]);
2041    }
2042
2043    @Override
2044    public PhoneConstants.State getState() {
2045        return mState;
2046    }
2047
2048    private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
2049            throws RemoteException {
2050        IImsVideoCallProvider imsVideoCallProvider =
2051                imsCall.getCallSession().getVideoCallProvider();
2052        if (imsVideoCallProvider != null) {
2053            ImsVideoCallProviderWrapper imsVideoCallProviderWrapper =
2054                    new ImsVideoCallProviderWrapper(imsVideoCallProvider);
2055            conn.setVideoProvider(imsVideoCallProviderWrapper);
2056        }
2057    }
2058
2059    public boolean isUtEnabled() {
2060        return (mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_LTE]
2061            || mImsFeatureEnabled[ImsConfig.FeatureConstants.FEATURE_TYPE_UT_OVER_WIFI]);
2062    }
2063
2064    /**
2065     * Given a call subject, removes any characters considered by the current carrier to be
2066     * invalid, as well as escaping (using \) any characters which the carrier requires to be
2067     * escaped.
2068     *
2069     * @param callSubject The call subject.
2070     * @return The call subject with invalid characters removed and escaping applied as required.
2071     */
2072    private String cleanseInstantLetteringMessage(String callSubject) {
2073        if (TextUtils.isEmpty(callSubject)) {
2074            return callSubject;
2075        }
2076
2077        // Get the carrier config for the current sub.
2078        CarrierConfigManager configMgr = (CarrierConfigManager)
2079                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
2080        // Bail if we can't find the carrier config service.
2081        if (configMgr == null) {
2082            return callSubject;
2083        }
2084
2085        PersistableBundle carrierConfig = configMgr.getConfigForSubId(mPhone.getSubId());
2086        // Bail if no carrier config found.
2087        if (carrierConfig == null) {
2088            return callSubject;
2089        }
2090
2091        // Try to replace invalid characters
2092        String invalidCharacters = carrierConfig.getString(
2093                CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING);
2094        if (!TextUtils.isEmpty(invalidCharacters)) {
2095            callSubject = callSubject.replaceAll(invalidCharacters, "");
2096        }
2097
2098        // Try to escape characters which need to be escaped.
2099        String escapedCharacters = carrierConfig.getString(
2100                CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING);
2101        if (!TextUtils.isEmpty(escapedCharacters)) {
2102            callSubject = escapeChars(escapedCharacters, callSubject);
2103        }
2104        return callSubject;
2105    }
2106
2107    /**
2108     * Given a source string, return a string where a set of characters are escaped using the
2109     * backslash character.
2110     *
2111     * @param toEscape The characters to escape with a backslash.
2112     * @param source The source string.
2113     * @return The source string with characters escaped.
2114     */
2115    private String escapeChars(String toEscape, String source) {
2116        StringBuilder escaped = new StringBuilder();
2117        for (char c : source.toCharArray()) {
2118            if (toEscape.contains(Character.toString(c))) {
2119                escaped.append("\\");
2120            }
2121            escaped.append(c);
2122        }
2123
2124        return escaped.toString();
2125    }
2126}
2127