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