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