1/*
2 * Copyright (C) 2006 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.cdma;
18
19import android.content.Context;
20import android.os.AsyncResult;
21import android.os.Handler;
22import android.os.Message;
23import android.os.Registrant;
24import android.os.RegistrantList;
25import android.telephony.DisconnectCause;
26import android.telephony.PhoneNumberUtils;
27import android.telephony.ServiceState;
28import android.telephony.Rlog;
29import android.telephony.TelephonyManager;
30
31import android.os.SystemProperties;
32import android.text.TextUtils;
33
34import com.android.internal.telephony.CallStateException;
35import com.android.internal.telephony.CallTracker;
36import com.android.internal.telephony.CommandsInterface;
37import com.android.internal.telephony.Connection;
38import com.android.internal.telephony.DriverCall;
39import com.android.internal.telephony.Phone;
40import com.android.internal.telephony.PhoneBase;
41import com.android.internal.telephony.PhoneConstants;
42import com.android.internal.telephony.TelephonyProperties;
43import com.android.internal.telephony.imsphone.ImsPhone;
44import com.android.internal.telephony.imsphone.ImsPhoneConnection;
45
46import java.io.FileDescriptor;
47import java.io.PrintWriter;
48import java.util.ArrayList;
49import java.util.List;
50
51
52/**
53 * {@hide}
54 */
55public final class CdmaCallTracker extends CallTracker {
56    static final String LOG_TAG = "CdmaCallTracker";
57
58    private static final boolean REPEAT_POLLING = false;
59
60    private static final boolean DBG_POLL = false;
61
62    //***** Constants
63
64    static final int MAX_CONNECTIONS = 8;
65    static final int MAX_CONNECTIONS_PER_CALL = 1; // only 1 connection allowed per call
66
67    //***** Instance Variables
68
69    CdmaConnection mConnections[] = new CdmaConnection[MAX_CONNECTIONS];
70    RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
71    RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
72    RegistrantList mCallWaitingRegistrants =  new RegistrantList();
73
74
75    // connections dropped during last poll
76    ArrayList<CdmaConnection> mDroppedDuringPoll
77        = new ArrayList<CdmaConnection>(MAX_CONNECTIONS);
78
79    CdmaCall mRingingCall = new CdmaCall(this);
80    // A call that is ringing or (call) waiting
81    CdmaCall mForegroundCall = new CdmaCall(this);
82    CdmaCall mBackgroundCall = new CdmaCall(this);
83
84    CdmaConnection mPendingMO;
85    boolean mHangupPendingMO;
86    boolean mPendingCallInEcm=false;
87    boolean mIsInEmergencyCall = false;
88    CDMAPhone mPhone;
89
90    boolean mDesiredMute = false;    // false = mute off
91
92    int mPendingCallClirMode;
93    PhoneConstants.State mState = PhoneConstants.State.IDLE;
94
95    private boolean mIsEcmTimerCanceled = false;
96
97    private int m3WayCallFlashDelay = 0;
98//    boolean needsPoll;
99
100
101
102    //***** Events
103
104    //***** Constructors
105    CdmaCallTracker(CDMAPhone phone) {
106        mPhone = phone;
107        mCi = phone.mCi;
108        mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
109        mCi.registerForOn(this, EVENT_RADIO_AVAILABLE, null);
110        mCi.registerForNotAvailable(this, EVENT_RADIO_NOT_AVAILABLE, null);
111        mCi.registerForCallWaitingInfo(this, EVENT_CALL_WAITING_INFO_CDMA, null);
112        mForegroundCall.setGeneric(false);
113    }
114
115    public void dispose() {
116        Rlog.d(LOG_TAG, "CdmaCallTracker dispose");
117        mCi.unregisterForLineControlInfo(this);
118        mCi.unregisterForCallStateChanged(this);
119        mCi.unregisterForOn(this);
120        mCi.unregisterForNotAvailable(this);
121        mCi.unregisterForCallWaitingInfo(this);
122
123        clearDisconnected();
124
125    }
126
127    @Override
128    protected void finalize() {
129        Rlog.d(LOG_TAG, "CdmaCallTracker finalized");
130    }
131
132    //***** Instance Methods
133
134    //***** Public Methods
135    @Override
136    public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
137        Registrant r = new Registrant(h, what, obj);
138        mVoiceCallStartedRegistrants.add(r);
139        // Notify if in call when registering
140        if (mState != PhoneConstants.State.IDLE) {
141            r.notifyRegistrant(new AsyncResult(null, null, null));
142        }
143    }
144    @Override
145    public void unregisterForVoiceCallStarted(Handler h) {
146        mVoiceCallStartedRegistrants.remove(h);
147    }
148
149    @Override
150    public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
151        Registrant r = new Registrant(h, what, obj);
152        mVoiceCallEndedRegistrants.add(r);
153    }
154
155    @Override
156    public void unregisterForVoiceCallEnded(Handler h) {
157        mVoiceCallEndedRegistrants.remove(h);
158    }
159
160    public void registerForCallWaiting(Handler h, int what, Object obj) {
161        Registrant r = new Registrant (h, what, obj);
162        mCallWaitingRegistrants.add(r);
163    }
164
165    public void unregisterForCallWaiting(Handler h) {
166        mCallWaitingRegistrants.remove(h);
167    }
168
169    /**
170     * clirMode is one of the CLIR_ constants
171     */
172    Connection
173    dial (String dialString, int clirMode) throws CallStateException {
174        // note that this triggers call state changed notif
175        clearDisconnected();
176
177        if (!canDial()) {
178            throw new CallStateException("cannot dial in current state");
179        }
180
181        TelephonyManager tm =
182                (TelephonyManager) mPhone.getContext().getSystemService(Context.TELEPHONY_SERVICE);
183        String origNumber = dialString;
184        String operatorIsoContry = tm.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
185        String simIsoContry = tm.getSimCountryIsoForPhone(mPhone.getPhoneId());
186        boolean internationalRoaming = !TextUtils.isEmpty(operatorIsoContry)
187                && !TextUtils.isEmpty(simIsoContry)
188                && !simIsoContry.equals(operatorIsoContry);
189        if (internationalRoaming) {
190            if ("us".equals(simIsoContry)) {
191                internationalRoaming = internationalRoaming && !"vi".equals(operatorIsoContry);
192            } else if ("vi".equals(simIsoContry)) {
193                internationalRoaming = internationalRoaming && !"us".equals(operatorIsoContry);
194            }
195        }
196        if (internationalRoaming) {
197            dialString = convertNumberIfNecessary(mPhone, dialString);
198        }
199
200        String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
201        boolean isPhoneInEcmMode = inEcm.equals("true");
202        boolean isEmergencyCall =
203                PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(), dialString);
204
205        // Cancel Ecm timer if a second emergency call is originating in Ecm mode
206        if (isPhoneInEcmMode && isEmergencyCall) {
207            handleEcmTimer(CDMAPhone.CANCEL_ECM_TIMER);
208        }
209
210        // We are initiating a call therefore even if we previously
211        // didn't know the state (i.e. Generic was true) we now know
212        // and therefore can set Generic to false.
213        mForegroundCall.setGeneric(false);
214
215        // The new call must be assigned to the foreground call.
216        // That call must be idle, so place anything that's
217        // there on hold
218        if (mForegroundCall.getState() == CdmaCall.State.ACTIVE) {
219            return dialThreeWay(dialString);
220        }
221
222        mPendingMO = new CdmaConnection(mPhone.getContext(), checkForTestEmergencyNumber(dialString),
223                this, mForegroundCall);
224        mHangupPendingMO = false;
225
226        if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0
227                || mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0 ) {
228            // Phone number is invalid
229            mPendingMO.mCause = DisconnectCause.INVALID_NUMBER;
230
231            // handlePollCalls() will notice this call not present
232            // and will mark it as dropped.
233            pollCallsWhenSafe();
234        } else {
235            // Always unmute when initiating a new call
236            setMute(false);
237
238            // Check data call
239            disableDataCallInEmergencyCall(dialString);
240
241            // In Ecm mode, if another emergency call is dialed, Ecm mode will not exit.
242            if(!isPhoneInEcmMode || (isPhoneInEcmMode && isEmergencyCall)) {
243                mCi.dial(mPendingMO.getAddress(), clirMode, obtainCompleteMessage());
244            } else {
245                mPhone.exitEmergencyCallbackMode();
246                mPhone.setOnEcbModeExitResponse(this,EVENT_EXIT_ECM_RESPONSE_CDMA, null);
247                mPendingCallClirMode=clirMode;
248                mPendingCallInEcm=true;
249            }
250        }
251
252        if (mNumberConverted) {
253            mPendingMO.setConverted(origNumber);
254            mNumberConverted = false;
255        }
256
257        updatePhoneState();
258        mPhone.notifyPreciseCallStateChanged();
259
260        return mPendingMO;
261    }
262
263
264    Connection
265    dial (String dialString) throws CallStateException {
266        return dial(dialString, CommandsInterface.CLIR_DEFAULT);
267    }
268
269    private Connection
270    dialThreeWay (String dialString) {
271        if (!mForegroundCall.isIdle()) {
272            // Check data call
273            disableDataCallInEmergencyCall(dialString);
274
275            // Attach the new connection to foregroundCall
276            mPendingMO = new CdmaConnection(mPhone.getContext(),
277                                checkForTestEmergencyNumber(dialString), this, mForegroundCall);
278            // Some network need a empty flash before sending the normal one
279            m3WayCallFlashDelay = mPhone.getContext().getResources()
280                    .getInteger(com.android.internal.R.integer.config_cdma_3waycall_flash_delay);
281            if (m3WayCallFlashDelay > 0) {
282                mCi.sendCDMAFeatureCode("", obtainMessage(EVENT_THREE_WAY_DIAL_BLANK_FLASH));
283            } else {
284                mCi.sendCDMAFeatureCode(mPendingMO.getAddress(),
285                        obtainMessage(EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA));
286            }
287            return mPendingMO;
288        }
289        return null;
290    }
291
292    void
293    acceptCall() throws CallStateException {
294        if (mRingingCall.getState() == CdmaCall.State.INCOMING) {
295            Rlog.i("phone", "acceptCall: incoming...");
296            // Always unmute when answering a new call
297            setMute(false);
298            mCi.acceptCall(obtainCompleteMessage());
299        } else if (mRingingCall.getState() == CdmaCall.State.WAITING) {
300            CdmaConnection cwConn = (CdmaConnection)(mRingingCall.getLatestConnection());
301
302            // Since there is no network response for supplimentary
303            // service for CDMA, we assume call waiting is answered.
304            // ringing Call state change to idle is in CdmaCall.detach
305            // triggered by updateParent.
306            cwConn.updateParent(mRingingCall, mForegroundCall);
307            cwConn.onConnectedInOrOut();
308            updatePhoneState();
309            switchWaitingOrHoldingAndActive();
310        } else {
311            throw new CallStateException("phone not ringing");
312        }
313    }
314
315    void
316    rejectCall () throws CallStateException {
317        // AT+CHLD=0 means "release held or UDUB"
318        // so if the phone isn't ringing, this could hang up held
319        if (mRingingCall.getState().isRinging()) {
320            mCi.rejectCall(obtainCompleteMessage());
321        } else {
322            throw new CallStateException("phone not ringing");
323        }
324    }
325
326    void
327    switchWaitingOrHoldingAndActive() throws CallStateException {
328        // Should we bother with this check?
329        if (mRingingCall.getState() == CdmaCall.State.INCOMING) {
330            throw new CallStateException("cannot be in the incoming state");
331        } else if (mForegroundCall.getConnections().size() > 1) {
332            flashAndSetGenericTrue();
333        } else {
334            // Send a flash command to CDMA network for putting the other party on hold.
335            // For CDMA networks which do not support this the user would just hear a beep
336            // from the network. For CDMA networks which do support it will put the other
337            // party on hold.
338            mCi.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT));
339        }
340    }
341
342    void
343    conference() {
344        // Should we be checking state?
345        flashAndSetGenericTrue();
346    }
347
348    void
349    explicitCallTransfer() {
350        mCi.explicitCallTransfer(obtainCompleteMessage(EVENT_ECT_RESULT));
351    }
352
353    void
354    clearDisconnected() {
355        internalClearDisconnected();
356
357        updatePhoneState();
358        mPhone.notifyPreciseCallStateChanged();
359    }
360
361    boolean
362    canConference() {
363        return mForegroundCall.getState() == CdmaCall.State.ACTIVE
364                && mBackgroundCall.getState() == CdmaCall.State.HOLDING
365                && !mBackgroundCall.isFull()
366                && !mForegroundCall.isFull();
367    }
368
369    boolean
370    canDial() {
371        boolean ret;
372        int serviceState = mPhone.getServiceState().getState();
373        String disableCall = SystemProperties.get(
374                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
375
376        ret = (serviceState != ServiceState.STATE_POWER_OFF)
377                && mPendingMO == null
378                && !mRingingCall.isRinging()
379                && !disableCall.equals("true")
380                && (!mForegroundCall.getState().isAlive()
381                    || (mForegroundCall.getState() == CdmaCall.State.ACTIVE)
382                    || !mBackgroundCall.getState().isAlive());
383
384        if (!ret) {
385            log(String.format("canDial is false\n" +
386                              "((serviceState=%d) != ServiceState.STATE_POWER_OFF)::=%s\n" +
387                              "&& pendingMO == null::=%s\n" +
388                              "&& !ringingCall.isRinging()::=%s\n" +
389                              "&& !disableCall.equals(\"true\")::=%s\n" +
390                              "&& (!foregroundCall.getState().isAlive()::=%s\n" +
391                              "   || foregroundCall.getState() == CdmaCall.State.ACTIVE::=%s\n" +
392                              "   ||!backgroundCall.getState().isAlive())::=%s)",
393                    serviceState,
394                    serviceState != ServiceState.STATE_POWER_OFF,
395                    mPendingMO == null,
396                    !mRingingCall.isRinging(),
397                    !disableCall.equals("true"),
398                    !mForegroundCall.getState().isAlive(),
399                    mForegroundCall.getState() == CdmaCall.State.ACTIVE,
400                    !mBackgroundCall.getState().isAlive()));
401        }
402        return ret;
403    }
404
405    boolean
406    canTransfer() {
407        Rlog.e(LOG_TAG, "canTransfer: not possible in CDMA");
408        return false;
409    }
410
411    //***** Private Instance Methods
412
413    private void
414    internalClearDisconnected() {
415        mRingingCall.clearDisconnected();
416        mForegroundCall.clearDisconnected();
417        mBackgroundCall.clearDisconnected();
418    }
419
420    /**
421     * Obtain a message to use for signalling "invoke getCurrentCalls() when
422     * this operation and all other pending operations are complete
423     */
424    private Message
425    obtainCompleteMessage() {
426        return obtainCompleteMessage(EVENT_OPERATION_COMPLETE);
427    }
428
429    /**
430     * Obtain a message to use for signalling "invoke getCurrentCalls() when
431     * this operation and all other pending operations are complete
432     */
433    private Message
434    obtainCompleteMessage(int what) {
435        mPendingOperations++;
436        mLastRelevantPoll = null;
437        mNeedsPoll = true;
438
439        if (DBG_POLL) log("obtainCompleteMessage: pendingOperations=" +
440                mPendingOperations + ", needsPoll=" + mNeedsPoll);
441
442        return obtainMessage(what);
443    }
444
445    private void
446    operationComplete() {
447        mPendingOperations--;
448
449        if (DBG_POLL) log("operationComplete: pendingOperations=" +
450                mPendingOperations + ", needsPoll=" + mNeedsPoll);
451
452        if (mPendingOperations == 0 && mNeedsPoll) {
453            mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
454            mCi.getCurrentCalls(mLastRelevantPoll);
455        } else if (mPendingOperations < 0) {
456            // this should never happen
457            Rlog.e(LOG_TAG,"CdmaCallTracker.pendingOperations < 0");
458            mPendingOperations = 0;
459        }
460    }
461
462
463
464    private void
465    updatePhoneState() {
466        PhoneConstants.State oldState = mState;
467
468        if (mRingingCall.isRinging()) {
469            mState = PhoneConstants.State.RINGING;
470        } else if (mPendingMO != null ||
471                !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
472            mState = PhoneConstants.State.OFFHOOK;
473        } else {
474            ImsPhone imsPhone = (ImsPhone)mPhone.getImsPhone();
475            if ( mState == PhoneConstants.State.OFFHOOK && (imsPhone != null)){
476                imsPhone.callEndCleanupHandOverCallIfAny();
477            }
478            mState = PhoneConstants.State.IDLE;
479        }
480
481        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
482            mVoiceCallEndedRegistrants.notifyRegistrants(
483                new AsyncResult(null, null, null));
484        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
485            mVoiceCallStartedRegistrants.notifyRegistrants (
486                    new AsyncResult(null, null, null));
487        }
488        if (Phone.DEBUG_PHONE) {
489            log("update phone state, old=" + oldState + " new="+ mState);
490        }
491        if (mState != oldState) {
492            mPhone.notifyPhoneStateChanged();
493        }
494    }
495
496    // ***** Overwritten from CallTracker
497
498    @Override
499    protected void
500    handlePollCalls(AsyncResult ar) {
501        List polledCalls;
502
503        if (ar.exception == null) {
504            polledCalls = (List)ar.result;
505        } else if (isCommandExceptionRadioNotAvailable(ar.exception)) {
506            // just a dummy empty ArrayList to cause the loop
507            // to hang up all the calls
508            polledCalls = new ArrayList();
509        } else {
510            // Radio probably wasn't ready--try again in a bit
511            // But don't keep polling if the channel is closed
512            pollCallsAfterDelay();
513            return;
514        }
515
516        Connection newRinging = null; //or waiting
517        Connection newUnknown = null;
518        boolean hasNonHangupStateChanged = false;   // Any change besides
519                                                    // a dropped connection
520        boolean hasAnyCallDisconnected = false;
521        boolean needsPollDelay = false;
522        boolean unknownConnectionAppeared = false;
523
524        for (int i = 0, curDC = 0, dcSize = polledCalls.size()
525                ; i < mConnections.length; i++) {
526            CdmaConnection conn = mConnections[i];
527            DriverCall dc = null;
528
529            // polledCall list is sparse
530            if (curDC < dcSize) {
531                dc = (DriverCall) polledCalls.get(curDC);
532
533                if (dc.index == i+1) {
534                    curDC++;
535                } else {
536                    dc = null;
537                }
538            }
539
540            if (DBG_POLL) log("poll: conn[i=" + i + "]=" +
541                    conn+", dc=" + dc);
542
543            if (conn == null && dc != null) {
544                // Connection appeared in CLCC response that we don't know about
545                if (mPendingMO != null && mPendingMO.compareTo(dc)) {
546
547                    if (DBG_POLL) log("poll: pendingMO=" + mPendingMO);
548
549                    // It's our pending mobile originating call
550                    mConnections[i] = mPendingMO;
551                    mPendingMO.mIndex = i;
552                    mPendingMO.update(dc);
553                    mPendingMO = null;
554
555                    // Someone has already asked to hangup this call
556                    if (mHangupPendingMO) {
557                        mHangupPendingMO = false;
558                        // Re-start Ecm timer when an uncompleted emergency call ends
559                        if (mIsEcmTimerCanceled) {
560                            handleEcmTimer(CDMAPhone.RESTART_ECM_TIMER);
561                        }
562
563                        try {
564                            if (Phone.DEBUG_PHONE) log(
565                                    "poll: hangupPendingMO, hangup conn " + i);
566                            hangup(mConnections[i]);
567                        } catch (CallStateException ex) {
568                            Rlog.e(LOG_TAG, "unexpected error on hangup");
569                        }
570
571                        // Do not continue processing this poll
572                        // Wait for hangup and repoll
573                        return;
574                    }
575                } else {
576                    if (Phone.DEBUG_PHONE) {
577                        log("pendingMo=" + mPendingMO + ", dc=" + dc);
578                    }
579                    mConnections[i] = new CdmaConnection(mPhone.getContext(), dc, this, i);
580
581                    Connection hoConnection = getHoConnection(dc);
582                    if (hoConnection != null) {
583                        // Single Radio Voice Call Continuity (SRVCC) completed
584                        mConnections[i].migrateFrom(hoConnection);
585                        mHandoverConnections.remove(hoConnection);
586                        mPhone.notifyHandoverStateChanged(mConnections[i]);
587                    } else {
588                        // find if the MT call is a new ring or unknown connection
589                        newRinging = checkMtFindNewRinging(dc,i);
590                        if (newRinging == null) {
591                            unknownConnectionAppeared = true;
592                            newUnknown = mConnections[i];
593                        }
594                    }
595                    checkAndEnableDataCallAfterEmergencyCallDropped();
596                }
597                hasNonHangupStateChanged = true;
598            } else if (conn != null && dc == null) {
599                // This case means the RIL has no more active call anymore and
600                // we need to clean up the foregroundCall and ringingCall.
601                // Loop through foreground call connections as
602                // it contains the known logical connections.
603                int count = mForegroundCall.mConnections.size();
604                for (int n = 0; n < count; n++) {
605                    if (Phone.DEBUG_PHONE) log("adding fgCall cn " + n + " to droppedDuringPoll");
606                    CdmaConnection cn = (CdmaConnection)mForegroundCall.mConnections.get(n);
607                    mDroppedDuringPoll.add(cn);
608                }
609                count = mRingingCall.mConnections.size();
610                // Loop through ringing call connections as
611                // it may contain the known logical connections.
612                for (int n = 0; n < count; n++) {
613                    if (Phone.DEBUG_PHONE) log("adding rgCall cn " + n + " to droppedDuringPoll");
614                    CdmaConnection cn = (CdmaConnection)mRingingCall.mConnections.get(n);
615                    mDroppedDuringPoll.add(cn);
616                }
617                mForegroundCall.setGeneric(false);
618                mRingingCall.setGeneric(false);
619
620                // Re-start Ecm timer when the connected emergency call ends
621                if (mIsEcmTimerCanceled) {
622                    handleEcmTimer(CDMAPhone.RESTART_ECM_TIMER);
623                }
624                // If emergency call is not going through while dialing
625                checkAndEnableDataCallAfterEmergencyCallDropped();
626
627                // Dropped connections are removed from the CallTracker
628                // list but kept in the Call list
629                mConnections[i] = null;
630            } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */
631                // Call collision case
632                if (conn.isIncoming() != dc.isMT) {
633                    if (dc.isMT == true){
634                        // Mt call takes precedence than Mo,drops Mo
635                        mDroppedDuringPoll.add(conn);
636                        // find if the MT call is a new ring or unknown connection
637                        newRinging = checkMtFindNewRinging(dc,i);
638                        if (newRinging == null) {
639                            unknownConnectionAppeared = true;
640                            newUnknown = conn;
641                        }
642                        checkAndEnableDataCallAfterEmergencyCallDropped();
643                    } else {
644                        // Call info stored in conn is not consistent with the call info from dc.
645                        // We should follow the rule of MT calls taking precedence over MO calls
646                        // when there is conflict, so here we drop the call info from dc and
647                        // continue to use the call info from conn, and only take a log.
648                        Rlog.e(LOG_TAG,"Error in RIL, Phantom call appeared " + dc);
649                    }
650                } else {
651                    boolean changed;
652                    changed = conn.update(dc);
653                    hasNonHangupStateChanged = hasNonHangupStateChanged || changed;
654                }
655            }
656
657            if (REPEAT_POLLING) {
658                if (dc != null) {
659                    // FIXME with RIL, we should not need this anymore
660                    if ((dc.state == DriverCall.State.DIALING
661                            /*&& cm.getOption(cm.OPTION_POLL_DIALING)*/)
662                        || (dc.state == DriverCall.State.ALERTING
663                            /*&& cm.getOption(cm.OPTION_POLL_ALERTING)*/)
664                        || (dc.state == DriverCall.State.INCOMING
665                            /*&& cm.getOption(cm.OPTION_POLL_INCOMING)*/)
666                        || (dc.state == DriverCall.State.WAITING
667                            /*&& cm.getOption(cm.OPTION_POLL_WAITING)*/)
668                    ) {
669                        // Sometimes there's no unsolicited notification
670                        // for state transitions
671                        needsPollDelay = true;
672                    }
673                }
674            }
675        }
676
677        // This is the first poll after an ATD.
678        // We expect the pending call to appear in the list
679        // If it does not, we land here
680        if (mPendingMO != null) {
681            Rlog.d(LOG_TAG,"Pending MO dropped before poll fg state:"
682                            + mForegroundCall.getState());
683
684            mDroppedDuringPoll.add(mPendingMO);
685            mPendingMO = null;
686            mHangupPendingMO = false;
687            if( mPendingCallInEcm) {
688                mPendingCallInEcm = false;
689            }
690        }
691
692        if (newRinging != null) {
693            mPhone.notifyNewRingingConnection(newRinging);
694        }
695
696        // clear the "local hangup" and "missed/rejected call"
697        // cases from the "dropped during poll" list
698        // These cases need no "last call fail" reason
699        for (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) {
700            CdmaConnection conn = mDroppedDuringPoll.get(i);
701
702            if (conn.isIncoming() && conn.getConnectTime() == 0) {
703                // Missed or rejected call
704                int cause;
705                if (conn.mCause == DisconnectCause.LOCAL) {
706                    cause = DisconnectCause.INCOMING_REJECTED;
707                } else {
708                    cause = DisconnectCause.INCOMING_MISSED;
709                }
710
711                if (Phone.DEBUG_PHONE) {
712                    log("missed/rejected call, conn.cause=" + conn.mCause);
713                    log("setting cause to " + cause);
714                }
715                mDroppedDuringPoll.remove(i);
716                hasAnyCallDisconnected |= conn.onDisconnect(cause);
717            } else if (conn.mCause == DisconnectCause.LOCAL
718                    || conn.mCause == DisconnectCause.INVALID_NUMBER) {
719                mDroppedDuringPoll.remove(i);
720                hasAnyCallDisconnected |= conn.onDisconnect(conn.mCause);
721            }
722        }
723
724        /* Disconnect any pending Handover connections */
725        for (Connection hoConnection : mHandoverConnections) {
726            log("handlePollCalls - disconnect hoConn= " + hoConnection.toString());
727            ((ImsPhoneConnection)hoConnection).onDisconnect(DisconnectCause.NOT_VALID);
728            mHandoverConnections.remove(hoConnection);
729        }
730
731        // Any non-local disconnects: determine cause
732        if (mDroppedDuringPoll.size() > 0) {
733            mCi.getLastCallFailCause(
734                obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
735        }
736
737        if (needsPollDelay) {
738            pollCallsAfterDelay();
739        }
740
741        // Cases when we can no longer keep disconnected Connection's
742        // with their previous calls
743        // 1) the phone has started to ring
744        // 2) A Call/Connection object has changed state...
745        //    we may have switched or held or answered (but not hung up)
746        if (newRinging != null || hasNonHangupStateChanged || hasAnyCallDisconnected) {
747            internalClearDisconnected();
748        }
749
750        updatePhoneState();
751
752        if (unknownConnectionAppeared) {
753            mPhone.notifyUnknownConnection(newUnknown);
754        }
755
756        if (hasNonHangupStateChanged || newRinging != null || hasAnyCallDisconnected) {
757            mPhone.notifyPreciseCallStateChanged();
758        }
759
760        //dumpState();
761    }
762
763    //***** Called from CdmaConnection
764    /*package*/ void
765    hangup (CdmaConnection conn) throws CallStateException {
766        if (conn.mOwner != this) {
767            throw new CallStateException ("CdmaConnection " + conn
768                                    + "does not belong to CdmaCallTracker " + this);
769        }
770
771        if (conn == mPendingMO) {
772            // We're hanging up an outgoing call that doesn't have it's
773            // GSM index assigned yet
774
775            if (Phone.DEBUG_PHONE) log("hangup: set hangupPendingMO to true");
776            mHangupPendingMO = true;
777        } else if ((conn.getCall() == mRingingCall)
778                && (mRingingCall.getState() == CdmaCall.State.WAITING)) {
779            // Handle call waiting hang up case.
780            //
781            // The ringingCall state will change to IDLE in CdmaCall.detach
782            // if the ringing call connection size is 0. We don't specifically
783            // set the ringing call state to IDLE here to avoid a race condition
784            // where a new call waiting could get a hang up from an old call
785            // waiting ringingCall.
786            //
787            // PhoneApp does the call log itself since only PhoneApp knows
788            // the hangup reason is user ignoring or timing out. So conn.onDisconnect()
789            // is not called here. Instead, conn.onLocalDisconnect() is called.
790            conn.onLocalDisconnect();
791            updatePhoneState();
792            mPhone.notifyPreciseCallStateChanged();
793            return;
794        } else {
795            try {
796                mCi.hangupConnection (conn.getCDMAIndex(), obtainCompleteMessage());
797            } catch (CallStateException ex) {
798                // Ignore "connection not found"
799                // Call may have hung up already
800                Rlog.w(LOG_TAG,"CdmaCallTracker WARN: hangup() on absent connection "
801                                + conn);
802            }
803        }
804
805        conn.onHangupLocal();
806    }
807
808    /*package*/ void
809    separate (CdmaConnection conn) throws CallStateException {
810        if (conn.mOwner != this) {
811            throw new CallStateException ("CdmaConnection " + conn
812                                    + "does not belong to CdmaCallTracker " + this);
813        }
814        try {
815            mCi.separateConnection (conn.getCDMAIndex(),
816                obtainCompleteMessage(EVENT_SEPARATE_RESULT));
817        } catch (CallStateException ex) {
818            // Ignore "connection not found"
819            // Call may have hung up already
820            Rlog.w(LOG_TAG,"CdmaCallTracker WARN: separate() on absent connection "
821                          + conn);
822        }
823    }
824
825    //***** Called from CDMAPhone
826
827    /*package*/ void
828    setMute(boolean mute) {
829        mDesiredMute = mute;
830        mCi.setMute(mDesiredMute, null);
831    }
832
833    /*package*/ boolean
834    getMute() {
835        return mDesiredMute;
836    }
837
838
839    //***** Called from CdmaCall
840
841    /* package */ void
842    hangup (CdmaCall call) throws CallStateException {
843        if (call.getConnections().size() == 0) {
844            throw new CallStateException("no connections in call");
845        }
846
847        if (call == mRingingCall) {
848            if (Phone.DEBUG_PHONE) log("(ringing) hangup waiting or background");
849            mCi.hangupWaitingOrBackground(obtainCompleteMessage());
850        } else if (call == mForegroundCall) {
851            if (call.isDialingOrAlerting()) {
852                if (Phone.DEBUG_PHONE) {
853                    log("(foregnd) hangup dialing or alerting...");
854                }
855                hangup((CdmaConnection)(call.getConnections().get(0)));
856            } else {
857                hangupForegroundResumeBackground();
858            }
859        } else if (call == mBackgroundCall) {
860            if (mRingingCall.isRinging()) {
861                if (Phone.DEBUG_PHONE) {
862                    log("hangup all conns in background call");
863                }
864                hangupAllConnections(call);
865            } else {
866                hangupWaitingOrBackground();
867            }
868        } else {
869            throw new RuntimeException ("CdmaCall " + call +
870                    "does not belong to CdmaCallTracker " + this);
871        }
872
873        call.onHangupLocal();
874        mPhone.notifyPreciseCallStateChanged();
875    }
876
877    /* package */
878    void hangupWaitingOrBackground() {
879        if (Phone.DEBUG_PHONE) log("hangupWaitingOrBackground");
880        mCi.hangupWaitingOrBackground(obtainCompleteMessage());
881    }
882
883    /* package */
884    void hangupForegroundResumeBackground() {
885        if (Phone.DEBUG_PHONE) log("hangupForegroundResumeBackground");
886        mCi.hangupForegroundResumeBackground(obtainCompleteMessage());
887    }
888
889    void hangupConnectionByIndex(CdmaCall call, int index)
890            throws CallStateException {
891        int count = call.mConnections.size();
892        for (int i = 0; i < count; i++) {
893            CdmaConnection cn = (CdmaConnection)call.mConnections.get(i);
894            if (cn.getCDMAIndex() == index) {
895                mCi.hangupConnection(index, obtainCompleteMessage());
896                return;
897            }
898        }
899
900        throw new CallStateException("no gsm index found");
901    }
902
903    void hangupAllConnections(CdmaCall call) {
904        try {
905            int count = call.mConnections.size();
906            for (int i = 0; i < count; i++) {
907                CdmaConnection cn = (CdmaConnection)call.mConnections.get(i);
908                mCi.hangupConnection(cn.getCDMAIndex(), obtainCompleteMessage());
909            }
910        } catch (CallStateException ex) {
911            Rlog.e(LOG_TAG, "hangupConnectionByIndex caught " + ex);
912        }
913    }
914
915    /* package */
916    CdmaConnection getConnectionByIndex(CdmaCall call, int index)
917            throws CallStateException {
918        int count = call.mConnections.size();
919        for (int i = 0; i < count; i++) {
920            CdmaConnection cn = (CdmaConnection)call.mConnections.get(i);
921            if (cn.getCDMAIndex() == index) {
922                return cn;
923            }
924        }
925
926        return null;
927    }
928
929    private void flashAndSetGenericTrue() {
930        mCi.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT));
931
932        // Set generic to true because in CDMA it is not known what
933        // the status of the call is after a call waiting is answered,
934        // 3 way call merged or a switch between calls.
935        mForegroundCall.setGeneric(true);
936        mPhone.notifyPreciseCallStateChanged();
937    }
938
939    private void handleRadioNotAvailable() {
940        // handlePollCalls will clear out its
941        // call list when it gets the CommandException
942        // error result from this
943        pollCallsWhenSafe();
944    }
945
946    private void notifyCallWaitingInfo(CdmaCallWaitingNotification obj) {
947        if (mCallWaitingRegistrants != null) {
948            mCallWaitingRegistrants.notifyRegistrants(new AsyncResult(null, obj, null));
949        }
950    }
951
952    private void handleCallWaitingInfo (CdmaCallWaitingNotification cw) {
953        // Check how many connections in foregroundCall.
954        // If the connection in foregroundCall is more
955        // than one, then the connection information is
956        // not reliable anymore since it means either
957        // call waiting is connected or 3 way call is
958        // dialed before, so set generic.
959        if (mForegroundCall.mConnections.size() > 1 ) {
960            mForegroundCall.setGeneric(true);
961        }
962
963        // Create a new CdmaConnection which attaches itself to ringingCall.
964        mRingingCall.setGeneric(false);
965        new CdmaConnection(mPhone.getContext(), cw, this, mRingingCall);
966        updatePhoneState();
967
968        // Finally notify application
969        notifyCallWaitingInfo(cw);
970    }
971    //****** Overridden from Handler
972
973    @Override
974    public void
975    handleMessage (Message msg) {
976        AsyncResult ar;
977
978        if (!mPhone.mIsTheCurrentActivePhone) {
979            Rlog.w(LOG_TAG, "Ignoring events received on inactive CdmaPhone");
980            return;
981        }
982        switch (msg.what) {
983            case EVENT_POLL_CALLS_RESULT:{
984                Rlog.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received");
985                ar = (AsyncResult)msg.obj;
986
987                if(msg == mLastRelevantPoll) {
988                    if(DBG_POLL) log(
989                            "handle EVENT_POLL_CALL_RESULT: set needsPoll=F");
990                    mNeedsPoll = false;
991                    mLastRelevantPoll = null;
992                    handlePollCalls((AsyncResult)msg.obj);
993                }
994            }
995            break;
996
997            case EVENT_OPERATION_COMPLETE:
998                operationComplete();
999            break;
1000
1001            case EVENT_SWITCH_RESULT:
1002                 // In GSM call operationComplete() here which gets the
1003                 // current call list. But in CDMA there is no list so
1004                 // there is nothing to do.
1005            break;
1006
1007            case EVENT_GET_LAST_CALL_FAIL_CAUSE:
1008                int causeCode;
1009                ar = (AsyncResult)msg.obj;
1010
1011                operationComplete();
1012
1013                if (ar.exception != null) {
1014                    // An exception occurred...just treat the disconnect
1015                    // cause as "normal"
1016                    causeCode = CallFailCause.NORMAL_CLEARING;
1017                    Rlog.i(LOG_TAG,
1018                            "Exception during getLastCallFailCause, assuming normal disconnect");
1019                } else {
1020                    causeCode = ((int[])ar.result)[0];
1021                }
1022
1023                for (int i = 0, s =  mDroppedDuringPoll.size()
1024                        ; i < s ; i++
1025                ) {
1026                    CdmaConnection conn = mDroppedDuringPoll.get(i);
1027
1028                    conn.onRemoteDisconnect(causeCode);
1029                }
1030
1031                updatePhoneState();
1032
1033                mPhone.notifyPreciseCallStateChanged();
1034                mDroppedDuringPoll.clear();
1035            break;
1036
1037            case EVENT_REPOLL_AFTER_DELAY:
1038            case EVENT_CALL_STATE_CHANGE:
1039                pollCallsWhenSafe();
1040            break;
1041
1042            case EVENT_RADIO_AVAILABLE:
1043                handleRadioAvailable();
1044            break;
1045
1046            case EVENT_RADIO_NOT_AVAILABLE:
1047                handleRadioNotAvailable();
1048            break;
1049
1050            case EVENT_EXIT_ECM_RESPONSE_CDMA:
1051                // no matter the result, we still do the same here
1052                if (mPendingCallInEcm) {
1053                    mCi.dial(mPendingMO.getAddress(), mPendingCallClirMode, obtainCompleteMessage());
1054                    mPendingCallInEcm = false;
1055                }
1056                mPhone.unsetOnEcbModeExitResponse(this);
1057            break;
1058
1059            case EVENT_CALL_WAITING_INFO_CDMA:
1060               ar = (AsyncResult)msg.obj;
1061               if (ar.exception == null) {
1062                   handleCallWaitingInfo((CdmaCallWaitingNotification)ar.result);
1063                   Rlog.d(LOG_TAG, "Event EVENT_CALL_WAITING_INFO_CDMA Received");
1064               }
1065            break;
1066
1067            case EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA:
1068                ar = (AsyncResult)msg.obj;
1069                if (ar.exception == null) {
1070                    // Assume 3 way call is connected
1071                    mPendingMO.onConnectedInOrOut();
1072                    mPendingMO = null;
1073                }
1074            break;
1075
1076            case EVENT_THREE_WAY_DIAL_BLANK_FLASH:
1077                ar = (AsyncResult) msg.obj;
1078                if (ar.exception == null) {
1079                    postDelayed(
1080                            new Runnable() {
1081                                public void run() {
1082                                    if (mPendingMO != null) {
1083                                        mCi.sendCDMAFeatureCode(mPendingMO.getAddress(),
1084                                                obtainMessage(EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA));
1085                                    }
1086                                }
1087                            }, m3WayCallFlashDelay);
1088                } else {
1089                    mPendingMO = null;
1090                    Rlog.w(LOG_TAG, "exception happened on Blank Flash for 3-way call");
1091                }
1092            break;
1093
1094            default:{
1095               throw new RuntimeException("unexpected event not handled");
1096            }
1097        }
1098    }
1099
1100    /**
1101     * Handle Ecm timer to be canceled or re-started
1102     */
1103    private void handleEcmTimer(int action) {
1104        mPhone.handleTimerInEmergencyCallbackMode(action);
1105        switch(action) {
1106        case CDMAPhone.CANCEL_ECM_TIMER: mIsEcmTimerCanceled = true; break;
1107        case CDMAPhone.RESTART_ECM_TIMER: mIsEcmTimerCanceled = false; break;
1108        default:
1109            Rlog.e(LOG_TAG, "handleEcmTimer, unsupported action " + action);
1110        }
1111    }
1112
1113    /**
1114     * Disable data call when emergency call is connected
1115     */
1116    private void disableDataCallInEmergencyCall(String dialString) {
1117        if (PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(), dialString)) {
1118            if (Phone.DEBUG_PHONE) log("disableDataCallInEmergencyCall");
1119            mIsInEmergencyCall = true;
1120            mPhone.mDcTracker.setInternalDataEnabled(false);
1121        }
1122    }
1123
1124    /**
1125     * Check and enable data call after an emergency call is dropped if it's
1126     * not in ECM
1127     */
1128    private void checkAndEnableDataCallAfterEmergencyCallDropped() {
1129        if (mIsInEmergencyCall) {
1130            mIsInEmergencyCall = false;
1131            String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
1132            if (Phone.DEBUG_PHONE) {
1133                log("checkAndEnableDataCallAfterEmergencyCallDropped,inEcm=" + inEcm);
1134            }
1135            if (inEcm.compareTo("false") == 0) {
1136                // Re-initiate data connection
1137                mPhone.mDcTracker.setInternalDataEnabled(true);
1138            }
1139        }
1140    }
1141
1142    /**
1143     * Check the MT call to see if it's a new ring or
1144     * a unknown connection.
1145     */
1146    private Connection checkMtFindNewRinging(DriverCall dc, int i) {
1147
1148        Connection newRinging = null;
1149
1150        // it's a ringing call
1151        if (mConnections[i].getCall() == mRingingCall) {
1152            newRinging = mConnections[i];
1153            if (Phone.DEBUG_PHONE) log("Notify new ring " + dc);
1154        } else {
1155            // Something strange happened: a call which is neither
1156            // a ringing call nor the one we created. It could be the
1157            // call collision result from RIL
1158            Rlog.e(LOG_TAG,"Phantom call appeared " + dc);
1159            // If it's a connected call, set the connect time so that
1160            // it's non-zero.  It may not be accurate, but at least
1161            // it won't appear as a Missed Call.
1162            if (dc.state != DriverCall.State.ALERTING
1163                && dc.state != DriverCall.State.DIALING) {
1164                mConnections[i].onConnectedInOrOut();
1165                if (dc.state == DriverCall.State.HOLDING) {
1166                    // We've transitioned into HOLDING
1167                    mConnections[i].onStartedHolding();
1168                }
1169            }
1170        }
1171        return newRinging;
1172    }
1173
1174    /**
1175     * Check if current call is in emergency call
1176     *
1177     * @return true if it is in emergency call
1178     *         false if it is not in emergency call
1179     */
1180    boolean isInEmergencyCall() {
1181        return mIsInEmergencyCall;
1182    }
1183
1184    @Override
1185    protected void log(String msg) {
1186        Rlog.d(LOG_TAG, "[CdmaCallTracker] " + msg);
1187    }
1188
1189    @Override
1190    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1191        pw.println("GsmCallTracker extends:");
1192        super.dump(fd, pw, args);
1193        pw.println("droppedDuringPoll: length=" + mConnections.length);
1194        for(int i=0; i < mConnections.length; i++) {
1195            pw.printf(" mConnections[%d]=%s\n", i, mConnections[i]);
1196        }
1197        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
1198        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
1199        pw.println(" mCallWaitingRegistrants=" + mCallWaitingRegistrants);
1200        pw.println("droppedDuringPoll: size=" + mDroppedDuringPoll.size());
1201        for(int i = 0; i < mDroppedDuringPoll.size(); i++) {
1202            pw.printf( " mDroppedDuringPoll[%d]=%s\n", i, mDroppedDuringPoll.get(i));
1203        }
1204        pw.println(" mRingingCall=" + mRingingCall);
1205        pw.println(" mForegroundCall=" + mForegroundCall);
1206        pw.println(" mBackgroundCall=" + mBackgroundCall);
1207        pw.println(" mPendingMO=" + mPendingMO);
1208        pw.println(" mHangupPendingMO=" + mHangupPendingMO);
1209        pw.println(" mPendingCallInEcm=" + mPendingCallInEcm);
1210        pw.println(" mIsInEmergencyCall=" + mIsInEmergencyCall);
1211        pw.println(" mPhone=" + mPhone);
1212        pw.println(" mDesiredMute=" + mDesiredMute);
1213        pw.println(" mPendingCallClirMode=" + mPendingCallClirMode);
1214        pw.println(" mState=" + mState);
1215        pw.println(" mIsEcmTimerCanceled=" + mIsEcmTimerCanceled);
1216    }
1217    @Override
1218    public PhoneConstants.State getState() {
1219        return mState;
1220    }
1221}
1222