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