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