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