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