ImsPhoneCallTracker.java revision 64e62340aae85179a6468ccac4a401900eb4dc2f
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 = ImsCallProfile.getCallTypeFromVideoState(videoState);
334        //TODO(vt): Is this sufficient?  At what point do we know the video state of the call?
335        conn.setVideoState(videoState);
336
337        try {
338            String[] callees = new String[] { conn.getAddress() };
339            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
340                    serviceType, callType);
341            profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
342
343            ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
344                    callees, mImsCallListener);
345            conn.setImsCall(imsCall);
346        } catch (ImsException e) {
347            loge("dialInternal : " + e);
348            conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
349            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
350        }
351    }
352
353    void
354    acceptCall () throws CallStateException {
355        if (DBG) log("acceptCall");
356
357        if (mForegroundCall.getState().isAlive()
358                && mBackgroundCall.getState().isAlive()) {
359            throw new CallStateException("cannot accept call");
360        }
361
362        if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING)
363                && mForegroundCall.getState().isAlive()) {
364            setMute(false);
365            switchWaitingOrHoldingAndActive();
366        } else if (mRingingCall.getState().isRinging()) {
367            if (DBG) log("acceptCall: incoming...");
368            // Always unmute when answering a new call
369            setMute(false);
370            try {
371                ImsCall imsCall = mRingingCall.getImsCall();
372                if (imsCall != null) {
373                    imsCall.accept();
374                } else {
375                    throw new CallStateException("no valid ims call");
376                }
377            } catch (ImsException e) {
378                throw new CallStateException("cannot accept call");
379            }
380        } else {
381            throw new CallStateException("phone not ringing");
382        }
383    }
384
385    void
386    rejectCall () throws CallStateException {
387        if (DBG) log("rejectCall");
388
389        if (mRingingCall.getState().isRinging()) {
390            hangup(mRingingCall);
391        } else {
392            throw new CallStateException("phone not ringing");
393        }
394    }
395
396    void
397    switchWaitingOrHoldingAndActive() throws CallStateException {
398        if (DBG) log("switchWaitingOrHoldingAndActive");
399
400        if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
401            throw new CallStateException("cannot be in the incoming state");
402        }
403
404        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
405            ImsCall imsCall = mForegroundCall.getImsCall();
406            if (imsCall == null) {
407                throw new CallStateException("no ims call");
408            }
409
410            mForegroundCall.switchWith(mBackgroundCall);
411
412            try {
413                imsCall.hold();
414            } catch (ImsException e) {
415                mForegroundCall.switchWith(mBackgroundCall);
416                throw new CallStateException(e.getMessage());
417            }
418        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
419            resumeWaitingOrHolding();
420        }
421    }
422
423    void
424    conference() {
425        if (DBG) log("conference");
426
427        ImsCall fgImsCall = mForegroundCall.getImsCall();
428        if (fgImsCall == null) {
429            log("conference no foreground ims call");
430            return;
431        }
432
433        ImsCall bgImsCall = mBackgroundCall.getImsCall();
434        if (bgImsCall == null) {
435            log("conference no background ims call");
436            return;
437        }
438
439        try {
440            fgImsCall.merge(bgImsCall);
441        } catch (ImsException e) {
442            log("conference " + e.getMessage());
443        }
444    }
445
446    void
447    explicitCallTransfer() {
448        //TODO : implement
449    }
450
451    void
452    clearDisconnected() {
453        if (DBG) log("clearDisconnected");
454
455        internalClearDisconnected();
456
457        updatePhoneState();
458        mPhone.notifyPreciseCallStateChanged();
459    }
460
461    boolean
462    canConference() {
463        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
464            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
465            && !mBackgroundCall.isFull()
466            && !mForegroundCall.isFull();
467    }
468
469    boolean
470    canDial() {
471        boolean ret;
472        int serviceState = mPhone.getServiceState().getState();
473        String disableCall = SystemProperties.get(
474                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
475
476        ret = (serviceState != ServiceState.STATE_POWER_OFF)
477            && mPendingMO == null
478            && !mRingingCall.isRinging()
479            && !disableCall.equals("true")
480            && (!mForegroundCall.getState().isAlive()
481                    || !mBackgroundCall.getState().isAlive());
482
483        return ret;
484    }
485
486    boolean
487    canTransfer() {
488        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
489            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
490    }
491
492    //***** Private Instance Methods
493
494    private void
495    internalClearDisconnected() {
496        mRingingCall.clearDisconnected();
497        mForegroundCall.clearDisconnected();
498        mBackgroundCall.clearDisconnected();
499        mHandoverCall.clearDisconnected();
500    }
501
502    private void
503    updatePhoneState() {
504        PhoneConstants.State oldState = mState;
505
506        if (mRingingCall.isRinging()) {
507            mState = PhoneConstants.State.RINGING;
508        } else if (mPendingMO != null ||
509                !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
510            mState = PhoneConstants.State.OFFHOOK;
511        } else {
512            mState = PhoneConstants.State.IDLE;
513        }
514
515        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
516            mVoiceCallEndedRegistrants.notifyRegistrants(
517                    new AsyncResult(null, null, null));
518        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
519            mVoiceCallStartedRegistrants.notifyRegistrants (
520                    new AsyncResult(null, null, null));
521        }
522
523        if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
524
525        if (mState != oldState) {
526            mPhone.notifyPhoneStateChanged();
527        }
528    }
529
530    private void
531    handleRadioNotAvailable() {
532        // handlePollCalls will clear out its
533        // call list when it gets the CommandException
534        // error result from this
535        pollCallsWhenSafe();
536    }
537
538    private void
539    dumpState() {
540        List l;
541
542        log("Phone State:" + mState);
543
544        log("Ringing call: " + mRingingCall.toString());
545
546        l = mRingingCall.getConnections();
547        for (int i = 0, s = l.size(); i < s; i++) {
548            log(l.get(i).toString());
549        }
550
551        log("Foreground call: " + mForegroundCall.toString());
552
553        l = mForegroundCall.getConnections();
554        for (int i = 0, s = l.size(); i < s; i++) {
555            log(l.get(i).toString());
556        }
557
558        log("Background call: " + mBackgroundCall.toString());
559
560        l = mBackgroundCall.getConnections();
561        for (int i = 0, s = l.size(); i < s; i++) {
562            log(l.get(i).toString());
563        }
564
565    }
566
567    //***** Called from ImsPhone
568
569    /*package*/ void
570    setMute(boolean mute) {
571        mDesiredMute = mute;
572        mForegroundCall.setMute(mute);
573    }
574
575    /*package*/ boolean
576    getMute() {
577        return mDesiredMute;
578    }
579
580    /*package*/ void
581    sendDtmf(char c) {
582        if (DBG) log("sendDtmf");
583
584        ImsCall imscall = mForegroundCall.getImsCall();
585        if (imscall != null) {
586            imscall.sendDtmf(convertDtmf(c));
587        }
588    }
589
590    private int convertDtmf(char c) {
591        int code = c - '0';
592        if ((code < 0) || (code > 9)) {
593            switch (c) {
594                case '*': return 10;
595                case '#': return 11;
596                case 'A': return 12;
597                case 'B': return 13;
598                case 'C': return 14;
599                case 'D': return 15;
600                default:
601                    throw new IllegalArgumentException(
602                            "invalid DTMF char: " + (int) c);
603            }
604        }
605        return code;
606    }
607
608    //***** Called from ImsPhoneConnection
609
610    /*package*/ void
611    hangup (ImsPhoneConnection conn) throws CallStateException {
612        if (DBG) log("hangup connection");
613
614        if (conn.getOwner() != this) {
615            throw new CallStateException ("ImsPhoneConnection " + conn
616                    + "does not belong to ImsPhoneCallTracker " + this);
617        }
618
619        hangup(conn.getCall());
620    }
621
622    //***** Called from ImsPhoneCall
623
624    /* package */ void
625    hangup (ImsPhoneCall call) throws CallStateException {
626        if (DBG) log("hangup call");
627
628        if (call.getConnections().size() == 0) {
629            throw new CallStateException("no connections");
630        }
631
632        ImsCall imsCall = call.getImsCall();
633        boolean rejectCall = false;
634
635        if (call == mRingingCall) {
636            if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
637            rejectCall = true;
638        } else if (call == mForegroundCall) {
639            if (call.isDialingOrAlerting()) {
640                if (Phone.DEBUG_PHONE) {
641                    log("(foregnd) hangup dialing or alerting...");
642                }
643            } else {
644                if (Phone.DEBUG_PHONE) {
645                    log("(foregnd) hangup foreground");
646                }
647                //held call will be resumed by onCallTerminated
648            }
649        } else if (call == mBackgroundCall) {
650            if (Phone.DEBUG_PHONE) {
651                log("(backgnd) hangup waiting or background");
652            }
653        } else {
654            throw new RuntimeException ("ImsPhoneCall " + call +
655                    "does not belong to ImsPhoneCallTracker " + this);
656        }
657
658        call.onHangupLocal();
659
660        try {
661            if (imsCall != null) {
662                if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
663                else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
664            } else if (mPendingMO != null && call == mForegroundCall) {
665                // is holding a foreground call
666                mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
667                mPendingMO.onDisconnect();
668                removeConnection(mPendingMO);
669                mPendingMO = null;
670                updatePhoneState();
671                removeMessages(EVENT_DIAL_PENDINGMO);
672            }
673        } catch (ImsException e) {
674            throw new CallStateException(e.getMessage());
675        }
676
677        mPhone.notifyPreciseCallStateChanged();
678    }
679
680    /* package */
681    void resumeWaitingOrHolding() throws CallStateException {
682        if (DBG) log("resumeWaitingOrHolding");
683
684        try {
685            if (mForegroundCall.getState().isAlive()) {
686                //resume foreground call after holding background call
687                //they were switched before holding
688                ImsCall imsCall = mForegroundCall.getImsCall();
689                if (imsCall != null) imsCall.resume();
690            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
691                //accept waiting call after holding background call
692                ImsCall imsCall = mRingingCall.getImsCall();
693                if (imsCall != null) imsCall.accept();
694            } else {
695                //Just resume background call.
696                //To distinguish resuming call with swapping calls
697                //we do not switch calls.here
698                //ImsPhoneConnection.update will chnage the parent when completed
699                ImsCall imsCall = mBackgroundCall.getImsCall();
700                if (imsCall != null) imsCall.resume();
701            }
702        } catch (ImsException e) {
703            throw new CallStateException(e.getMessage());
704        }
705    }
706
707    /* package */
708    void sendUSSD (String ussdString, Message response) {
709        if (DBG) log("sendUSSD");
710
711        try {
712            if (mUssdSession != null) {
713                mUssdSession.sendUssd(ussdString);
714                AsyncResult.forMessage(response, null, null);
715                response.sendToTarget();
716                return;
717            }
718
719            String[] callees = new String[] { ussdString };
720            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
721                    ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
722            profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
723                    ImsCallProfile.DIALSTRING_USSD);
724
725            mUssdSession = mImsManager.makeCall(mServiceId, profile,
726                    callees, mImsUssdListener);
727        } catch (ImsException e) {
728            loge("sendUSSD : " + e);
729            mPhone.sendErrorResponse(response, e);
730        }
731    }
732
733    /* package */
734    void cancelUSSD() {
735        if (mUssdSession == null) return;
736
737        try {
738            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
739        } catch (ImsException e) {
740        }
741
742    }
743
744    private synchronized ImsPhoneConnection findConnection(ImsCall imsCall) {
745        for (ImsPhoneConnection conn : mConnections) {
746            if (conn.getImsCall() == imsCall) {
747                return conn;
748            }
749        }
750        return null;
751    }
752
753    private synchronized void removeConnection(ImsPhoneConnection conn) {
754        mConnections.remove(conn);
755    }
756
757    private synchronized void addConnection(ImsPhoneConnection conn) {
758        mConnections.add(conn);
759    }
760
761    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
762        if (DBG) log("processCallStateChange state=" + state + " cause=" + cause);
763
764        if (imsCall == null) return;
765
766        boolean changed = false;
767        ImsPhoneConnection conn = findConnection(imsCall);
768
769        if (conn == null) {
770            // TODO : what should be done?
771            return;
772        }
773
774        changed = conn.update(imsCall, state);
775
776        if (state == ImsPhoneCall.State.DISCONNECTED) {
777            changed = conn.onDisconnect(cause) || changed;
778            removeConnection(conn);
779        }
780
781        if (changed) {
782            if (conn.getCall() == mHandoverCall) return;
783            updatePhoneState();
784            mPhone.notifyPreciseCallStateChanged();
785        }
786    }
787
788    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
789        int cause = DisconnectCause.ERROR_UNSPECIFIED;
790
791        //int type = reasonInfo.getReasonType();
792        int code = reasonInfo.getCode();
793        switch (code) {
794            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
795            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
796                return DisconnectCause.NUMBER_UNREACHABLE;
797
798            case ImsReasonInfo.CODE_SIP_BUSY:
799                return DisconnectCause.BUSY;
800
801            case ImsReasonInfo.CODE_USER_TERMINATED:
802                return DisconnectCause.LOCAL;
803
804            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
805                return DisconnectCause.NORMAL;
806
807            case ImsReasonInfo.CODE_SIP_REDIRECTED:
808            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
809            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
810            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
811            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
812            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
813                return DisconnectCause.SERVER_ERROR;
814
815            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
816            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
817            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
818                return DisconnectCause.SERVER_UNREACHABLE;
819
820            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
821            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
822            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
823            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
824            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
825            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
826            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
827            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
828                return DisconnectCause.OUT_OF_SERVICE;
829
830            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
831            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
832            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
833            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
834                return DisconnectCause.TIMED_OUT;
835
836            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
837            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
838                return DisconnectCause.POWER_OFF;
839
840            default:
841        }
842
843        return cause;
844    }
845
846    /**
847     * Listen to the IMS call state change
848     */
849    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
850        @Override
851        public void onCallProgressing(ImsCall imsCall) {
852            if (DBG) log("onCallProgressing");
853
854            mPendingMO = null;
855            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
856                    DisconnectCause.NOT_DISCONNECTED);
857        }
858
859        @Override
860        public void onCallStarted(ImsCall imsCall) {
861            if (DBG) log("onCallStarted");
862
863            mPendingMO = null;
864            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
865                    DisconnectCause.NOT_DISCONNECTED);
866        }
867
868        /**
869         * onCallStartFailed will be invoked when:
870         * case 1) Dialing fails
871         * case 2) Ringing call is disconnected by local or remote user
872         */
873        @Override
874        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
875            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
876
877            if (mPendingMO != null) {
878                // To initiate dialing circuit-switched call
879                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
880                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
881                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
882                    mForegroundCall.detach(mPendingMO);
883                    removeConnection(mPendingMO);
884                    mPendingMO.finalize();
885                    mPendingMO = null;
886                    mPhone.initiateSilentRedial();
887                    return;
888                }
889                mPendingMO = null;
890            }
891            onCallTerminated(imsCall, reasonInfo);
892        }
893
894        @Override
895        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
896            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
897
898            ImsPhoneCall.State oldState = mForegroundCall.getState();
899
900            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED,
901                    getDisconnectCauseFromReasonInfo(reasonInfo));
902
903            if (reasonInfo.getCode() == ImsReasonInfo.CODE_USER_TERMINATED) {
904                if ((oldState == ImsPhoneCall.State.DISCONNECTING)
905                        && (mForegroundCall.getState() == ImsPhoneCall.State.DISCONNECTED)
906                        && (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING)) {
907                    sendEmptyMessage(EVENT_RESUME_BACKGROUND);
908                }
909            }
910        }
911
912        @Override
913        public void onCallHeld(ImsCall imsCall) {
914            if (DBG) log("onCallHeld");
915
916            synchronized (mSyncHold) {
917                ImsPhoneCall.State oldState = mBackgroundCall.getState();
918                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
919                        DisconnectCause.NOT_DISCONNECTED);
920
921                if (oldState == ImsPhoneCall.State.ACTIVE) {
922                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
923                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
924                        sendEmptyMessage(EVENT_RESUME_BACKGROUND);
925                    } else {
926                        //when multiple connections belong to background call,
927                        //only the first callback reaches here
928                        //otherwise the oldState is already HOLDING
929                        if (mPendingMO != null) {
930                            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
931                        }
932                    }
933                }
934            }
935        }
936
937        @Override
938        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
939            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
940
941            synchronized (mSyncHold) {
942                ImsPhoneCall.State bgState = mBackgroundCall.getState();
943                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
944                    // disconnected while processing hold
945                    if (mPendingMO != null) {
946                        sendEmptyMessage(EVENT_DIAL_PENDINGMO);
947                    }
948                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
949                    mForegroundCall.switchWith(mBackgroundCall);
950
951                    if (mPendingMO != null) {
952                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
953                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
954                    }
955                }
956            }
957        }
958
959        @Override
960        public void onCallResumed(ImsCall imsCall) {
961            if (DBG) log("onCallResumed");
962
963            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
964                    DisconnectCause.NOT_DISCONNECTED);
965        }
966
967        @Override
968        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
969            // TODO : What should be done?
970        }
971
972        @Override
973        public void onCallResumeReceived(ImsCall imsCall) {
974            if (DBG) log("onCallResumeReceived");
975
976            if (mOnHoldToneStarted) {
977                mPhone.stopOnHoldTone();
978                mOnHoldToneStarted = false;
979            }
980        }
981
982        @Override
983        public void onCallHoldReceived(ImsCall imsCall) {
984            if (DBG) log("onCallHoldReceived");
985
986            ImsPhoneConnection conn = findConnection(imsCall);
987            if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
988                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
989                    mPhone.startOnHoldTone();
990                    mOnHoldToneStarted = true;
991                }
992            }
993        }
994
995        @Override
996        public void onCallMerged(ImsCall call, ImsCall newCall) {
997            if (DBG) log("onCallMerged");
998
999            mForegroundCall.merge(mBackgroundCall, mForegroundCall.getState());
1000            updatePhoneState();
1001            mPhone.notifyPreciseCallStateChanged();
1002        }
1003
1004        @Override
1005        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
1006            if (DBG) log("onCallMergeFailed reasonCode=" + reasonInfo.getCode());
1007            mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
1008        }
1009    };
1010
1011    /**
1012     * Listen to the IMS call state change
1013     */
1014    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
1015        @Override
1016        public void onCallStarted(ImsCall imsCall) {
1017            if (DBG) log("mImsUssdListener onCallStarted");
1018
1019            if (imsCall == mUssdSession) {
1020                if (mPendingUssd != null) {
1021                    AsyncResult.forMessage(mPendingUssd);
1022                    mPendingUssd.sendToTarget();
1023                    mPendingUssd = null;
1024                }
1025            }
1026        }
1027
1028        @Override
1029        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1030            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
1031
1032            onCallTerminated(imsCall, reasonInfo);
1033        }
1034
1035        @Override
1036        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
1037            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
1038
1039            if (imsCall == mUssdSession) {
1040                mUssdSession = null;
1041                if (mPendingUssd != null) {
1042                    CommandException ex =
1043                            new CommandException(CommandException.Error.GENERIC_FAILURE);
1044                    AsyncResult.forMessage(mPendingUssd, null, ex);
1045                    mPendingUssd.sendToTarget();
1046                    mPendingUssd = null;
1047                }
1048            }
1049            imsCall.close();
1050        }
1051
1052        @Override
1053        public void onCallUssdMessageReceived(ImsCall call,
1054                int mode, String ussdMessage) {
1055            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
1056
1057            int ussdMode = -1;
1058
1059            switch(mode) {
1060                case ImsCall.USSD_MODE_REQUEST:
1061                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
1062                    break;
1063
1064                case ImsCall.USSD_MODE_NOTIFY:
1065                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
1066                    break;
1067            }
1068
1069            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
1070        }
1071    };
1072
1073    /**
1074     * Listen to the IMS service state change
1075     *
1076     */
1077    private ImsConnectionStateListener mImsConnectionStateListener =
1078        new ImsConnectionStateListener() {
1079        @Override
1080        public void onImsConnected() {
1081            if (DBG) log("onImsConnected");
1082            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1083        }
1084
1085        @Override
1086        public void onImsDisconnected() {
1087            if (DBG) log("onImsDisconnected");
1088            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1089        }
1090
1091        @Override
1092        public void onImsResumed() {
1093            if (DBG) log("onImsResumed");
1094            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
1095        }
1096
1097        @Override
1098        public void onImsSuspended() {
1099            if (DBG) log("onImsSuspended");
1100            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
1101        }
1102    };
1103
1104    /* package */
1105    ImsUtInterface getUtInterface() throws ImsException {
1106        if (mImsManager == null) {
1107            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
1108        }
1109
1110        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
1111        return ut;
1112    }
1113
1114    /* package */
1115    void notifySrvccState(Call.SrvccState state) {
1116        if (DBG) log("notifySrvccState state=" + state);
1117
1118        mSrvccState = state;
1119
1120        if (mSrvccState == Call.SrvccState.COMPLETED) {
1121            if (mForegroundCall.getConnections().size() > 0) {
1122                mHandoverCall.switchWith(mForegroundCall);
1123            } else if (mBackgroundCall.getConnections().size() > 0) {
1124                mHandoverCall.switchWith(mBackgroundCall);
1125            }
1126        }
1127    }
1128
1129    //****** Overridden from Handler
1130
1131    @Override
1132    public void
1133    handleMessage (Message msg) {
1134        AsyncResult ar;
1135        if (DBG) log("handleMessage what=" + msg.what);
1136
1137        switch (msg.what) {
1138            case EVENT_HANGUP_PENDINGMO:
1139                if (mPendingMO != null) {
1140                    mPendingMO.onDisconnect();
1141                    removeConnection(mPendingMO);
1142                    mPendingMO = null;
1143                }
1144
1145                updatePhoneState();
1146                mPhone.notifyPreciseCallStateChanged();
1147                break;
1148            case EVENT_RESUME_BACKGROUND:
1149                try {
1150                    resumeWaitingOrHolding();
1151                } catch (CallStateException e) {
1152                    if (Phone.DEBUG_PHONE) {
1153                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
1154                    }
1155                }
1156                break;
1157            case EVENT_DIAL_PENDINGMO:
1158                dialInternal(mPendingMO, mClirMode, VideoCallProfile.VIDEO_STATE_AUDIO_ONLY);
1159                break;
1160        }
1161    }
1162
1163    @Override
1164    protected void log(String msg) {
1165        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1166    }
1167
1168    protected void loge(String msg) {
1169        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
1170    }
1171
1172    @Override
1173    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1174        pw.println("ImsPhoneCallTracker extends:");
1175        super.dump(fd, pw, args);
1176        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
1177        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
1178        pw.println(" mRingingCall=" + mRingingCall);
1179        pw.println(" mForegroundCall=" + mForegroundCall);
1180        pw.println(" mBackgroundCall=" + mBackgroundCall);
1181        pw.println(" mHandoverCall=" + mHandoverCall);
1182        pw.println(" mPendingMO=" + mPendingMO);
1183        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
1184        pw.println(" mPhone=" + mPhone);
1185        pw.println(" mDesiredMute=" + mDesiredMute);
1186        pw.println(" mState=" + mState);
1187    }
1188
1189    @Override
1190    protected void handlePollCalls(AsyncResult ar) {
1191    }
1192}
1193