CdmaCallTracker.java revision 767a662ecde33c3979bf02b793d392aca0403162
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;
27
28import com.android.internal.telephony.CallStateException;
29import com.android.internal.telephony.CallTracker;
30import com.android.internal.telephony.CommandsInterface;
31import com.android.internal.telephony.Connection;
32import com.android.internal.telephony.DriverCall;
33import com.android.internal.telephony.Phone;
34import com.android.internal.telephony.PhoneProxy;
35
36import java.util.ArrayList;
37import java.util.List;
38
39/**
40 * {@hide}
41 */
42public final class CdmaCallTracker extends CallTracker {
43    static final String LOG_TAG = "CDMA";
44
45    private static final boolean REPEAT_POLLING = false;
46
47    private static final boolean DBG_POLL = false;
48
49    //***** Constants
50
51    static final int MAX_CONNECTIONS = 1;   // only 1 connection allowed in CDMA
52    static final int MAX_CONNECTIONS_PER_CALL = 1; // only 1 connection allowed per call
53
54    //***** Instance Variables
55
56    CdmaConnection connections[] = new CdmaConnection[MAX_CONNECTIONS];
57    RegistrantList voiceCallEndedRegistrants = new RegistrantList();
58    RegistrantList voiceCallStartedRegistrants = new RegistrantList();
59
60
61    // connections dropped durin last poll
62    ArrayList<CdmaConnection> droppedDuringPoll
63        = new ArrayList<CdmaConnection>(MAX_CONNECTIONS);
64
65    CdmaCall ringingCall = new CdmaCall(this);
66    // A call that is ringing or (call) waiting
67    CdmaCall foregroundCall = new CdmaCall(this);
68    CdmaCall backgroundCall = new CdmaCall(this);
69
70    CdmaConnection pendingMO;
71    boolean hangupPendingMO;
72
73    CDMAPhone phone;
74
75    boolean desiredMute = false;    // false = mute off
76
77    Phone.State state = Phone.State.IDLE;
78
79
80//    boolean needsPoll;
81
82
83
84    //***** Events
85
86    //***** Constructors
87    CdmaCallTracker(CDMAPhone phone) {
88        this.phone = phone;
89        cm = phone.mCM;
90        cm.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
91        cm.registerForOn(this, EVENT_RADIO_AVAILABLE, null);
92        cm.registerForNotAvailable(this, EVENT_RADIO_NOT_AVAILABLE, null);
93    }
94
95    public void dispose() {
96        cm.unregisterForCallStateChanged(this);
97        cm.unregisterForOn(this);
98        cm.unregisterForNotAvailable(this);
99
100        for(CdmaConnection c : connections) {
101            try {
102                if(c != null) hangup(c);
103            } catch (CallStateException ex) {
104                Log.e(LOG_TAG, "unexpected error on hangup during dispose");
105            }
106        }
107
108        try {
109            if(pendingMO != null) hangup(pendingMO);
110        } catch (CallStateException ex) {
111            Log.e(LOG_TAG, "unexpected error on hangup during dispose");
112        }
113
114        clearDisconnected();
115
116    }
117
118    protected void finalize() {
119        Log.d(LOG_TAG, "CdmaCallTracker finalized");
120    }
121
122    //***** Instance Methods
123
124    //***** Public Methods
125    public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
126        Registrant r = new Registrant(h, what, obj);
127        voiceCallStartedRegistrants.add(r);
128    }
129    public void unregisterForVoiceCallStarted(Handler h) {
130        voiceCallStartedRegistrants.remove(h);
131    }
132
133    public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
134        Registrant r = new Registrant(h, what, obj);
135        voiceCallEndedRegistrants.add(r);
136    }
137
138    public void unregisterForVoiceCallEnded(Handler h) {
139        voiceCallEndedRegistrants.remove(h);
140    }
141
142    private void
143    fakeHoldForegroundBeforeDial() {
144        List<Connection> connCopy;
145
146        // We need to make a copy here, since fakeHoldBeforeDial()
147        // modifies the lists, and we don't want to reverse the order
148        connCopy = (List<Connection>) foregroundCall.connections.clone();
149
150        for (int i = 0, s = connCopy.size() ; i < s ; i++) {
151            CdmaConnection conn = (CdmaConnection)connCopy.get(i);
152
153            conn.fakeHoldBeforeDial();
154        }
155    }
156
157    /**
158     * clirMode is one of the CLIR_ constants
159     */
160    Connection
161    dial (String dialString, int clirMode) throws CallStateException {
162        // note that this triggers call state changed notif
163        clearDisconnected();
164
165        if (!canDial()) {
166            throw new CallStateException("cannot dial in current state");
167        }
168
169        // The new call must be assigned to the foreground call.
170        // That call must be idle, so place anything that's
171        // there on hold
172        if (foregroundCall.getState() == CdmaCall.State.ACTIVE) {
173            // this will probably be done by the radio anyway
174            // but the dial might fail before this happens
175            // and we need to make sure the foreground call is clear
176            // for the newly dialed connection
177            switchWaitingOrHoldingAndActive();
178
179            // Fake local state so that
180            // a) foregroundCall is empty for the newly dialed connection
181            // b) hasNonHangupStateChanged remains false in the
182            // next poll, so that we don't clear a failed dialing call
183            fakeHoldForegroundBeforeDial();
184        }
185
186        if (foregroundCall.getState() != CdmaCall.State.IDLE) {
187            //we should have failed in !canDial() above before we get here
188            throw new CallStateException("cannot dial in current state");
189        }
190
191        pendingMO = new CdmaConnection(phone.getContext(), dialString, this, foregroundCall);
192        hangupPendingMO = false;
193
194        if (pendingMO.address == null || pendingMO.address.length() == 0
195            || pendingMO.address.indexOf(PhoneNumberUtils.WILD) >= 0
196        ) {
197            // Phone number is invalid
198            pendingMO.cause = Connection.DisconnectCause.INVALID_NUMBER;
199
200            // handlePollCalls() will notice this call not present
201            // and will mark it as dropped.
202            pollCallsWhenSafe();
203        } else {
204            // Always unmute when initiating a new call
205            setMute(false);
206
207            cm.dial(pendingMO.address, clirMode, obtainCompleteMessage());
208        }
209
210        updatePhoneState();
211        phone.notifyCallStateChanged();
212
213        return pendingMO;
214    }
215
216
217    Connection
218    dial (String dialString) throws CallStateException {
219        return dial(dialString, CommandsInterface.CLIR_DEFAULT);
220    }
221
222    void
223    acceptCall () throws CallStateException {
224        // FIXME if SWITCH fails, should retry with ANSWER
225        // in case the active/holding call disappeared and this
226        // is no longer call waiting
227
228        if (ringingCall.getState() == CdmaCall.State.INCOMING) {
229            Log.i("phone", "acceptCall: incoming...");
230            // Always unmute when answering a new call
231            setMute(false);
232            cm.acceptCall(obtainCompleteMessage());
233        } else if (ringingCall.getState() == CdmaCall.State.WAITING) {
234            setMute(false);
235            switchWaitingOrHoldingAndActive();
236        } else {
237            throw new CallStateException("phone not ringing");
238        }
239    }
240
241    void
242    rejectCall () throws CallStateException {
243        // AT+CHLD=0 means "release held or UDUB"
244        // so if the phone isn't ringing, this could hang up held
245        if (ringingCall.getState().isRinging()) {
246            cm.rejectCall(obtainCompleteMessage());
247        } else {
248            throw new CallStateException("phone not ringing");
249        }
250    }
251
252    void
253    switchWaitingOrHoldingAndActive() throws CallStateException {
254        // Should we bother with this check?
255        if (ringingCall.getState() == CdmaCall.State.INCOMING) {
256            throw new CallStateException("cannot be in the incoming state");
257        } else {
258            cm.sendCDMAFeatureCode("", obtainCompleteMessage(EVENT_SWITCH_RESULT));
259        }
260    }
261
262    void
263    conference() throws CallStateException {
264        // three way calls in CDMA will be handled by feature codes
265        Log.e(LOG_TAG, "conference: not possible in CDMA");
266    }
267
268    void
269    explicitCallTransfer() throws CallStateException {
270        cm.explicitCallTransfer(obtainCompleteMessage(EVENT_ECT_RESULT));
271    }
272
273    void
274    clearDisconnected() {
275        internalClearDisconnected();
276
277        updatePhoneState();
278        phone.notifyCallStateChanged();
279    }
280
281    boolean
282    canConference() {
283        return foregroundCall.getState() == CdmaCall.State.ACTIVE
284                && backgroundCall.getState() == CdmaCall.State.HOLDING
285                && !backgroundCall.isFull()
286                && !foregroundCall.isFull();
287    }
288
289    boolean
290    canDial() {
291        boolean ret;
292        int serviceState = phone.getServiceState().getState();
293
294        ret = (serviceState != ServiceState.STATE_POWER_OFF) &&
295                pendingMO == null
296                && !ringingCall.isRinging()
297                && (!foregroundCall.getState().isAlive()
298                || !backgroundCall.getState().isAlive());
299
300        return ret;
301    }
302
303    boolean
304    canTransfer() {
305        Log.e(LOG_TAG, "canTransfer: not possible in CDMA");
306        return false;
307    }
308
309    //***** Private Instance Methods
310
311    private void
312    internalClearDisconnected() {
313        ringingCall.clearDisconnected();
314        foregroundCall.clearDisconnected();
315        backgroundCall.clearDisconnected();
316    }
317
318    /**
319     * Obtain a message to use for signalling "invoke getCurrentCalls() when
320     * this operation and all other pending operations are complete
321     */
322    private Message
323    obtainCompleteMessage() {
324        return obtainCompleteMessage(EVENT_OPERATION_COMPLETE);
325    }
326
327    /**
328     * Obtain a message to use for signalling "invoke getCurrentCalls() when
329     * this operation and all other pending operations are complete
330     */
331    private Message
332    obtainCompleteMessage(int what) {
333        pendingOperations++;
334        lastRelevantPoll = null;
335        needsPoll = true;
336
337        if (DBG_POLL) log("obtainCompleteMessage: pendingOperations=" +
338                pendingOperations + ", needsPoll=" + needsPoll);
339
340        return obtainMessage(what);
341    }
342
343    private void
344    operationComplete() {
345        pendingOperations--;
346
347        if (DBG_POLL) log("operationComplete: pendingOperations=" +
348                pendingOperations + ", needsPoll=" + needsPoll);
349
350        if (pendingOperations == 0 && needsPoll) {
351            lastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
352            cm.getCurrentCalls(lastRelevantPoll);
353        } else if (pendingOperations < 0) {
354            // this should never happen
355            Log.e(LOG_TAG,"CdmaCallTracker.pendingOperations < 0");
356            pendingOperations = 0;
357        }
358    }
359
360
361
362    private void
363    updatePhoneState() {
364        Phone.State oldState = state;
365
366        if (ringingCall.isRinging()) {
367            state = Phone.State.RINGING;
368        } else if (pendingMO != null ||
369                !(foregroundCall.isIdle() && backgroundCall.isIdle())) {
370            state = Phone.State.OFFHOOK;
371        } else {
372            state = Phone.State.IDLE;
373        }
374
375        if (state == Phone.State.IDLE && oldState != state) {
376            voiceCallEndedRegistrants.notifyRegistrants(
377                new AsyncResult(null, null, null));
378        } else if (oldState == Phone.State.IDLE && oldState != state) {
379            voiceCallStartedRegistrants.notifyRegistrants (
380                    new AsyncResult(null, null, null));
381        }
382
383        if (state != oldState) {
384            phone.notifyPhoneStateChanged();
385        }
386    }
387
388    // ***** Overwritten from CallTracker
389
390    protected void
391    handlePollCalls(AsyncResult ar) {
392        List polledCalls;
393
394        if (ar.exception == null) {
395            polledCalls = (List)ar.result;
396        } else if (isCommandExceptionRadioNotAvailable(ar.exception)) {
397            // just a dummy empty ArrayList to cause the loop
398            // to hang up all the calls
399            polledCalls = new ArrayList();
400        } else {
401            // Radio probably wasn't ready--try again in a bit
402            // But don't keep polling if the channel is closed
403            pollCallsAfterDelay();
404            return;
405        }
406
407        Connection newRinging = null; //or waiting
408        boolean hasNonHangupStateChanged = false;   // Any change besides
409                                                    // a dropped connection
410        boolean needsPollDelay = false;
411        boolean unknownConnectionAppeared = false;
412
413        for (int i = 0, curDC = 0, dcSize = polledCalls.size()
414                ; i < connections.length; i++) {
415            CdmaConnection conn = connections[i];
416            DriverCall dc = null;
417
418            // polledCall list is sparse
419            if (curDC < dcSize) {
420                dc = (DriverCall) polledCalls.get(curDC);
421
422                if (dc.index == i+1) {
423                    curDC++;
424                } else {
425                    dc = null;
426                }
427            }
428
429            if (DBG_POLL) log("poll: conn[i=" + i + "]=" +
430                    conn+", dc=" + dc);
431
432            if (conn == null && dc != null) {
433                // Connection appeared in CLCC response that we don't know about
434                if (pendingMO != null && pendingMO.compareTo(dc)) {
435
436                    if (DBG_POLL) log("poll: pendingMO=" + pendingMO);
437
438                    // It's our pending mobile originating call
439                    connections[i] = pendingMO;
440                    pendingMO.index = i;
441                    pendingMO.update(dc);
442                    pendingMO = null;
443
444                    // Someone has already asked to hangup this call
445                    if (hangupPendingMO) {
446                        hangupPendingMO = false;
447                        try {
448                            if (Phone.DEBUG_PHONE) log(
449                                    "poll: hangupPendingMO, hangup conn " + i);
450                            hangup(connections[i]);
451                        } catch (CallStateException ex) {
452                            Log.e(LOG_TAG, "unexpected error on hangup");
453                        }
454
455                        // Do not continue processing this poll
456                        // Wait for hangup and repoll
457                        return;
458                    }
459                } else {
460                    connections[i] = new CdmaConnection(phone.getContext(), dc, this, i);
461
462                    // it's a ringing call
463                    if (connections[i].getCall() == ringingCall) {
464                        newRinging = connections[i];
465                    } else {
466                        // Something strange happened: a call appeared
467                        // which is neither a ringing call or one we created.
468                        // Either we've crashed and re-attached to an existing
469                        // call, or something else (eg, SIM) initiated the call.
470
471                        Log.i(LOG_TAG,"Phantom call appeared " + dc);
472
473                        // If it's a connected call, set the connect time so that
474                        // it's non-zero.  It may not be accurate, but at least
475                        // it won't appear as a Missed Call.
476                        if (dc.state != DriverCall.State.ALERTING
477                                && dc.state != DriverCall.State.DIALING) {
478                            connections[i].connectTime = System.currentTimeMillis();
479                        }
480
481                        unknownConnectionAppeared = true;
482                    }
483                }
484                hasNonHangupStateChanged = true;
485            } else if (conn != null && dc == null) {
486                // Connection missing in CLCC response that we were
487                // tracking.
488                droppedDuringPoll.add(conn);
489                // Dropped connections are removed from the CallTracker
490                // list but kept in the Call list
491                connections[i] = null;
492            } else if (conn != null && dc != null && !conn.compareTo(dc)) {
493                // Connection in CLCC response does not match what
494                // we were tracking. Assume dropped call and new call
495
496                droppedDuringPoll.add(conn);
497                connections[i] = new CdmaConnection (phone.getContext(), dc, this, i);
498
499                if (connections[i].getCall() == ringingCall) {
500                    newRinging = connections[i];
501                } // else something strange happened
502                hasNonHangupStateChanged = true;
503            } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */
504                boolean changed;
505                changed = conn.update(dc);
506                hasNonHangupStateChanged = hasNonHangupStateChanged || changed;
507            }
508
509            if (REPEAT_POLLING) {
510                if (dc != null) {
511                    // FIXME with RIL, we should not need this anymore
512                    if ((dc.state == DriverCall.State.DIALING
513                            /*&& cm.getOption(cm.OPTION_POLL_DIALING)*/)
514                        || (dc.state == DriverCall.State.ALERTING
515                            /*&& cm.getOption(cm.OPTION_POLL_ALERTING)*/)
516                        || (dc.state == DriverCall.State.INCOMING
517                            /*&& cm.getOption(cm.OPTION_POLL_INCOMING)*/)
518                        || (dc.state == DriverCall.State.WAITING
519                            /*&& cm.getOption(cm.OPTION_POLL_WAITING)*/)
520                    ) {
521                        // Sometimes there's no unsolicited notification
522                        // for state transitions
523                        needsPollDelay = true;
524                    }
525                }
526            }
527        }
528
529        // This is the first poll after an ATD.
530        // We expect the pending call to appear in the list
531        // If it does not, we land here
532        if (pendingMO != null) {
533            Log.d(LOG_TAG,"Pending MO dropped before poll fg state:"
534                            + foregroundCall.getState());
535
536            droppedDuringPoll.add(pendingMO);
537            pendingMO = null;
538            hangupPendingMO = false;
539        }
540
541        if (newRinging != null) {
542            phone.notifyNewRingingConnection(newRinging);
543        }
544
545        // clear the "local hangup" and "missed/rejected call"
546        // cases from the "dropped during poll" list
547        // These cases need no "last call fail" reason
548        for (int i = droppedDuringPoll.size() - 1; i >= 0 ; i--) {
549            CdmaConnection conn = droppedDuringPoll.get(i);
550
551            if (conn.isIncoming() && conn.getConnectTime() == 0) {
552                // Missed or rejected call
553                Connection.DisconnectCause cause;
554                if (conn.cause == Connection.DisconnectCause.LOCAL) {
555                    cause = Connection.DisconnectCause.INCOMING_REJECTED;
556                } else {
557                    cause = Connection.DisconnectCause.INCOMING_MISSED;
558                }
559
560                if (Phone.DEBUG_PHONE) {
561                    log("missed/rejected call, conn.cause=" + conn.cause);
562                    log("setting cause to " + cause);
563                }
564                droppedDuringPoll.remove(i);
565                conn.onDisconnect(cause);
566            } else if (conn.cause == Connection.DisconnectCause.LOCAL) {
567                // Local hangup
568                droppedDuringPoll.remove(i);
569                conn.onDisconnect(Connection.DisconnectCause.LOCAL);
570            } else if (conn.cause == Connection.DisconnectCause.INVALID_NUMBER) {
571                droppedDuringPoll.remove(i);
572                conn.onDisconnect(Connection.DisconnectCause.INVALID_NUMBER);
573            }
574        }
575
576        // Any non-local disconnects: determine cause
577        if (droppedDuringPoll.size() > 0) {
578            cm.getLastCallFailCause(
579                obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
580        }
581
582        if (needsPollDelay) {
583            pollCallsAfterDelay();
584        }
585
586        // Cases when we can no longer keep disconnected Connection's
587        // with their previous calls
588        // 1) the phone has started to ring
589        // 2) A Call/Connection object has changed state...
590        //    we may have switched or held or answered (but not hung up)
591        if (newRinging != null || hasNonHangupStateChanged) {
592            internalClearDisconnected();
593        }
594
595        updatePhoneState();
596
597        if (unknownConnectionAppeared) {
598            phone.notifyUnknownConnection();
599        }
600
601        if (hasNonHangupStateChanged || newRinging != null) {
602            phone.notifyCallStateChanged();
603        }
604
605        //dumpState();
606    }
607
608    //***** Called from CdmaConnection
609    /*package*/ void
610    hangup (CdmaConnection conn) throws CallStateException {
611        if (conn.owner != this) {
612            throw new CallStateException ("CdmaConnection " + conn
613                                    + "does not belong to CdmaCallTracker " + this);
614        }
615
616        if (conn == pendingMO) {
617            // We're hanging up an outgoing call that doesn't have it's
618            // GSM index assigned yet
619
620            if (Phone.DEBUG_PHONE) log("hangup: set hangupPendingMO to true");
621            hangupPendingMO = true;
622        } else {
623            try {
624                cm.hangupConnection (conn.getCDMAIndex(), obtainCompleteMessage());
625            } catch (CallStateException ex) {
626                // Ignore "connection not found"
627                // Call may have hung up already
628                Log.w(LOG_TAG,"CdmaCallTracker WARN: hangup() on absent connection "
629                                + conn);
630            }
631        }
632
633        conn.onHangupLocal();
634    }
635
636    /*package*/ void
637    separate (CdmaConnection conn) throws CallStateException {
638        if (conn.owner != this) {
639            throw new CallStateException ("CdmaConnection " + conn
640                                    + "does not belong to CdmaCallTracker " + this);
641        }
642        try {
643            cm.separateConnection (conn.getCDMAIndex(),
644                obtainCompleteMessage(EVENT_SEPARATE_RESULT));
645        } catch (CallStateException ex) {
646            // Ignore "connection not found"
647            // Call may have hung up already
648            Log.w(LOG_TAG,"CdmaCallTracker WARN: separate() on absent connection "
649                          + conn);
650        }
651    }
652
653    //***** Called from CDMAPhone
654
655    /*package*/ void
656    setMute(boolean mute) {
657        desiredMute = mute;
658        cm.setMute(desiredMute, null);
659    }
660
661    /*package*/ boolean
662    getMute() {
663        return desiredMute;
664    }
665
666
667    //***** Called from CdmaCall
668
669    /* package */ void
670    hangup (CdmaCall call) throws CallStateException {
671        if (call.getConnections().size() == 0) {
672            throw new CallStateException("no connections in call");
673        }
674
675        if (call == ringingCall) {
676            if (Phone.DEBUG_PHONE) log("(ringing) hangup waiting or background");
677            cm.hangupWaitingOrBackground(obtainCompleteMessage());
678        } else if (call == foregroundCall) {
679            if (call.isDialingOrAlerting()) {
680                if (Phone.DEBUG_PHONE) {
681                    log("(foregnd) hangup dialing or alerting...");
682                }
683                hangup((CdmaConnection)(call.getConnections().get(0)));
684            } else {
685                hangupForegroundResumeBackground();
686            }
687        } else if (call == backgroundCall) {
688            if (ringingCall.isRinging()) {
689                if (Phone.DEBUG_PHONE) {
690                    log("hangup all conns in background call");
691                }
692                hangupAllConnections(call);
693            } else {
694                hangupWaitingOrBackground();
695            }
696        } else {
697            throw new RuntimeException ("CdmaCall " + call +
698                    "does not belong to CdmaCallTracker " + this);
699        }
700
701        call.onHangupLocal();
702    }
703
704    /* package */
705    void hangupWaitingOrBackground() {
706        if (Phone.DEBUG_PHONE) log("hangupWaitingOrBackground");
707        cm.hangupWaitingOrBackground(obtainCompleteMessage());
708    }
709
710    /* package */
711    void hangupForegroundResumeBackground() {
712        if (Phone.DEBUG_PHONE) log("hangupForegroundResumeBackground");
713        cm.hangupForegroundResumeBackground(obtainCompleteMessage());
714    }
715
716    void hangupConnectionByIndex(CdmaCall call, int index)
717            throws CallStateException {
718        int count = call.connections.size();
719        for (int i = 0; i < count; i++) {
720            CdmaConnection cn = (CdmaConnection)call.connections.get(i);
721            if (cn.getCDMAIndex() == index) {
722                cm.hangupConnection(index, obtainCompleteMessage());
723                return;
724            }
725        }
726
727        throw new CallStateException("no gsm index found");
728    }
729
730    void hangupAllConnections(CdmaCall call) throws CallStateException{
731        try {
732            int count = call.connections.size();
733            for (int i = 0; i < count; i++) {
734                CdmaConnection cn = (CdmaConnection)call.connections.get(i);
735                cm.hangupConnection(cn.getCDMAIndex(), obtainCompleteMessage());
736            }
737        } catch (CallStateException ex) {
738            Log.e(LOG_TAG, "hangupConnectionByIndex caught " + ex);
739        }
740    }
741
742    /* package */
743    CdmaConnection getConnectionByIndex(CdmaCall call, int index)
744            throws CallStateException {
745        int count = call.connections.size();
746        for (int i = 0; i < count; i++) {
747            CdmaConnection cn = (CdmaConnection)call.connections.get(i);
748            if (cn.getCDMAIndex() == index) {
749                return cn;
750            }
751        }
752
753        return null;
754    }
755
756    private Phone.SuppService getFailedService(int what) {
757        switch (what) {
758            case EVENT_SWITCH_RESULT:
759                return Phone.SuppService.SWITCH;
760            case EVENT_CONFERENCE_RESULT:
761                return Phone.SuppService.CONFERENCE;
762            case EVENT_SEPARATE_RESULT:
763                return Phone.SuppService.SEPARATE;
764            case EVENT_ECT_RESULT:
765                return Phone.SuppService.TRANSFER;
766        }
767        return Phone.SuppService.UNKNOWN;
768    }
769
770    private void handleRadioNotAvailable() {
771        // handlePollCalls will clear out its
772        // call list when it gets the CommandException
773        // error result from this
774        pollCallsWhenSafe();
775    }
776
777    //****** Overridden from Handler
778
779    public void
780    handleMessage (Message msg) {
781        AsyncResult ar;
782
783        switch (msg.what) {
784            case EVENT_POLL_CALLS_RESULT:{
785                Log.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received");
786                ar = (AsyncResult)msg.obj;
787
788                if(msg == lastRelevantPoll) {
789                    if(DBG_POLL) log(
790                            "handle EVENT_POLL_CALL_RESULT: set needsPoll=F");
791                    needsPoll = false;
792                    lastRelevantPoll = null;
793                    handlePollCalls((AsyncResult)msg.obj);
794                }
795            }
796            break;
797
798            case EVENT_OPERATION_COMPLETE:
799                ar = (AsyncResult)msg.obj;
800                operationComplete();
801            break;
802
803            case EVENT_SWITCH_RESULT:
804                  ar = (AsyncResult)msg.obj;
805                  operationComplete();
806            break;
807
808            case EVENT_GET_LAST_CALL_FAIL_CAUSE:
809                int causeCode;
810                ar = (AsyncResult)msg.obj;
811
812                operationComplete();
813
814                if (ar.exception != null) {
815                    // An exception occurred...just treat the disconnect
816                    // cause as "normal"
817                    causeCode = CallFailCause.NORMAL_CLEARING;
818                    Log.i(LOG_TAG,
819                            "Exception during getLastCallFailCause, assuming normal disconnect");
820                } else {
821                    causeCode = ((int[])ar.result)[0];
822                }
823
824                for (int i = 0, s =  droppedDuringPoll.size()
825                        ; i < s ; i++
826                ) {
827                    CdmaConnection conn = droppedDuringPoll.get(i);
828
829                    conn.onRemoteDisconnect(causeCode);
830                }
831
832                updatePhoneState();
833
834                phone.notifyCallStateChanged();
835                droppedDuringPoll.clear();
836            break;
837
838            case EVENT_CALL_STATE_CHANGE:
839                pollCallsWhenSafe();
840            break;
841
842            case EVENT_RADIO_AVAILABLE:
843                handleRadioAvailable();
844            break;
845
846            case EVENT_RADIO_NOT_AVAILABLE:
847                handleRadioNotAvailable();
848            break;
849
850            default:{
851             throw new RuntimeException("unexpected event not handled");
852            }
853        }
854    }
855
856    protected void log(String msg) {
857        Log.d(LOG_TAG, "[CdmaCallTracker] " + msg);
858    }
859
860}
861