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