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