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