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