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