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