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