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