1/*
2 * Copyright (C) 2015 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;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.os.AsyncResult;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.Message;
27import android.os.Registrant;
28import android.os.RegistrantList;
29import android.os.SystemProperties;
30import android.telephony.CellLocation;
31import android.telephony.DisconnectCause;
32import android.telephony.PhoneNumberUtils;
33import android.telephony.ServiceState;
34import android.telephony.TelephonyManager;
35import android.telephony.cdma.CdmaCellLocation;
36import android.telephony.gsm.GsmCellLocation;
37import android.text.TextUtils;
38import java.util.Iterator;
39import android.telephony.Rlog;
40import android.util.EventLog;
41
42import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
43import com.android.internal.telephony.metrics.TelephonyMetrics;
44
45import java.io.FileDescriptor;
46import java.io.PrintWriter;
47import java.util.List;
48import java.util.ArrayList;
49
50/**
51 * {@hide}
52 */
53public class GsmCdmaCallTracker extends CallTracker {
54    private static final String LOG_TAG = "GsmCdmaCallTracker";
55    private static final boolean REPEAT_POLLING = false;
56
57    private static final boolean DBG_POLL = false;
58    private static final boolean VDBG = false;
59
60    //***** Constants
61
62    public static final int MAX_CONNECTIONS_GSM = 19;   //7 allowed in GSM + 12 from IMS for SRVCC
63    private static final int MAX_CONNECTIONS_PER_CALL_GSM = 5; //only 5 connections allowed per call
64
65    private static final int MAX_CONNECTIONS_CDMA = 8;
66    private static final int MAX_CONNECTIONS_PER_CALL_CDMA = 1; //only 1 connection allowed per call
67
68    //***** Instance Variables
69    private GsmCdmaConnection mConnections[];
70    private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
71    private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
72
73    // connections dropped during last poll
74    private ArrayList<GsmCdmaConnection> mDroppedDuringPoll =
75            new ArrayList<GsmCdmaConnection>(MAX_CONNECTIONS_GSM);
76
77    public GsmCdmaCall mRingingCall = new GsmCdmaCall(this);
78    // A call that is ringing or (call) waiting
79    public GsmCdmaCall mForegroundCall = new GsmCdmaCall(this);
80    public GsmCdmaCall mBackgroundCall = new GsmCdmaCall(this);
81
82    private GsmCdmaConnection mPendingMO;
83    private boolean mHangupPendingMO;
84
85    private GsmCdmaPhone mPhone;
86
87    private boolean mDesiredMute = false;    // false = mute off
88
89    public PhoneConstants.State mState = PhoneConstants.State.IDLE;
90
91    private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
92
93    // Following member variables are for CDMA only
94    private RegistrantList mCallWaitingRegistrants = new RegistrantList();
95    private boolean mPendingCallInEcm;
96    private boolean mIsInEmergencyCall;
97    private int mPendingCallClirMode;
98    private boolean mIsEcmTimerCanceled;
99    private int m3WayCallFlashDelay;
100
101    /**
102     * Listens for Emergency Callback Mode state change intents
103     */
104    private BroadcastReceiver mEcmExitReceiver = new BroadcastReceiver() {
105        @Override
106        public void onReceive(Context context, Intent intent) {
107            if (intent.getAction().equals(
108                    TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
109
110                boolean isInEcm = intent.getBooleanExtra(PhoneConstants.PHONE_IN_ECM_STATE, false);
111                log("Received ACTION_EMERGENCY_CALLBACK_MODE_CHANGED isInEcm = " + isInEcm);
112
113                // If we exit ECM mode, notify all connections.
114                if (!isInEcm) {
115                    // Although mConnections seems to be the place to look, it is not guaranteed
116                    // to have all of the connections we're tracking.  THe best place to look is in
117                    // the Call objects associated with the tracker.
118                    List<Connection> toNotify = new ArrayList<Connection>();
119                    toNotify.addAll(mRingingCall.getConnections());
120                    toNotify.addAll(mForegroundCall.getConnections());
121                    toNotify.addAll(mBackgroundCall.getConnections());
122                    if (mPendingMO != null) {
123                        toNotify.add(mPendingMO);
124                    }
125
126                    // Notify connections that ECM mode exited.
127                    for (Connection connection : toNotify) {
128                        if (connection != null) {
129                            connection.onExitedEcmMode();
130                        }
131                    }
132                }
133            }
134        }
135    };
136
137    //***** Events
138
139
140    //***** Constructors
141
142    public GsmCdmaCallTracker (GsmCdmaPhone phone) {
143        this.mPhone = phone;
144        mCi = phone.mCi;
145        mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
146        mCi.registerForOn(this, EVENT_RADIO_AVAILABLE, null);
147        mCi.registerForNotAvailable(this, EVENT_RADIO_NOT_AVAILABLE, null);
148
149        // Register receiver for ECM exit
150        IntentFilter filter = new IntentFilter();
151        filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
152        mPhone.getContext().registerReceiver(mEcmExitReceiver, filter);
153
154        updatePhoneType(true);
155    }
156
157    public void updatePhoneType() {
158        updatePhoneType(false);
159    }
160
161    private void updatePhoneType(boolean duringInit) {
162        if (!duringInit) {
163            reset();
164            pollCallsWhenSafe();
165        }
166        if (mPhone.isPhoneTypeGsm()) {
167            mConnections = new GsmCdmaConnection[MAX_CONNECTIONS_GSM];
168            mCi.unregisterForCallWaitingInfo(this);
169        } else {
170            mConnections = new GsmCdmaConnection[MAX_CONNECTIONS_CDMA];
171            mPendingCallInEcm = false;
172            mIsInEmergencyCall = false;
173            mPendingCallClirMode = CommandsInterface.CLIR_DEFAULT;
174            mIsEcmTimerCanceled = false;
175            m3WayCallFlashDelay = 0;
176            mCi.registerForCallWaitingInfo(this, EVENT_CALL_WAITING_INFO_CDMA, null);
177        }
178    }
179
180    private void reset() {
181        Rlog.d(LOG_TAG, "reset");
182
183        clearDisconnected();
184
185        for (GsmCdmaConnection gsmCdmaConnection : mConnections) {
186            if (gsmCdmaConnection != null) {
187                gsmCdmaConnection.dispose();
188            }
189        }
190
191        if (mPendingMO != null) {
192            mPendingMO.dispose();
193        }
194
195        mConnections = null;
196        mPendingMO = null;
197        mState = PhoneConstants.State.IDLE;
198    }
199
200    @Override
201    protected void finalize() {
202        Rlog.d(LOG_TAG, "GsmCdmaCallTracker finalized");
203    }
204
205    //***** Instance Methods
206
207    //***** Public Methods
208    @Override
209    public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
210        Registrant r = new Registrant(h, what, obj);
211        mVoiceCallStartedRegistrants.add(r);
212        // Notify if in call when registering
213        if (mState != PhoneConstants.State.IDLE) {
214            r.notifyRegistrant(new AsyncResult(null, null, null));
215        }
216    }
217
218    @Override
219    public void unregisterForVoiceCallStarted(Handler h) {
220        mVoiceCallStartedRegistrants.remove(h);
221    }
222
223    @Override
224    public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
225        Registrant r = new Registrant(h, what, obj);
226        mVoiceCallEndedRegistrants.add(r);
227    }
228
229    @Override
230    public void unregisterForVoiceCallEnded(Handler h) {
231        mVoiceCallEndedRegistrants.remove(h);
232    }
233
234    public void registerForCallWaiting(Handler h, int what, Object obj) {
235        Registrant r = new Registrant (h, what, obj);
236        mCallWaitingRegistrants.add(r);
237    }
238
239    public void unregisterForCallWaiting(Handler h) {
240        mCallWaitingRegistrants.remove(h);
241    }
242
243    private void fakeHoldForegroundBeforeDial() {
244        List<Connection> connCopy;
245
246        // We need to make a copy here, since fakeHoldBeforeDial()
247        // modifies the lists, and we don't want to reverse the order
248        connCopy = (List<Connection>) mForegroundCall.mConnections.clone();
249
250        for (int i = 0, s = connCopy.size() ; i < s ; i++) {
251            GsmCdmaConnection conn = (GsmCdmaConnection)connCopy.get(i);
252
253            conn.fakeHoldBeforeDial();
254        }
255    }
256
257    //GSM
258    /**
259     * clirMode is one of the CLIR_ constants
260     */
261    public synchronized Connection dial(String dialString, int clirMode, UUSInfo uusInfo,
262                                        Bundle intentExtras)
263            throws CallStateException {
264        // note that this triggers call state changed notif
265        clearDisconnected();
266
267        if (!canDial()) {
268            throw new CallStateException("cannot dial in current state");
269        }
270
271        String origNumber = dialString;
272        dialString = convertNumberIfNecessary(mPhone, dialString);
273
274        // The new call must be assigned to the foreground call.
275        // That call must be idle, so place anything that's
276        // there on hold
277        if (mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE) {
278            // this will probably be done by the radio anyway
279            // but the dial might fail before this happens
280            // and we need to make sure the foreground call is clear
281            // for the newly dialed connection
282            switchWaitingOrHoldingAndActive();
283            // This is a hack to delay DIAL so that it is sent out to RIL only after
284            // EVENT_SWITCH_RESULT is received. We've seen failures when adding a new call to
285            // multi-way conference calls due to DIAL being sent out before SWITCH is processed
286            try {
287                Thread.sleep(500);
288            } catch (InterruptedException e) {
289                // do nothing
290            }
291
292            // Fake local state so that
293            // a) foregroundCall is empty for the newly dialed connection
294            // b) hasNonHangupStateChanged remains false in the
295            // next poll, so that we don't clear a failed dialing call
296            fakeHoldForegroundBeforeDial();
297        }
298
299        if (mForegroundCall.getState() != GsmCdmaCall.State.IDLE) {
300            //we should have failed in !canDial() above before we get here
301            throw new CallStateException("cannot dial in current state");
302        }
303        boolean isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(),
304                dialString);
305        mPendingMO = new GsmCdmaConnection(mPhone, checkForTestEmergencyNumber(dialString),
306                this, mForegroundCall, isEmergencyCall);
307        mHangupPendingMO = false;
308        mMetrics.writeRilDial(mPhone.getPhoneId(), mPendingMO, clirMode, uusInfo);
309
310
311        if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0
312                || mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
313            // Phone number is invalid
314            mPendingMO.mCause = DisconnectCause.INVALID_NUMBER;
315
316            // handlePollCalls() will notice this call not present
317            // and will mark it as dropped.
318            pollCallsWhenSafe();
319        } else {
320            // Always unmute when initiating a new call
321            setMute(false);
322
323            mCi.dial(mPendingMO.getAddress(), clirMode, uusInfo, obtainCompleteMessage());
324        }
325
326        if (mNumberConverted) {
327            mPendingMO.setConverted(origNumber);
328            mNumberConverted = false;
329        }
330
331        updatePhoneState();
332        mPhone.notifyPreciseCallStateChanged();
333
334        return mPendingMO;
335    }
336
337    //CDMA
338    /**
339     * Handle Ecm timer to be canceled or re-started
340     */
341    private void handleEcmTimer(int action) {
342        mPhone.handleTimerInEmergencyCallbackMode(action);
343        switch(action) {
344            case GsmCdmaPhone.CANCEL_ECM_TIMER: mIsEcmTimerCanceled = true; break;
345            case GsmCdmaPhone.RESTART_ECM_TIMER: mIsEcmTimerCanceled = false; break;
346            default:
347                Rlog.e(LOG_TAG, "handleEcmTimer, unsupported action " + action);
348        }
349    }
350
351    //CDMA
352    /**
353     * Disable data call when emergency call is connected
354     */
355    private void disableDataCallInEmergencyCall(String dialString) {
356        if (PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(), dialString)) {
357            if (Phone.DEBUG_PHONE) log("disableDataCallInEmergencyCall");
358            setIsInEmergencyCall();
359        }
360    }
361
362    //CDMA
363    public void setIsInEmergencyCall() {
364        mIsInEmergencyCall = true;
365        mPhone.mDcTracker.setInternalDataEnabled(false);
366        mPhone.notifyEmergencyCallRegistrants(true);
367        mPhone.sendEmergencyCallStateChange(true);
368    }
369
370    //CDMA
371    /**
372     * clirMode is one of the CLIR_ constants
373     */
374    private Connection dial(String dialString, int clirMode) throws CallStateException {
375        // note that this triggers call state changed notif
376        clearDisconnected();
377
378        if (!canDial()) {
379            throw new CallStateException("cannot dial in current state");
380        }
381
382        TelephonyManager tm =
383                (TelephonyManager) mPhone.getContext().getSystemService(Context.TELEPHONY_SERVICE);
384        String origNumber = dialString;
385        String operatorIsoContry = tm.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
386        String simIsoContry = tm.getSimCountryIsoForPhone(mPhone.getPhoneId());
387        boolean internationalRoaming = !TextUtils.isEmpty(operatorIsoContry)
388                && !TextUtils.isEmpty(simIsoContry)
389                && !simIsoContry.equals(operatorIsoContry);
390        if (internationalRoaming) {
391            if ("us".equals(simIsoContry)) {
392                internationalRoaming = internationalRoaming && !"vi".equals(operatorIsoContry);
393            } else if ("vi".equals(simIsoContry)) {
394                internationalRoaming = internationalRoaming && !"us".equals(operatorIsoContry);
395            }
396        }
397        if (internationalRoaming) {
398            dialString = convertNumberIfNecessary(mPhone, dialString);
399        }
400
401        String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
402        boolean isPhoneInEcmMode = inEcm.equals("true");
403        boolean isEmergencyCall =
404                PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(), dialString);
405
406        // Cancel Ecm timer if a second emergency call is originating in Ecm mode
407        if (isPhoneInEcmMode && isEmergencyCall) {
408            handleEcmTimer(GsmCdmaPhone.CANCEL_ECM_TIMER);
409        }
410
411        // The new call must be assigned to the foreground call.
412        // That call must be idle, so place anything that's
413        // there on hold
414        if (mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE) {
415            return dialThreeWay(dialString);
416        }
417
418        mPendingMO = new GsmCdmaConnection(mPhone, checkForTestEmergencyNumber(dialString),
419                this, mForegroundCall, isEmergencyCall);
420        mHangupPendingMO = false;
421
422        if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0
423                || mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0 ) {
424            // Phone number is invalid
425            mPendingMO.mCause = DisconnectCause.INVALID_NUMBER;
426
427            // handlePollCalls() will notice this call not present
428            // and will mark it as dropped.
429            pollCallsWhenSafe();
430        } else {
431            // Always unmute when initiating a new call
432            setMute(false);
433
434            // Check data call
435            disableDataCallInEmergencyCall(dialString);
436
437            // In Ecm mode, if another emergency call is dialed, Ecm mode will not exit.
438            if(!isPhoneInEcmMode || (isPhoneInEcmMode && isEmergencyCall)) {
439                mCi.dial(mPendingMO.getAddress(), clirMode, obtainCompleteMessage());
440            } else {
441                mPhone.exitEmergencyCallbackMode();
442                mPhone.setOnEcbModeExitResponse(this,EVENT_EXIT_ECM_RESPONSE_CDMA, null);
443                mPendingCallClirMode=clirMode;
444                mPendingCallInEcm=true;
445            }
446        }
447
448        if (mNumberConverted) {
449            mPendingMO.setConverted(origNumber);
450            mNumberConverted = false;
451        }
452
453        updatePhoneState();
454        mPhone.notifyPreciseCallStateChanged();
455
456        return mPendingMO;
457    }
458
459    //CDMA
460    private Connection dialThreeWay(String dialString) {
461        if (!mForegroundCall.isIdle()) {
462            // Check data call and possibly set mIsInEmergencyCall
463            disableDataCallInEmergencyCall(dialString);
464
465            // Attach the new connection to foregroundCall
466            mPendingMO = new GsmCdmaConnection(mPhone,
467                    checkForTestEmergencyNumber(dialString), this, mForegroundCall,
468                    mIsInEmergencyCall);
469            // Some network need a empty flash before sending the normal one
470            m3WayCallFlashDelay = mPhone.getContext().getResources()
471                    .getInteger(com.android.internal.R.integer.config_cdma_3waycall_flash_delay);
472            if (m3WayCallFlashDelay > 0) {
473                mCi.sendCDMAFeatureCode("", obtainMessage(EVENT_THREE_WAY_DIAL_BLANK_FLASH));
474            } else {
475                mCi.sendCDMAFeatureCode(mPendingMO.getAddress(),
476                        obtainMessage(EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA));
477            }
478            return mPendingMO;
479        }
480        return null;
481    }
482
483    public Connection dial(String dialString) throws CallStateException {
484        if (isPhoneTypeGsm()) {
485            return dial(dialString, CommandsInterface.CLIR_DEFAULT, null);
486        } else {
487            return dial(dialString, CommandsInterface.CLIR_DEFAULT);
488        }
489    }
490
491    //GSM
492    public Connection dial(String dialString, UUSInfo uusInfo, Bundle intentExtras)
493            throws CallStateException {
494        return dial(dialString, CommandsInterface.CLIR_DEFAULT, uusInfo, intentExtras);
495    }
496
497    //GSM
498    private Connection dial(String dialString, int clirMode, Bundle intentExtras)
499            throws CallStateException {
500        return dial(dialString, clirMode, null, intentExtras);
501    }
502
503    public void acceptCall() throws CallStateException {
504        // FIXME if SWITCH fails, should retry with ANSWER
505        // in case the active/holding call disappeared and this
506        // is no longer call waiting
507
508        if (mRingingCall.getState() == GsmCdmaCall.State.INCOMING) {
509            Rlog.i("phone", "acceptCall: incoming...");
510            // Always unmute when answering a new call
511            setMute(false);
512            mCi.acceptCall(obtainCompleteMessage());
513        } else if (mRingingCall.getState() == GsmCdmaCall.State.WAITING) {
514            if (isPhoneTypeGsm()) {
515                setMute(false);
516            } else {
517                GsmCdmaConnection cwConn = (GsmCdmaConnection)(mRingingCall.getLatestConnection());
518
519                // Since there is no network response for supplimentary
520                // service for CDMA, we assume call waiting is answered.
521                // ringing Call state change to idle is in GsmCdmaCall.detach
522                // triggered by updateParent.
523                cwConn.updateParent(mRingingCall, mForegroundCall);
524                cwConn.onConnectedInOrOut();
525                updatePhoneState();
526            }
527            switchWaitingOrHoldingAndActive();
528        } else {
529            throw new CallStateException("phone not ringing");
530        }
531    }
532
533    public void rejectCall() throws CallStateException {
534        // AT+CHLD=0 means "release held or UDUB"
535        // so if the phone isn't ringing, this could hang up held
536        if (mRingingCall.getState().isRinging()) {
537            mCi.rejectCall(obtainCompleteMessage());
538        } else {
539            throw new CallStateException("phone not ringing");
540        }
541    }
542
543    //CDMA
544    private void flashAndSetGenericTrue() {
545        mCi.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT));
546
547        mPhone.notifyPreciseCallStateChanged();
548    }
549
550    public void switchWaitingOrHoldingAndActive() throws CallStateException {
551        // Should we bother with this check?
552        if (mRingingCall.getState() == GsmCdmaCall.State.INCOMING) {
553            throw new CallStateException("cannot be in the incoming state");
554        } else {
555            if (isPhoneTypeGsm()) {
556                mCi.switchWaitingOrHoldingAndActive(
557                        obtainCompleteMessage(EVENT_SWITCH_RESULT));
558            } else {
559                if (mForegroundCall.getConnections().size() > 1) {
560                    flashAndSetGenericTrue();
561                } else {
562                    // Send a flash command to CDMA network for putting the other party on hold.
563                    // For CDMA networks which do not support this the user would just hear a beep
564                    // from the network. For CDMA networks which do support it will put the other
565                    // party on hold.
566                    mCi.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT));
567                }
568            }
569        }
570    }
571
572    public void conference() {
573        if (isPhoneTypeGsm()) {
574            mCi.conference(obtainCompleteMessage(EVENT_CONFERENCE_RESULT));
575        } else {
576            // Should we be checking state?
577            flashAndSetGenericTrue();
578        }
579    }
580
581    public void explicitCallTransfer() {
582        mCi.explicitCallTransfer(obtainCompleteMessage(EVENT_ECT_RESULT));
583    }
584
585    public void clearDisconnected() {
586        internalClearDisconnected();
587
588        updatePhoneState();
589        mPhone.notifyPreciseCallStateChanged();
590    }
591
592    public boolean canConference() {
593        return mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE
594                && mBackgroundCall.getState() == GsmCdmaCall.State.HOLDING
595                && !mBackgroundCall.isFull()
596                && !mForegroundCall.isFull();
597    }
598
599    private boolean canDial() {
600        boolean ret;
601        int serviceState = mPhone.getServiceState().getState();
602        String disableCall = SystemProperties.get(
603                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
604
605        ret = (serviceState != ServiceState.STATE_POWER_OFF)
606                && mPendingMO == null
607                && !mRingingCall.isRinging()
608                && !disableCall.equals("true")
609                && (!mForegroundCall.getState().isAlive()
610                    || !mBackgroundCall.getState().isAlive()
611                    || (!isPhoneTypeGsm()
612                        && mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE));
613
614        if (!ret) {
615            log(String.format("canDial is false\n" +
616                            "((serviceState=%d) != ServiceState.STATE_POWER_OFF)::=%s\n" +
617                            "&& pendingMO == null::=%s\n" +
618                            "&& !ringingCall.isRinging()::=%s\n" +
619                            "&& !disableCall.equals(\"true\")::=%s\n" +
620                            "&& (!foregroundCall.getState().isAlive()::=%s\n" +
621                            "   || foregroundCall.getState() == GsmCdmaCall.State.ACTIVE::=%s\n" +
622                            "   ||!backgroundCall.getState().isAlive())::=%s)",
623                    serviceState,
624                    serviceState != ServiceState.STATE_POWER_OFF,
625                    mPendingMO == null,
626                    !mRingingCall.isRinging(),
627                    !disableCall.equals("true"),
628                    !mForegroundCall.getState().isAlive(),
629                    mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE,
630                    !mBackgroundCall.getState().isAlive()));
631        }
632
633        return ret;
634    }
635
636    public boolean canTransfer() {
637        if (isPhoneTypeGsm()) {
638            return (mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE
639                    || mForegroundCall.getState() == GsmCdmaCall.State.ALERTING
640                    || mForegroundCall.getState() == GsmCdmaCall.State.DIALING)
641                    && mBackgroundCall.getState() == GsmCdmaCall.State.HOLDING;
642        } else {
643            Rlog.e(LOG_TAG, "canTransfer: not possible in CDMA");
644            return false;
645        }
646    }
647
648    //***** Private Instance Methods
649
650    private void internalClearDisconnected() {
651        mRingingCall.clearDisconnected();
652        mForegroundCall.clearDisconnected();
653        mBackgroundCall.clearDisconnected();
654    }
655
656    /**
657     * Obtain a message to use for signalling "invoke getCurrentCalls() when
658     * this operation and all other pending operations are complete
659     */
660    private Message obtainCompleteMessage() {
661        return obtainCompleteMessage(EVENT_OPERATION_COMPLETE);
662    }
663
664    /**
665     * Obtain a message to use for signalling "invoke getCurrentCalls() when
666     * this operation and all other pending operations are complete
667     */
668    private Message obtainCompleteMessage(int what) {
669        mPendingOperations++;
670        mLastRelevantPoll = null;
671        mNeedsPoll = true;
672
673        if (DBG_POLL) log("obtainCompleteMessage: pendingOperations=" +
674                mPendingOperations + ", needsPoll=" + mNeedsPoll);
675
676        return obtainMessage(what);
677    }
678
679    private void operationComplete() {
680        mPendingOperations--;
681
682        if (DBG_POLL) log("operationComplete: pendingOperations=" +
683                mPendingOperations + ", needsPoll=" + mNeedsPoll);
684
685        if (mPendingOperations == 0 && mNeedsPoll) {
686            mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
687            mCi.getCurrentCalls(mLastRelevantPoll);
688        } else if (mPendingOperations < 0) {
689            // this should never happen
690            Rlog.e(LOG_TAG,"GsmCdmaCallTracker.pendingOperations < 0");
691            mPendingOperations = 0;
692        }
693    }
694
695    private void updatePhoneState() {
696        PhoneConstants.State oldState = mState;
697        if (mRingingCall.isRinging()) {
698            mState = PhoneConstants.State.RINGING;
699        } else if (mPendingMO != null ||
700                !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
701            mState = PhoneConstants.State.OFFHOOK;
702        } else {
703            Phone imsPhone = mPhone.getImsPhone();
704            if ( mState == PhoneConstants.State.OFFHOOK && (imsPhone != null)){
705                imsPhone.callEndCleanupHandOverCallIfAny();
706            }
707            mState = PhoneConstants.State.IDLE;
708        }
709
710        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
711            mVoiceCallEndedRegistrants.notifyRegistrants(
712                new AsyncResult(null, null, null));
713        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
714            mVoiceCallStartedRegistrants.notifyRegistrants (
715                    new AsyncResult(null, null, null));
716        }
717        if (Phone.DEBUG_PHONE) {
718            log("update phone state, old=" + oldState + " new="+ mState);
719        }
720        if (mState != oldState) {
721            mPhone.notifyPhoneStateChanged();
722            mMetrics.writePhoneState(mPhone.getPhoneId(), mState);
723        }
724    }
725
726    // ***** Overwritten from CallTracker
727
728    @Override
729    protected synchronized void handlePollCalls(AsyncResult ar) {
730        List polledCalls;
731
732        if (VDBG) log("handlePollCalls");
733        if (ar.exception == null) {
734            polledCalls = (List)ar.result;
735        } else if (isCommandExceptionRadioNotAvailable(ar.exception)) {
736            // just a dummy empty ArrayList to cause the loop
737            // to hang up all the calls
738            polledCalls = new ArrayList();
739        } else {
740            // Radio probably wasn't ready--try again in a bit
741            // But don't keep polling if the channel is closed
742            pollCallsAfterDelay();
743            return;
744        }
745
746        Connection newRinging = null; //or waiting
747        ArrayList<Connection> newUnknownConnectionsGsm = new ArrayList<Connection>();
748        Connection newUnknownConnectionCdma = null;
749        boolean hasNonHangupStateChanged = false;   // Any change besides
750                                                    // a dropped connection
751        boolean hasAnyCallDisconnected = false;
752        boolean needsPollDelay = false;
753        boolean unknownConnectionAppeared = false;
754        int handoverConnectionsSize = mHandoverConnections.size();
755
756        //CDMA
757        boolean noConnectionExists = true;
758
759        for (int i = 0, curDC = 0, dcSize = polledCalls.size()
760                ; i < mConnections.length; i++) {
761            GsmCdmaConnection conn = mConnections[i];
762            DriverCall dc = null;
763
764            // polledCall list is sparse
765            if (curDC < dcSize) {
766                dc = (DriverCall) polledCalls.get(curDC);
767
768                if (dc.index == i+1) {
769                    curDC++;
770                } else {
771                    dc = null;
772                }
773            }
774
775            //CDMA
776            if (conn != null || dc != null) {
777                noConnectionExists = false;
778            }
779
780            if (DBG_POLL) log("poll: conn[i=" + i + "]=" +
781                    conn+", dc=" + dc);
782
783            if (conn == null && dc != null) {
784                // Connection appeared in CLCC response that we don't know about
785                if (mPendingMO != null && mPendingMO.compareTo(dc)) {
786
787                    if (DBG_POLL) log("poll: pendingMO=" + mPendingMO);
788
789                    // It's our pending mobile originating call
790                    mConnections[i] = mPendingMO;
791                    mPendingMO.mIndex = i;
792                    mPendingMO.update(dc);
793                    mPendingMO = null;
794
795                    // Someone has already asked to hangup this call
796                    if (mHangupPendingMO) {
797                        mHangupPendingMO = false;
798
799                        // Re-start Ecm timer when an uncompleted emergency call ends
800                        if (!isPhoneTypeGsm() && mIsEcmTimerCanceled) {
801                            handleEcmTimer(GsmCdmaPhone.RESTART_ECM_TIMER);
802                        }
803
804                        try {
805                            if (Phone.DEBUG_PHONE) log(
806                                    "poll: hangupPendingMO, hangup conn " + i);
807                            hangup(mConnections[i]);
808                        } catch (CallStateException ex) {
809                            Rlog.e(LOG_TAG, "unexpected error on hangup");
810                        }
811
812                        // Do not continue processing this poll
813                        // Wait for hangup and repoll
814                        return;
815                    }
816                } else {
817                    if (Phone.DEBUG_PHONE) {
818                        log("pendingMo=" + mPendingMO + ", dc=" + dc);
819                    }
820
821                    mConnections[i] = new GsmCdmaConnection(mPhone, dc, this, i);
822
823                    Connection hoConnection = getHoConnection(dc);
824                    if (hoConnection != null) {
825                        // Single Radio Voice Call Continuity (SRVCC) completed
826                        mConnections[i].migrateFrom(hoConnection);
827                        // Updating connect time for silent redial cases (ex: Calls are transferred
828                        // from DIALING/ALERTING/INCOMING/WAITING to ACTIVE)
829                        if (hoConnection.mPreHandoverState != GsmCdmaCall.State.ACTIVE &&
830                                hoConnection.mPreHandoverState != GsmCdmaCall.State.HOLDING &&
831                                dc.state == DriverCall.State.ACTIVE) {
832                            mConnections[i].onConnectedInOrOut();
833                        }
834
835                        mHandoverConnections.remove(hoConnection);
836
837                        if (isPhoneTypeGsm()) {
838                            for (Iterator<Connection> it = mHandoverConnections.iterator();
839                                 it.hasNext(); ) {
840                                Connection c = it.next();
841                                Rlog.i(LOG_TAG, "HO Conn state is " + c.mPreHandoverState);
842                                if (c.mPreHandoverState == mConnections[i].getState()) {
843                                    Rlog.i(LOG_TAG, "Removing HO conn "
844                                            + hoConnection + c.mPreHandoverState);
845                                    it.remove();
846                                }
847                            }
848                        }
849
850                        mPhone.notifyHandoverStateChanged(mConnections[i]);
851                    } else {
852                        // find if the MT call is a new ring or unknown connection
853                        newRinging = checkMtFindNewRinging(dc,i);
854                        if (newRinging == null) {
855                            unknownConnectionAppeared = true;
856                            if (isPhoneTypeGsm()) {
857                                newUnknownConnectionsGsm.add(mConnections[i]);
858                            } else {
859                                newUnknownConnectionCdma = mConnections[i];
860                            }
861                        }
862                    }
863                }
864                hasNonHangupStateChanged = true;
865            } else if (conn != null && dc == null) {
866                if (isPhoneTypeGsm()) {
867                    // Connection missing in CLCC response that we were
868                    // tracking.
869                    mDroppedDuringPoll.add(conn);
870                } else {
871                    // This case means the RIL has no more active call anymore and
872                    // we need to clean up the foregroundCall and ringingCall.
873                    // Loop through foreground call connections as
874                    // it contains the known logical connections.
875                    int count = mForegroundCall.mConnections.size();
876                    for (int n = 0; n < count; n++) {
877                        if (Phone.DEBUG_PHONE) log("adding fgCall cn " + n + " to droppedDuringPoll");
878                        GsmCdmaConnection cn = (GsmCdmaConnection)mForegroundCall.mConnections.get(n);
879                        mDroppedDuringPoll.add(cn);
880                    }
881                    count = mRingingCall.mConnections.size();
882                    // Loop through ringing call connections as
883                    // it may contain the known logical connections.
884                    for (int n = 0; n < count; n++) {
885                        if (Phone.DEBUG_PHONE) log("adding rgCall cn " + n + " to droppedDuringPoll");
886                        GsmCdmaConnection cn = (GsmCdmaConnection)mRingingCall.mConnections.get(n);
887                        mDroppedDuringPoll.add(cn);
888                    }
889
890                    // Re-start Ecm timer when the connected emergency call ends
891                    if (mIsEcmTimerCanceled) {
892                        handleEcmTimer(GsmCdmaPhone.RESTART_ECM_TIMER);
893                    }
894                    // If emergency call is not going through while dialing
895                    checkAndEnableDataCallAfterEmergencyCallDropped();
896                }
897                // Dropped connections are removed from the CallTracker
898                // list but kept in the Call list
899                mConnections[i] = null;
900            } else if (conn != null && dc != null && !conn.compareTo(dc) && isPhoneTypeGsm()) {
901                // Connection in CLCC response does not match what
902                // we were tracking. Assume dropped call and new call
903
904                mDroppedDuringPoll.add(conn);
905                mConnections[i] = new GsmCdmaConnection (mPhone, dc, this, i);
906
907                if (mConnections[i].getCall() == mRingingCall) {
908                    newRinging = mConnections[i];
909                } // else something strange happened
910                hasNonHangupStateChanged = true;
911            } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */
912                // Call collision case
913                if (!isPhoneTypeGsm() && conn.isIncoming() != dc.isMT) {
914                    if (dc.isMT == true) {
915                        // Mt call takes precedence than Mo,drops Mo
916                        mDroppedDuringPoll.add(conn);
917                        // find if the MT call is a new ring or unknown connection
918                        newRinging = checkMtFindNewRinging(dc,i);
919                        if (newRinging == null) {
920                            unknownConnectionAppeared = true;
921                            newUnknownConnectionCdma = conn;
922                        }
923                        checkAndEnableDataCallAfterEmergencyCallDropped();
924                    } else {
925                        // Call info stored in conn is not consistent with the call info from dc.
926                        // We should follow the rule of MT calls taking precedence over MO calls
927                        // when there is conflict, so here we drop the call info from dc and
928                        // continue to use the call info from conn, and only take a log.
929                        Rlog.e(LOG_TAG,"Error in RIL, Phantom call appeared " + dc);
930                    }
931                } else {
932                    boolean changed;
933                    changed = conn.update(dc);
934                    hasNonHangupStateChanged = hasNonHangupStateChanged || changed;
935                }
936            }
937
938            if (REPEAT_POLLING) {
939                if (dc != null) {
940                    // FIXME with RIL, we should not need this anymore
941                    if ((dc.state == DriverCall.State.DIALING
942                            /*&& cm.getOption(cm.OPTION_POLL_DIALING)*/)
943                        || (dc.state == DriverCall.State.ALERTING
944                            /*&& cm.getOption(cm.OPTION_POLL_ALERTING)*/)
945                        || (dc.state == DriverCall.State.INCOMING
946                            /*&& cm.getOption(cm.OPTION_POLL_INCOMING)*/)
947                        || (dc.state == DriverCall.State.WAITING
948                            /*&& cm.getOption(cm.OPTION_POLL_WAITING)*/)) {
949                        // Sometimes there's no unsolicited notification
950                        // for state transitions
951                        needsPollDelay = true;
952                    }
953                }
954            }
955        }
956
957        // Safety check so that obj is not stuck with mIsInEmergencyCall set to true (and data
958        // disabled). This should never happen though.
959        if (!isPhoneTypeGsm() && noConnectionExists) {
960            checkAndEnableDataCallAfterEmergencyCallDropped();
961        }
962
963        // This is the first poll after an ATD.
964        // We expect the pending call to appear in the list
965        // If it does not, we land here
966        if (mPendingMO != null) {
967            Rlog.d(LOG_TAG, "Pending MO dropped before poll fg state:"
968                    + mForegroundCall.getState());
969
970            mDroppedDuringPoll.add(mPendingMO);
971            mPendingMO = null;
972            mHangupPendingMO = false;
973
974            if (!isPhoneTypeGsm()) {
975                if( mPendingCallInEcm) {
976                    mPendingCallInEcm = false;
977                }
978                checkAndEnableDataCallAfterEmergencyCallDropped();
979            }
980        }
981
982        if (newRinging != null) {
983            mPhone.notifyNewRingingConnection(newRinging);
984        }
985
986        // clear the "local hangup" and "missed/rejected call"
987        // cases from the "dropped during poll" list
988        // These cases need no "last call fail" reason
989        ArrayList<GsmCdmaConnection> locallyDisconnectedConnections = new ArrayList<>();
990        for (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) {
991            GsmCdmaConnection conn = mDroppedDuringPoll.get(i);
992            //CDMA
993            boolean wasDisconnected = false;
994
995            if (conn.isIncoming() && conn.getConnectTime() == 0) {
996                // Missed or rejected call
997                int cause;
998                if (conn.mCause == DisconnectCause.LOCAL) {
999                    cause = DisconnectCause.INCOMING_REJECTED;
1000                } else {
1001                    cause = DisconnectCause.INCOMING_MISSED;
1002                }
1003
1004                if (Phone.DEBUG_PHONE) {
1005                    log("missed/rejected call, conn.cause=" + conn.mCause);
1006                    log("setting cause to " + cause);
1007                }
1008                mDroppedDuringPoll.remove(i);
1009                hasAnyCallDisconnected |= conn.onDisconnect(cause);
1010                wasDisconnected = true;
1011                locallyDisconnectedConnections.add(conn);
1012            } else if (conn.mCause == DisconnectCause.LOCAL
1013                    || conn.mCause == DisconnectCause.INVALID_NUMBER) {
1014                mDroppedDuringPoll.remove(i);
1015                hasAnyCallDisconnected |= conn.onDisconnect(conn.mCause);
1016                wasDisconnected = true;
1017                locallyDisconnectedConnections.add(conn);
1018            }
1019
1020            if (!isPhoneTypeGsm() && wasDisconnected && unknownConnectionAppeared
1021                    && conn == newUnknownConnectionCdma) {
1022                unknownConnectionAppeared = false;
1023                newUnknownConnectionCdma = null;
1024            }
1025        }
1026        if (locallyDisconnectedConnections.size() > 0) {
1027            mMetrics.writeRilCallList(mPhone.getPhoneId(), locallyDisconnectedConnections);
1028        }
1029
1030        /* Disconnect any pending Handover connections */
1031        for (Iterator<Connection> it = mHandoverConnections.iterator();
1032                it.hasNext();) {
1033            Connection hoConnection = it.next();
1034            log("handlePollCalls - disconnect hoConn= " + hoConnection +
1035                    " hoConn.State= " + hoConnection.getState());
1036            if (hoConnection.getState().isRinging()) {
1037                hoConnection.onDisconnect(DisconnectCause.INCOMING_MISSED);
1038            } else {
1039                hoConnection.onDisconnect(DisconnectCause.NOT_VALID);
1040            }
1041            // TODO: Do we need to update these hoConnections in Metrics ?
1042            it.remove();
1043        }
1044
1045        // Any non-local disconnects: determine cause
1046        if (mDroppedDuringPoll.size() > 0) {
1047            mCi.getLastCallFailCause(
1048                obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
1049        }
1050
1051        if (needsPollDelay) {
1052            pollCallsAfterDelay();
1053        }
1054
1055        // Cases when we can no longer keep disconnected Connection's
1056        // with their previous calls
1057        // 1) the phone has started to ring
1058        // 2) A Call/Connection object has changed state...
1059        //    we may have switched or held or answered (but not hung up)
1060        if (newRinging != null || hasNonHangupStateChanged || hasAnyCallDisconnected) {
1061            internalClearDisconnected();
1062        }
1063
1064        if (VDBG) log("handlePollCalls calling updatePhoneState()");
1065        updatePhoneState();
1066
1067        if (unknownConnectionAppeared) {
1068            if (isPhoneTypeGsm()) {
1069                for (Connection c : newUnknownConnectionsGsm) {
1070                    log("Notify unknown for " + c);
1071                    mPhone.notifyUnknownConnection(c);
1072                }
1073            } else {
1074                mPhone.notifyUnknownConnection(newUnknownConnectionCdma);
1075            }
1076        }
1077
1078        if (hasNonHangupStateChanged || newRinging != null || hasAnyCallDisconnected) {
1079            mPhone.notifyPreciseCallStateChanged();
1080            updateMetrics(mConnections);
1081        }
1082
1083        // If all handover connections are mapped during this poll process clean it up
1084        if (handoverConnectionsSize > 0 && mHandoverConnections.size() == 0) {
1085            Phone imsPhone = mPhone.getImsPhone();
1086            if (imsPhone != null) {
1087                imsPhone.callEndCleanupHandOverCallIfAny();
1088            }
1089        }
1090        //dumpState();
1091    }
1092
1093    private void updateMetrics(GsmCdmaConnection []connections) {
1094        ArrayList<GsmCdmaConnection> activeConnections = new ArrayList<>();
1095        for(GsmCdmaConnection conn : connections) {
1096            if(conn != null) activeConnections.add(conn);
1097        }
1098        mMetrics.writeRilCallList(mPhone.getPhoneId(), activeConnections);
1099    }
1100
1101    private void handleRadioNotAvailable() {
1102        // handlePollCalls will clear out its
1103        // call list when it gets the CommandException
1104        // error result from this
1105        pollCallsWhenSafe();
1106    }
1107
1108    private void dumpState() {
1109        List l;
1110
1111        Rlog.i(LOG_TAG,"Phone State:" + mState);
1112
1113        Rlog.i(LOG_TAG,"Ringing call: " + mRingingCall.toString());
1114
1115        l = mRingingCall.getConnections();
1116        for (int i = 0, s = l.size(); i < s; i++) {
1117            Rlog.i(LOG_TAG,l.get(i).toString());
1118        }
1119
1120        Rlog.i(LOG_TAG,"Foreground call: " + mForegroundCall.toString());
1121
1122        l = mForegroundCall.getConnections();
1123        for (int i = 0, s = l.size(); i < s; i++) {
1124            Rlog.i(LOG_TAG,l.get(i).toString());
1125        }
1126
1127        Rlog.i(LOG_TAG,"Background call: " + mBackgroundCall.toString());
1128
1129        l = mBackgroundCall.getConnections();
1130        for (int i = 0, s = l.size(); i < s; i++) {
1131            Rlog.i(LOG_TAG,l.get(i).toString());
1132        }
1133
1134    }
1135
1136    //***** Called from GsmCdmaConnection
1137
1138    public void hangup(GsmCdmaConnection conn) throws CallStateException {
1139        if (conn.mOwner != this) {
1140            throw new CallStateException ("GsmCdmaConnection " + conn
1141                                    + "does not belong to GsmCdmaCallTracker " + this);
1142        }
1143
1144        if (conn == mPendingMO) {
1145            // We're hanging up an outgoing call that doesn't have it's
1146            // GsmCdma index assigned yet
1147
1148            if (Phone.DEBUG_PHONE) log("hangup: set hangupPendingMO to true");
1149            mHangupPendingMO = true;
1150        } else if (!isPhoneTypeGsm()
1151                && conn.getCall() == mRingingCall
1152                && mRingingCall.getState() == GsmCdmaCall.State.WAITING) {
1153            // Handle call waiting hang up case.
1154            //
1155            // The ringingCall state will change to IDLE in GsmCdmaCall.detach
1156            // if the ringing call connection size is 0. We don't specifically
1157            // set the ringing call state to IDLE here to avoid a race condition
1158            // where a new call waiting could get a hang up from an old call
1159            // waiting ringingCall.
1160            //
1161            // PhoneApp does the call log itself since only PhoneApp knows
1162            // the hangup reason is user ignoring or timing out. So conn.onDisconnect()
1163            // is not called here. Instead, conn.onLocalDisconnect() is called.
1164            conn.onLocalDisconnect();
1165
1166            updatePhoneState();
1167            mPhone.notifyPreciseCallStateChanged();
1168            return;
1169        } else {
1170            try {
1171                mMetrics.writeRilHangup(mPhone.getPhoneId(), conn, conn.getGsmCdmaIndex());
1172                mCi.hangupConnection (conn.getGsmCdmaIndex(), obtainCompleteMessage());
1173            } catch (CallStateException ex) {
1174                // Ignore "connection not found"
1175                // Call may have hung up already
1176                Rlog.w(LOG_TAG,"GsmCdmaCallTracker WARN: hangup() on absent connection "
1177                                + conn);
1178            }
1179        }
1180
1181        conn.onHangupLocal();
1182    }
1183
1184    public void separate(GsmCdmaConnection conn) throws CallStateException {
1185        if (conn.mOwner != this) {
1186            throw new CallStateException ("GsmCdmaConnection " + conn
1187                                    + "does not belong to GsmCdmaCallTracker " + this);
1188        }
1189        try {
1190            mCi.separateConnection (conn.getGsmCdmaIndex(),
1191                obtainCompleteMessage(EVENT_SEPARATE_RESULT));
1192        } catch (CallStateException ex) {
1193            // Ignore "connection not found"
1194            // Call may have hung up already
1195            Rlog.w(LOG_TAG,"GsmCdmaCallTracker WARN: separate() on absent connection " + conn);
1196        }
1197    }
1198
1199    //***** Called from GsmCdmaPhone
1200
1201    public void setMute(boolean mute) {
1202        mDesiredMute = mute;
1203        mCi.setMute(mDesiredMute, null);
1204    }
1205
1206    public boolean getMute() {
1207        return mDesiredMute;
1208    }
1209
1210
1211    //***** Called from GsmCdmaCall
1212
1213    public void hangup(GsmCdmaCall call) throws CallStateException {
1214        if (call.getConnections().size() == 0) {
1215            throw new CallStateException("no connections in call");
1216        }
1217
1218        if (call == mRingingCall) {
1219            if (Phone.DEBUG_PHONE) log("(ringing) hangup waiting or background");
1220            logHangupEvent(call);
1221            mCi.hangupWaitingOrBackground(obtainCompleteMessage());
1222        } else if (call == mForegroundCall) {
1223            if (call.isDialingOrAlerting()) {
1224                if (Phone.DEBUG_PHONE) {
1225                    log("(foregnd) hangup dialing or alerting...");
1226                }
1227                hangup((GsmCdmaConnection)(call.getConnections().get(0)));
1228            } else if (isPhoneTypeGsm()
1229                    && mRingingCall.isRinging()) {
1230                // Do not auto-answer ringing on CHUP, instead just end active calls
1231                log("hangup all conns in active/background call, without affecting ringing call");
1232                hangupAllConnections(call);
1233            } else {
1234                logHangupEvent(call);
1235                hangupForegroundResumeBackground();
1236            }
1237        } else if (call == mBackgroundCall) {
1238            if (mRingingCall.isRinging()) {
1239                if (Phone.DEBUG_PHONE) {
1240                    log("hangup all conns in background call");
1241                }
1242                hangupAllConnections(call);
1243            } else {
1244                hangupWaitingOrBackground();
1245            }
1246        } else {
1247            throw new RuntimeException ("GsmCdmaCall " + call +
1248                    "does not belong to GsmCdmaCallTracker " + this);
1249        }
1250
1251        call.onHangupLocal();
1252        mPhone.notifyPreciseCallStateChanged();
1253    }
1254
1255    private void logHangupEvent(GsmCdmaCall call) {
1256        int count = call.mConnections.size();
1257        for (int i = 0; i < count; i++) {
1258            GsmCdmaConnection cn = (GsmCdmaConnection) call.mConnections.get(i);
1259            int call_index;
1260            try {
1261                call_index = cn.getGsmCdmaIndex();
1262            } catch (CallStateException ex) {
1263                call_index = -1;
1264            }
1265            mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, call_index);
1266        }
1267        if (VDBG) Rlog.v(LOG_TAG, "logHangupEvent logged " + count + " Connections ");
1268    }
1269
1270    public void hangupWaitingOrBackground() {
1271        if (Phone.DEBUG_PHONE) log("hangupWaitingOrBackground");
1272        logHangupEvent(mBackgroundCall);
1273        mCi.hangupWaitingOrBackground(obtainCompleteMessage());
1274    }
1275
1276    public void hangupForegroundResumeBackground() {
1277        if (Phone.DEBUG_PHONE) log("hangupForegroundResumeBackground");
1278        mCi.hangupForegroundResumeBackground(obtainCompleteMessage());
1279    }
1280
1281    public void hangupConnectionByIndex(GsmCdmaCall call, int index)
1282            throws CallStateException {
1283        int count = call.mConnections.size();
1284        for (int i = 0; i < count; i++) {
1285            GsmCdmaConnection cn = (GsmCdmaConnection)call.mConnections.get(i);
1286            if (cn.getGsmCdmaIndex() == index) {
1287                mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, cn.getGsmCdmaIndex());
1288                mCi.hangupConnection(index, obtainCompleteMessage());
1289                return;
1290            }
1291        }
1292
1293        throw new CallStateException("no GsmCdma index found");
1294    }
1295
1296    public void hangupAllConnections(GsmCdmaCall call) {
1297        try {
1298            int count = call.mConnections.size();
1299            for (int i = 0; i < count; i++) {
1300                GsmCdmaConnection cn = (GsmCdmaConnection)call.mConnections.get(i);
1301                mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, cn.getGsmCdmaIndex());
1302                mCi.hangupConnection(cn.getGsmCdmaIndex(), obtainCompleteMessage());
1303            }
1304        } catch (CallStateException ex) {
1305            Rlog.e(LOG_TAG, "hangupConnectionByIndex caught " + ex);
1306        }
1307    }
1308
1309    public GsmCdmaConnection getConnectionByIndex(GsmCdmaCall call, int index)
1310            throws CallStateException {
1311        int count = call.mConnections.size();
1312        for (int i = 0; i < count; i++) {
1313            GsmCdmaConnection cn = (GsmCdmaConnection)call.mConnections.get(i);
1314            if (cn.getGsmCdmaIndex() == index) {
1315                return cn;
1316            }
1317        }
1318
1319        return null;
1320    }
1321
1322    //CDMA
1323    private void notifyCallWaitingInfo(CdmaCallWaitingNotification obj) {
1324        if (mCallWaitingRegistrants != null) {
1325            mCallWaitingRegistrants.notifyRegistrants(new AsyncResult(null, obj, null));
1326        }
1327    }
1328
1329    //CDMA
1330    private void handleCallWaitingInfo(CdmaCallWaitingNotification cw) {
1331        // Create a new GsmCdmaConnection which attaches itself to ringingCall.
1332        new GsmCdmaConnection(mPhone.getContext(), cw, this, mRingingCall);
1333        updatePhoneState();
1334
1335        // Finally notify application
1336        notifyCallWaitingInfo(cw);
1337    }
1338
1339    private Phone.SuppService getFailedService(int what) {
1340        switch (what) {
1341            case EVENT_SWITCH_RESULT:
1342                return Phone.SuppService.SWITCH;
1343            case EVENT_CONFERENCE_RESULT:
1344                return Phone.SuppService.CONFERENCE;
1345            case EVENT_SEPARATE_RESULT:
1346                return Phone.SuppService.SEPARATE;
1347            case EVENT_ECT_RESULT:
1348                return Phone.SuppService.TRANSFER;
1349        }
1350        return Phone.SuppService.UNKNOWN;
1351    }
1352
1353    //****** Overridden from Handler
1354
1355    @Override
1356    public void handleMessage(Message msg) {
1357        AsyncResult ar;
1358
1359        switch (msg.what) {
1360            case EVENT_POLL_CALLS_RESULT:
1361                Rlog.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received");
1362
1363                if (msg == mLastRelevantPoll) {
1364                    if (DBG_POLL) log(
1365                            "handle EVENT_POLL_CALL_RESULT: set needsPoll=F");
1366                    mNeedsPoll = false;
1367                    mLastRelevantPoll = null;
1368                    handlePollCalls((AsyncResult)msg.obj);
1369                }
1370            break;
1371
1372            case EVENT_OPERATION_COMPLETE:
1373                operationComplete();
1374            break;
1375
1376            case EVENT_CONFERENCE_RESULT:
1377                if (isPhoneTypeGsm()) {
1378                    // The conference merge failed, so notify listeners.  Ultimately this bubbles up
1379                    // to Telecom, which will inform the InCall UI of the failure.
1380                    Connection connection = mForegroundCall.getLatestConnection();
1381                    if (connection != null) {
1382                        connection.onConferenceMergeFailed();
1383                    }
1384                }
1385                // fall through
1386            case EVENT_SEPARATE_RESULT:
1387            case EVENT_ECT_RESULT:
1388            case EVENT_SWITCH_RESULT:
1389                if (isPhoneTypeGsm()) {
1390                    ar = (AsyncResult) msg.obj;
1391                    if (ar.exception != null) {
1392                        mPhone.notifySuppServiceFailed(getFailedService(msg.what));
1393                    }
1394                    operationComplete();
1395                } else {
1396                    if (msg.what != EVENT_SWITCH_RESULT) {
1397                        // EVENT_SWITCH_RESULT in GSM call triggers operationComplete() which gets
1398                        // the current call list. But in CDMA there is no list so there is nothing
1399                        // to do. Other messages however are not expected in CDMA.
1400                        throw new RuntimeException("unexpected event " + msg.what + " not handled by " +
1401                                "phone type " + mPhone.getPhoneType());
1402                    }
1403                }
1404            break;
1405
1406            case EVENT_GET_LAST_CALL_FAIL_CAUSE:
1407                int causeCode;
1408                String vendorCause = null;
1409                ar = (AsyncResult)msg.obj;
1410
1411                operationComplete();
1412
1413                if (ar.exception != null) {
1414                    // An exception occurred...just treat the disconnect
1415                    // cause as "normal"
1416                    causeCode = CallFailCause.NORMAL_CLEARING;
1417                    Rlog.i(LOG_TAG,
1418                            "Exception during getLastCallFailCause, assuming normal disconnect");
1419                } else {
1420                    LastCallFailCause failCause = (LastCallFailCause)ar.result;
1421                    causeCode = failCause.causeCode;
1422                    vendorCause = failCause.vendorCause;
1423                }
1424                // Log the causeCode if its not normal
1425                if (causeCode == CallFailCause.NO_CIRCUIT_AVAIL ||
1426                    causeCode == CallFailCause.TEMPORARY_FAILURE ||
1427                    causeCode == CallFailCause.SWITCHING_CONGESTION ||
1428                    causeCode == CallFailCause.CHANNEL_NOT_AVAIL ||
1429                    causeCode == CallFailCause.QOS_NOT_AVAIL ||
1430                    causeCode == CallFailCause.BEARER_NOT_AVAIL ||
1431                    causeCode == CallFailCause.ERROR_UNSPECIFIED) {
1432
1433                    CellLocation loc = mPhone.getCellLocation();
1434                    int cid = -1;
1435                    if (loc != null) {
1436                        if (isPhoneTypeGsm()) {
1437                            cid = ((GsmCellLocation)loc).getCid();
1438                        } else {
1439                            cid = ((CdmaCellLocation)loc).getBaseStationId();
1440                        }
1441                    }
1442                    EventLog.writeEvent(EventLogTags.CALL_DROP, causeCode, cid,
1443                            TelephonyManager.getDefault().getNetworkType());
1444                }
1445
1446                for (int i = 0, s = mDroppedDuringPoll.size(); i < s ; i++) {
1447                    GsmCdmaConnection conn = mDroppedDuringPoll.get(i);
1448
1449                    conn.onRemoteDisconnect(causeCode, vendorCause);
1450                }
1451
1452                updatePhoneState();
1453
1454                mPhone.notifyPreciseCallStateChanged();
1455                mMetrics.writeRilCallList(mPhone.getPhoneId(), mDroppedDuringPoll);
1456                mDroppedDuringPoll.clear();
1457            break;
1458
1459            case EVENT_REPOLL_AFTER_DELAY:
1460            case EVENT_CALL_STATE_CHANGE:
1461                pollCallsWhenSafe();
1462            break;
1463
1464            case EVENT_RADIO_AVAILABLE:
1465                handleRadioAvailable();
1466            break;
1467
1468            case EVENT_RADIO_NOT_AVAILABLE:
1469                handleRadioNotAvailable();
1470            break;
1471
1472            case EVENT_EXIT_ECM_RESPONSE_CDMA:
1473                if (!isPhoneTypeGsm()) {
1474                    // no matter the result, we still do the same here
1475                    if (mPendingCallInEcm) {
1476                        mCi.dial(mPendingMO.getAddress(), mPendingCallClirMode, obtainCompleteMessage());
1477                        mPendingCallInEcm = false;
1478                    }
1479                    mPhone.unsetOnEcbModeExitResponse(this);
1480                } else {
1481                    throw new RuntimeException("unexpected event " + msg.what + " not handled by " +
1482                            "phone type " + mPhone.getPhoneType());
1483                }
1484                break;
1485
1486            case EVENT_CALL_WAITING_INFO_CDMA:
1487                if (!isPhoneTypeGsm()) {
1488                    ar = (AsyncResult)msg.obj;
1489                    if (ar.exception == null) {
1490                        handleCallWaitingInfo((CdmaCallWaitingNotification)ar.result);
1491                        Rlog.d(LOG_TAG, "Event EVENT_CALL_WAITING_INFO_CDMA Received");
1492                    }
1493                } else {
1494                    throw new RuntimeException("unexpected event " + msg.what + " not handled by " +
1495                            "phone type " + mPhone.getPhoneType());
1496                }
1497                break;
1498
1499            case EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA:
1500                if (!isPhoneTypeGsm()) {
1501                    ar = (AsyncResult)msg.obj;
1502                    if (ar.exception == null) {
1503                        // Assume 3 way call is connected
1504                        mPendingMO.onConnectedInOrOut();
1505                        mPendingMO = null;
1506                    }
1507                } else {
1508                    throw new RuntimeException("unexpected event " + msg.what + " not handled by " +
1509                            "phone type " + mPhone.getPhoneType());
1510                }
1511                break;
1512
1513            case EVENT_THREE_WAY_DIAL_BLANK_FLASH:
1514                if (!isPhoneTypeGsm()) {
1515                    ar = (AsyncResult) msg.obj;
1516                    if (ar.exception == null) {
1517                        postDelayed(
1518                                new Runnable() {
1519                                    public void run() {
1520                                        if (mPendingMO != null) {
1521                                            mCi.sendCDMAFeatureCode(mPendingMO.getAddress(),
1522                                                    obtainMessage(EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA));
1523                                        }
1524                                    }
1525                                }, m3WayCallFlashDelay);
1526                    } else {
1527                        mPendingMO = null;
1528                        Rlog.w(LOG_TAG, "exception happened on Blank Flash for 3-way call");
1529                    }
1530                } else {
1531                    throw new RuntimeException("unexpected event " + msg.what + " not handled by " +
1532                            "phone type " + mPhone.getPhoneType());
1533                }
1534                break;
1535
1536            default:{
1537                throw new RuntimeException("unexpected event " + msg.what + " not handled by " +
1538                        "phone type " + mPhone.getPhoneType());
1539            }
1540        }
1541    }
1542
1543    //CDMA
1544    /**
1545     * Check and enable data call after an emergency call is dropped if it's
1546     * not in ECM
1547     */
1548    private void checkAndEnableDataCallAfterEmergencyCallDropped() {
1549        if (mIsInEmergencyCall) {
1550            mIsInEmergencyCall = false;
1551            String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
1552            if (Phone.DEBUG_PHONE) {
1553                log("checkAndEnableDataCallAfterEmergencyCallDropped,inEcm=" + inEcm);
1554            }
1555            if (inEcm.compareTo("false") == 0) {
1556                // Re-initiate data connection
1557                mPhone.mDcTracker.setInternalDataEnabled(true);
1558                mPhone.notifyEmergencyCallRegistrants(false);
1559            }
1560            mPhone.sendEmergencyCallStateChange(false);
1561        }
1562    }
1563
1564    /**
1565     * Check the MT call to see if it's a new ring or
1566     * a unknown connection.
1567     */
1568    private Connection checkMtFindNewRinging(DriverCall dc, int i) {
1569
1570        Connection newRinging = null;
1571
1572        // it's a ringing call
1573        if (mConnections[i].getCall() == mRingingCall) {
1574            newRinging = mConnections[i];
1575            if (Phone.DEBUG_PHONE) log("Notify new ring " + dc);
1576        } else {
1577            // Something strange happened: a call which is neither
1578            // a ringing call nor the one we created. It could be the
1579            // call collision result from RIL
1580            Rlog.e(LOG_TAG,"Phantom call appeared " + dc);
1581            // If it's a connected call, set the connect time so that
1582            // it's non-zero.  It may not be accurate, but at least
1583            // it won't appear as a Missed Call.
1584            if (dc.state != DriverCall.State.ALERTING
1585                    && dc.state != DriverCall.State.DIALING) {
1586                mConnections[i].onConnectedInOrOut();
1587                if (dc.state == DriverCall.State.HOLDING) {
1588                    // We've transitioned into HOLDING
1589                    mConnections[i].onStartedHolding();
1590                }
1591            }
1592        }
1593        return newRinging;
1594    }
1595
1596    //CDMA
1597    /**
1598     * Check if current call is in emergency call
1599     *
1600     * @return true if it is in emergency call
1601     *         false if it is not in emergency call
1602     */
1603    public boolean isInEmergencyCall() {
1604        return mIsInEmergencyCall;
1605    }
1606
1607    private boolean isPhoneTypeGsm() {
1608        return mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM;
1609    }
1610
1611    public GsmCdmaPhone getPhone() {
1612        return mPhone;
1613    }
1614
1615    @Override
1616    protected void log(String msg) {
1617        Rlog.d(LOG_TAG, "[GsmCdmaCallTracker] " + msg);
1618    }
1619
1620    @Override
1621    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1622        pw.println("GsmCdmaCallTracker extends:");
1623        super.dump(fd, pw, args);
1624        pw.println("mConnections: length=" + mConnections.length);
1625        for(int i=0; i < mConnections.length; i++) {
1626            pw.printf("  mConnections[%d]=%s\n", i, mConnections[i]);
1627        }
1628        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
1629        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
1630        if (!isPhoneTypeGsm()) {
1631            pw.println(" mCallWaitingRegistrants=" + mCallWaitingRegistrants);
1632        }
1633        pw.println(" mDroppedDuringPoll: size=" + mDroppedDuringPoll.size());
1634        for(int i = 0; i < mDroppedDuringPoll.size(); i++) {
1635            pw.printf( "  mDroppedDuringPoll[%d]=%s\n", i, mDroppedDuringPoll.get(i));
1636        }
1637        pw.println(" mRingingCall=" + mRingingCall);
1638        pw.println(" mForegroundCall=" + mForegroundCall);
1639        pw.println(" mBackgroundCall=" + mBackgroundCall);
1640        pw.println(" mPendingMO=" + mPendingMO);
1641        pw.println(" mHangupPendingMO=" + mHangupPendingMO);
1642        pw.println(" mPhone=" + mPhone);
1643        pw.println(" mDesiredMute=" + mDesiredMute);
1644        pw.println(" mState=" + mState);
1645        if (!isPhoneTypeGsm()) {
1646            pw.println(" mPendingCallInEcm=" + mPendingCallInEcm);
1647            pw.println(" mIsInEmergencyCall=" + mIsInEmergencyCall);
1648            pw.println(" mPendingCallClirMode=" + mPendingCallClirMode);
1649            pw.println(" mIsEcmTimerCanceled=" + mIsEcmTimerCanceled);
1650        }
1651
1652    }
1653
1654    @Override
1655    public PhoneConstants.State getState() {
1656        return mState;
1657    }
1658
1659    public int getMaxConnectionsPerCall() {
1660        return mPhone.isPhoneTypeGsm() ?
1661                MAX_CONNECTIONS_PER_CALL_GSM :
1662                MAX_CONNECTIONS_PER_CALL_CDMA;
1663    }
1664}
1665