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