ImsPhoneCallTracker.java revision 04e36a78936967df68d9175ac09dd3f087727888
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.Handler;
32import android.os.Message;
33import android.os.Registrant;
34import android.os.RegistrantList;
35import android.os.SystemProperties;
36import android.preference.PreferenceManager;
37import android.telecomm.VideoCallProfile;
38import android.telephony.DisconnectCause;
39import android.telephony.PhoneNumberUtils;
40import android.telephony.Rlog;
41import android.telephony.ServiceState;
42
43import com.android.ims.ImsCall;
44import com.android.ims.ImsCallProfile;
45import com.android.ims.ImsConnectionStateListener;
46import com.android.ims.ImsEcbm;
47import com.android.ims.ImsException;
48import com.android.ims.ImsManager;
49import com.android.ims.ImsReasonInfo;
50import com.android.ims.ImsServiceClass;
51import com.android.ims.ImsUtInterface;
52import com.android.internal.telephony.Call;
53import com.android.internal.telephony.CallStateException;
54import com.android.internal.telephony.CallTracker;
55import com.android.internal.telephony.CommandException;
56import com.android.internal.telephony.CommandsInterface;
57import com.android.internal.telephony.Connection;
58import com.android.internal.telephony.Phone;
59import com.android.internal.telephony.PhoneBase;
60import com.android.internal.telephony.PhoneConstants;
61import com.android.internal.telephony.TelephonyProperties;
62
63/**
64 * {@hide}
65 */
66public final class ImsPhoneCallTracker extends CallTracker {
67    static final String LOG_TAG = "ImsPhoneCallTracker";
68
69    private static final boolean DBG = true;
70
71    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
72        @Override
73        public void onReceive(Context context, Intent intent) {
74            if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) {
75                if (DBG) log("onReceive : incoming call intent");
76
77                if (mImsManager == null) return;
78
79                if (mServiceId < 0) return;
80
81                try {
82                    // Network initiated USSD will be treated by mImsUssdListener
83                    boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false);
84                    if (isUssd) {
85                        if (DBG) log("onReceive : USSD");
86                        mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener);
87                        if (mUssdSession != null) {
88                            mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE);
89                        }
90                        return;
91                    }
92
93                    // Normal MT call
94                    ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener);
95
96                    ImsPhoneConnection conn = new ImsPhoneConnection(mPhone.getContext(), imsCall,
97                            ImsPhoneCallTracker.this, mRingingCall);
98                    addConnection(conn);
99
100                    if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) ||
101                            (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) {
102                        conn.update(imsCall, ImsPhoneCall.State.WAITING);
103                    }
104
105                    mPhone.notifyNewRingingConnection(conn);
106                    mPhone.notifyIncomingRing();
107
108                    updatePhoneState();
109                    mPhone.notifyPreciseCallStateChanged();
110                } catch (ImsException e) {
111                    loge("onReceive : exception " + e);
112                }
113            }
114        }
115    };
116
117    //***** Constants
118
119    static final int MAX_CONNECTIONS = 7;
120    static final int MAX_CONNECTIONS_PER_CALL = 5;
121
122    private static final int EVENT_HANGUP_PENDINGMO = 18;
123    private static final int EVENT_RESUME_BACKGROUND = 19;
124    private static final int EVENT_DIAL_PENDINGMO = 20;
125
126    private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
127
128    //***** Instance Variables
129    private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>();
130    private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
131    private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
132
133    ImsPhoneCall mRingingCall = new ImsPhoneCall(this);
134    ImsPhoneCall mForegroundCall = new ImsPhoneCall(this);
135    ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this);
136    ImsPhoneCall mHandoverCall = new ImsPhoneCall(this);
137
138    private ImsPhoneConnection mPendingMO;
139    private int mClirMode = CommandsInterface.CLIR_DEFAULT;
140    private Object mSyncHold = new Object();
141
142    private ImsCall mUssdSession = null;
143    private Message mPendingUssd = null;
144
145    ImsPhone mPhone;
146
147    private boolean mDesiredMute = false;    // false = mute off
148    private boolean mOnHoldToneStarted = false;
149
150    PhoneConstants.State mState = PhoneConstants.State.IDLE;
151
152    private ImsManager mImsManager;
153    private int mServiceId = -1;
154
155    private Call.SrvccState mSrvccState = Call.SrvccState.NONE;
156
157    private boolean mIsInEmergencyCall = false;
158
159    private int pendingCallClirMode;
160    private int pendingCallVideoState;
161    private boolean pendingCallInEcm = false;
162
163    //***** Events
164
165
166    //***** Constructors
167
168    ImsPhoneCallTracker(ImsPhone phone) {
169        this.mPhone = phone;
170
171        IntentFilter intentfilter = new IntentFilter();
172        intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL);
173        mPhone.getContext().registerReceiver(mReceiver, intentfilter);
174
175        Thread t = new Thread() {
176            public void run() {
177                getImsService();
178            }
179        };
180        t.start();
181    }
182
183    private PendingIntent createIncomingCallPendingIntent() {
184        Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
185        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
186        return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
187                PendingIntent.FLAG_UPDATE_CURRENT);
188    }
189
190    private void getImsService() {
191        if (DBG) log("getImsService");
192        mImsManager = ImsManager.getInstance(mPhone.getContext(), mPhone.getSubId());
193        try {
194            mServiceId = mImsManager.open(ImsServiceClass.MMTEL,
195                    createIncomingCallPendingIntent(),
196                    mImsConnectionStateListener);
197
198            // Get the ECBM interface and set IMSPhone's listener object for notifications
199            getEcbmInterface().setEcbmStateListener(mPhone.mImsEcbmStateListener);
200            if (mPhone.isInEcm()) {
201                // Call exit ECBM which will invoke onECBMExited
202                mPhone.exitEmergencyCallbackMode();
203            }
204        } catch (ImsException e) {
205            loge("getImsService: " + e);
206            //Leave mImsManager as null, then CallStateException will be thrown when dialing
207            mImsManager = null;
208        }
209    }
210
211    public void dispose() {
212        if (DBG) log("dispose");
213        mRingingCall.dispose();
214        mBackgroundCall.dispose();
215        mForegroundCall.dispose();
216        mHandoverCall.dispose();
217
218        clearDisconnected();
219        mPhone.getContext().unregisterReceiver(mReceiver);
220    }
221
222    @Override
223    protected void finalize() {
224        log("ImsPhoneCallTracker finalized");
225    }
226
227    //***** Instance Methods
228
229    //***** Public Methods
230    @Override
231    public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
232        Registrant r = new Registrant(h, what, obj);
233        mVoiceCallStartedRegistrants.add(r);
234    }
235
236    @Override
237    public void unregisterForVoiceCallStarted(Handler h) {
238        mVoiceCallStartedRegistrants.remove(h);
239    }
240
241    @Override
242    public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
243        Registrant r = new Registrant(h, what, obj);
244        mVoiceCallEndedRegistrants.add(r);
245    }
246
247    @Override
248    public void unregisterForVoiceCallEnded(Handler h) {
249        mVoiceCallEndedRegistrants.remove(h);
250    }
251
252    Connection
253    dial(String dialString, int videoState) throws CallStateException {
254        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
255        int oirMode = sp.getInt(PhoneBase.CLIR_KEY, CommandsInterface.CLIR_DEFAULT);
256        return dial(dialString, oirMode, videoState);
257    }
258
259    /**
260     * oirMode is one of the CLIR_ constants
261     */
262    synchronized Connection
263    dial(String dialString, int clirMode, int videoState) throws CallStateException {
264        boolean isPhoneInEcmMode = SystemProperties.getBoolean(
265                TelephonyProperties.PROPERTY_INECM_MODE, false);
266        boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString);
267
268        if (DBG) log("dial clirMode=" + clirMode);
269
270        // note that this triggers call state changed notif
271        clearDisconnected();
272
273        if (mImsManager == null) {
274            throw new CallStateException("service not available");
275        }
276
277        if (!canDial()) {
278            throw new CallStateException("cannot dial in current state");
279        }
280
281        if (isPhoneInEcmMode && isEmergencyNumber) {
282            handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER);
283        }
284
285        boolean holdBeforeDial = false;
286
287        // The new call must be assigned to the foreground call.
288        // That call must be idle, so place anything that's
289        // there on hold
290        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
291            if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
292                //we should have failed in !canDial() above before we get here
293                throw new CallStateException("cannot dial in current state");
294            }
295            // foreground call is empty for the newly dialed connection
296            holdBeforeDial = true;
297            switchWaitingOrHoldingAndActive();
298        }
299
300        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
301        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
302
303        mClirMode = clirMode;
304
305        synchronized (mSyncHold) {
306            if (holdBeforeDial) {
307                fgState = mForegroundCall.getState();
308                bgState = mBackgroundCall.getState();
309
310                //holding foreground call failed
311                if (fgState == ImsPhoneCall.State.ACTIVE) {
312                    throw new CallStateException("cannot dial in current state");
313                }
314
315                //holding foreground call succeeded
316                if (bgState == ImsPhoneCall.State.HOLDING) {
317                    holdBeforeDial = false;
318                }
319            }
320
321            mPendingMO = new ImsPhoneConnection(mPhone.getContext(),
322                    checkForTestEmergencyNumber(dialString), this, mForegroundCall);
323        }
324        addConnection(mPendingMO);
325
326        if (!holdBeforeDial) {
327            if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
328                dialInternal(mPendingMO, clirMode, videoState);
329            } else {
330                try {
331                    getEcbmInterface().exitEmergencyCallbackMode();
332                } catch (ImsException e) {
333                    e.printStackTrace();
334                    throw new CallStateException("service not available");
335                }
336                mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
337                pendingCallClirMode = clirMode;
338                pendingCallVideoState = videoState;
339                pendingCallInEcm = true;
340            }
341        }
342
343        updatePhoneState();
344        mPhone.notifyPreciseCallStateChanged();
345
346        return mPendingMO;
347    }
348
349    private void handleEcmTimer(int action) {
350        mPhone.handleTimerInEmergencyCallbackMode(action);
351        switch (action) {
352            case ImsPhone.CANCEL_ECM_TIMER:
353                break;
354            case ImsPhone.RESTART_ECM_TIMER:
355                break;
356            default:
357                log("handleEcmTimer, unsupported action " + action);
358        }
359    }
360
361    private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState) {
362        if (conn == null) {
363            return;
364        }
365
366        if (conn.getAddress()== null || conn.getAddress().length() == 0
367                || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
368            // Phone number is invalid
369            conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER);
370            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
371            return;
372        }
373
374        // Always unmute when initiating a new call
375        setMute(false);
376        int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ?
377                ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
378        int callType = ImsCallProfile.getCallTypeFromVideoState(videoState);
379        //TODO(vt): Is this sufficient?  At what point do we know the video state of the call?
380        conn.setVideoState(videoState);
381
382        try {
383            String[] callees = new String[] { conn.getAddress() };
384            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
385                    serviceType, callType);
386            profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
387
388            ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
389                    callees, mImsCallListener);
390            conn.setImsCall(imsCall);
391        } catch (ImsException e) {
392            loge("dialInternal : " + e);
393            conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
394            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
395        }
396    }
397
398    /**
399     * Accepts a call with the specified video state.  The video state is the video state that the
400     * user has agreed upon in the InCall UI.
401     *
402     * @param videoState The video State
403     * @throws CallStateException
404     */
405    void acceptCall (int videoState) throws CallStateException {
406        if (DBG) log("acceptCall");
407
408        if (mForegroundCall.getState().isAlive()
409                && mBackgroundCall.getState().isAlive()) {
410            throw new CallStateException("cannot accept call");
411        }
412
413        if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING)
414                && mForegroundCall.getState().isAlive()) {
415            setMute(false);
416            switchWaitingOrHoldingAndActive();
417        } else if (mRingingCall.getState().isRinging()) {
418            if (DBG) log("acceptCall: incoming...");
419            // Always unmute when answering a new call
420            setMute(false);
421            try {
422                ImsCall imsCall = mRingingCall.getImsCall();
423                if (imsCall != null) {
424                    imsCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
425                } else {
426                    throw new CallStateException("no valid ims call");
427                }
428            } catch (ImsException e) {
429                throw new CallStateException("cannot accept call");
430            }
431        } else {
432            throw new CallStateException("phone not ringing");
433        }
434    }
435
436    void
437    rejectCall () throws CallStateException {
438        if (DBG) log("rejectCall");
439
440        if (mRingingCall.getState().isRinging()) {
441            hangup(mRingingCall);
442        } else {
443            throw new CallStateException("phone not ringing");
444        }
445    }
446
447    void
448    switchWaitingOrHoldingAndActive() throws CallStateException {
449        if (DBG) log("switchWaitingOrHoldingAndActive");
450
451        if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
452            throw new CallStateException("cannot be in the incoming state");
453        }
454
455        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
456            ImsCall imsCall = mForegroundCall.getImsCall();
457            if (imsCall == null) {
458                throw new CallStateException("no ims call");
459            }
460
461            mForegroundCall.switchWith(mBackgroundCall);
462
463            try {
464                imsCall.hold();
465            } catch (ImsException e) {
466                mForegroundCall.switchWith(mBackgroundCall);
467                throw new CallStateException(e.getMessage());
468            }
469        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
470            resumeWaitingOrHolding();
471        }
472    }
473
474    void
475    conference() {
476        if (DBG) log("conference");
477
478        ImsCall fgImsCall = mForegroundCall.getImsCall();
479        if (fgImsCall == null) {
480            log("conference no foreground ims call");
481            return;
482        }
483
484        ImsCall bgImsCall = mBackgroundCall.getImsCall();
485        if (bgImsCall == null) {
486            log("conference no background ims call");
487            return;
488        }
489
490        try {
491            fgImsCall.merge(bgImsCall);
492        } catch (ImsException e) {
493            log("conference " + e.getMessage());
494        }
495    }
496
497    void
498    explicitCallTransfer() {
499        //TODO : implement
500    }
501
502    void
503    clearDisconnected() {
504        if (DBG) log("clearDisconnected");
505
506        internalClearDisconnected();
507
508        updatePhoneState();
509        mPhone.notifyPreciseCallStateChanged();
510    }
511
512    boolean
513    canConference() {
514        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
515            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
516            && !mBackgroundCall.isFull()
517            && !mForegroundCall.isFull();
518    }
519
520    boolean
521    canDial() {
522        boolean ret;
523        int serviceState = mPhone.getServiceState().getState();
524        String disableCall = SystemProperties.get(
525                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
526
527        ret = (serviceState != ServiceState.STATE_POWER_OFF)
528            && mPendingMO == null
529            && !mRingingCall.isRinging()
530            && !disableCall.equals("true")
531            && (!mForegroundCall.getState().isAlive()
532                    || !mBackgroundCall.getState().isAlive());
533
534        return ret;
535    }
536
537    boolean
538    canTransfer() {
539        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
540            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
541    }
542
543    //***** Private Instance Methods
544
545    private void
546    internalClearDisconnected() {
547        mRingingCall.clearDisconnected();
548        mForegroundCall.clearDisconnected();
549        mBackgroundCall.clearDisconnected();
550        mHandoverCall.clearDisconnected();
551    }
552
553    private void
554    updatePhoneState() {
555        PhoneConstants.State oldState = mState;
556
557        if (mRingingCall.isRinging()) {
558            mState = PhoneConstants.State.RINGING;
559        } else if (mPendingMO != null ||
560                !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
561            mState = PhoneConstants.State.OFFHOOK;
562        } else {
563            mState = PhoneConstants.State.IDLE;
564        }
565
566        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
567            mVoiceCallEndedRegistrants.notifyRegistrants(
568                    new AsyncResult(null, null, null));
569        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
570            mVoiceCallStartedRegistrants.notifyRegistrants (
571                    new AsyncResult(null, null, null));
572        }
573
574        if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
575
576        if (mState != oldState) {
577            mPhone.notifyPhoneStateChanged();
578        }
579    }
580
581    private void
582    handleRadioNotAvailable() {
583        // handlePollCalls will clear out its
584        // call list when it gets the CommandException
585        // error result from this
586        pollCallsWhenSafe();
587    }
588
589    private void
590    dumpState() {
591        List l;
592
593        log("Phone State:" + mState);
594
595        log("Ringing call: " + mRingingCall.toString());
596
597        l = mRingingCall.getConnections();
598        for (int i = 0, s = l.size(); i < s; i++) {
599            log(l.get(i).toString());
600        }
601
602        log("Foreground call: " + mForegroundCall.toString());
603
604        l = mForegroundCall.getConnections();
605        for (int i = 0, s = l.size(); i < s; i++) {
606            log(l.get(i).toString());
607        }
608
609        log("Background call: " + mBackgroundCall.toString());
610
611        l = mBackgroundCall.getConnections();
612        for (int i = 0, s = l.size(); i < s; i++) {
613            log(l.get(i).toString());
614        }
615
616    }
617
618    //***** Called from ImsPhone
619
620    /*package*/ void
621    setMute(boolean mute) {
622        mDesiredMute = mute;
623        mForegroundCall.setMute(mute);
624    }
625
626    /*package*/ boolean
627    getMute() {
628        return mDesiredMute;
629    }
630
631    /*package*/ void
632    sendDtmf(char c) {
633        if (DBG) log("sendDtmf");
634
635        ImsCall imscall = mForegroundCall.getImsCall();
636        if (imscall != null) {
637            imscall.sendDtmf(convertDtmf(c));
638        }
639    }
640
641    private int convertDtmf(char c) {
642        int code = c - '0';
643        if ((code < 0) || (code > 9)) {
644            switch (c) {
645                case '*': return 10;
646                case '#': return 11;
647                case 'A': return 12;
648                case 'B': return 13;
649                case 'C': return 14;
650                case 'D': return 15;
651                default:
652                    throw new IllegalArgumentException(
653                            "invalid DTMF char: " + (int) c);
654            }
655        }
656        return code;
657    }
658
659    //***** Called from ImsPhoneConnection
660
661    /*package*/ void
662    hangup (ImsPhoneConnection conn) throws CallStateException {
663        if (DBG) log("hangup connection");
664
665        if (conn.getOwner() != this) {
666            throw new CallStateException ("ImsPhoneConnection " + conn
667                    + "does not belong to ImsPhoneCallTracker " + this);
668        }
669
670        hangup(conn.getCall());
671    }
672
673    //***** Called from ImsPhoneCall
674
675    /* package */ void
676    hangup (ImsPhoneCall call) throws CallStateException {
677        if (DBG) log("hangup call");
678
679        if (call.getConnections().size() == 0) {
680            throw new CallStateException("no connections");
681        }
682
683        ImsCall imsCall = call.getImsCall();
684        boolean rejectCall = false;
685
686        if (call == mRingingCall) {
687            if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
688            rejectCall = true;
689        } else if (call == mForegroundCall) {
690            if (call.isDialingOrAlerting()) {
691                if (Phone.DEBUG_PHONE) {
692                    log("(foregnd) hangup dialing or alerting...");
693                }
694            } else {
695                if (Phone.DEBUG_PHONE) {
696                    log("(foregnd) hangup foreground");
697                }
698                //held call will be resumed by onCallTerminated
699            }
700        } else if (call == mBackgroundCall) {
701            if (Phone.DEBUG_PHONE) {
702                log("(backgnd) hangup waiting or background");
703            }
704        } else {
705            throw new RuntimeException ("ImsPhoneCall " + call +
706                    "does not belong to ImsPhoneCallTracker " + this);
707        }
708
709        call.onHangupLocal();
710
711        try {
712            if (imsCall != null) {
713                if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
714                else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
715            } else if (mPendingMO != null && call == mForegroundCall) {
716                // is holding a foreground call
717                mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
718                mPendingMO.onDisconnect();
719                removeConnection(mPendingMO);
720                mPendingMO = null;
721                updatePhoneState();
722                removeMessages(EVENT_DIAL_PENDINGMO);
723            }
724        } catch (ImsException e) {
725            throw new CallStateException(e.getMessage());
726        }
727
728        mPhone.notifyPreciseCallStateChanged();
729    }
730
731    /* package */
732    void resumeWaitingOrHolding() throws CallStateException {
733        if (DBG) log("resumeWaitingOrHolding");
734
735        try {
736            if (mForegroundCall.getState().isAlive()) {
737                //resume foreground call after holding background call
738                //they were switched before holding
739                ImsCall imsCall = mForegroundCall.getImsCall();
740                if (imsCall != null) imsCall.resume();
741            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
742                //accept waiting call after holding background call
743                ImsCall imsCall = mRingingCall.getImsCall();
744                if (imsCall != null) imsCall.accept(ImsCallProfile.CALL_TYPE_VOICE);
745            } else {
746                //Just resume background call.
747                //To distinguish resuming call with swapping calls
748                //we do not switch calls.here
749                //ImsPhoneConnection.update will chnage the parent when completed
750                ImsCall imsCall = mBackgroundCall.getImsCall();
751                if (imsCall != null) imsCall.resume();
752            }
753        } catch (ImsException e) {
754            throw new CallStateException(e.getMessage());
755        }
756    }
757
758    /* package */
759    void sendUSSD (String ussdString, Message response) {
760        if (DBG) log("sendUSSD");
761
762        try {
763            if (mUssdSession != null) {
764                mUssdSession.sendUssd(ussdString);
765                AsyncResult.forMessage(response, null, null);
766                response.sendToTarget();
767                return;
768            }
769
770            String[] callees = new String[] { ussdString };
771            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
772                    ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
773            profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
774                    ImsCallProfile.DIALSTRING_USSD);
775
776            mUssdSession = mImsManager.makeCall(mServiceId, profile,
777                    callees, mImsUssdListener);
778        } catch (ImsException e) {
779            loge("sendUSSD : " + e);
780            mPhone.sendErrorResponse(response, e);
781        }
782    }
783
784    /* package */
785    void cancelUSSD() {
786        if (mUssdSession == null) return;
787
788        try {
789            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
790        } catch (ImsException e) {
791        }
792
793    }
794
795    private synchronized ImsPhoneConnection findConnection(ImsCall imsCall) {
796        for (ImsPhoneConnection conn : mConnections) {
797            if (conn.getImsCall() == imsCall) {
798                return conn;
799            }
800        }
801        return null;
802    }
803
804    private synchronized void removeConnection(ImsPhoneConnection conn) {
805        mConnections.remove(conn);
806    }
807
808    private synchronized void addConnection(ImsPhoneConnection conn) {
809        mConnections.add(conn);
810    }
811
812    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
813        if (DBG) log("processCallStateChange state=" + state + " cause=" + cause);
814
815        if (imsCall == null) return;
816
817        boolean changed = false;
818        ImsPhoneConnection conn = findConnection(imsCall);
819
820        if (conn == null) {
821            // TODO : what should be done?
822            return;
823        }
824
825        changed = conn.update(imsCall, state);
826
827        if (state == ImsPhoneCall.State.DISCONNECTED) {
828            changed = conn.onDisconnect(cause) || changed;
829            removeConnection(conn);
830        }
831
832        if (changed) {
833            if (conn.getCall() == mHandoverCall) return;
834            updatePhoneState();
835            mPhone.notifyPreciseCallStateChanged();
836        }
837    }
838
839    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
840        int cause = DisconnectCause.ERROR_UNSPECIFIED;
841
842        //int type = reasonInfo.getReasonType();
843        int code = reasonInfo.getCode();
844        switch (code) {
845            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
846            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
847                return DisconnectCause.NUMBER_UNREACHABLE;
848
849            case ImsReasonInfo.CODE_SIP_BUSY:
850                return DisconnectCause.BUSY;
851
852            case ImsReasonInfo.CODE_USER_TERMINATED:
853                return DisconnectCause.LOCAL;
854
855            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
856                return DisconnectCause.NORMAL;
857
858            case ImsReasonInfo.CODE_SIP_REDIRECTED:
859            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
860            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
861            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
862            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
863            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
864                return DisconnectCause.SERVER_ERROR;
865
866            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
867            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
868            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
869                return DisconnectCause.SERVER_UNREACHABLE;
870
871            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
872            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
873            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
874            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
875            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
876            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
877            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
878            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
879                return DisconnectCause.OUT_OF_SERVICE;
880
881            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
882            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
883            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
884            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
885                return DisconnectCause.TIMED_OUT;
886
887            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
888            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
889                return DisconnectCause.POWER_OFF;
890
891            default:
892        }
893
894        return cause;
895    }
896
897    /**
898     * Listen to the IMS call state change
899     */
900    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
901        @Override
902        public void onCallProgressing(ImsCall imsCall) {
903            if (DBG) log("onCallProgressing");
904
905            mPendingMO = null;
906            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
907                    DisconnectCause.NOT_DISCONNECTED);
908        }
909
910        @Override
911        public void onCallStarted(ImsCall imsCall) {
912            if (DBG) log("onCallStarted");
913
914            mPendingMO = null;
915            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
916                    DisconnectCause.NOT_DISCONNECTED);
917        }
918
919        /**
920         * onCallStartFailed will be invoked when:
921         * case 1) Dialing fails
922         * case 2) Ringing call is disconnected by local or remote user
923         */
924        @Override
925        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
926            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
927
928            if (mPendingMO != null) {
929                // To initiate dialing circuit-switched call
930                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
931                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
932                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
933                    mForegroundCall.detach(mPendingMO);
934                    removeConnection(mPendingMO);
935                    mPendingMO.finalize();
936                    mPendingMO = null;
937                    mPhone.initiateSilentRedial();
938                    return;
939                }
940                mPendingMO = null;
941            }
942            onCallTerminated(imsCall, reasonInfo);
943        }
944
945        @Override
946        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
947            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
948
949            ImsPhoneCall.State oldState = mForegroundCall.getState();
950
951            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED,
952                    getDisconnectCauseFromReasonInfo(reasonInfo));
953
954            if (reasonInfo.getCode() == ImsReasonInfo.CODE_USER_TERMINATED) {
955                if ((oldState == ImsPhoneCall.State.DISCONNECTING)
956                        && (mForegroundCall.getState() == ImsPhoneCall.State.DISCONNECTED)
957                        && (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING)) {
958                    sendEmptyMessage(EVENT_RESUME_BACKGROUND);
959                }
960            }
961        }
962
963        @Override
964        public void onCallHeld(ImsCall imsCall) {
965            if (DBG) log("onCallHeld");
966
967            synchronized (mSyncHold) {
968                ImsPhoneCall.State oldState = mBackgroundCall.getState();
969                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
970                        DisconnectCause.NOT_DISCONNECTED);
971
972                if (oldState == ImsPhoneCall.State.ACTIVE) {
973                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
974                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
975                        sendEmptyMessage(EVENT_RESUME_BACKGROUND);
976                    } else {
977                        //when multiple connections belong to background call,
978                        //only the first callback reaches here
979                        //otherwise the oldState is already HOLDING
980                        if (mPendingMO != null) {
981                            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
982                        }
983                    }
984                }
985            }
986        }
987
988        @Override
989        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
990            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
991
992            synchronized (mSyncHold) {
993                ImsPhoneCall.State bgState = mBackgroundCall.getState();
994                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
995                    // disconnected while processing hold
996                    if (mPendingMO != null) {
997                        sendEmptyMessage(EVENT_DIAL_PENDINGMO);
998                    }
999                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
1000                    mForegroundCall.switchWith(mBackgroundCall);
1001
1002                    if (mPendingMO != null) {
1003                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
1004                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
1005                    }
1006                }
1007            }
1008        }
1009
1010        @Override
1011        public void onCallResumed(ImsCall imsCall) {
1012            if (DBG) log("onCallResumed");
1013
1014            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
1015                    DisconnectCause.NOT_DISCONNECTED);
1016        }
1017
1018        @Override
1019        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1020            // TODO : What should be done?
1021        }
1022
1023        @Override
1024        public void onCallResumeReceived(ImsCall imsCall) {
1025            if (DBG) log("onCallResumeReceived");
1026
1027            if (mOnHoldToneStarted) {
1028                mPhone.stopOnHoldTone();
1029                mOnHoldToneStarted = false;
1030            }
1031        }
1032
1033        @Override
1034        public void onCallHoldReceived(ImsCall imsCall) {
1035            if (DBG) log("onCallHoldReceived");
1036
1037            ImsPhoneConnection conn = findConnection(imsCall);
1038            if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
1039                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
1040                    mPhone.startOnHoldTone();
1041                    mOnHoldToneStarted = true;
1042                }
1043            }
1044        }
1045
1046        @Override
1047        public void onCallMerged(ImsCall call, ImsCall newCall) {
1048            if (DBG) log("onCallMerged");
1049
1050            mForegroundCall.merge(mBackgroundCall, mForegroundCall.getState());
1051            updatePhoneState();
1052            mPhone.notifyPreciseCallStateChanged();
1053        }
1054
1055        @Override
1056        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
1057            if (DBG) log("onCallMergeFailed reasonCode=" + reasonInfo.getCode());
1058            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
1059        }
1060    };
1061
1062    /**
1063     * Listen to the IMS call state change
1064     */
1065    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
1066        @Override
1067        public void onCallStarted(ImsCall imsCall) {
1068            if (DBG) log("mImsUssdListener onCallStarted");
1069
1070            if (imsCall == mUssdSession) {
1071                if (mPendingUssd != null) {
1072                    AsyncResult.forMessage(mPendingUssd);
1073                    mPendingUssd.sendToTarget();
1074                    mPendingUssd = null;
1075                }
1076            }
1077        }
1078
1079        @Override
1080        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1081            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
1082
1083            onCallTerminated(imsCall, reasonInfo);
1084        }
1085
1086        @Override
1087        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1088            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
1089
1090            if (imsCall == mUssdSession) {
1091                mUssdSession = null;
1092                if (mPendingUssd != null) {
1093                    CommandException ex =
1094                            new CommandException(CommandException.Error.GENERIC_FAILURE);
1095                    AsyncResult.forMessage(mPendingUssd, null, ex);
1096                    mPendingUssd.sendToTarget();
1097                    mPendingUssd = null;
1098                }
1099            }
1100            imsCall.close();
1101        }
1102
1103        @Override
1104        public void onCallUssdMessageReceived(ImsCall call,
1105                int mode, String ussdMessage) {
1106            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
1107
1108            int ussdMode = -1;
1109
1110            switch(mode) {
1111                case ImsCall.USSD_MODE_REQUEST:
1112                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
1113                    break;
1114
1115                case ImsCall.USSD_MODE_NOTIFY:
1116                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
1117                    break;
1118            }
1119
1120            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
1121        }
1122    };
1123
1124    /**
1125     * Listen to the IMS service state change
1126     *
1127     */
1128    private ImsConnectionStateListener mImsConnectionStateListener =
1129        new ImsConnectionStateListener() {
1130        @Override
1131        public void onImsConnected() {
1132            if (DBG) log("onImsConnected");
1133            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1134        }
1135
1136        @Override
1137        public void onImsDisconnected() {
1138            if (DBG) log("onImsDisconnected");
1139            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1140        }
1141
1142        @Override
1143        public void onImsResumed() {
1144            if (DBG) log("onImsResumed");
1145            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1146        }
1147
1148        @Override
1149        public void onImsSuspended() {
1150            if (DBG) log("onImsSuspended");
1151            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1152        }
1153    };
1154
1155    /* package */
1156    ImsUtInterface getUtInterface() throws ImsException {
1157        if (mImsManager == null) {
1158            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1159        }
1160
1161        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
1162        return ut;
1163    }
1164
1165    /* package */
1166    void notifySrvccState(Call.SrvccState state) {
1167        if (DBG) log("notifySrvccState state=" + state);
1168
1169        mSrvccState = state;
1170
1171        if (mSrvccState == Call.SrvccState.COMPLETED) {
1172            if (mForegroundCall.getConnections().size() > 0) {
1173                mHandoverCall.switchWith(mForegroundCall);
1174            } else if (mBackgroundCall.getConnections().size() > 0) {
1175                mHandoverCall.switchWith(mBackgroundCall);
1176            }
1177        }
1178    }
1179
1180    //****** Overridden from Handler
1181
1182    @Override
1183    public void
1184    handleMessage (Message msg) {
1185        AsyncResult ar;
1186        if (DBG) log("handleMessage what=" + msg.what);
1187
1188        switch (msg.what) {
1189            case EVENT_HANGUP_PENDINGMO:
1190                if (mPendingMO != null) {
1191                    mPendingMO.onDisconnect();
1192                    removeConnection(mPendingMO);
1193                    mPendingMO = null;
1194                }
1195
1196                updatePhoneState();
1197                mPhone.notifyPreciseCallStateChanged();
1198                break;
1199            case EVENT_RESUME_BACKGROUND:
1200                try {
1201                    resumeWaitingOrHolding();
1202                } catch (CallStateException e) {
1203                    if (Phone.DEBUG_PHONE) {
1204                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
1205                    }
1206                }
1207                break;
1208            case EVENT_DIAL_PENDINGMO:
1209                dialInternal(mPendingMO, mClirMode, VideoCallProfile.VideoState.AUDIO_ONLY);
1210                break;
1211
1212            case EVENT_EXIT_ECM_RESPONSE_CDMA:
1213                // no matter the result, we still do the same here
1214                if (pendingCallInEcm) {
1215                    dialInternal(mPendingMO, pendingCallClirMode, pendingCallVideoState);
1216                    pendingCallInEcm = false;
1217                }
1218                mPhone.unsetOnEcbModeExitResponse(this);
1219                break;
1220        }
1221    }
1222
1223    @Override
1224    protected void log(String msg) {
1225        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1226    }
1227
1228    protected void loge(String msg) {
1229        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1230    }
1231
1232    @Override
1233    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1234        pw.println("ImsPhoneCallTracker extends:");
1235        super.dump(fd, pw, args);
1236        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
1237        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
1238        pw.println(" mRingingCall=" + mRingingCall);
1239        pw.println(" mForegroundCall=" + mForegroundCall);
1240        pw.println(" mBackgroundCall=" + mBackgroundCall);
1241        pw.println(" mHandoverCall=" + mHandoverCall);
1242        pw.println(" mPendingMO=" + mPendingMO);
1243        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
1244        pw.println(" mPhone=" + mPhone);
1245        pw.println(" mDesiredMute=" + mDesiredMute);
1246        pw.println(" mState=" + mState);
1247    }
1248
1249    @Override
1250    protected void handlePollCalls(AsyncResult ar) {
1251    }
1252
1253    /* package */
1254    ImsEcbm getEcbmInterface() throws ImsException {
1255        if (mImsManager == null) {
1256            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1257        }
1258
1259        ImsEcbm ecbm = mImsManager.getEcbmInterface(mServiceId);
1260        return ecbm;
1261    }
1262
1263    public boolean isInEmergencyCall() {
1264        return mIsInEmergencyCall;
1265    }
1266}
1267