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