Home | History | Annotate | Download | only in cdma
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.telephony.cdma;
     18 
     19 import android.os.AsyncResult;
     20 import android.os.Handler;
     21 import android.os.Message;
     22 import android.os.Registrant;
     23 import android.os.RegistrantList;
     24 import android.telephony.PhoneNumberUtils;
     25 import android.telephony.ServiceState;
     26 import android.util.Log;
     27 import android.os.SystemProperties;
     28 
     29 import com.android.internal.telephony.CallStateException;
     30 import com.android.internal.telephony.CallTracker;
     31 import com.android.internal.telephony.CommandsInterface;
     32 import com.android.internal.telephony.Connection;
     33 import com.android.internal.telephony.DriverCall;
     34 import com.android.internal.telephony.Phone;
     35 import com.android.internal.telephony.PhoneConstants;
     36 import com.android.internal.telephony.TelephonyProperties;
     37 
     38 import java.io.FileDescriptor;
     39 import java.io.PrintWriter;
     40 import java.util.ArrayList;
     41 import java.util.List;
     42 
     43 
     44 /**
     45  * {@hide}
     46  */
     47 public final class CdmaCallTracker extends CallTracker {
     48     static final String LOG_TAG = "CDMA";
     49 
     50     private static final boolean REPEAT_POLLING = false;
     51 
     52     private static final boolean DBG_POLL = false;
     53 
     54     //***** Constants
     55 
     56     static final int MAX_CONNECTIONS = 1;   // only 1 connection allowed in CDMA
     57     static final int MAX_CONNECTIONS_PER_CALL = 1; // only 1 connection allowed per call
     58 
     59     //***** Instance Variables
     60 
     61     CdmaConnection connections[] = new CdmaConnection[MAX_CONNECTIONS];
     62     RegistrantList voiceCallEndedRegistrants = new RegistrantList();
     63     RegistrantList voiceCallStartedRegistrants = new RegistrantList();
     64     RegistrantList callWaitingRegistrants =  new RegistrantList();
     65 
     66 
     67     // connections dropped during last poll
     68     ArrayList<CdmaConnection> droppedDuringPoll
     69         = new ArrayList<CdmaConnection>(MAX_CONNECTIONS);
     70 
     71     CdmaCall ringingCall = new CdmaCall(this);
     72     // A call that is ringing or (call) waiting
     73     CdmaCall foregroundCall = new CdmaCall(this);
     74     CdmaCall backgroundCall = new CdmaCall(this);
     75 
     76     CdmaConnection pendingMO;
     77     boolean hangupPendingMO;
     78     boolean pendingCallInEcm=false;
     79     boolean mIsInEmergencyCall = false;
     80     CDMAPhone phone;
     81 
     82     boolean desiredMute = false;    // false = mute off
     83 
     84     int pendingCallClirMode;
     85     PhoneConstants.State state = PhoneConstants.State.IDLE;
     86 
     87     private boolean mIsEcmTimerCanceled = false;
     88 
     89 //    boolean needsPoll;
     90 
     91 
     92 
     93     //***** Events
     94 
     95     //***** Constructors
     96     CdmaCallTracker(CDMAPhone phone) {
     97         this.phone = phone;
     98         cm = phone.mCM;
     99         cm.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
    100         cm.registerForOn(this, EVENT_RADIO_AVAILABLE, null);
    101         cm.registerForNotAvailable(this, EVENT_RADIO_NOT_AVAILABLE, null);
    102         cm.registerForCallWaitingInfo(this, EVENT_CALL_WAITING_INFO_CDMA, null);
    103         foregroundCall.setGeneric(false);
    104     }
    105 
    106     public void dispose() {
    107         cm.unregisterForCallStateChanged(this);
    108         cm.unregisterForOn(this);
    109         cm.unregisterForNotAvailable(this);
    110         cm.unregisterForCallWaitingInfo(this);
    111         for(CdmaConnection c : connections) {
    112             try {
    113                 if(c != null) hangup(c);
    114             } catch (CallStateException ex) {
    115                 Log.e(LOG_TAG, "unexpected error on hangup during dispose");
    116             }
    117         }
    118 
    119         try {
    120             if(pendingMO != null) hangup(pendingMO);
    121         } catch (CallStateException ex) {
    122             Log.e(LOG_TAG, "unexpected error on hangup during dispose");
    123         }
    124 
    125         clearDisconnected();
    126 
    127     }
    128 
    129     @Override
    130     protected void finalize() {
    131         Log.d(LOG_TAG, "CdmaCallTracker finalized");
    132     }
    133 
    134     //***** Instance Methods
    135 
    136     //***** Public Methods
    137     public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
    138         Registrant r = new Registrant(h, what, obj);
    139         voiceCallStartedRegistrants.add(r);
    140         // Notify if in call when registering
    141         if (state != PhoneConstants.State.IDLE) {
    142             r.notifyRegistrant(new AsyncResult(null, null, null));
    143         }
    144     }
    145     public void unregisterForVoiceCallStarted(Handler h) {
    146         voiceCallStartedRegistrants.remove(h);
    147     }
    148 
    149     public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
    150         Registrant r = new Registrant(h, what, obj);
    151         voiceCallEndedRegistrants.add(r);
    152     }
    153 
    154     public void unregisterForVoiceCallEnded(Handler h) {
    155         voiceCallEndedRegistrants.remove(h);
    156     }
    157 
    158     public void registerForCallWaiting(Handler h, int what, Object obj) {
    159         Registrant r = new Registrant (h, what, obj);
    160         callWaitingRegistrants.add(r);
    161     }
    162 
    163     public void unregisterForCallWaiting(Handler h) {
    164         callWaitingRegistrants.remove(h);
    165     }
    166 
    167     private void
    168     fakeHoldForegroundBeforeDial() {
    169         List<Connection> connCopy;
    170 
    171         // We need to make a copy here, since fakeHoldBeforeDial()
    172         // modifies the lists, and we don't want to reverse the order
    173         connCopy = (List<Connection>) foregroundCall.connections.clone();
    174 
    175         for (int i = 0, s = connCopy.size() ; i < s ; i++) {
    176             CdmaConnection conn = (CdmaConnection)connCopy.get(i);
    177 
    178             conn.fakeHoldBeforeDial();
    179         }
    180     }
    181 
    182     /**
    183      * clirMode is one of the CLIR_ constants
    184      */
    185     Connection
    186     dial (String dialString, int clirMode) throws CallStateException {
    187         // note that this triggers call state changed notif
    188         clearDisconnected();
    189 
    190         if (!canDial()) {
    191             throw new CallStateException("cannot dial in current state");
    192         }
    193 
    194         String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
    195         boolean isPhoneInEcmMode = inEcm.equals("true");
    196         boolean isEmergencyCall =
    197                 PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext());
    198 
    199         // Cancel Ecm timer if a second emergency call is originating in Ecm mode
    200         if (isPhoneInEcmMode && isEmergencyCall) {
    201             handleEcmTimer(phone.CANCEL_ECM_TIMER);
    202         }
    203 
    204         // We are initiating a call therefore even if we previously
    205         // didn't know the state (i.e. Generic was true) we now know
    206         // and therefore can set Generic to false.
    207         foregroundCall.setGeneric(false);
    208 
    209         // The new call must be assigned to the foreground call.
    210         // That call must be idle, so place anything that's
    211         // there on hold
    212         if (foregroundCall.getState() == CdmaCall.State.ACTIVE) {
    213             return dialThreeWay(dialString);
    214         }
    215 
    216         pendingMO = new CdmaConnection(phone.getContext(), checkForTestEmergencyNumber(dialString),
    217                 this, foregroundCall);
    218         hangupPendingMO = false;
    219 
    220         if (pendingMO.address == null || pendingMO.address.length() == 0
    221             || pendingMO.address.indexOf(PhoneNumberUtils.WILD) >= 0) {
    222             // Phone number is invalid
    223             pendingMO.cause = Connection.DisconnectCause.INVALID_NUMBER;
    224 
    225             // handlePollCalls() will notice this call not present
    226             // and will mark it as dropped.
    227             pollCallsWhenSafe();
    228         } else {
    229             // Always unmute when initiating a new call
    230             setMute(false);
    231 
    232             // Check data call
    233             disableDataCallInEmergencyCall(dialString);
    234 
    235             // In Ecm mode, if another emergency call is dialed, Ecm mode will not exit.
    236             if(!isPhoneInEcmMode || (isPhoneInEcmMode && isEmergencyCall)) {
    237                 cm.dial(pendingMO.address, clirMode, obtainCompleteMessage());
    238             } else {
    239                 phone.exitEmergencyCallbackMode();
    240                 phone.setOnEcbModeExitResponse(this,EVENT_EXIT_ECM_RESPONSE_CDMA, null);
    241                 pendingCallClirMode=clirMode;
    242                 pendingCallInEcm=true;
    243             }
    244         }
    245 
    246         updatePhoneState();
    247         phone.notifyPreciseCallStateChanged();
    248 
    249         return pendingMO;
    250     }
    251 
    252 
    253     Connection
    254     dial (String dialString) throws CallStateException {
    255         return dial(dialString, CommandsInterface.CLIR_DEFAULT);
    256     }
    257 
    258     private Connection
    259     dialThreeWay (String dialString) {
    260         if (!foregroundCall.isIdle()) {
    261             // Check data call
    262             disableDataCallInEmergencyCall(dialString);
    263 
    264             // Attach the new connection to foregroundCall
    265             pendingMO = new CdmaConnection(phone.getContext(),
    266                                 checkForTestEmergencyNumber(dialString), this, foregroundCall);
    267             cm.sendCDMAFeatureCode(pendingMO.address,
    268                 obtainMessage(EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA));
    269             return pendingMO;
    270         }
    271         return null;
    272     }
    273 
    274     void
    275     acceptCall() throws CallStateException {
    276         if (ringingCall.getState() == CdmaCall.State.INCOMING) {
    277             Log.i("phone", "acceptCall: incoming...");
    278             // Always unmute when answering a new call
    279             setMute(false);
    280             cm.acceptCall(obtainCompleteMessage());
    281         } else if (ringingCall.getState() == CdmaCall.State.WAITING) {
    282             CdmaConnection cwConn = (CdmaConnection)(ringingCall.getLatestConnection());
    283 
    284             // Since there is no network response for supplimentary
    285             // service for CDMA, we assume call waiting is answered.
    286             // ringing Call state change to idle is in CdmaCall.detach
    287             // triggered by updateParent.
    288             cwConn.updateParent(ringingCall, foregroundCall);
    289             cwConn.onConnectedInOrOut();
    290             updatePhoneState();
    291             switchWaitingOrHoldingAndActive();
    292         } else {
    293             throw new CallStateException("phone not ringing");
    294         }
    295     }
    296 
    297     void
    298     rejectCall () throws CallStateException {
    299         // AT+CHLD=0 means "release held or UDUB"
    300         // so if the phone isn't ringing, this could hang up held
    301         if (ringingCall.getState().isRinging()) {
    302             cm.rejectCall(obtainCompleteMessage());
    303         } else {
    304             throw new CallStateException("phone not ringing");
    305         }
    306     }
    307 
    308     void
    309     switchWaitingOrHoldingAndActive() throws CallStateException {
    310         // Should we bother with this check?
    311         if (ringingCall.getState() == CdmaCall.State.INCOMING) {
    312             throw new CallStateException("cannot be in the incoming state");
    313         } else if (foregroundCall.getConnections().size() > 1) {
    314             flashAndSetGenericTrue();
    315         } else {
    316             // Send a flash command to CDMA network for putting the other party on hold.
    317             // For CDMA networks which do not support this the user would just hear a beep
    318             // from the network. For CDMA networks which do support it will put the other
    319             // party on hold.
    320             cm.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT));
    321         }
    322     }
    323 
    324     void
    325     conference() throws CallStateException {
    326         // Should we be checking state?
    327         flashAndSetGenericTrue();
    328     }
    329 
    330     void
    331     explicitCallTransfer() throws CallStateException {
    332         cm.explicitCallTransfer(obtainCompleteMessage(EVENT_ECT_RESULT));
    333     }
    334 
    335     void
    336     clearDisconnected() {
    337         internalClearDisconnected();
    338 
    339         updatePhoneState();
    340         phone.notifyPreciseCallStateChanged();
    341     }
    342 
    343     boolean
    344     canConference() {
    345         return foregroundCall.getState() == CdmaCall.State.ACTIVE
    346                 && backgroundCall.getState() == CdmaCall.State.HOLDING
    347                 && !backgroundCall.isFull()
    348                 && !foregroundCall.isFull();
    349     }
    350 
    351     boolean
    352     canDial() {
    353         boolean ret;
    354         int serviceState = phone.getServiceState().getState();
    355         String disableCall = SystemProperties.get(
    356                 TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
    357 
    358         ret = (serviceState != ServiceState.STATE_POWER_OFF)
    359                 && pendingMO == null
    360                 && !ringingCall.isRinging()
    361                 && !disableCall.equals("true")
    362                 && (!foregroundCall.getState().isAlive()
    363                     || (foregroundCall.getState() == CdmaCall.State.ACTIVE)
    364                     || !backgroundCall.getState().isAlive());
    365 
    366         if (!ret) {
    367             log(String.format("canDial is false\n" +
    368                               "((serviceState=%d) != ServiceState.STATE_POWER_OFF)::=%s\n" +
    369                               "&& pendingMO == null::=%s\n" +
    370                               "&& !ringingCall.isRinging()::=%s\n" +
    371                               "&& !disableCall.equals(\"true\")::=%s\n" +
    372                               "&& (!foregroundCall.getState().isAlive()::=%s\n" +
    373                               "   || foregroundCall.getState() == CdmaCall.State.ACTIVE::=%s\n" +
    374                               "   ||!backgroundCall.getState().isAlive())::=%s)",
    375                     serviceState,
    376                     serviceState != ServiceState.STATE_POWER_OFF,
    377                     pendingMO == null,
    378                     !ringingCall.isRinging(),
    379                     !disableCall.equals("true"),
    380                     !foregroundCall.getState().isAlive(),
    381                     foregroundCall.getState() == CdmaCall.State.ACTIVE,
    382                     !backgroundCall.getState().isAlive()));
    383         }
    384         return ret;
    385     }
    386 
    387     boolean
    388     canTransfer() {
    389         Log.e(LOG_TAG, "canTransfer: not possible in CDMA");
    390         return false;
    391     }
    392 
    393     //***** Private Instance Methods
    394 
    395     private void
    396     internalClearDisconnected() {
    397         ringingCall.clearDisconnected();
    398         foregroundCall.clearDisconnected();
    399         backgroundCall.clearDisconnected();
    400     }
    401 
    402     /**
    403      * Obtain a message to use for signalling "invoke getCurrentCalls() when
    404      * this operation and all other pending operations are complete
    405      */
    406     private Message
    407     obtainCompleteMessage() {
    408         return obtainCompleteMessage(EVENT_OPERATION_COMPLETE);
    409     }
    410 
    411     /**
    412      * Obtain a message to use for signalling "invoke getCurrentCalls() when
    413      * this operation and all other pending operations are complete
    414      */
    415     private Message
    416     obtainCompleteMessage(int what) {
    417         pendingOperations++;
    418         lastRelevantPoll = null;
    419         needsPoll = true;
    420 
    421         if (DBG_POLL) log("obtainCompleteMessage: pendingOperations=" +
    422                 pendingOperations + ", needsPoll=" + needsPoll);
    423 
    424         return obtainMessage(what);
    425     }
    426 
    427     private void
    428     operationComplete() {
    429         pendingOperations--;
    430 
    431         if (DBG_POLL) log("operationComplete: pendingOperations=" +
    432                 pendingOperations + ", needsPoll=" + needsPoll);
    433 
    434         if (pendingOperations == 0 && needsPoll) {
    435             lastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
    436             cm.getCurrentCalls(lastRelevantPoll);
    437         } else if (pendingOperations < 0) {
    438             // this should never happen
    439             Log.e(LOG_TAG,"CdmaCallTracker.pendingOperations < 0");
    440             pendingOperations = 0;
    441         }
    442     }
    443 
    444 
    445 
    446     private void
    447     updatePhoneState() {
    448         PhoneConstants.State oldState = state;
    449 
    450         if (ringingCall.isRinging()) {
    451             state = PhoneConstants.State.RINGING;
    452         } else if (pendingMO != null ||
    453                 !(foregroundCall.isIdle() && backgroundCall.isIdle())) {
    454             state = PhoneConstants.State.OFFHOOK;
    455         } else {
    456             state = PhoneConstants.State.IDLE;
    457         }
    458 
    459         if (state == PhoneConstants.State.IDLE && oldState != state) {
    460             voiceCallEndedRegistrants.notifyRegistrants(
    461                 new AsyncResult(null, null, null));
    462         } else if (oldState == PhoneConstants.State.IDLE && oldState != state) {
    463             voiceCallStartedRegistrants.notifyRegistrants (
    464                     new AsyncResult(null, null, null));
    465         }
    466         if (Phone.DEBUG_PHONE) {
    467             log("update phone state, old=" + oldState + " new="+ state);
    468         }
    469         if (state != oldState) {
    470             phone.notifyPhoneStateChanged();
    471         }
    472     }
    473 
    474     // ***** Overwritten from CallTracker
    475 
    476     protected void
    477     handlePollCalls(AsyncResult ar) {
    478         List polledCalls;
    479 
    480         if (ar.exception == null) {
    481             polledCalls = (List)ar.result;
    482         } else if (isCommandExceptionRadioNotAvailable(ar.exception)) {
    483             // just a dummy empty ArrayList to cause the loop
    484             // to hang up all the calls
    485             polledCalls = new ArrayList();
    486         } else {
    487             // Radio probably wasn't ready--try again in a bit
    488             // But don't keep polling if the channel is closed
    489             pollCallsAfterDelay();
    490             return;
    491         }
    492 
    493         Connection newRinging = null; //or waiting
    494         boolean hasNonHangupStateChanged = false;   // Any change besides
    495                                                     // a dropped connection
    496         boolean needsPollDelay = false;
    497         boolean unknownConnectionAppeared = false;
    498 
    499         for (int i = 0, curDC = 0, dcSize = polledCalls.size()
    500                 ; i < connections.length; i++) {
    501             CdmaConnection conn = connections[i];
    502             DriverCall dc = null;
    503 
    504             // polledCall list is sparse
    505             if (curDC < dcSize) {
    506                 dc = (DriverCall) polledCalls.get(curDC);
    507 
    508                 if (dc.index == i+1) {
    509                     curDC++;
    510                 } else {
    511                     dc = null;
    512                 }
    513             }
    514 
    515             if (DBG_POLL) log("poll: conn[i=" + i + "]=" +
    516                     conn+", dc=" + dc);
    517 
    518             if (conn == null && dc != null) {
    519                 // Connection appeared in CLCC response that we don't know about
    520                 if (pendingMO != null && pendingMO.compareTo(dc)) {
    521 
    522                     if (DBG_POLL) log("poll: pendingMO=" + pendingMO);
    523 
    524                     // It's our pending mobile originating call
    525                     connections[i] = pendingMO;
    526                     pendingMO.index = i;
    527                     pendingMO.update(dc);
    528                     pendingMO = null;
    529 
    530                     // Someone has already asked to hangup this call
    531                     if (hangupPendingMO) {
    532                         hangupPendingMO = false;
    533                         // Re-start Ecm timer when an uncompleted emergency call ends
    534                         if (mIsEcmTimerCanceled) {
    535                             handleEcmTimer(phone.RESTART_ECM_TIMER);
    536                         }
    537 
    538                         try {
    539                             if (Phone.DEBUG_PHONE) log(
    540                                     "poll: hangupPendingMO, hangup conn " + i);
    541                             hangup(connections[i]);
    542                         } catch (CallStateException ex) {
    543                             Log.e(LOG_TAG, "unexpected error on hangup");
    544                         }
    545 
    546                         // Do not continue processing this poll
    547                         // Wait for hangup and repoll
    548                         return;
    549                     }
    550                 } else {
    551                     if (Phone.DEBUG_PHONE) {
    552                         log("pendingMo=" + pendingMO + ", dc=" + dc);
    553                     }
    554                     // find if the MT call is a new ring or unknown connection
    555                     newRinging = checkMtFindNewRinging(dc,i);
    556                     if (newRinging == null) {
    557                         unknownConnectionAppeared = true;
    558                     }
    559                     checkAndEnableDataCallAfterEmergencyCallDropped();
    560                 }
    561                 hasNonHangupStateChanged = true;
    562             } else if (conn != null && dc == null) {
    563                 // This case means the RIL has no more active call anymore and
    564                 // we need to clean up the foregroundCall and ringingCall.
    565                 // Loop through foreground call connections as
    566                 // it contains the known logical connections.
    567                 int count = foregroundCall.connections.size();
    568                 for (int n = 0; n < count; n++) {
    569                     if (Phone.DEBUG_PHONE) log("adding fgCall cn " + n + " to droppedDuringPoll");
    570                     CdmaConnection cn = (CdmaConnection)foregroundCall.connections.get(n);
    571                     droppedDuringPoll.add(cn);
    572                 }
    573                 count = ringingCall.connections.size();
    574                 // Loop through ringing call connections as
    575                 // it may contain the known logical connections.
    576                 for (int n = 0; n < count; n++) {
    577                     if (Phone.DEBUG_PHONE) log("adding rgCall cn " + n + " to droppedDuringPoll");
    578                     CdmaConnection cn = (CdmaConnection)ringingCall.connections.get(n);
    579                     droppedDuringPoll.add(cn);
    580                 }
    581                 foregroundCall.setGeneric(false);
    582                 ringingCall.setGeneric(false);
    583 
    584                 // Re-start Ecm timer when the connected emergency call ends
    585                 if (mIsEcmTimerCanceled) {
    586                     handleEcmTimer(phone.RESTART_ECM_TIMER);
    587                 }
    588                 // If emergency call is not going through while dialing
    589                 checkAndEnableDataCallAfterEmergencyCallDropped();
    590 
    591                 // Dropped connections are removed from the CallTracker
    592                 // list but kept in the Call list
    593                 connections[i] = null;
    594             } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */
    595                 // Call collision case
    596                 if (conn.isIncoming != dc.isMT) {
    597                     if (dc.isMT == true){
    598                         // Mt call takes precedence than Mo,drops Mo
    599                         droppedDuringPoll.add(conn);
    600                         // find if the MT call is a new ring or unknown connection
    601                         newRinging = checkMtFindNewRinging(dc,i);
    602                         if (newRinging == null) {
    603                             unknownConnectionAppeared = true;
    604                         }
    605                         checkAndEnableDataCallAfterEmergencyCallDropped();
    606                     } else {
    607                         // Call info stored in conn is not consistent with the call info from dc.
    608                         // We should follow the rule of MT calls taking precedence over MO calls
    609                         // when there is conflict, so here we drop the call info from dc and
    610                         // continue to use the call info from conn, and only take a log.
    611                         Log.e(LOG_TAG,"Error in RIL, Phantom call appeared " + dc);
    612                     }
    613                 } else {
    614                     boolean changed;
    615                     changed = conn.update(dc);
    616                     hasNonHangupStateChanged = hasNonHangupStateChanged || changed;
    617                 }
    618             }
    619 
    620             if (REPEAT_POLLING) {
    621                 if (dc != null) {
    622                     // FIXME with RIL, we should not need this anymore
    623                     if ((dc.state == DriverCall.State.DIALING
    624                             /*&& cm.getOption(cm.OPTION_POLL_DIALING)*/)
    625                         || (dc.state == DriverCall.State.ALERTING
    626                             /*&& cm.getOption(cm.OPTION_POLL_ALERTING)*/)
    627                         || (dc.state == DriverCall.State.INCOMING
    628                             /*&& cm.getOption(cm.OPTION_POLL_INCOMING)*/)
    629                         || (dc.state == DriverCall.State.WAITING
    630                             /*&& cm.getOption(cm.OPTION_POLL_WAITING)*/)
    631                     ) {
    632                         // Sometimes there's no unsolicited notification
    633                         // for state transitions
    634                         needsPollDelay = true;
    635                     }
    636                 }
    637             }
    638         }
    639 
    640         // This is the first poll after an ATD.
    641         // We expect the pending call to appear in the list
    642         // If it does not, we land here
    643         if (pendingMO != null) {
    644             Log.d(LOG_TAG,"Pending MO dropped before poll fg state:"
    645                             + foregroundCall.getState());
    646 
    647             droppedDuringPoll.add(pendingMO);
    648             pendingMO = null;
    649             hangupPendingMO = false;
    650             if( pendingCallInEcm) {
    651                 pendingCallInEcm = false;
    652             }
    653         }
    654 
    655         if (newRinging != null) {
    656             phone.notifyNewRingingConnection(newRinging);
    657         }
    658 
    659         // clear the "local hangup" and "missed/rejected call"
    660         // cases from the "dropped during poll" list
    661         // These cases need no "last call fail" reason
    662         for (int i = droppedDuringPoll.size() - 1; i >= 0 ; i--) {
    663             CdmaConnection conn = droppedDuringPoll.get(i);
    664 
    665             if (conn.isIncoming() && conn.getConnectTime() == 0) {
    666                 // Missed or rejected call
    667                 Connection.DisconnectCause cause;
    668                 if (conn.cause == Connection.DisconnectCause.LOCAL) {
    669                     cause = Connection.DisconnectCause.INCOMING_REJECTED;
    670                 } else {
    671                     cause = Connection.DisconnectCause.INCOMING_MISSED;
    672                 }
    673 
    674                 if (Phone.DEBUG_PHONE) {
    675                     log("missed/rejected call, conn.cause=" + conn.cause);
    676                     log("setting cause to " + cause);
    677                 }
    678                 droppedDuringPoll.remove(i);
    679                 conn.onDisconnect(cause);
    680             } else if (conn.cause == Connection.DisconnectCause.LOCAL) {
    681                 // Local hangup
    682                 droppedDuringPoll.remove(i);
    683                 conn.onDisconnect(Connection.DisconnectCause.LOCAL);
    684             } else if (conn.cause == Connection.DisconnectCause.INVALID_NUMBER) {
    685                 droppedDuringPoll.remove(i);
    686                 conn.onDisconnect(Connection.DisconnectCause.INVALID_NUMBER);
    687             }
    688         }
    689 
    690         // Any non-local disconnects: determine cause
    691         if (droppedDuringPoll.size() > 0) {
    692             cm.getLastCallFailCause(
    693                 obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
    694         }
    695 
    696         if (needsPollDelay) {
    697             pollCallsAfterDelay();
    698         }
    699 
    700         // Cases when we can no longer keep disconnected Connection's
    701         // with their previous calls
    702         // 1) the phone has started to ring
    703         // 2) A Call/Connection object has changed state...
    704         //    we may have switched or held or answered (but not hung up)
    705         if (newRinging != null || hasNonHangupStateChanged) {
    706             internalClearDisconnected();
    707         }
    708 
    709         updatePhoneState();
    710 
    711         if (unknownConnectionAppeared) {
    712             phone.notifyUnknownConnection();
    713         }
    714 
    715         if (hasNonHangupStateChanged || newRinging != null) {
    716             phone.notifyPreciseCallStateChanged();
    717         }
    718 
    719         //dumpState();
    720     }
    721 
    722     //***** Called from CdmaConnection
    723     /*package*/ void
    724     hangup (CdmaConnection conn) throws CallStateException {
    725         if (conn.owner != this) {
    726             throw new CallStateException ("CdmaConnection " + conn
    727                                     + "does not belong to CdmaCallTracker " + this);
    728         }
    729 
    730         if (conn == pendingMO) {
    731             // We're hanging up an outgoing call that doesn't have it's
    732             // GSM index assigned yet
    733 
    734             if (Phone.DEBUG_PHONE) log("hangup: set hangupPendingMO to true");
    735             hangupPendingMO = true;
    736         } else if ((conn.getCall() == ringingCall)
    737                 && (ringingCall.getState() == CdmaCall.State.WAITING)) {
    738             // Handle call waiting hang up case.
    739             //
    740             // The ringingCall state will change to IDLE in CdmaCall.detach
    741             // if the ringing call connection size is 0. We don't specifically
    742             // set the ringing call state to IDLE here to avoid a race condition
    743             // where a new call waiting could get a hang up from an old call
    744             // waiting ringingCall.
    745             //
    746             // PhoneApp does the call log itself since only PhoneApp knows
    747             // the hangup reason is user ignoring or timing out. So conn.onDisconnect()
    748             // is not called here. Instead, conn.onLocalDisconnect() is called.
    749             conn.onLocalDisconnect();
    750             updatePhoneState();
    751             phone.notifyPreciseCallStateChanged();
    752             return;
    753         } else {
    754             try {
    755                 cm.hangupConnection (conn.getCDMAIndex(), obtainCompleteMessage());
    756             } catch (CallStateException ex) {
    757                 // Ignore "connection not found"
    758                 // Call may have hung up already
    759                 Log.w(LOG_TAG,"CdmaCallTracker WARN: hangup() on absent connection "
    760                                 + conn);
    761             }
    762         }
    763 
    764         conn.onHangupLocal();
    765     }
    766 
    767     /*package*/ void
    768     separate (CdmaConnection conn) throws CallStateException {
    769         if (conn.owner != this) {
    770             throw new CallStateException ("CdmaConnection " + conn
    771                                     + "does not belong to CdmaCallTracker " + this);
    772         }
    773         try {
    774             cm.separateConnection (conn.getCDMAIndex(),
    775                 obtainCompleteMessage(EVENT_SEPARATE_RESULT));
    776         } catch (CallStateException ex) {
    777             // Ignore "connection not found"
    778             // Call may have hung up already
    779             Log.w(LOG_TAG,"CdmaCallTracker WARN: separate() on absent connection "
    780                           + conn);
    781         }
    782     }
    783 
    784     //***** Called from CDMAPhone
    785 
    786     /*package*/ void
    787     setMute(boolean mute) {
    788         desiredMute = mute;
    789         cm.setMute(desiredMute, null);
    790     }
    791 
    792     /*package*/ boolean
    793     getMute() {
    794         return desiredMute;
    795     }
    796 
    797 
    798     //***** Called from CdmaCall
    799 
    800     /* package */ void
    801     hangup (CdmaCall call) throws CallStateException {
    802         if (call.getConnections().size() == 0) {
    803             throw new CallStateException("no connections in call");
    804         }
    805 
    806         if (call == ringingCall) {
    807             if (Phone.DEBUG_PHONE) log("(ringing) hangup waiting or background");
    808             cm.hangupWaitingOrBackground(obtainCompleteMessage());
    809         } else if (call == foregroundCall) {
    810             if (call.isDialingOrAlerting()) {
    811                 if (Phone.DEBUG_PHONE) {
    812                     log("(foregnd) hangup dialing or alerting...");
    813                 }
    814                 hangup((CdmaConnection)(call.getConnections().get(0)));
    815             } else {
    816                 hangupForegroundResumeBackground();
    817             }
    818         } else if (call == backgroundCall) {
    819             if (ringingCall.isRinging()) {
    820                 if (Phone.DEBUG_PHONE) {
    821                     log("hangup all conns in background call");
    822                 }
    823                 hangupAllConnections(call);
    824             } else {
    825                 hangupWaitingOrBackground();
    826             }
    827         } else {
    828             throw new RuntimeException ("CdmaCall " + call +
    829                     "does not belong to CdmaCallTracker " + this);
    830         }
    831 
    832         call.onHangupLocal();
    833         phone.notifyPreciseCallStateChanged();
    834     }
    835 
    836     /* package */
    837     void hangupWaitingOrBackground() {
    838         if (Phone.DEBUG_PHONE) log("hangupWaitingOrBackground");
    839         cm.hangupWaitingOrBackground(obtainCompleteMessage());
    840     }
    841 
    842     /* package */
    843     void hangupForegroundResumeBackground() {
    844         if (Phone.DEBUG_PHONE) log("hangupForegroundResumeBackground");
    845         cm.hangupForegroundResumeBackground(obtainCompleteMessage());
    846     }
    847 
    848     void hangupConnectionByIndex(CdmaCall call, int index)
    849             throws CallStateException {
    850         int count = call.connections.size();
    851         for (int i = 0; i < count; i++) {
    852             CdmaConnection cn = (CdmaConnection)call.connections.get(i);
    853             if (cn.getCDMAIndex() == index) {
    854                 cm.hangupConnection(index, obtainCompleteMessage());
    855                 return;
    856             }
    857         }
    858 
    859         throw new CallStateException("no gsm index found");
    860     }
    861 
    862     void hangupAllConnections(CdmaCall call) throws CallStateException{
    863         try {
    864             int count = call.connections.size();
    865             for (int i = 0; i < count; i++) {
    866                 CdmaConnection cn = (CdmaConnection)call.connections.get(i);
    867                 cm.hangupConnection(cn.getCDMAIndex(), obtainCompleteMessage());
    868             }
    869         } catch (CallStateException ex) {
    870             Log.e(LOG_TAG, "hangupConnectionByIndex caught " + ex);
    871         }
    872     }
    873 
    874     /* package */
    875     CdmaConnection getConnectionByIndex(CdmaCall call, int index)
    876             throws CallStateException {
    877         int count = call.connections.size();
    878         for (int i = 0; i < count; i++) {
    879             CdmaConnection cn = (CdmaConnection)call.connections.get(i);
    880             if (cn.getCDMAIndex() == index) {
    881                 return cn;
    882             }
    883         }
    884 
    885         return null;
    886     }
    887 
    888     private void flashAndSetGenericTrue() throws CallStateException {
    889         cm.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT));
    890 
    891         // Set generic to true because in CDMA it is not known what
    892         // the status of the call is after a call waiting is answered,
    893         // 3 way call merged or a switch between calls.
    894         foregroundCall.setGeneric(true);
    895         phone.notifyPreciseCallStateChanged();
    896     }
    897 
    898     private Phone.SuppService getFailedService(int what) {
    899         switch (what) {
    900             case EVENT_SWITCH_RESULT:
    901                 return Phone.SuppService.SWITCH;
    902             case EVENT_CONFERENCE_RESULT:
    903                 return Phone.SuppService.CONFERENCE;
    904             case EVENT_SEPARATE_RESULT:
    905                 return Phone.SuppService.SEPARATE;
    906             case EVENT_ECT_RESULT:
    907                 return Phone.SuppService.TRANSFER;
    908         }
    909         return Phone.SuppService.UNKNOWN;
    910     }
    911 
    912     private void handleRadioNotAvailable() {
    913         // handlePollCalls will clear out its
    914         // call list when it gets the CommandException
    915         // error result from this
    916         pollCallsWhenSafe();
    917     }
    918 
    919     private void notifyCallWaitingInfo(CdmaCallWaitingNotification obj) {
    920         if (callWaitingRegistrants != null) {
    921             callWaitingRegistrants.notifyRegistrants(new AsyncResult(null, obj, null));
    922         }
    923     }
    924 
    925     private void handleCallWaitingInfo (CdmaCallWaitingNotification cw) {
    926         // Check how many connections in foregroundCall.
    927         // If the connection in foregroundCall is more
    928         // than one, then the connection information is
    929         // not reliable anymore since it means either
    930         // call waiting is connected or 3 way call is
    931         // dialed before, so set generic.
    932         if (foregroundCall.connections.size() > 1 ) {
    933             foregroundCall.setGeneric(true);
    934         }
    935 
    936         // Create a new CdmaConnection which attaches itself to ringingCall.
    937         ringingCall.setGeneric(false);
    938         new CdmaConnection(phone.getContext(), cw, this, ringingCall);
    939         updatePhoneState();
    940 
    941         // Finally notify application
    942         notifyCallWaitingInfo(cw);
    943     }
    944     //****** Overridden from Handler
    945 
    946     public void
    947     handleMessage (Message msg) {
    948         AsyncResult ar;
    949 
    950         switch (msg.what) {
    951             case EVENT_POLL_CALLS_RESULT:{
    952                 Log.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received");
    953                 ar = (AsyncResult)msg.obj;
    954 
    955                 if(msg == lastRelevantPoll) {
    956                     if(DBG_POLL) log(
    957                             "handle EVENT_POLL_CALL_RESULT: set needsPoll=F");
    958                     needsPoll = false;
    959                     lastRelevantPoll = null;
    960                     handlePollCalls((AsyncResult)msg.obj);
    961                 }
    962             }
    963             break;
    964 
    965             case EVENT_OPERATION_COMPLETE:
    966                 operationComplete();
    967             break;
    968 
    969             case EVENT_SWITCH_RESULT:
    970                  // In GSM call operationComplete() here which gets the
    971                  // current call list. But in CDMA there is no list so
    972                  // there is nothing to do.
    973             break;
    974 
    975             case EVENT_GET_LAST_CALL_FAIL_CAUSE:
    976                 int causeCode;
    977                 ar = (AsyncResult)msg.obj;
    978 
    979                 operationComplete();
    980 
    981                 if (ar.exception != null) {
    982                     // An exception occurred...just treat the disconnect
    983                     // cause as "normal"
    984                     causeCode = CallFailCause.NORMAL_CLEARING;
    985                     Log.i(LOG_TAG,
    986                             "Exception during getLastCallFailCause, assuming normal disconnect");
    987                 } else {
    988                     causeCode = ((int[])ar.result)[0];
    989                 }
    990 
    991                 for (int i = 0, s =  droppedDuringPoll.size()
    992                         ; i < s ; i++
    993                 ) {
    994                     CdmaConnection conn = droppedDuringPoll.get(i);
    995 
    996                     conn.onRemoteDisconnect(causeCode);
    997                 }
    998 
    999                 updatePhoneState();
   1000 
   1001                 phone.notifyPreciseCallStateChanged();
   1002                 droppedDuringPoll.clear();
   1003             break;
   1004 
   1005             case EVENT_REPOLL_AFTER_DELAY:
   1006             case EVENT_CALL_STATE_CHANGE:
   1007                 pollCallsWhenSafe();
   1008             break;
   1009 
   1010             case EVENT_RADIO_AVAILABLE:
   1011                 handleRadioAvailable();
   1012             break;
   1013 
   1014             case EVENT_RADIO_NOT_AVAILABLE:
   1015                 handleRadioNotAvailable();
   1016             break;
   1017 
   1018             case EVENT_EXIT_ECM_RESPONSE_CDMA:
   1019                //no matter the result, we still do the same here
   1020                if (pendingCallInEcm) {
   1021                    cm.dial(pendingMO.address, pendingCallClirMode, obtainCompleteMessage());
   1022                    pendingCallInEcm = false;
   1023                }
   1024                phone.unsetOnEcbModeExitResponse(this);
   1025             break;
   1026 
   1027             case EVENT_CALL_WAITING_INFO_CDMA:
   1028                ar = (AsyncResult)msg.obj;
   1029                if (ar.exception == null) {
   1030                    handleCallWaitingInfo((CdmaCallWaitingNotification)ar.result);
   1031                    Log.d(LOG_TAG, "Event EVENT_CALL_WAITING_INFO_CDMA Received");
   1032                }
   1033             break;
   1034 
   1035             case EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA:
   1036                 ar = (AsyncResult)msg.obj;
   1037                 if (ar.exception == null) {
   1038                     // Assume 3 way call is connected
   1039                     pendingMO.onConnectedInOrOut();
   1040                     pendingMO = null;
   1041                 }
   1042             break;
   1043 
   1044             default:{
   1045                throw new RuntimeException("unexpected event not handled");
   1046             }
   1047         }
   1048     }
   1049 
   1050     /**
   1051      * Handle Ecm timer to be canceled or re-started
   1052      */
   1053     private void handleEcmTimer(int action) {
   1054         phone.handleTimerInEmergencyCallbackMode(action);
   1055         switch(action) {
   1056         case CDMAPhone.CANCEL_ECM_TIMER: mIsEcmTimerCanceled = true; break;
   1057         case CDMAPhone.RESTART_ECM_TIMER: mIsEcmTimerCanceled = false; break;
   1058         default:
   1059             Log.e(LOG_TAG, "handleEcmTimer, unsupported action " + action);
   1060         }
   1061     }
   1062 
   1063     /**
   1064      * Disable data call when emergency call is connected
   1065      */
   1066     private void disableDataCallInEmergencyCall(String dialString) {
   1067         if (PhoneNumberUtils.isLocalEmergencyNumber(dialString, phone.getContext())) {
   1068             if (Phone.DEBUG_PHONE) log("disableDataCallInEmergencyCall");
   1069             mIsInEmergencyCall = true;
   1070             phone.mDataConnectionTracker.setInternalDataEnabled(false);
   1071         }
   1072     }
   1073 
   1074     /**
   1075      * Check and enable data call after an emergency call is dropped if it's
   1076      * not in ECM
   1077      */
   1078     private void checkAndEnableDataCallAfterEmergencyCallDropped() {
   1079         if (mIsInEmergencyCall) {
   1080             mIsInEmergencyCall = false;
   1081             String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
   1082             if (Phone.DEBUG_PHONE) {
   1083                 log("checkAndEnableDataCallAfterEmergencyCallDropped,inEcm=" + inEcm);
   1084             }
   1085             if (inEcm.compareTo("false") == 0) {
   1086                 // Re-initiate data connection
   1087                 phone.mDataConnectionTracker.setInternalDataEnabled(true);
   1088             }
   1089         }
   1090     }
   1091 
   1092     /**
   1093      * Check the MT call to see if it's a new ring or
   1094      * a unknown connection.
   1095      */
   1096     private Connection checkMtFindNewRinging(DriverCall dc, int i) {
   1097 
   1098         Connection newRinging = null;
   1099 
   1100         connections[i] = new CdmaConnection(phone.getContext(), dc, this, i);
   1101         // it's a ringing call
   1102         if (connections[i].getCall() == ringingCall) {
   1103             newRinging = connections[i];
   1104             if (Phone.DEBUG_PHONE) log("Notify new ring " + dc);
   1105         } else {
   1106             // Something strange happened: a call which is neither
   1107             // a ringing call nor the one we created. It could be the
   1108             // call collision result from RIL
   1109             Log.e(LOG_TAG,"Phantom call appeared " + dc);
   1110             // If it's a connected call, set the connect time so that
   1111             // it's non-zero.  It may not be accurate, but at least
   1112             // it won't appear as a Missed Call.
   1113             if (dc.state != DriverCall.State.ALERTING
   1114                 && dc.state != DriverCall.State.DIALING) {
   1115                 connections[i].connectTime = System.currentTimeMillis();
   1116             }
   1117         }
   1118         return newRinging;
   1119     }
   1120 
   1121     /**
   1122      * Check if current call is in emergency call
   1123      *
   1124      * @return true if it is in emergency call
   1125      *         false if it is not in emergency call
   1126      */
   1127     boolean isInEmergencyCall() {
   1128         return mIsInEmergencyCall;
   1129     }
   1130 
   1131     protected void log(String msg) {
   1132         Log.d(LOG_TAG, "[CdmaCallTracker] " + msg);
   1133     }
   1134 
   1135     @Override
   1136     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   1137         pw.println("GsmCallTracker extends:");
   1138         super.dump(fd, pw, args);
   1139         pw.println("droppedDuringPoll: length=" + connections.length);
   1140         for(int i=0; i < connections.length; i++) {
   1141             pw.printf(" connections[%d]=%s\n", i, connections[i]);
   1142         }
   1143         pw.println(" voiceCallEndedRegistrants=" + voiceCallEndedRegistrants);
   1144         pw.println(" voiceCallStartedRegistrants=" + voiceCallStartedRegistrants);
   1145         pw.println(" callWaitingRegistrants=" + callWaitingRegistrants);
   1146         pw.println("droppedDuringPoll: size=" + droppedDuringPoll.size());
   1147         for(int i = 0; i < droppedDuringPoll.size(); i++) {
   1148             pw.printf( " droppedDuringPoll[%d]=%s\n", i, droppedDuringPoll.get(i));
   1149         }
   1150         pw.println(" ringingCall=" + ringingCall);
   1151         pw.println(" foregroundCall=" + foregroundCall);
   1152         pw.println(" backgroundCall=" + backgroundCall);
   1153         pw.println(" pendingMO=" + pendingMO);
   1154         pw.println(" hangupPendingMO=" + hangupPendingMO);
   1155         pw.println(" pendingCallInEcm=" + pendingCallInEcm);
   1156         pw.println(" mIsInEmergencyCall=" + mIsInEmergencyCall);
   1157         pw.println(" phone=" + phone);
   1158         pw.println(" desiredMute=" + desiredMute);
   1159         pw.println(" pendingCallClirMode=" + pendingCallClirMode);
   1160         pw.println(" state=" + state);
   1161         pw.println(" mIsEcmTimerCanceled=" + mIsEcmTimerCanceled);
   1162     }
   1163 }
   1164