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