1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony.cdma;
18
19import android.app.AlarmManager;
20import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.SharedPreferences;
26import android.net.ConnectivityManager;
27import android.net.IConnectivityManager;
28import android.net.NetworkInfo;
29import android.net.TrafficStats;
30import android.net.wifi.WifiManager;
31import android.os.AsyncResult;
32import android.os.Message;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.os.SystemClock;
36import android.os.SystemProperties;
37import android.preference.PreferenceManager;
38import android.telephony.ServiceState;
39import android.telephony.TelephonyManager;
40import android.telephony.cdma.CdmaCellLocation;
41import android.text.TextUtils;
42import android.util.EventLog;
43import android.util.Log;
44
45import com.android.internal.telephony.CommandsInterface;
46import com.android.internal.telephony.DataCallState;
47import com.android.internal.telephony.DataConnection.FailCause;
48import com.android.internal.telephony.DataConnection;
49import com.android.internal.telephony.DataConnectionTracker;
50import com.android.internal.telephony.EventLogTags;
51import com.android.internal.telephony.gsm.ApnSetting;
52import com.android.internal.telephony.Phone;
53import com.android.internal.telephony.RetryManager;
54import com.android.internal.telephony.ServiceStateTracker;
55
56import java.util.ArrayList;
57
58/**
59 * {@hide}
60 */
61public final class CdmaDataConnectionTracker extends DataConnectionTracker {
62    protected final String LOG_TAG = "CDMA";
63
64    private CDMAPhone mCdmaPhone;
65
66    // Indicates baseband will not auto-attach
67    private boolean noAutoAttach = false;
68    private boolean mIsScreenOn = true;
69
70    //useful for debugging
71    boolean failNextConnect = false;
72
73    /**
74     * dataConnectionList holds all the Data connection
75     */
76    private ArrayList<DataConnection> dataConnectionList;
77
78    /** Currently active CdmaDataConnection */
79    private CdmaDataConnection mActiveDataConnection;
80
81    private boolean mPendingRestartRadio = false;
82    private static final int TIME_DELAYED_TO_RESTART_RADIO =
83            SystemProperties.getInt("ro.cdma.timetoradiorestart", 60000);
84
85    /**
86     * Pool size of CdmaDataConnection objects.
87     */
88    private static final int DATA_CONNECTION_POOL_SIZE = 1;
89
90    private static final int POLL_CONNECTION_MILLIS = 5 * 1000;
91    private static final String INTENT_RECONNECT_ALARM =
92            "com.android.internal.telephony.cdma-reconnect";
93    private static final String INTENT_RECONNECT_ALARM_EXTRA_REASON = "reason";
94
95    /**
96     * Constants for the data connection activity:
97     * physical link down/up
98     */
99     private static final int DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE = 0;
100     private static final int DATA_CONNECTION_ACTIVE_PH_LINK_DOWN = 1;
101     private static final int DATA_CONNECTION_ACTIVE_PH_LINK_UP = 2;
102
103    private static final String[] mSupportedApnTypes = {
104            Phone.APN_TYPE_DEFAULT,
105            Phone.APN_TYPE_MMS,
106            Phone.APN_TYPE_DUN,
107            Phone.APN_TYPE_HIPRI };
108
109    private static final String[] mDefaultApnTypes = {
110            Phone.APN_TYPE_DEFAULT,
111            Phone.APN_TYPE_MMS,
112            Phone.APN_TYPE_HIPRI };
113
114    // if we have no active Apn this is null
115    protected ApnSetting mActiveApn;
116
117    // Possibly promote to base class, the only difference is
118    // the INTENT_RECONNECT_ALARM action is a different string.
119    // Do consider technology changes if it is promoted.
120    BroadcastReceiver mIntentReceiver = new BroadcastReceiver ()
121    {
122        @Override
123        public void onReceive(Context context, Intent intent)
124        {
125            String action = intent.getAction();
126            if (action.equals(Intent.ACTION_SCREEN_ON)) {
127                mIsScreenOn = true;
128                stopNetStatPoll();
129                startNetStatPoll();
130            } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
131                mIsScreenOn = false;
132                stopNetStatPoll();
133                startNetStatPoll();
134            } else if (action.equals((INTENT_RECONNECT_ALARM))) {
135                Log.d(LOG_TAG, "Data reconnect alarm. Previous state was " + state);
136
137                String reason = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON);
138                if (state == State.FAILED) {
139                    cleanUpConnection(false, reason);
140                }
141                trySetupData(reason);
142            } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
143                final android.net.NetworkInfo networkInfo = (NetworkInfo)
144                        intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
145                mIsWifiConnected = (networkInfo != null && networkInfo.isConnected());
146            } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
147                final boolean enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
148                        WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
149
150                if (!enabled) {
151                    // when wifi got disabeled, the NETWORK_STATE_CHANGED_ACTION
152                    // quit and wont report disconnected til next enalbing.
153                    mIsWifiConnected = false;
154                }
155            }
156        }
157    };
158
159
160    /* Constructor */
161
162    CdmaDataConnectionTracker(CDMAPhone p) {
163        super(p);
164        mCdmaPhone = p;
165
166        p.mCM.registerForAvailable (this, EVENT_RADIO_AVAILABLE, null);
167        p.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
168        p.mRuimRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null);
169        p.mCM.registerForNVReady(this, EVENT_NV_READY, null);
170        p.mCM.registerForDataStateChanged (this, EVENT_DATA_STATE_CHANGED, null);
171        p.mCT.registerForVoiceCallEnded (this, EVENT_VOICE_CALL_ENDED, null);
172        p.mCT.registerForVoiceCallStarted (this, EVENT_VOICE_CALL_STARTED, null);
173        p.mSST.registerForCdmaDataConnectionAttached(this, EVENT_TRY_SETUP_DATA, null);
174        p.mSST.registerForCdmaDataConnectionDetached(this, EVENT_CDMA_DATA_DETACHED, null);
175        p.mSST.registerForRoamingOn(this, EVENT_ROAMING_ON, null);
176        p.mSST.registerForRoamingOff(this, EVENT_ROAMING_OFF, null);
177        p.mCM.registerForCdmaOtaProvision(this, EVENT_CDMA_OTA_PROVISION, null);
178
179        IntentFilter filter = new IntentFilter();
180        filter.addAction(INTENT_RECONNECT_ALARM);
181        filter.addAction(Intent.ACTION_SCREEN_ON);
182        filter.addAction(Intent.ACTION_SCREEN_OFF);
183        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
184        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
185
186        // TODO: Why is this registering the phone as the receiver of the intent
187        //       and not its own handler?
188        p.getContext().registerReceiver(mIntentReceiver, filter, null, p);
189
190        mDataConnectionTracker = this;
191
192        createAllDataConnectionList();
193
194        // This preference tells us 1) initial condition for "dataEnabled",
195        // and 2) whether the RIL will setup the baseband to auto-PS attach.
196        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(phone.getContext());
197
198        boolean dataEnabledSetting = true;
199        try {
200            dataEnabledSetting = IConnectivityManager.Stub.asInterface(ServiceManager.
201                    getService(Context.CONNECTIVITY_SERVICE)).getMobileDataEnabled();
202        } catch (Exception e) {
203            // nothing to do - use the old behavior and leave data on
204        }
205        dataEnabled[APN_DEFAULT_ID] =
206                !sp.getBoolean(CDMAPhone.DATA_DISABLED_ON_BOOT_KEY, false) &&
207                dataEnabledSetting;
208        if (dataEnabled[APN_DEFAULT_ID]) {
209            enabledCount++;
210        }
211        noAutoAttach = !dataEnabled[APN_DEFAULT_ID];
212
213        if (!mRetryMgr.configure(SystemProperties.get("ro.cdma.data_retry_config"))) {
214            if (!mRetryMgr.configure(DEFAULT_DATA_RETRY_CONFIG)) {
215                // Should never happen, log an error and default to a simple linear sequence.
216                Log.e(LOG_TAG, "Could not configure using DEFAULT_DATA_RETRY_CONFIG="
217                        + DEFAULT_DATA_RETRY_CONFIG);
218                mRetryMgr.configure(20, 2000, 1000);
219            }
220        }
221    }
222
223    public void dispose() {
224        // Unregister from all events
225        phone.mCM.unregisterForAvailable(this);
226        phone.mCM.unregisterForOffOrNotAvailable(this);
227        mCdmaPhone.mRuimRecords.unregisterForRecordsLoaded(this);
228        phone.mCM.unregisterForNVReady(this);
229        phone.mCM.unregisterForDataStateChanged(this);
230        mCdmaPhone.mCT.unregisterForVoiceCallEnded(this);
231        mCdmaPhone.mCT.unregisterForVoiceCallStarted(this);
232        mCdmaPhone.mSST.unregisterForCdmaDataConnectionAttached(this);
233        mCdmaPhone.mSST.unregisterForCdmaDataConnectionDetached(this);
234        mCdmaPhone.mSST.unregisterForRoamingOn(this);
235        mCdmaPhone.mSST.unregisterForRoamingOff(this);
236        phone.mCM.unregisterForCdmaOtaProvision(this);
237
238        phone.getContext().unregisterReceiver(this.mIntentReceiver);
239        destroyAllDataConnectionList();
240    }
241
242    protected void finalize() {
243        if(DBG) Log.d(LOG_TAG, "CdmaDataConnectionTracker finalized");
244    }
245
246    protected void setState(State s) {
247        if (DBG) log ("setState: " + s);
248        if (state != s) {
249            EventLog.writeEvent(EventLogTags.CDMA_DATA_STATE_CHANGE,
250                    state.toString(), s.toString());
251            state = s;
252        }
253    }
254
255    @Override
256    protected boolean isApnTypeActive(String type) {
257        return mActiveApn != null && mActiveApn.canHandleType(type);
258    }
259
260    @Override
261    protected boolean isApnTypeAvailable(String type) {
262        for (String s : mSupportedApnTypes) {
263            if (TextUtils.equals(type, s)) {
264                return true;
265            }
266        }
267        return false;
268    }
269
270    protected String[] getActiveApnTypes() {
271        String[] result;
272        if (mActiveApn != null) {
273            result = mActiveApn.types;
274        } else {
275            result = new String[1];
276            result[0] = Phone.APN_TYPE_DEFAULT;
277        }
278        return result;
279    }
280
281    protected String getActiveApnString() {
282        return null;
283    }
284
285    /**
286     * The data connection is expected to be setup while device
287     *  1. has ruim card or non-volatile data store
288     *  2. registered to data connection service
289     *  3. user doesn't explicitly disable data service
290     *  4. wifi is not on
291     *
292     * @return false while no data connection if all above requirements are met.
293     */
294    public boolean isDataConnectionAsDesired() {
295        boolean roaming = phone.getServiceState().getRoaming();
296
297        if (((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) ||
298                 mCdmaPhone.mRuimRecords.getRecordsLoaded()) &&
299                (mCdmaPhone.mSST.getCurrentCdmaDataConnectionState() ==
300                 ServiceState.STATE_IN_SERVICE) &&
301                (!roaming || getDataOnRoamingEnabled()) &&
302                !mIsWifiConnected ) {
303            return (state == State.CONNECTED);
304        }
305        return true;
306    }
307
308    private boolean isDataAllowed() {
309        boolean roaming = phone.getServiceState().getRoaming();
310        return getAnyDataEnabled() && (!roaming || getDataOnRoamingEnabled()) && mMasterDataEnabled;
311    }
312
313    private boolean trySetupData(String reason) {
314        if (DBG) log("***trySetupData due to " + (reason == null ? "(unspecified)" : reason));
315
316        if (phone.getSimulatedRadioControl() != null) {
317            // Assume data is connected on the simulator
318            // FIXME  this can be improved
319            setState(State.CONNECTED);
320            phone.notifyDataConnection(reason);
321
322            Log.i(LOG_TAG, "(fix?) We're on the simulator; assuming data is connected");
323            return true;
324        }
325
326        int psState = mCdmaPhone.mSST.getCurrentCdmaDataConnectionState();
327        boolean roaming = phone.getServiceState().getRoaming();
328        boolean desiredPowerState = mCdmaPhone.mSST.getDesiredPowerState();
329
330        if ((state == State.IDLE || state == State.SCANNING)
331                && (psState == ServiceState.STATE_IN_SERVICE)
332                && ((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) ||
333                        mCdmaPhone.mRuimRecords.getRecordsLoaded())
334                && (mCdmaPhone.mSST.isConcurrentVoiceAndData() ||
335                        phone.getState() == Phone.State.IDLE )
336                && isDataAllowed()
337                && desiredPowerState
338                && !mPendingRestartRadio
339                && !mCdmaPhone.needsOtaServiceProvisioning()) {
340
341            return setupData(reason);
342
343        } else {
344            if (DBG) {
345                    log("trySetupData: Not ready for data: " +
346                    " dataState=" + state +
347                    " PS state=" + psState +
348                    " radio state=" + phone.mCM.getRadioState() +
349                    " ruim=" + mCdmaPhone.mRuimRecords.getRecordsLoaded() +
350                    " concurrentVoice&Data=" + mCdmaPhone.mSST.isConcurrentVoiceAndData() +
351                    " phoneState=" + phone.getState() +
352                    " dataEnabled=" + getAnyDataEnabled() +
353                    " roaming=" + roaming +
354                    " dataOnRoamingEnable=" + getDataOnRoamingEnabled() +
355                    " desiredPowerState=" + desiredPowerState +
356                    " PendingRestartRadio=" + mPendingRestartRadio +
357                    " MasterDataEnabled=" + mMasterDataEnabled +
358                    " needsOtaServiceProvisioning=" + mCdmaPhone.needsOtaServiceProvisioning());
359            }
360            return false;
361        }
362    }
363
364    /**
365     * If tearDown is true, this only tears down a CONNECTED session. Presently,
366     * there is no mechanism for abandoning an INITING/CONNECTING session,
367     * but would likely involve cancelling pending async requests or
368     * setting a flag or new state to ignore them when they came in
369     * @param tearDown true if the underlying DataConnection should be
370     * disconnected.
371     * @param reason reason for the clean up.
372     */
373    private void cleanUpConnection(boolean tearDown, String reason) {
374        if (DBG) log("cleanUpConnection: reason: " + reason);
375
376        // Clear the reconnect alarm, if set.
377        if (mReconnectIntent != null) {
378            AlarmManager am =
379                (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
380            am.cancel(mReconnectIntent);
381            mReconnectIntent = null;
382        }
383
384        setState(State.DISCONNECTING);
385
386        boolean notificationDeferred = false;
387        for (DataConnection conn : dataConnectionList) {
388            if(conn != null) {
389                if (tearDown) {
390                    if (DBG) log("cleanUpConnection: teardown, call conn.disconnect");
391                    conn.disconnect(obtainMessage(EVENT_DISCONNECT_DONE, reason));
392                    notificationDeferred = true;
393                } else {
394                    if (DBG) log("cleanUpConnection: !tearDown, call conn.resetSynchronously");
395                    conn.resetSynchronously();
396                    notificationDeferred = false;
397                }
398            }
399        }
400
401        stopNetStatPoll();
402
403        if (!notificationDeferred) {
404            if (DBG) log("cleanupConnection: !notificationDeferred");
405            gotoIdleAndNotifyDataConnection(reason);
406        }
407    }
408
409    private CdmaDataConnection findFreeDataConnection() {
410        for (DataConnection connBase : dataConnectionList) {
411            CdmaDataConnection conn = (CdmaDataConnection) connBase;
412            if (conn.isInactive()) {
413                return conn;
414            }
415        }
416        return null;
417    }
418
419    private boolean setupData(String reason) {
420        CdmaDataConnection conn = findFreeDataConnection();
421
422        if (conn == null) {
423            if (DBG) log("setupData: No free CdmaDataConnection found!");
424            return false;
425        }
426
427        mActiveDataConnection = conn;
428        String[] types;
429        if (mRequestedApnType.equals(Phone.APN_TYPE_DUN)) {
430            types = new String[1];
431            types[0] = Phone.APN_TYPE_DUN;
432        } else {
433            types = mDefaultApnTypes;
434        }
435        mActiveApn = new ApnSetting(0, "", "", "", "", "", "", "", "", "", "",
436                                    0, types, "IP", "IP");
437
438        Message msg = obtainMessage();
439        msg.what = EVENT_DATA_SETUP_COMPLETE;
440        msg.obj = reason;
441        conn.connect(msg, mActiveApn);
442
443        setState(State.INITING);
444        phone.notifyDataConnection(reason);
445        return true;
446    }
447
448    private void notifyDefaultData(String reason) {
449        setState(State.CONNECTED);
450        phone.notifyDataConnection(reason);
451        startNetStatPoll();
452        mRetryMgr.resetRetryCount();
453    }
454
455    private void resetPollStats() {
456        txPkts = -1;
457        rxPkts = -1;
458        sentSinceLastRecv = 0;
459        netStatPollPeriod = POLL_NETSTAT_MILLIS;
460        mNoRecvPollCount = 0;
461    }
462
463    protected void startNetStatPoll() {
464        if (state == State.CONNECTED && netStatPollEnabled == false) {
465            Log.d(LOG_TAG, "[DataConnection] Start poll NetStat");
466            resetPollStats();
467            netStatPollEnabled = true;
468            mPollNetStat.run();
469        }
470    }
471
472    protected void stopNetStatPoll() {
473        netStatPollEnabled = false;
474        removeCallbacks(mPollNetStat);
475        Log.d(LOG_TAG, "[DataConnection] Stop poll NetStat");
476    }
477
478    protected void restartRadio() {
479        if (DBG) log("Cleanup connection and wait " +
480                (TIME_DELAYED_TO_RESTART_RADIO / 1000) + "s to restart radio");
481        cleanUpConnection(true, Phone.REASON_RADIO_TURNED_OFF);
482        sendEmptyMessageDelayed(EVENT_RESTART_RADIO, TIME_DELAYED_TO_RESTART_RADIO);
483        mPendingRestartRadio = true;
484    }
485
486    private Runnable mPollNetStat = new Runnable() {
487
488        public void run() {
489            long sent, received;
490            long preTxPkts = -1, preRxPkts = -1;
491
492            Activity newActivity;
493
494            preTxPkts = txPkts;
495            preRxPkts = rxPkts;
496
497            txPkts = TrafficStats.getMobileTxPackets();
498            rxPkts = TrafficStats.getMobileRxPackets();
499
500            //Log.d(LOG_TAG, "rx " + String.valueOf(rxPkts) + " tx " + String.valueOf(txPkts));
501
502            if (netStatPollEnabled && (preTxPkts > 0 || preRxPkts > 0)) {
503                sent = txPkts - preTxPkts;
504                received = rxPkts - preRxPkts;
505
506                if ( sent > 0 && received > 0 ) {
507                    sentSinceLastRecv = 0;
508                    newActivity = Activity.DATAINANDOUT;
509                } else if (sent > 0 && received == 0) {
510                    if (phone.getState()  == Phone.State.IDLE) {
511                        sentSinceLastRecv += sent;
512                    } else {
513                        sentSinceLastRecv = 0;
514                    }
515                    newActivity = Activity.DATAOUT;
516                } else if (sent == 0 && received > 0) {
517                    sentSinceLastRecv = 0;
518                    newActivity = Activity.DATAIN;
519                } else if (sent == 0 && received == 0) {
520                    newActivity = (activity == Activity.DORMANT) ? activity : Activity.NONE;
521                } else {
522                    sentSinceLastRecv = 0;
523                    newActivity = (activity == Activity.DORMANT) ? activity : Activity.NONE;
524                }
525
526                if (activity != newActivity) {
527                    activity = newActivity;
528                    phone.notifyDataActivity();
529                }
530            }
531
532            if (sentSinceLastRecv >= NUMBER_SENT_PACKETS_OF_HANG) {
533                // Packets sent without ack exceeded threshold.
534
535                if (mNoRecvPollCount == 0) {
536                    EventLog.writeEvent(
537                            EventLogTags.PDP_RADIO_RESET_COUNTDOWN_TRIGGERED,
538                            sentSinceLastRecv);
539                }
540
541                if (mNoRecvPollCount < NO_RECV_POLL_LIMIT) {
542                    mNoRecvPollCount++;
543                    // Slow down the poll interval to let things happen
544                    netStatPollPeriod = POLL_NETSTAT_SLOW_MILLIS;
545                } else {
546                    if (DBG) log("Sent " + String.valueOf(sentSinceLastRecv) +
547                                        " pkts since last received");
548                    // We've exceeded the threshold.  Restart the radio.
549                    netStatPollEnabled = false;
550                    stopNetStatPoll();
551                    restartRadio();
552                    EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET, NO_RECV_POLL_LIMIT);
553                }
554            } else {
555                mNoRecvPollCount = 0;
556                netStatPollPeriod = POLL_NETSTAT_MILLIS;
557            }
558
559            if (netStatPollEnabled) {
560                mDataConnectionTracker.postDelayed(this, netStatPollPeriod);
561            }
562        }
563    };
564
565    /**
566     * Returns true if the last fail cause is something that
567     * seems like it deserves an error notification.
568     * Transient errors are ignored
569     */
570    private boolean
571    shouldPostNotification(FailCause cause) {
572        return (cause != FailCause.UNKNOWN);
573    }
574
575    /**
576     * Return true if data connection need to be setup after disconnected due to
577     * reason.
578     *
579     * @param reason the reason why data is disconnected
580     * @return true if try setup data connection is need for this reason
581     */
582    private boolean retryAfterDisconnected(String reason) {
583        boolean retry = true;
584
585        if ( Phone.REASON_RADIO_TURNED_OFF.equals(reason) ) {
586            retry = false;
587        }
588        return retry;
589    }
590
591    private void reconnectAfterFail(FailCause lastFailCauseCode, String reason) {
592        if (state == State.FAILED) {
593            /**
594             * For now With CDMA we never try to reconnect on
595             * error and instead just continue to retry
596             * at the last time until the state is changed.
597             * TODO: Make this configurable?
598             */
599            int nextReconnectDelay = mRetryMgr.getRetryTimer();
600            Log.d(LOG_TAG, "Data Connection activate failed. Scheduling next attempt for "
601                    + (nextReconnectDelay / 1000) + "s");
602
603            AlarmManager am =
604                (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
605            Intent intent = new Intent(INTENT_RECONNECT_ALARM);
606            intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, reason);
607            mReconnectIntent = PendingIntent.getBroadcast(
608                    phone.getContext(), 0, intent, 0);
609            am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
610                    SystemClock.elapsedRealtime() + nextReconnectDelay,
611                    mReconnectIntent);
612
613            mRetryMgr.increaseRetryCount();
614
615            if (!shouldPostNotification(lastFailCauseCode)) {
616                Log.d(LOG_TAG,"NOT Posting Data Connection Unavailable notification "
617                                + "-- likely transient error");
618            } else {
619                notifyNoData(lastFailCauseCode);
620            }
621        }
622    }
623
624    private void notifyNoData(FailCause lastFailCauseCode) {
625        setState(State.FAILED);
626    }
627
628    private void gotoIdleAndNotifyDataConnection(String reason) {
629        if (DBG) log("gotoIdleAndNotifyDataConnection: reason=" + reason);
630        setState(State.IDLE);
631        phone.notifyDataConnection(reason);
632        mActiveApn = null;
633    }
634
635    protected void onRecordsLoaded() {
636        if (state == State.FAILED) {
637            cleanUpConnection(false, null);
638        }
639        sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA, Phone.REASON_SIM_LOADED));
640    }
641
642    protected void onNVReady() {
643        if (state == State.FAILED) {
644            cleanUpConnection(false, null);
645        }
646        sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA));
647    }
648
649    /**
650     * @override com.android.internal.telephony.DataConnectionTracker
651     */
652    @Override
653    protected void onEnableNewApn() {
654          cleanUpConnection(true, Phone.REASON_APN_SWITCHED);
655    }
656
657    /**
658     * @override com.android.internal.telephony.DataConnectionTracker
659     */
660    protected boolean onTrySetupData(String reason) {
661        return trySetupData(reason);
662    }
663
664    /**
665     * @override com.android.internal.telephony.DataConnectionTracker
666     */
667    protected void onRoamingOff() {
668        trySetupData(Phone.REASON_ROAMING_OFF);
669    }
670
671    /**
672     * @override com.android.internal.telephony.DataConnectionTracker
673     */
674    protected void onRoamingOn() {
675        if (getDataOnRoamingEnabled()) {
676            trySetupData(Phone.REASON_ROAMING_ON);
677        } else {
678            if (DBG) log("Tear down data connection on roaming.");
679            cleanUpConnection(true, Phone.REASON_ROAMING_ON);
680        }
681    }
682
683    /**
684     * @override com.android.internal.telephony.DataConnectionTracker
685     */
686    protected void onRadioAvailable() {
687        if (phone.getSimulatedRadioControl() != null) {
688            // Assume data is connected on the simulator
689            // FIXME  this can be improved
690            setState(State.CONNECTED);
691            phone.notifyDataConnection(null);
692
693            Log.i(LOG_TAG, "We're on the simulator; assuming data is connected");
694        }
695
696        if (state != State.IDLE) {
697            cleanUpConnection(true, null);
698        }
699    }
700
701    /**
702     * @override com.android.internal.telephony.DataConnectionTracker
703     */
704    protected void onRadioOffOrNotAvailable() {
705        mRetryMgr.resetRetryCount();
706
707        if (phone.getSimulatedRadioControl() != null) {
708            // Assume data is connected on the simulator
709            // FIXME  this can be improved
710            Log.i(LOG_TAG, "We're on the simulator; assuming radio off is meaningless");
711        } else {
712            if (DBG) log("Radio is off and clean up all connection");
713            cleanUpConnection(false, Phone.REASON_RADIO_TURNED_OFF);
714        }
715    }
716
717    /**
718     * @override com.android.internal.telephony.DataConnectionTracker
719     */
720    protected void onDataSetupComplete(AsyncResult ar) {
721        String reason = null;
722        if (ar.userObj instanceof String) {
723            reason = (String) ar.userObj;
724        }
725
726        if (ar.exception == null) {
727            // everything is setup
728            notifyDefaultData(reason);
729        } else {
730            FailCause cause = (FailCause) (ar.result);
731            if(DBG) log("Data Connection setup failed " + cause);
732
733            // No try for permanent failure
734            if (cause.isPermanentFail()) {
735                notifyNoData(cause);
736                return;
737            }
738            startDelayedRetry(cause, reason);
739        }
740    }
741
742    /**
743     * Called when EVENT_DISCONNECT_DONE is received.
744     */
745    protected void onDisconnectDone(AsyncResult ar) {
746        if(DBG) log("EVENT_DISCONNECT_DONE");
747        String reason = null;
748        if (ar.userObj instanceof String) {
749            reason = (String) ar.userObj;
750        }
751        setState(State.IDLE);
752
753        // Since the pending request to turn off or restart radio will be processed here,
754        // remove the pending event to restart radio from the message queue.
755        if (mPendingRestartRadio) removeMessages(EVENT_RESTART_RADIO);
756
757        // Process the pending request to turn off radio in ServiceStateTracker first.
758        // If radio is turned off in ServiceStateTracker, ignore the pending event to restart radio.
759        CdmaServiceStateTracker ssTracker = mCdmaPhone.mSST;
760        if (ssTracker.processPendingRadioPowerOffAfterDataOff()) {
761            mPendingRestartRadio = false;
762        } else {
763            onRestartRadio();
764        }
765
766        phone.notifyDataConnection(reason);
767        mActiveApn = null;
768        if (retryAfterDisconnected(reason)) {
769          trySetupData(reason);
770      }
771    }
772
773    /**
774     * Called when EVENT_RESET_DONE is received so goto
775     * IDLE state and send notifications to those interested.
776     */
777    @Override
778    protected void onResetDone(AsyncResult ar) {
779      if (DBG) log("EVENT_RESET_DONE");
780      String reason = null;
781      if (ar.userObj instanceof String) {
782          reason = (String) ar.userObj;
783      }
784      gotoIdleAndNotifyDataConnection(reason);
785    }
786
787    /**
788     * @override com.android.internal.telephony.DataConnectionTracker
789     */
790    protected void onVoiceCallStarted() {
791        if (state == State.CONNECTED && !mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
792            stopNetStatPoll();
793            phone.notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
794        }
795    }
796
797    /**
798     * @override com.android.internal.telephony.DataConnectionTracker
799     */
800    protected void onVoiceCallEnded() {
801        if (state == State.CONNECTED) {
802            if (!mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
803                startNetStatPoll();
804                phone.notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
805            } else {
806                // clean slate after call end.
807                resetPollStats();
808            }
809        } else {
810            mRetryMgr.resetRetryCount();
811            // in case data setup was attempted when we were on a voice call
812            trySetupData(Phone.REASON_VOICE_CALL_ENDED);
813        }
814    }
815
816    /**
817     * @override com.android.internal.telephony.DataConnectionTracker
818     */
819    protected void onCleanUpConnection(boolean tearDown, String reason) {
820        cleanUpConnection(tearDown, reason);
821    }
822
823    private void createAllDataConnectionList() {
824       dataConnectionList = new ArrayList<DataConnection>();
825        CdmaDataConnection dataConn;
826
827       for (int i = 0; i < DATA_CONNECTION_POOL_SIZE; i++) {
828            dataConn = CdmaDataConnection.makeDataConnection(mCdmaPhone);
829            dataConnectionList.add(dataConn);
830       }
831    }
832
833    private void destroyAllDataConnectionList() {
834        if(dataConnectionList != null) {
835            dataConnectionList.removeAll(dataConnectionList);
836        }
837    }
838
839    private void onCdmaDataDetached() {
840        if (state == State.CONNECTED) {
841            startNetStatPoll();
842            phone.notifyDataConnection(Phone.REASON_CDMA_DATA_DETACHED);
843        } else {
844            if (state == State.FAILED) {
845                cleanUpConnection(false, Phone.REASON_CDMA_DATA_DETACHED);
846                mRetryMgr.resetRetryCount();
847
848                CdmaCellLocation loc = (CdmaCellLocation)(phone.getCellLocation());
849                EventLog.writeEvent(EventLogTags.CDMA_DATA_SETUP_FAILED,
850                        loc != null ? loc.getBaseStationId() : -1,
851                        TelephonyManager.getDefault().getNetworkType());
852            }
853            trySetupData(Phone.REASON_CDMA_DATA_DETACHED);
854        }
855    }
856
857    private void onCdmaOtaProvision(AsyncResult ar) {
858        if (ar.exception != null) {
859            int [] otaPrivision = (int [])ar.result;
860            if ((otaPrivision != null) && (otaPrivision.length > 1)) {
861                switch (otaPrivision[0]) {
862                case Phone.CDMA_OTA_PROVISION_STATUS_COMMITTED:
863                case Phone.CDMA_OTA_PROVISION_STATUS_OTAPA_STOPPED:
864                    mRetryMgr.resetRetryCount();
865                    break;
866                default:
867                    break;
868                }
869            }
870        }
871    }
872
873    private void onRestartRadio() {
874        if (mPendingRestartRadio) {
875            Log.d(LOG_TAG, "************TURN OFF RADIO**************");
876            phone.mCM.setRadioPower(false, null);
877            /* Note: no need to call setRadioPower(true).  Assuming the desired
878             * radio power state is still ON (as tracked by ServiceStateTracker),
879             * ServiceStateTracker will call setRadioPower when it receives the
880             * RADIO_STATE_CHANGED notification for the power off.  And if the
881             * desired power state has changed in the interim, we don't want to
882             * override it with an unconditional power on.
883             */
884            mPendingRestartRadio = false;
885        }
886    }
887
888    private void writeEventLogCdmaDataDrop() {
889        CdmaCellLocation loc = (CdmaCellLocation)(phone.getCellLocation());
890        EventLog.writeEvent(EventLogTags.CDMA_DATA_DROP,
891                loc != null ? loc.getBaseStationId() : -1,
892                TelephonyManager.getDefault().getNetworkType());
893    }
894
895    protected void onDataStateChanged(AsyncResult ar) {
896        ArrayList<DataCallState> dataCallStates = (ArrayList<DataCallState>)(ar.result);
897
898        if (ar.exception != null) {
899            // This is probably "radio not available" or something
900            // of that sort. If so, the whole connection is going
901            // to come down soon anyway
902            return;
903        }
904
905        if (state == State.CONNECTED) {
906            boolean isActiveOrDormantConnectionPresent = false;
907            int connectionState = DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE;
908
909            // Check for an active or dormant connection element in
910            // the DATA_CALL_LIST array
911            for (int index = 0; index < dataCallStates.size(); index++) {
912                connectionState = dataCallStates.get(index).active;
913                if (connectionState != DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
914                    isActiveOrDormantConnectionPresent = true;
915                    break;
916                }
917            }
918
919            if (!isActiveOrDormantConnectionPresent) {
920                // No active or dormant connection
921                Log.i(LOG_TAG, "onDataStateChanged: No active connection"
922                        + "state is CONNECTED, disconnecting/cleanup");
923                writeEventLogCdmaDataDrop();
924                cleanUpConnection(true, null);
925                return;
926            }
927
928            switch (connectionState) {
929                case DATA_CONNECTION_ACTIVE_PH_LINK_UP:
930                    Log.v(LOG_TAG, "onDataStateChanged: active=LINK_ACTIVE && CONNECTED, ignore");
931                    activity = Activity.NONE;
932                    phone.notifyDataActivity();
933                    startNetStatPoll();
934                    break;
935
936                case DATA_CONNECTION_ACTIVE_PH_LINK_DOWN:
937                    Log.v(LOG_TAG, "onDataStateChanged active=LINK_DOWN && CONNECTED, dormant");
938                    activity = Activity.DORMANT;
939                    phone.notifyDataActivity();
940                    stopNetStatPoll();
941                    break;
942
943                default:
944                    Log.v(LOG_TAG, "onDataStateChanged: IGNORE unexpected DataCallState.active="
945                            + connectionState);
946            }
947        } else {
948            // TODO: Do we need to do anything?
949            Log.i(LOG_TAG, "onDataStateChanged: not connected, state=" + state + " ignoring");
950        }
951    }
952
953    protected String getInterfaceName(String apnType) {
954        if (mActiveDataConnection != null) {
955            return mActiveDataConnection.getInterface();
956        }
957        return null;
958    }
959
960    protected String getIpAddress(String apnType) {
961        if (mActiveDataConnection != null) {
962            return mActiveDataConnection.getIpAddress();
963        }
964        return null;
965    }
966
967    protected String getGateway(String apnType) {
968        if (mActiveDataConnection != null) {
969            return mActiveDataConnection.getGatewayAddress();
970        }
971        return null;
972    }
973
974    protected String[] getDnsServers(String apnType) {
975        if (mActiveDataConnection != null) {
976            return mActiveDataConnection.getDnsServers();
977        }
978        return null;
979    }
980
981    public ArrayList<DataConnection> getAllDataConnections() {
982        return dataConnectionList;
983    }
984
985    private void startDelayedRetry(FailCause cause, String reason) {
986        notifyNoData(cause);
987        reconnectAfterFail(cause, reason);
988    }
989
990    public void handleMessage (Message msg) {
991
992        if (!phone.mIsTheCurrentActivePhone) {
993            Log.d(LOG_TAG, "Ignore CDMA msgs since CDMA phone is inactive");
994            return;
995        }
996
997        switch (msg.what) {
998            case EVENT_RECORDS_LOADED:
999                onRecordsLoaded();
1000                break;
1001
1002            case EVENT_NV_READY:
1003                onNVReady();
1004                break;
1005
1006            case EVENT_CDMA_DATA_DETACHED:
1007                onCdmaDataDetached();
1008                break;
1009
1010            case EVENT_DATA_STATE_CHANGED:
1011                onDataStateChanged((AsyncResult) msg.obj);
1012                break;
1013
1014            case EVENT_CDMA_OTA_PROVISION:
1015                onCdmaOtaProvision((AsyncResult) msg.obj);
1016                break;
1017
1018            case EVENT_RESTART_RADIO:
1019                if (DBG) log("EVENT_RESTART_RADIO");
1020                onRestartRadio();
1021                break;
1022
1023            default:
1024                // handle the message in the super class DataConnectionTracker
1025                super.handleMessage(msg);
1026                break;
1027        }
1028    }
1029
1030    protected void log(String s) {
1031        Log.d(LOG_TAG, "[CdmaDataConnectionTracker] " + s);
1032    }
1033}
1034