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