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