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