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