blob: 761eb8b1a7c969f2ca8169ae5150b54371553f4d [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony.cdma;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.INetStatService;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.preference.PreferenceManager;
import android.provider.Checkin;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaCellLocation;
import android.util.EventLog;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.DataCallState;
import com.android.internal.telephony.DataConnection;
import com.android.internal.telephony.DataConnection.FailCause;
import com.android.internal.telephony.DataConnectionTracker;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.TelephonyEventLog;
import java.util.ArrayList;
/**
* {@hide}
*/
public final class CdmaDataConnectionTracker extends DataConnectionTracker {
private static final String LOG_TAG = "CDMA";
private static final boolean DBG = true;
private CDMAPhone mCdmaPhone;
// Indicates baseband will not auto-attach
private boolean noAutoAttach = false;
long nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS;
private boolean mIsScreenOn = true;
//useful for debugging
boolean failNextConnect = false;
/**
* dataConnectionList holds all the Data connection
*/
private ArrayList<DataConnection> dataConnectionList;
/** Currently active CdmaDataConnection */
private CdmaDataConnection mActiveDataConnection;
/** Defined cdma connection profiles */
private static final int EXTERNAL_NETWORK_DEFAULT_ID = 0;
private static final int EXTERNAL_NETWORK_NUM_TYPES = 1;
private boolean[] dataEnabled = new boolean[EXTERNAL_NETWORK_NUM_TYPES];
/**
* Pool size of CdmaDataConnection objects.
*/
private static final int DATA_CONNECTION_POOL_SIZE = 1;
private static final int POLL_CONNECTION_MILLIS = 5 * 1000;
private static final String INTENT_RECONNECT_ALARM =
"com.android.internal.telephony.cdma-reconnect";
private static final String INTENT_RECONNECT_ALARM_EXTRA_REASON = "reason";
/**
* Constants for the data connection activity:
* physical link down/up
*/
private static final int DATA_CONNECTION_ACTIVE_PH_LINK_DOWN = 1;
private static final int DATA_CONNECTION_ACTIVE_PH_LINK_UP = 2;
// Possibly promoate to base class, the only difference is
// the INTENT_RECONNECT_ALARM action is a different string.
// Do consider technology changes if it is promoted.
BroadcastReceiver mIntentReceiver = new BroadcastReceiver ()
{
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (action.equals(Intent.ACTION_SCREEN_ON)) {
mIsScreenOn = true;
stopNetStatPoll();
startNetStatPoll();
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
mIsScreenOn = false;
stopNetStatPoll();
startNetStatPoll();
} else if (action.equals((INTENT_RECONNECT_ALARM))) {
Log.d(LOG_TAG, "Data reconnect alarm. Previous state was " + state);
String reason = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON);
if (state == State.FAILED) {
cleanUpConnection(false, reason);
}
trySetupData(reason);
} else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
final android.net.NetworkInfo networkInfo = (NetworkInfo)
intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
mIsWifiConnected = (networkInfo != null && networkInfo.isConnected());
} else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
final boolean enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
if (!enabled) {
// when wifi got disabeled, the NETWORK_STATE_CHANGED_ACTION
// quit and wont report disconnected til next enalbing.
mIsWifiConnected = false;
}
}
}
};
//***** Constructor
CdmaDataConnectionTracker(CDMAPhone p) {
super(p);
mCdmaPhone = p;
p.mCM.registerForAvailable (this, EVENT_RADIO_AVAILABLE, null);
p.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
p.mRuimRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null);
p.mCM.registerForNVReady(this, EVENT_NV_READY, null);
p.mCM.registerForDataStateChanged (this, EVENT_DATA_STATE_CHANGED, null);
p.mCT.registerForVoiceCallEnded (this, EVENT_VOICE_CALL_ENDED, null);
p.mCT.registerForVoiceCallStarted (this, EVENT_VOICE_CALL_STARTED, null);
p.mSST.registerForCdmaDataConnectionAttached(this, EVENT_TRY_SETUP_DATA, null);
p.mSST.registerForCdmaDataConnectionDetached(this, EVENT_CDMA_DATA_DETACHED, null);
p.mSST.registerForRoamingOn(this, EVENT_ROAMING_ON, null);
p.mSST.registerForRoamingOff(this, EVENT_ROAMING_OFF, null);
this.netstat = INetStatService.Stub.asInterface(ServiceManager.getService("netstat"));
IntentFilter filter = new IntentFilter();
filter.addAction(INTENT_RECONNECT_ALARM);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
p.getContext().registerReceiver(mIntentReceiver, filter, null, p.h);
mDataConnectionTracker = this;
createAllDataConnectionList();
// This preference tells us 1) initial condition for "dataEnabled",
// and 2) whether the RIL will setup the baseband to auto-PS attach.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(phone.getContext());
dataEnabled[EXTERNAL_NETWORK_DEFAULT_ID] =
!sp.getBoolean(CDMAPhone.DATA_DISABLED_ON_BOOT_KEY, false);
noAutoAttach = !dataEnabled[EXTERNAL_NETWORK_DEFAULT_ID];
}
public void dispose() {
//Unregister from all events
phone.mCM.unregisterForAvailable(this);
phone.mCM.unregisterForOffOrNotAvailable(this);
mCdmaPhone.mRuimRecords.unregisterForRecordsLoaded(this);
phone.mCM.unregisterForNVReady(this);
phone.mCM.unregisterForDataStateChanged(this);
mCdmaPhone.mCT.unregisterForVoiceCallEnded(this);
mCdmaPhone.mCT.unregisterForVoiceCallStarted(this);
mCdmaPhone.mSST.unregisterForCdmaDataConnectionAttached(this);
mCdmaPhone.mSST.unregisterForCdmaDataConnectionDetached(this);
mCdmaPhone.mSST.unregisterForRoamingOn(this);
mCdmaPhone.mSST.unregisterForRoamingOff(this);
phone.getContext().unregisterReceiver(this.mIntentReceiver);
destroyAllDataConnectionList();
}
protected void finalize() {
if(DBG) Log.d(LOG_TAG, "CdmaDataConnectionTracker finalized");
}
void setState(State s) {
if (DBG) log ("setState: " + s);
if (state != s) {
if (s == State.INITING) { // request Data connection context
Checkin.updateStats(phone.getContext().getContentResolver(),
Checkin.Stats.Tag.PHONE_CDMA_DATA_ATTEMPTED, 1, 0.0);
}
if (s == State.CONNECTED) { // pppd is up
Checkin.updateStats(phone.getContext().getContentResolver(),
Checkin.Stats.Tag.PHONE_CDMA_DATA_CONNECTED, 1, 0.0);
}
}
state = s;
}
public int enableApnType(String type) {
// This request is mainly used to enable MMS APN
// In CDMA there is no need to enable/disable a different APN for MMS
Log.d(LOG_TAG, "Request to enableApnType("+type+")");
if (TextUtils.equals(type, Phone.APN_TYPE_MMS)) {
return Phone.APN_ALREADY_ACTIVE;
} else if (TextUtils.equals(type, Phone.APN_TYPE_SUPL)) {
Log.w(LOG_TAG, "Phone.APN_TYPE_SUPL not enabled for CDMA");
return Phone.APN_REQUEST_FAILED;
} else {
return Phone.APN_REQUEST_FAILED;
}
}
public int disableApnType(String type) {
// This request is mainly used to disable MMS APN
// In CDMA there is no need to enable/disable a different APN for MMS
Log.d(LOG_TAG, "Request to disableApnType("+type+")");
if (TextUtils.equals(type, Phone.APN_TYPE_MMS)) {
return Phone.APN_REQUEST_STARTED;
} else {
return Phone.APN_REQUEST_FAILED;
}
}
private boolean isEnabled(int cdmaDataProfile) {
return dataEnabled[cdmaDataProfile];
}
private void setEnabled(int cdmaDataProfile, boolean enable) {
Log.d(LOG_TAG, "setEnabled(" + cdmaDataProfile + ", " + enable + ')');
dataEnabled[cdmaDataProfile] = enable;
Log.d(LOG_TAG, "dataEnabled[DEFAULT_PROFILE]=" + dataEnabled[EXTERNAL_NETWORK_DEFAULT_ID]);
}
/**
* Simply tear down data connections due to radio off
* and don't setup again.
*/
public void cleanConnectionBeforeRadioOff() {
cleanUpConnection(true, Phone.REASON_RADIO_TURNED_OFF);
}
/**
* The data connection is expected to be setup while device
* 1. has ruim card or non-volatile data store
* 2. registered to data connection service
* 3. user doesn't explicitly disable data service
* 4. wifi is not on
*
* @return false while no data connection if all above requirements are met.
*/
public boolean isDataConnectionAsDesired() {
boolean roaming = phone.getServiceState().getRoaming();
if (((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) ||
mCdmaPhone.mRuimRecords.getRecordsLoaded()) &&
(mCdmaPhone.mSST.getCurrentCdmaDataConnectionState() ==
ServiceState.STATE_IN_SERVICE) &&
(!roaming || getDataOnRoamingEnabled()) &&
!mIsWifiConnected ) {
return (state == State.CONNECTED);
}
return true;
}
/**
* Prevent mobile data connections from being established,
* or once again allow mobile data connections. If the state
* toggles, then either tear down or set up data, as
* appropriate to match the new state.
* <p>This operation only affects the default connection
* @param enable indicates whether to enable ({@code true}) or disable ({@code false}) data
* @return {@code true} if the operation succeeded
*/
public boolean setDataEnabled(boolean enable) {
boolean isEnabled = isEnabled(EXTERNAL_NETWORK_DEFAULT_ID);
Log.d(LOG_TAG, "setDataEnabled("+enable+") isEnabled=" + isEnabled);
if (!isEnabled && enable) {
setEnabled(EXTERNAL_NETWORK_DEFAULT_ID, true);
return trySetupData(Phone.REASON_DATA_ENABLED);
} else if (!enable) {
setEnabled(EXTERNAL_NETWORK_DEFAULT_ID, false);
cleanUpConnection(true, Phone.REASON_DATA_DISABLED);
return true;
} else // isEnabled && enable
return true;
}
/**
* Report the current state of data connectivity (enabled or disabled)
* @return {@code false} if data connectivity has been explicitly disabled,
* {@code true} otherwise.
*/
public boolean getDataEnabled() {
return dataEnabled[EXTERNAL_NETWORK_DEFAULT_ID];
}
/**
* Report on whether data connectivity is enabled
* @return {@code false} if data connectivity has been explicitly disabled,
* {@code true} otherwise.
*/
public boolean getAnyDataEnabled() {
for (int i=0; i < EXTERNAL_NETWORK_NUM_TYPES; i++) {
if (isEnabled(i)) return true;
}
return false;
}
private boolean isDataAllowed() {
boolean roaming = phone.getServiceState().getRoaming();
return getAnyDataEnabled() && (!roaming || getDataOnRoamingEnabled());
}
private boolean trySetupData(String reason) {
if (DBG) log("***trySetupData due to " + (reason == null ? "(unspecified)" : reason));
if (phone.getSimulatedRadioControl() != null) {
// Assume data is connected on the simulator
// FIXME this can be improved
setState(State.CONNECTED);
phone.notifyDataConnection(reason);
Log.i(LOG_TAG, "(fix?) We're on the simulator; assuming data is connected");
return true;
}
int psState = mCdmaPhone.mSST.getCurrentCdmaDataConnectionState();
boolean roaming = phone.getServiceState().getRoaming();
if ((state == State.IDLE || state == State.SCANNING)
&& (psState == ServiceState.RADIO_TECHNOLOGY_1xRTT ||
psState == ServiceState.RADIO_TECHNOLOGY_EVDO_0 ||
psState == ServiceState.RADIO_TECHNOLOGY_EVDO_A)
&& ((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) ||
mCdmaPhone.mRuimRecords.getRecordsLoaded())
&& (mCdmaPhone.mSST.isConcurrentVoiceAndData() ||
phone.getState() == Phone.State.IDLE )
&& isDataAllowed()) {
return setupData(reason);
} else {
if (DBG) {
log("trySetupData: Not ready for data: " +
" dataState=" + state +
" PS state=" + psState +
" radio state=" + phone.mCM.getRadioState() +
" ruim=" + mCdmaPhone.mRuimRecords.getRecordsLoaded() +
" concurrentVoice&Data=" + mCdmaPhone.mSST.isConcurrentVoiceAndData() +
" phoneState=" + phone.getState() +
" dataEnabled=" + getAnyDataEnabled() +
" roaming=" + roaming +
" dataOnRoamingEnable=" + getDataOnRoamingEnabled());
}
return false;
}
}
/**
* If tearDown is true, this only tears down a CONNECTED session. Presently,
* there is no mechanism for abandoning an INITING/CONNECTING session,
* but would likely involve cancelling pending async requests or
* setting a flag or new state to ignore them when they came in
* @param tearDown true if the underlying DataConnection should be
* disconnected.
* @param reason reason for the clean up.
*/
private void cleanUpConnection(boolean tearDown, String reason) {
if (DBG) log("Clean up connection due to " + reason);
// Clear the reconnect alarm, if set.
if (mReconnectIntent != null) {
AlarmManager am =
(AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
am.cancel(mReconnectIntent);
mReconnectIntent = null;
}
for (DataConnection connBase : dataConnectionList) {
CdmaDataConnection conn = (CdmaDataConnection) connBase;
if(conn != null) {
if (tearDown) {
Message msg = obtainMessage(EVENT_DISCONNECT_DONE, reason);
conn.disconnect(msg);
} else {
conn.clearSettings();
}
}
}
stopNetStatPoll();
/*
* If we've been asked to tear down the connection,
* set the state to DISCONNECTING. However, there's
* a race that can occur if for some reason we were
* already in the IDLE state. In that case, the call
* to conn.disconnect() above will immediately post
* a message to the handler thread that the disconnect
* is done, and if the handler runs before the code
* below does, the handler will have set the state to
* IDLE before the code below runs. If we didn't check
* for that, future calls to trySetupData would fail,
* and we would never get out of the DISCONNECTING state.
*/
if (!tearDown) {
setState(State.IDLE);
phone.notifyDataConnection(reason);
} else if (state != State.IDLE) {
setState(State.DISCONNECTING);
}
}
private CdmaDataConnection findFreeDataConnection() {
for (DataConnection connBase : dataConnectionList) {
CdmaDataConnection conn = (CdmaDataConnection) connBase;
if (conn.getState() == DataConnection.State.INACTIVE) {
return conn;
}
}
return null;
}
private boolean setupData(String reason) {
CdmaDataConnection conn = findFreeDataConnection();
if (conn == null) {
if (DBG) log("setupData: No free CdmaDataConnectionfound!");
return false;
}
mActiveDataConnection = conn;
Message msg = obtainMessage();
msg.what = EVENT_DATA_SETUP_COMPLETE;
msg.obj = reason;
conn.connect(msg);
setState(State.INITING);
phone.notifyDataConnection(reason);
return true;
}
private void notifyDefaultData(String reason) {
setState(State.CONNECTED);
phone.notifyDataConnection(reason);
startNetStatPoll();
// reset reconnect timer
nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS;
}
private void resetPollStats() {
txPkts = -1;
rxPkts = -1;
sentSinceLastRecv = 0;
netStatPollPeriod = POLL_NETSTAT_MILLIS;
mNoRecvPollCount = 0;
}
protected void startNetStatPoll() {
if (state == State.CONNECTED && netStatPollEnabled == false) {
Log.d(LOG_TAG, "[DataConnection] Start poll NetStat");
resetPollStats();
netStatPollEnabled = true;
mPollNetStat.run();
}
}
protected void stopNetStatPoll() {
netStatPollEnabled = false;
removeCallbacks(mPollNetStat);
Log.d(LOG_TAG, "[DataConnection] Stop poll NetStat");
}
protected void restartRadio() {
Log.d(LOG_TAG, "************TURN OFF RADIO**************");
cleanUpConnection(true, Phone.REASON_RADIO_TURNED_OFF);
phone.mCM.setRadioPower(false, null);
/* Note: no need to call setRadioPower(true). Assuming the desired
* radio power state is still ON (as tracked by ServiceStateTracker),
* ServiceStateTracker will call setRadioPower when it receives the
* RADIO_STATE_CHANGED notification for the power off. And if the
* desired power state has changed in the interim, we don't want to
* override it with an unconditional power on.
*/
}
private Runnable mPollNetStat = new Runnable() {
public void run() {
long sent, received;
long preTxPkts = -1, preRxPkts = -1;
Activity newActivity;
preTxPkts = txPkts;
preRxPkts = rxPkts;
// check if netstat is still valid to avoid NullPointerException after NTC
if (netstat != null) {
try {
txPkts = netstat.getMobileTxPackets();
rxPkts = netstat.getMobileRxPackets();
} catch (RemoteException e) {
txPkts = 0;
rxPkts = 0;
}
//Log.d(LOG_TAG, "rx " + String.valueOf(rxPkts) + " tx " + String.valueOf(txPkts));
if (netStatPollEnabled && (preTxPkts > 0 || preRxPkts > 0)) {
sent = txPkts - preTxPkts;
received = rxPkts - preRxPkts;
if ( sent > 0 && received > 0 ) {
sentSinceLastRecv = 0;
newActivity = Activity.DATAINANDOUT;
} else if (sent > 0 && received == 0) {
if (phone.getState() == Phone.State.IDLE) {
sentSinceLastRecv += sent;
} else {
sentSinceLastRecv = 0;
}
newActivity = Activity.DATAOUT;
} else if (sent == 0 && received > 0) {
sentSinceLastRecv = 0;
newActivity = Activity.DATAIN;
} else if (sent == 0 && received == 0) {
newActivity = Activity.NONE;
} else {
sentSinceLastRecv = 0;
newActivity = Activity.NONE;
}
if (activity != newActivity) {
activity = newActivity;
phone.notifyDataActivity();
}
}
if (sentSinceLastRecv >= NUMBER_SENT_PACKETS_OF_HANG) {
// Packets sent without ack exceeded threshold.
if (mNoRecvPollCount == 0) {
EventLog.writeEvent(
TelephonyEventLog.EVENT_LOG_RADIO_RESET_COUNTDOWN_TRIGGERED,
sentSinceLastRecv);
}
if (mNoRecvPollCount < NO_RECV_POLL_LIMIT) {
mNoRecvPollCount++;
// Slow down the poll interval to let things happen
netStatPollPeriod = POLL_NETSTAT_SLOW_MILLIS;
} else {
if (DBG) log("Sent " + String.valueOf(sentSinceLastRecv) +
" pkts since last received");
// We've exceeded the threshold. Restart the radio.
netStatPollEnabled = false;
stopNetStatPoll();
restartRadio();
EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_RADIO_RESET,
NO_RECV_POLL_LIMIT);
}
} else {
mNoRecvPollCount = 0;
netStatPollPeriod = POLL_NETSTAT_MILLIS;
}
if (netStatPollEnabled) {
mDataConnectionTracker.postDelayed(this, netStatPollPeriod);
}
}
}
};
/**
* Returns true if the last fail cause is something that
* seems like it deserves an error notification.
* Transient errors are ignored
*/
private boolean
shouldPostNotification(FailCause cause) {
return (cause != FailCause.UNKNOWN);
}
/**
* Return true if data connection need to be setup after disconnected due to
* reason.
*
* @param reason the reason why data is disconnected
* @return true if try setup data connection is need for this reason
*/
private boolean retryAfterDisconnected(String reason) {
boolean retry = true;
if ( Phone.REASON_RADIO_TURNED_OFF.equals(reason) ||
Phone.REASON_DATA_DISABLED.equals(reason) ) {
retry = false;
}
return retry;
}
private void reconnectAfterFail(FailCause lastFailCauseCode, String reason) {
if (state == State.FAILED) {
Log.d(LOG_TAG, "Data Connection activate failed. Scheduling next attempt for "
+ (nextReconnectDelay / 1000) + "s");
AlarmManager am =
(AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(INTENT_RECONNECT_ALARM);
intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, reason);
mReconnectIntent = PendingIntent.getBroadcast(
phone.getContext(), 0, intent, 0);
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + nextReconnectDelay,
mReconnectIntent);
// double it for next time
nextReconnectDelay *= 2;
if (nextReconnectDelay > RECONNECT_DELAY_MAX_MILLIS) {
nextReconnectDelay = RECONNECT_DELAY_MAX_MILLIS;
}
if (!shouldPostNotification(lastFailCauseCode)) {
Log.d(LOG_TAG,"NOT Posting Data Connection Unavailable notification "
+ "-- likely transient error");
} else {
notifyNoData(lastFailCauseCode);
}
}
}
private void notifyNoData(FailCause lastFailCauseCode) {
setState(State.FAILED);
}
protected void onRecordsLoaded() {
if (state == State.FAILED) {
cleanUpConnection(false, null);
}
sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA));
}
protected void onNVReady() {
if (state == State.FAILED) {
cleanUpConnection(false, null);
}
sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA));
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onTrySetupData() {
trySetupData(null);
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onRoamingOff() {
trySetupData(Phone.REASON_ROAMING_OFF);
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onRoamingOn() {
if (getDataOnRoamingEnabled()) {
trySetupData(Phone.REASON_ROAMING_ON);
} else {
if (DBG) log("Tear down data connection on roaming.");
cleanUpConnection(true, Phone.REASON_ROAMING_ON);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onRadioAvailable() {
if (phone.getSimulatedRadioControl() != null) {
// Assume data is connected on the simulator
// FIXME this can be improved
setState(State.CONNECTED);
phone.notifyDataConnection(null);
Log.i(LOG_TAG, "We're on the simulator; assuming data is connected");
}
if (state != State.IDLE) {
cleanUpConnection(true, null);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onRadioOffOrNotAvailable() {
// Make sure our reconnect delay starts at the initial value
// next time the radio comes on
nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS;
if (phone.getSimulatedRadioControl() != null) {
// Assume data is connected on the simulator
// FIXME this can be improved
Log.i(LOG_TAG, "We're on the simulator; assuming radio off is meaningless");
} else {
if (DBG) log("Radio is off and clean up all connection");
cleanUpConnection(false, Phone.REASON_RADIO_TURNED_OFF);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onDataSetupComplete(AsyncResult ar) {
String reason = null;
if (ar.userObj instanceof String) {
reason = (String) ar.userObj;
}
if (ar.exception == null) {
// everything is setup
notifyDefaultData(reason);
} else {
FailCause cause = (FailCause) (ar.result);
if(DBG) log("Data Connection setup failed " + cause);
// No try for permanent failure
if (cause.isPermanentFail()) {
notifyNoData(cause);
}
if (tryAgain(cause)) {
trySetupData(reason);
} else {
startDelayedRetry(cause, reason);
}
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onDisconnectDone(AsyncResult ar) {
if(DBG) log("EVENT_DISCONNECT_DONE");
String reason = null;
if (ar.userObj instanceof String) {
reason = (String) ar.userObj;
}
setState(State.IDLE);
phone.notifyDataConnection(reason);
if (retryAfterDisconnected(reason)) {
trySetupData(reason);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onVoiceCallStarted() {
if (state == State.CONNECTED && !mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
stopNetStatPoll();
phone.notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onVoiceCallEnded() {
if (state == State.CONNECTED) {
if (!mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
startNetStatPoll();
phone.notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
} else {
// clean slate after call end.
resetPollStats();
}
} else {
// in case data setup was attempted when we were on a voice call
trySetupData(Phone.REASON_VOICE_CALL_ENDED);
}
}
private boolean tryAgain(FailCause cause) {
return (cause != FailCause.RADIO_NOT_AVAILABLE)
&& (cause != FailCause.RADIO_OFF)
&& (cause != FailCause.RADIO_ERROR_RETRY)
&& (cause != FailCause.NO_SIGNAL)
&& (cause != FailCause.SIM_LOCKED);
}
private void createAllDataConnectionList() {
dataConnectionList = new ArrayList<DataConnection>();
CdmaDataConnection dataConn;
for (int i = 0; i < DATA_CONNECTION_POOL_SIZE; i++) {
dataConn = new CdmaDataConnection(mCdmaPhone);
dataConnectionList.add(dataConn);
}
}
private void destroyAllDataConnectionList() {
if(dataConnectionList != null) {
dataConnectionList.removeAll(dataConnectionList);
}
}
private void onCdmaDataAttached() {
if (state == State.CONNECTED) {
startNetStatPoll();
phone.notifyDataConnection(Phone.REASON_CDMA_DATA_DETACHED);
} else {
if (state == State.FAILED) {
cleanUpConnection(false, Phone.REASON_CDMA_DATA_DETACHED);
nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS;
CdmaCellLocation loc = (CdmaCellLocation)(phone.getCellLocation());
int bsid = (loc != null) ? loc.getBaseStationId() : -1;
EventLog.List val = new EventLog.List(bsid,
TelephonyManager.getDefault().getNetworkType());
EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_CDMA_DATA_SETUP_FAILED, val);
}
trySetupData(Phone.REASON_CDMA_DATA_DETACHED);
}
}
protected void onDataStateChanged (AsyncResult ar) {
ArrayList<DataCallState> dataCallStates = (ArrayList<DataCallState>)(ar.result);
if (ar.exception != null) {
// This is probably "radio not available" or something
// of that sort. If so, the whole connection is going
// to come down soon anyway
return;
}
if (state == State.CONNECTED) {
if (dataCallStates.get(0).active == DATA_CONNECTION_ACTIVE_PH_LINK_UP ) {
activity = Activity.NONE;
phone.notifyDataActivity();
} else if (dataCallStates.get(0).active == DATA_CONNECTION_ACTIVE_PH_LINK_DOWN ) {
activity = Activity.DORMANT;
phone.notifyDataActivity();
}
} else {
CdmaCellLocation loc = (CdmaCellLocation)(phone.getCellLocation());
int bsid = (loc != null) ? loc.getBaseStationId() : -1;
EventLog.List val = new EventLog.List(bsid,
TelephonyManager.getDefault().getNetworkType());
EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_CDMA_DATA_DROP, val);
cleanUpConnection(true, null);
}
Log.i(LOG_TAG, "Data connection has changed.");
}
String getInterfaceName() {
if (mActiveDataConnection != null) {
return mActiveDataConnection.getInterface();
}
return null;
}
protected String getIpAddress() {
if (mActiveDataConnection != null) {
return mActiveDataConnection.getIpAddress();
}
return null;
}
String getGateway() {
if (mActiveDataConnection != null) {
return mActiveDataConnection.getGatewayAddress();
}
return null;
}
protected String[] getDnsServers() {
if (mActiveDataConnection != null) {
return mActiveDataConnection.getDnsServers();
}
return null;
}
public ArrayList<DataConnection> getAllDataConnections() {
return dataConnectionList;
}
private void startDelayedRetry(FailCause cause, String reason) {
notifyNoData(cause);
reconnectAfterFail(cause, reason);
}
public void handleMessage (Message msg) {
switch (msg.what) {
case EVENT_RECORDS_LOADED:
onRecordsLoaded();
break;
case EVENT_NV_READY:
onNVReady();
break;
case EVENT_CDMA_DATA_DETACHED:
onCdmaDataAttached();
break;
case EVENT_DATA_STATE_CHANGED:
onDataStateChanged((AsyncResult) msg.obj);
break;
default:
// handle the message in the super class DataConnectionTracker
super.handleMessage(msg);
break;
}
}
protected void log(String s) {
Log.d(LOG_TAG, "[CdmaDataConnectionTracker] " + s);
}
}