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