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