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