blob: 683a6baa2a307ed254c6825e58448c197dc0306d [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.dataconnection;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.net.LinkCapabilities;
import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.TrafficStats;
import android.net.wifi.WifiManager;
import android.os.AsyncResult;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Messenger;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.EventLog;
import android.telephony.Rlog;
import com.android.internal.R;
import com.android.internal.telephony.DctConstants;
import com.android.internal.telephony.EventLogTags;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.uicc.IccRecords;
import com.android.internal.telephony.uicc.UiccController;
import com.android.internal.util.AsyncChannel;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* {@hide}
*/
public abstract class DcTrackerBase extends Handler {
protected static final boolean DBG = true;
protected static final boolean VDBG = false; // STOPSHIP if true
protected static final boolean VDBG_STALL = true; // STOPSHIP if true
protected static final boolean RADIO_TESTS = false;
/**
* Constants for the data connection activity:
* physical link down/up
*/
protected static final int DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE = 0;
protected static final int DATA_CONNECTION_ACTIVE_PH_LINK_DOWN = 1;
protected static final int DATA_CONNECTION_ACTIVE_PH_LINK_UP = 2;
/** Delay between APN attempts.
Note the property override mechanism is there just for testing purpose only. */
protected static final int APN_DELAY_DEFAULT_MILLIS = 20000;
/** Delay between APN attempts when in fail fast mode */
protected static final int APN_FAIL_FAST_DELAY_DEFAULT_MILLIS = 3000;
AlarmManager mAlarmManager;
protected Object mDataEnabledLock = new Object();
// responds to the setInternalDataEnabled call - used internally to turn off data
// for example during emergency calls
protected boolean mInternalDataEnabled = true;
// responds to public (user) API to enable/disable data use
// independent of mInternalDataEnabled and requests for APN access
// persisted
protected boolean mUserDataEnabled = true;
// TODO: move away from static state once 5587429 is fixed.
protected static boolean sPolicyDataEnabled = true;
private boolean[] mDataEnabled = new boolean[DctConstants.APN_NUM_TYPES];
private int mEnabledCount = 0;
/* Currently requested APN type (TODO: This should probably be a parameter not a member) */
protected String mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
/** Retry configuration: A doubling of retry times from 5secs to 30minutes */
protected static final String DEFAULT_DATA_RETRY_CONFIG = "default_randomization=2000,"
+ "5000,10000,20000,40000,80000:5000,160000:5000,"
+ "320000:5000,640000:5000,1280000:5000,1800000:5000";
/** Retry configuration for secondary networks: 4 tries in 20 sec */
protected static final String SECONDARY_DATA_RETRY_CONFIG =
"max_retries=3, 5000, 5000, 5000";
/** Slow poll when attempting connection recovery. */
protected static final int POLL_NETSTAT_SLOW_MILLIS = 5000;
/** Default max failure count before attempting to network re-registration. */
protected static final int DEFAULT_MAX_PDP_RESET_FAIL = 3;
/**
* After detecting a potential connection problem, this is the max number
* of subsequent polls before attempting recovery.
*/
protected static final int NO_RECV_POLL_LIMIT = 24;
// 1 sec. default polling interval when screen is on.
protected static final int POLL_NETSTAT_MILLIS = 1000;
// 10 min. default polling interval when screen is off.
protected static final int POLL_NETSTAT_SCREEN_OFF_MILLIS = 1000*60*10;
// 2 min for round trip time
protected static final int POLL_LONGEST_RTT = 120 * 1000;
// Default sent packets without ack which triggers initial recovery steps
protected static final int NUMBER_SENT_PACKETS_OF_HANG = 10;
// how long to wait before switching back to default APN
protected static final int RESTORE_DEFAULT_APN_DELAY = 1 * 60 * 1000;
// system property that can override the above value
protected static final String APN_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore";
// represents an invalid IP address
protected static final String NULL_IP = "0.0.0.0";
// Default for the data stall alarm while non-aggressive stall detection
protected static final int DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60 * 6;
// Default for the data stall alarm for aggressive stall detection
protected static final int DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60;
// If attempt is less than this value we're doing first level recovery
protected static final int DATA_STALL_NO_RECV_POLL_LIMIT = 1;
// Tag for tracking stale alarms
protected static final String DATA_STALL_ALARM_TAG_EXTRA = "data.stall.alram.tag";
protected static final boolean DATA_STALL_SUSPECTED = true;
protected static final boolean DATA_STALL_NOT_SUSPECTED = false;
protected String RADIO_RESET_PROPERTY = "gsm.radioreset";
protected static final String INTENT_RECONNECT_ALARM =
"com.android.internal.telephony.data-reconnect";
protected static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "reconnect_alarm_extra_type";
protected static final String INTENT_RECONNECT_ALARM_EXTRA_REASON =
"reconnect_alarm_extra_reason";
protected static final String INTENT_RESTART_TRYSETUP_ALARM =
"com.android.internal.telephony.data-restart-trysetup";
protected static final String INTENT_RESTART_TRYSETUP_ALARM_EXTRA_TYPE =
"restart_trysetup_alarm_extra_type";
protected static final String INTENT_DATA_STALL_ALARM =
"com.android.internal.telephony.data-stall";
protected static final String DEFALUT_DATA_ON_BOOT_PROP = "net.def_data_on_boot";
protected DcTesterFailBringUpAll mDcTesterFailBringUpAll;
protected DcController mDcc;
// member variables
protected PhoneBase mPhone;
protected UiccController mUiccController;
protected AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>();
protected DctConstants.Activity mActivity = DctConstants.Activity.NONE;
protected DctConstants.State mState = DctConstants.State.IDLE;
protected Handler mDataConnectionTracker = null;
protected long mTxPkts;
protected long mRxPkts;
protected int mNetStatPollPeriod;
protected boolean mNetStatPollEnabled = false;
protected TxRxSum mDataStallTxRxSum = new TxRxSum(0, 0);
// Used to track stale data stall alarms.
protected int mDataStallAlarmTag = (int) SystemClock.elapsedRealtime();
// The current data stall alarm intent
protected PendingIntent mDataStallAlarmIntent = null;
// Number of packets sent since the last received packet
protected long mSentSinceLastRecv;
// Controls when a simple recovery attempt it to be tried
protected int mNoRecvPollCount = 0;
// Refrence counter for enabling fail fast
protected static int sEnableFailFastRefCounter = 0;
// True if data stall detection is enabled
protected volatile boolean mDataStallDetectionEnabled = true;
protected volatile boolean mFailFast = false;
// True when in voice call
protected boolean mInVoiceCall = false;
// wifi connection status will be updated by sticky intent
protected boolean mIsWifiConnected = false;
/** Intent sent when the reconnect alarm fires. */
protected PendingIntent mReconnectIntent = null;
/** CID of active data connection */
protected int mCidActive;
// When false we will not auto attach and manually attaching is required.
protected boolean mAutoAttachOnCreation = false;
// State of screen
// (TODO: Reconsider tying directly to screen, maybe this is
// really a lower power mode")
protected boolean mIsScreenOn = true;
/** Allows the generation of unique Id's for DataConnection objects */
protected AtomicInteger mUniqueIdGenerator = new AtomicInteger(0);
/** The data connections. */
protected HashMap<Integer, DataConnection> mDataConnections =
new HashMap<Integer, DataConnection>();
/** The data connection async channels */
protected HashMap<Integer, DcAsyncChannel> mDataConnectionAcHashMap =
new HashMap<Integer, DcAsyncChannel>();
/** Convert an ApnType string to Id (TODO: Use "enumeration" instead of String for ApnType) */
protected HashMap<String, Integer> mApnToDataConnectionId =
new HashMap<String, Integer>();
/** Phone.APN_TYPE_* ===> ApnContext */
protected ConcurrentHashMap<String, ApnContext> mApnContexts =
new ConcurrentHashMap<String, ApnContext>();
/* Currently active APN */
protected ApnSetting mActiveApn;
/** allApns holds all apns */
protected ArrayList<ApnSetting> mAllApnSettings = null;
/** preferred apn */
protected ApnSetting mPreferredApn = null;
/** Is packet service restricted by network */
protected boolean mIsPsRestricted = false;
/* Once disposed dont handle any messages */
protected boolean mIsDisposed = false;
protected ContentResolver mResolver;
protected BroadcastReceiver mIntentReceiver = new BroadcastReceiver ()
{
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (DBG) log("onReceive: action=" + action);
if (action.equals(Intent.ACTION_SCREEN_ON)) {
mIsScreenOn = true;
stopNetStatPoll();
startNetStatPoll();
restartDataStallAlarm();
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
mIsScreenOn = false;
stopNetStatPoll();
startNetStatPoll();
restartDataStallAlarm();
} else if (action.startsWith(INTENT_RECONNECT_ALARM)) {
if (DBG) log("Reconnect alarm. Previous state was " + mState);
onActionIntentReconnectAlarm(intent);
} else if (action.startsWith(INTENT_RESTART_TRYSETUP_ALARM)) {
if (DBG) log("Restart trySetup alarm");
onActionIntentRestartTrySetupAlarm(intent);
} else if (action.equals(INTENT_DATA_STALL_ALARM)) {
onActionIntentDataStallAlarm(intent);
} 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());
if (DBG) log("NETWORK_STATE_CHANGED_ACTION: mIsWifiConnected=" + mIsWifiConnected);
} 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 disabled, the NETWORK_STATE_CHANGED_ACTION
// quit and won't report disconnected until next enabling.
mIsWifiConnected = false;
}
if (DBG) log("WIFI_STATE_CHANGED_ACTION: enabled=" + enabled
+ " mIsWifiConnected=" + mIsWifiConnected);
}
}
};
private Runnable mPollNetStat = new Runnable()
{
@Override
public void run() {
updateDataActivity();
if (mIsScreenOn) {
mNetStatPollPeriod = Settings.Global.getInt(mResolver,
Settings.Global.PDP_WATCHDOG_POLL_INTERVAL_MS, POLL_NETSTAT_MILLIS);
} else {
mNetStatPollPeriod = Settings.Global.getInt(mResolver,
Settings.Global.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
POLL_NETSTAT_SCREEN_OFF_MILLIS);
}
if (mNetStatPollEnabled) {
mDataConnectionTracker.postDelayed(this, mNetStatPollPeriod);
}
}
};
private class DataRoamingSettingObserver extends ContentObserver {
public DataRoamingSettingObserver(Handler handler, Context context) {
super(handler);
mResolver = context.getContentResolver();
}
public void register() {
mResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.DATA_ROAMING), false, this);
}
public void unregister() {
mResolver.unregisterContentObserver(this);
}
@Override
public void onChange(boolean selfChange) {
// already running on mPhone handler thread
if (mPhone.getServiceState().getRoaming()) {
sendMessage(obtainMessage(DctConstants.EVENT_ROAMING_ON));
}
}
}
private final DataRoamingSettingObserver mDataRoamingSettingObserver;
/**
* The Initial MaxRetry sent to a DataConnection as a parameter
* to DataConnectionAc.bringUp. This value can be defined at compile
* time using the SystemProperty Settings.Global.DCT_INITIAL_MAX_RETRY
* and at runtime using gservices to change Settings.Global.DCT_INITIAL_MAX_RETRY.
*/
private static final int DEFAULT_MDC_INITIAL_RETRY = 1;
protected int getInitialMaxRetry() {
if (mFailFast) {
return 0;
}
// Get default value from system property or use DEFAULT_MDC_INITIAL_RETRY
int value = SystemProperties.getInt(
Settings.Global.MDC_INITIAL_MAX_RETRY, DEFAULT_MDC_INITIAL_RETRY);
// Check if its been overridden
return Settings.Global.getInt(mResolver,
Settings.Global.MDC_INITIAL_MAX_RETRY, value);
}
/**
* Maintain the sum of transmit and receive packets.
*
* The packet counts are initialized and reset to -1 and
* remain -1 until they can be updated.
*/
public class TxRxSum {
public long txPkts;
public long rxPkts;
public TxRxSum() {
reset();
}
public TxRxSum(long txPkts, long rxPkts) {
this.txPkts = txPkts;
this.rxPkts = rxPkts;
}
public TxRxSum(TxRxSum sum) {
txPkts = sum.txPkts;
rxPkts = sum.rxPkts;
}
public void reset() {
txPkts = -1;
rxPkts = -1;
}
@Override
public String toString() {
return "{txSum=" + txPkts + " rxSum=" + rxPkts + "}";
}
public void updateTxRxSum() {
this.txPkts = TrafficStats.getMobileTcpTxPackets();
this.rxPkts = TrafficStats.getMobileTcpRxPackets();
}
}
protected void onActionIntentReconnectAlarm(Intent intent) {
String reason = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON);
String apnType = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE);
ApnContext apnContext = mApnContexts.get(apnType);
if (DBG) {
log("onActionIntentReconnectAlarm: mState=" + mState + " reason=" + reason +
" apnType=" + apnType + " apnContext=" + apnContext +
" mDataConnectionAsyncChannels=" + mDataConnectionAcHashMap);
}
if ((apnContext != null) && (apnContext.isEnabled())) {
apnContext.setReason(reason);
DctConstants.State apnContextState = apnContext.getState();
if (DBG) {
log("onActionIntentReconnectAlarm: apnContext state=" + apnContextState);
}
if ((apnContextState == DctConstants.State.FAILED)
|| (apnContextState == DctConstants.State.IDLE)) {
if (DBG) {
log("onActionIntentReconnectAlarm: state is FAILED|IDLE, disassociate");
}
DcAsyncChannel dcac = apnContext.getDcAc();
if (dcac != null) {
dcac.tearDown(apnContext, "", null);
}
apnContext.setDataConnectionAc(null);
apnContext.setState(DctConstants.State.IDLE);
} else {
if (DBG) log("onActionIntentReconnectAlarm: keep associated");
}
// TODO: IF already associated should we send the EVENT_TRY_SETUP_DATA???
sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, apnContext));
apnContext.setReconnectIntent(null);
}
}
protected void onActionIntentRestartTrySetupAlarm(Intent intent) {
String apnType = intent.getStringExtra(INTENT_RESTART_TRYSETUP_ALARM_EXTRA_TYPE);
ApnContext apnContext = mApnContexts.get(apnType);
if (DBG) {
log("onActionIntentRestartTrySetupAlarm: mState=" + mState +
" apnType=" + apnType + " apnContext=" + apnContext +
" mDataConnectionAsyncChannels=" + mDataConnectionAcHashMap);
}
sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, apnContext));
}
protected void onActionIntentDataStallAlarm(Intent intent) {
if (VDBG_STALL) log("onActionIntentDataStallAlarm: action=" + intent.getAction());
Message msg = obtainMessage(DctConstants.EVENT_DATA_STALL_ALARM,
intent.getAction());
msg.arg1 = intent.getIntExtra(DATA_STALL_ALARM_TAG_EXTRA, 0);
sendMessage(msg);
}
/**
* Default constructor
*/
protected DcTrackerBase(PhoneBase phone) {
super();
if (DBG) log("DCT.constructor");
mPhone = phone;
mResolver = mPhone.getContext().getContentResolver();
mUiccController = UiccController.getInstance();
mUiccController.registerForIccChanged(this, DctConstants.EVENT_ICC_CHANGED, null);
mAlarmManager =
(AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
IntentFilter filter = new IntentFilter();
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);
filter.addAction(INTENT_DATA_STALL_ALARM);
mUserDataEnabled = Settings.Global.getInt(
mPhone.getContext().getContentResolver(), Settings.Global.MOBILE_DATA, 1) == 1;
mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
// This preference tells us 1) initial condition for "dataEnabled",
// and 2) whether the RIL will setup the baseband to auto-PS attach.
mDataEnabled[DctConstants.APN_DEFAULT_ID] =
SystemProperties.getBoolean(DEFALUT_DATA_ON_BOOT_PROP,true);
if (mDataEnabled[DctConstants.APN_DEFAULT_ID]) {
mEnabledCount++;
}
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
mAutoAttachOnCreation = sp.getBoolean(PhoneBase.DATA_DISABLED_ON_BOOT_KEY, false);
// Watch for changes to Settings.Global.DATA_ROAMING
mDataRoamingSettingObserver = new DataRoamingSettingObserver(mPhone, mPhone.getContext());
mDataRoamingSettingObserver.register();
HandlerThread dcHandlerThread = new HandlerThread("DcHandlerThread");
dcHandlerThread.start();
Handler dcHandler = new Handler(dcHandlerThread.getLooper());
mDcc = DcController.makeDcc(mPhone, this, dcHandler);
mDcTesterFailBringUpAll = new DcTesterFailBringUpAll(mPhone, dcHandler);
}
public void dispose() {
if (DBG) log("DCT.dispose");
for (DcAsyncChannel dcac : mDataConnectionAcHashMap.values()) {
dcac.disconnect();
}
mDataConnectionAcHashMap.clear();
mIsDisposed = true;
mPhone.getContext().unregisterReceiver(mIntentReceiver);
mUiccController.unregisterForIccChanged(this);
mDataRoamingSettingObserver.unregister();
mDcc.dispose();
mDcTesterFailBringUpAll.dispose();
}
public DctConstants.Activity getActivity() {
return mActivity;
}
public boolean isApnTypeActive(String type) {
// TODO: support simultaneous with List instead
if (PhoneConstants.APN_TYPE_DUN.equals(type)) {
ApnSetting dunApn = fetchDunApn();
if (dunApn != null) {
return ((mActiveApn != null) && (dunApn.toString().equals(mActiveApn.toString())));
}
}
return mActiveApn != null && mActiveApn.canHandleType(type);
}
protected ApnSetting fetchDunApn() {
if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)) {
log("fetchDunApn: net.tethering.noprovisioning=true ret: null");
return null;
}
Context c = mPhone.getContext();
String apnData = Settings.Global.getString(c.getContentResolver(),
Settings.Global.TETHER_DUN_APN);
ApnSetting dunSetting = ApnSetting.fromString(apnData);
if (dunSetting != null) {
if (VDBG) log("fetchDunApn: global TETHER_DUN_APN dunSetting=" + dunSetting);
return dunSetting;
}
apnData = c.getResources().getString(R.string.config_tether_apndata);
dunSetting = ApnSetting.fromString(apnData);
if (VDBG) log("fetchDunApn: config_tether_apndata dunSetting=" + dunSetting);
return dunSetting;
}
public String[] getActiveApnTypes() {
String[] result;
if (mActiveApn != null) {
result = mActiveApn.types;
} else {
result = new String[1];
result[0] = PhoneConstants.APN_TYPE_DEFAULT;
}
return result;
}
/** TODO: See if we can remove */
public String getActiveApnString(String apnType) {
String result = null;
if (mActiveApn != null) {
result = mActiveApn.apn;
}
return result;
}
/**
* Modify {@link android.provider.Settings.Global#DATA_ROAMING} value.
*/
public void setDataOnRoamingEnabled(boolean enabled) {
if (getDataOnRoamingEnabled() != enabled) {
final ContentResolver resolver = mPhone.getContext().getContentResolver();
Settings.Global.putInt(resolver, Settings.Global.DATA_ROAMING, enabled ? 1 : 0);
// will trigger handleDataOnRoamingChange() through observer
}
}
/**
* Return current {@link android.provider.Settings.Global#DATA_ROAMING} value.
*/
public boolean getDataOnRoamingEnabled() {
try {
final ContentResolver resolver = mPhone.getContext().getContentResolver();
return Settings.Global.getInt(resolver, Settings.Global.DATA_ROAMING) != 0;
} catch (SettingNotFoundException snfe) {
return false;
}
}
// abstract methods
protected abstract void restartRadio();
protected abstract void log(String s);
protected abstract void loge(String s);
protected abstract boolean isDataAllowed();
protected abstract boolean isApnTypeAvailable(String type);
public abstract DctConstants.State getState(String apnType);
protected abstract void setState(DctConstants.State s);
protected abstract void gotoIdleAndNotifyDataConnection(String reason);
protected abstract boolean onTrySetupData(String reason);
protected abstract void onRoamingOff();
protected abstract void onRoamingOn();
protected abstract void onRadioAvailable();
protected abstract void onRadioOffOrNotAvailable();
protected abstract void onDataSetupComplete(AsyncResult ar);
protected abstract void onDataSetupCompleteError(AsyncResult ar);
protected abstract void onDisconnectDone(int connId, AsyncResult ar);
protected abstract void onDisconnectDcRetrying(int connId, AsyncResult ar);
protected abstract void onVoiceCallStarted();
protected abstract void onVoiceCallEnded();
protected abstract void onCleanUpConnection(boolean tearDown, int apnId, String reason);
protected abstract void onCleanUpAllConnections(String cause);
public abstract boolean isDataPossible(String apnType);
protected abstract void onUpdateIcc();
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
log("DISCONNECTED_CONNECTED: msg=" + msg);
DcAsyncChannel dcac = (DcAsyncChannel) msg.obj;
mDataConnectionAcHashMap.remove(dcac.getDataConnectionIdSync());
dcac.disconnected();
break;
}
case DctConstants.EVENT_ENABLE_NEW_APN:
onEnableApn(msg.arg1, msg.arg2);
break;
case DctConstants.EVENT_TRY_SETUP_DATA:
String reason = null;
if (msg.obj instanceof String) {
reason = (String) msg.obj;
}
onTrySetupData(reason);
break;
case DctConstants.EVENT_DATA_STALL_ALARM:
onDataStallAlarm(msg.arg1);
break;
case DctConstants.EVENT_ROAMING_OFF:
onRoamingOff();
break;
case DctConstants.EVENT_ROAMING_ON:
onRoamingOn();
break;
case DctConstants.EVENT_RADIO_AVAILABLE:
onRadioAvailable();
break;
case DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
onRadioOffOrNotAvailable();
break;
case DctConstants.EVENT_DATA_SETUP_COMPLETE:
mCidActive = msg.arg1;
onDataSetupComplete((AsyncResult) msg.obj);
break;
case DctConstants.EVENT_DATA_SETUP_COMPLETE_ERROR:
onDataSetupCompleteError((AsyncResult) msg.obj);
break;
case DctConstants.EVENT_DISCONNECT_DONE:
log("DataConnectionTracker.handleMessage: EVENT_DISCONNECT_DONE msg=" + msg);
onDisconnectDone(msg.arg1, (AsyncResult) msg.obj);
break;
case DctConstants.EVENT_DISCONNECT_DC_RETRYING:
log("DataConnectionTracker.handleMessage: EVENT_DISCONNECT_DC_RETRYING msg=" + msg);
onDisconnectDcRetrying(msg.arg1, (AsyncResult) msg.obj);
break;
case DctConstants.EVENT_VOICE_CALL_STARTED:
onVoiceCallStarted();
break;
case DctConstants.EVENT_VOICE_CALL_ENDED:
onVoiceCallEnded();
break;
case DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS: {
onCleanUpAllConnections((String) msg.obj);
break;
}
case DctConstants.EVENT_CLEAN_UP_CONNECTION: {
boolean tearDown = (msg.arg1 == 0) ? false : true;
onCleanUpConnection(tearDown, msg.arg2, (String) msg.obj);
break;
}
case DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE: {
boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false;
onSetInternalDataEnabled(enabled);
break;
}
case DctConstants.EVENT_RESET_DONE: {
if (DBG) log("EVENT_RESET_DONE");
onResetDone((AsyncResult) msg.obj);
break;
}
case DctConstants.CMD_SET_USER_DATA_ENABLE: {
final boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false;
if (DBG) log("CMD_SET_USER_DATA_ENABLE enabled=" + enabled);
onSetUserDataEnabled(enabled);
break;
}
case DctConstants.CMD_SET_DEPENDENCY_MET: {
boolean met = (msg.arg1 == DctConstants.ENABLED) ? true : false;
if (DBG) log("CMD_SET_DEPENDENCY_MET met=" + met);
Bundle bundle = msg.getData();
if (bundle != null) {
String apnType = (String)bundle.get(DctConstants.APN_TYPE_KEY);
if (apnType != null) {
onSetDependencyMet(apnType, met);
}
}
break;
}
case DctConstants.CMD_SET_POLICY_DATA_ENABLE: {
final boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false;
onSetPolicyDataEnabled(enabled);
break;
}
case DctConstants.CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: {
sEnableFailFastRefCounter += (msg.arg1 == DctConstants.ENABLED) ? 1 : -1;
if (DBG) {
log("CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: "
+ " sEnableFailFastRefCounter=" + sEnableFailFastRefCounter);
}
if (sEnableFailFastRefCounter < 0) {
final String s = "CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: "
+ "sEnableFailFastRefCounter:" + sEnableFailFastRefCounter + " < 0";
loge(s);
sEnableFailFastRefCounter = 0;
if (Build.IS_DEBUGGABLE) {
throw new RuntimeException(s);
}
}
final boolean enabled = sEnableFailFastRefCounter > 0;
if (DBG) {
log("CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: enabled=" + enabled
+ " sEnableFailFastRefCounter=" + sEnableFailFastRefCounter);
}
if (mFailFast != enabled) {
mFailFast = enabled;
mDataStallDetectionEnabled = !enabled;
if (mDataStallDetectionEnabled
&& (getOverallState() == DctConstants.State.CONNECTED)
&& (!mInVoiceCall ||
mPhone.getServiceStateTracker()
.isConcurrentVoiceAndDataAllowed())) {
if (DBG) log("CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: start data stall");
stopDataStallAlarm();
startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
} else {
if (DBG) log("CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: stop data stall");
stopDataStallAlarm();
}
}
break;
}
case DctConstants.EVENT_ICC_CHANGED: {
onUpdateIcc();
break;
}
case DctConstants.EVENT_RESTART_RADIO: {
restartRadio();
break;
}
default:
Rlog.e("DATA", "Unidentified event msg=" + msg);
break;
}
}
/**
* Report on whether data connectivity is enabled
*
* @return {@code false} if data connectivity has been explicitly disabled,
* {@code true} otherwise.
*/
public boolean getAnyDataEnabled() {
final boolean result;
synchronized (mDataEnabledLock) {
result = (mInternalDataEnabled && mUserDataEnabled && sPolicyDataEnabled
&& (mEnabledCount != 0));
}
if (!result && DBG) log("getAnyDataEnabled " + result);
return result;
}
protected boolean isEmergency() {
final boolean result;
synchronized (mDataEnabledLock) {
result = mPhone.isInEcm() || mPhone.isInEmergencyCall();
}
log("isEmergency: result=" + result);
return result;
}
protected int apnTypeToId(String type) {
if (TextUtils.equals(type, PhoneConstants.APN_TYPE_DEFAULT)) {
return DctConstants.APN_DEFAULT_ID;
} else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_MMS)) {
return DctConstants.APN_MMS_ID;
} else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_SUPL)) {
return DctConstants.APN_SUPL_ID;
} else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_DUN)) {
return DctConstants.APN_DUN_ID;
} else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_HIPRI)) {
return DctConstants.APN_HIPRI_ID;
} else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_IMS)) {
return DctConstants.APN_IMS_ID;
} else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_FOTA)) {
return DctConstants.APN_FOTA_ID;
} else if (TextUtils.equals(type, PhoneConstants.APN_TYPE_CBS)) {
return DctConstants.APN_CBS_ID;
} else {
return DctConstants.APN_INVALID_ID;
}
}
protected String apnIdToType(int id) {
switch (id) {
case DctConstants.APN_DEFAULT_ID:
return PhoneConstants.APN_TYPE_DEFAULT;
case DctConstants.APN_MMS_ID:
return PhoneConstants.APN_TYPE_MMS;
case DctConstants.APN_SUPL_ID:
return PhoneConstants.APN_TYPE_SUPL;
case DctConstants.APN_DUN_ID:
return PhoneConstants.APN_TYPE_DUN;
case DctConstants.APN_HIPRI_ID:
return PhoneConstants.APN_TYPE_HIPRI;
case DctConstants.APN_IMS_ID:
return PhoneConstants.APN_TYPE_IMS;
case DctConstants.APN_FOTA_ID:
return PhoneConstants.APN_TYPE_FOTA;
case DctConstants.APN_CBS_ID:
return PhoneConstants.APN_TYPE_CBS;
default:
log("Unknown id (" + id + ") in apnIdToType");
return PhoneConstants.APN_TYPE_DEFAULT;
}
}
public LinkProperties getLinkProperties(String apnType) {
int id = apnTypeToId(apnType);
if (isApnIdEnabled(id)) {
DcAsyncChannel dcac = mDataConnectionAcHashMap.get(0);
return dcac.getLinkPropertiesSync();
} else {
return new LinkProperties();
}
}
public LinkCapabilities getLinkCapabilities(String apnType) {
int id = apnTypeToId(apnType);
if (isApnIdEnabled(id)) {
DcAsyncChannel dcac = mDataConnectionAcHashMap.get(0);
return dcac.getLinkCapabilitiesSync();
} else {
return new LinkCapabilities();
}
}
// tell all active apns of the current condition
protected void notifyDataConnection(String reason) {
for (int id = 0; id < DctConstants.APN_NUM_TYPES; id++) {
if (mDataEnabled[id]) {
mPhone.notifyDataConnection(reason, apnIdToType(id));
}
}
notifyOffApnsOfAvailability(reason);
}
// a new APN has gone active and needs to send events to catch up with the
// current condition
private void notifyApnIdUpToCurrent(String reason, int apnId) {
switch (mState) {
case IDLE:
break;
case RETRYING:
case CONNECTING:
case SCANNING:
mPhone.notifyDataConnection(reason, apnIdToType(apnId),
PhoneConstants.DataState.CONNECTING);
break;
case CONNECTED:
case DISCONNECTING:
mPhone.notifyDataConnection(reason, apnIdToType(apnId),
PhoneConstants.DataState.CONNECTING);
mPhone.notifyDataConnection(reason, apnIdToType(apnId),
PhoneConstants.DataState.CONNECTED);
break;
default:
// Ignore
break;
}
}
// since we normally don't send info to a disconnected APN, we need to do this specially
private void notifyApnIdDisconnected(String reason, int apnId) {
mPhone.notifyDataConnection(reason, apnIdToType(apnId),
PhoneConstants.DataState.DISCONNECTED);
}
// disabled apn's still need avail/unavail notificiations - send them out
protected void notifyOffApnsOfAvailability(String reason) {
if (DBG) log("notifyOffApnsOfAvailability - reason= " + reason);
for (int id = 0; id < DctConstants.APN_NUM_TYPES; id++) {
if (!isApnIdEnabled(id)) {
notifyApnIdDisconnected(reason, id);
}
}
}
public boolean isApnTypeEnabled(String apnType) {
if (apnType == null) {
return false;
} else {
return isApnIdEnabled(apnTypeToId(apnType));
}
}
protected synchronized boolean isApnIdEnabled(int id) {
if (id != DctConstants.APN_INVALID_ID) {
return mDataEnabled[id];
}
return false;
}
/**
* Ensure that we are connected to an APN of the specified type.
*
* @param type the APN type (currently the only valid values are
* {@link PhoneConstants#APN_TYPE_MMS} and {@link PhoneConstants#APN_TYPE_SUPL})
* @return Success is indicated by {@code Phone.APN_ALREADY_ACTIVE} or
* {@code Phone.APN_REQUEST_STARTED}. In the latter case, a
* broadcast will be sent by the ConnectivityManager when a
* connection to the APN has been established.
*/
public synchronized int enableApnType(String type) {
int id = apnTypeToId(type);
if (id == DctConstants.APN_INVALID_ID) {
return PhoneConstants.APN_REQUEST_FAILED;
}
if (DBG) {
log("enableApnType(" + type + "), isApnTypeActive = " + isApnTypeActive(type)
+ ", isApnIdEnabled =" + isApnIdEnabled(id) + " and state = " + mState);
}
if (!isApnTypeAvailable(type)) {
if (DBG) log("type not available");
return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
}
if (isApnIdEnabled(id)) {
return PhoneConstants.APN_ALREADY_ACTIVE;
} else {
setEnabled(id, true);
}
return PhoneConstants.APN_REQUEST_STARTED;
}
/**
* The APN of the specified type is no longer needed. Ensure that if use of
* the default APN has not been explicitly disabled, we are connected to the
* default APN.
*
* @param type the APN type. The only valid values are currently
* {@link PhoneConstants#APN_TYPE_MMS} and {@link PhoneConstants#APN_TYPE_SUPL}.
* @return Success is indicated by {@code PhoneConstants.APN_ALREADY_ACTIVE} or
* {@code PhoneConstants.APN_REQUEST_STARTED}. In the latter case, a
* broadcast will be sent by the ConnectivityManager when a
* connection to the APN has been disconnected. A {@code
* PhoneConstants.APN_REQUEST_FAILED} is returned if the type parameter is
* invalid or if the apn wasn't enabled.
*/
public synchronized int disableApnType(String type) {
if (DBG) log("disableApnType(" + type + ")");
int id = apnTypeToId(type);
if (id == DctConstants.APN_INVALID_ID) {
return PhoneConstants.APN_REQUEST_FAILED;
}
if (isApnIdEnabled(id)) {
setEnabled(id, false);
if (isApnTypeActive(PhoneConstants.APN_TYPE_DEFAULT)) {
if (mDataEnabled[DctConstants.APN_DEFAULT_ID]) {
return PhoneConstants.APN_ALREADY_ACTIVE;
} else {
return PhoneConstants.APN_REQUEST_STARTED;
}
} else {
return PhoneConstants.APN_REQUEST_STARTED;
}
} else {
return PhoneConstants.APN_REQUEST_FAILED;
}
}
protected void setEnabled(int id, boolean enable) {
if (DBG) {
log("setEnabled(" + id + ", " + enable + ") with old state = " + mDataEnabled[id]
+ " and enabledCount = " + mEnabledCount);
}
Message msg = obtainMessage(DctConstants.EVENT_ENABLE_NEW_APN);
msg.arg1 = id;
msg.arg2 = (enable ? DctConstants.ENABLED : DctConstants.DISABLED);
sendMessage(msg);
}
protected void onEnableApn(int apnId, int enabled) {
if (DBG) {
log("EVENT_APN_ENABLE_REQUEST apnId=" + apnId + ", apnType=" + apnIdToType(apnId) +
", enabled=" + enabled + ", dataEnabled = " + mDataEnabled[apnId] +
", enabledCount = " + mEnabledCount + ", isApnTypeActive = " +
isApnTypeActive(apnIdToType(apnId)));
}
if (enabled == DctConstants.ENABLED) {
synchronized (this) {
if (!mDataEnabled[apnId]) {
mDataEnabled[apnId] = true;
mEnabledCount++;
}
}
String type = apnIdToType(apnId);
if (!isApnTypeActive(type)) {
mRequestedApnType = type;
onEnableNewApn();
} else {
notifyApnIdUpToCurrent(Phone.REASON_APN_SWITCHED, apnId);
}
} else {
// disable
boolean didDisable = false;
synchronized (this) {
if (mDataEnabled[apnId]) {
mDataEnabled[apnId] = false;
mEnabledCount--;
didDisable = true;
}
}
if (didDisable) {
if ((mEnabledCount == 0) || (apnId == DctConstants.APN_DUN_ID)) {
mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
onCleanUpConnection(true, apnId, Phone.REASON_DATA_DISABLED);
}
// send the disconnect msg manually, since the normal route wont send
// it (it's not enabled)
notifyApnIdDisconnected(Phone.REASON_DATA_DISABLED, apnId);
if (mDataEnabled[DctConstants.APN_DEFAULT_ID] == true
&& !isApnTypeActive(PhoneConstants.APN_TYPE_DEFAULT)) {
// TODO - this is an ugly way to restore the default conn - should be done
// by a real contention manager and policy that disconnects the lower pri
// stuff as enable requests come in and pops them back on as we disable back
// down to the lower pri stuff
mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
onEnableNewApn();
}
}
}
}
/**
* Called when we switch APNs.
*
* mRequestedApnType is set prior to call
* To be overridden.
*/
protected void onEnableNewApn() {
}
/**
* Called when EVENT_RESET_DONE is received so goto
* IDLE state and send notifications to those interested.
*
* TODO - currently unused. Needs to be hooked into DataConnection cleanup
* TODO - needs to pass some notion of which connection is reset..
*/
protected void onResetDone(AsyncResult ar) {
if (DBG) log("EVENT_RESET_DONE");
String reason = null;
if (ar.userObj instanceof String) {
reason = (String) ar.userObj;
}
gotoIdleAndNotifyDataConnection(reason);
}
/**
* 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.
*
* @param enable indicates whether to enable ({@code true}) or disable (
* {@code false}) data
* @return {@code true} if the operation succeeded
*/
public boolean setInternalDataEnabled(boolean enable) {
if (DBG)
log("setInternalDataEnabled(" + enable + ")");
Message msg = obtainMessage(DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE);
msg.arg1 = (enable ? DctConstants.ENABLED : DctConstants.DISABLED);
sendMessage(msg);
return true;
}
protected void onSetInternalDataEnabled(boolean enabled) {
synchronized (mDataEnabledLock) {
mInternalDataEnabled = enabled;
if (enabled) {
log("onSetInternalDataEnabled: changed to enabled, try to setup data call");
onTrySetupData(Phone.REASON_DATA_ENABLED);
} else {
log("onSetInternalDataEnabled: changed to disabled, cleanUpAllConnections");
cleanUpAllConnections(null);
}
}
}
public void cleanUpAllConnections(String cause) {
Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS);
msg.obj = cause;
sendMessage(msg);
}
public abstract boolean isDisconnected();
protected void onSetUserDataEnabled(boolean enabled) {
synchronized (mDataEnabledLock) {
final boolean prevEnabled = getAnyDataEnabled();
if (mUserDataEnabled != enabled) {
mUserDataEnabled = enabled;
Settings.Global.putInt(mPhone.getContext().getContentResolver(),
Settings.Global.MOBILE_DATA, enabled ? 1 : 0);
if (getDataOnRoamingEnabled() == false &&
mPhone.getServiceState().getRoaming() == true) {
if (enabled) {
notifyOffApnsOfAvailability(Phone.REASON_ROAMING_ON);
} else {
notifyOffApnsOfAvailability(Phone.REASON_DATA_DISABLED);
}
}
if (prevEnabled != getAnyDataEnabled()) {
if (!prevEnabled) {
onTrySetupData(Phone.REASON_DATA_ENABLED);
} else {
onCleanUpAllConnections(Phone.REASON_DATA_DISABLED);
}
}
}
}
}
protected void onSetDependencyMet(String apnType, boolean met) {
}
protected void onSetPolicyDataEnabled(boolean enabled) {
synchronized (mDataEnabledLock) {
final boolean prevEnabled = getAnyDataEnabled();
if (sPolicyDataEnabled != enabled) {
sPolicyDataEnabled = enabled;
if (prevEnabled != getAnyDataEnabled()) {
if (!prevEnabled) {
onTrySetupData(Phone.REASON_DATA_ENABLED);
} else {
onCleanUpAllConnections(Phone.REASON_DATA_DISABLED);
}
}
}
}
}
protected String getReryConfig(boolean forDefault) {
int nt = mPhone.getServiceState().getNetworkType();
if ((nt == TelephonyManager.NETWORK_TYPE_CDMA) ||
(nt == TelephonyManager.NETWORK_TYPE_1xRTT) ||
(nt == TelephonyManager.NETWORK_TYPE_EVDO_0) ||
(nt == TelephonyManager.NETWORK_TYPE_EVDO_A) ||
(nt == TelephonyManager.NETWORK_TYPE_EVDO_B) ||
(nt == TelephonyManager.NETWORK_TYPE_EHRPD)) {
// CDMA variant
return SystemProperties.get("ro.cdma.data_retry_config");
} else {
// Use GSM varient for all others.
if (forDefault) {
return SystemProperties.get("ro.gsm.data_retry_config");
} else {
return SystemProperties.get("ro.gsm.2nd_data_retry_config");
}
}
}
protected void resetPollStats() {
mTxPkts = -1;
mRxPkts = -1;
mNetStatPollPeriod = POLL_NETSTAT_MILLIS;
}
protected abstract DctConstants.State getOverallState();
protected void startNetStatPoll() {
if (getOverallState() == DctConstants.State.CONNECTED
&& mNetStatPollEnabled == false) {
if (DBG) log("startNetStatPoll");
resetPollStats();
mNetStatPollEnabled = true;
mPollNetStat.run();
}
}
protected void stopNetStatPoll() {
mNetStatPollEnabled = false;
removeCallbacks(mPollNetStat);
if (DBG) log("stopNetStatPoll");
}
public void updateDataActivity() {
long sent, received;
DctConstants.Activity newActivity;
TxRxSum preTxRxSum = new TxRxSum(mTxPkts, mRxPkts);
TxRxSum curTxRxSum = new TxRxSum();
curTxRxSum.updateTxRxSum();
mTxPkts = curTxRxSum.txPkts;
mRxPkts = curTxRxSum.rxPkts;
if (VDBG) {
log("updateDataActivity: curTxRxSum=" + curTxRxSum + " preTxRxSum=" + preTxRxSum);
}
if (mNetStatPollEnabled && (preTxRxSum.txPkts > 0 || preTxRxSum.rxPkts > 0)) {
sent = mTxPkts - preTxRxSum.txPkts;
received = mRxPkts - preTxRxSum.rxPkts;
if (VDBG)
log("updateDataActivity: sent=" + sent + " received=" + received);
if (sent > 0 && received > 0) {
newActivity = DctConstants.Activity.DATAINANDOUT;
} else if (sent > 0 && received == 0) {
newActivity = DctConstants.Activity.DATAOUT;
} else if (sent == 0 && received > 0) {
newActivity = DctConstants.Activity.DATAIN;
} else {
newActivity = (mActivity == DctConstants.Activity.DORMANT) ?
mActivity : DctConstants.Activity.NONE;
}
if (mActivity != newActivity && mIsScreenOn) {
if (VDBG)
log("updateDataActivity: newActivity=" + newActivity);
mActivity = newActivity;
mPhone.notifyDataActivity();
}
}
}
// Recovery action taken in case of data stall
protected static class RecoveryAction {
public static final int GET_DATA_CALL_LIST = 0;
public static final int CLEANUP = 1;
public static final int REREGISTER = 2;
public static final int RADIO_RESTART = 3;
public static final int RADIO_RESTART_WITH_PROP = 4;
private static boolean isAggressiveRecovery(int value) {
return ((value == RecoveryAction.CLEANUP) ||
(value == RecoveryAction.REREGISTER) ||
(value == RecoveryAction.RADIO_RESTART) ||
(value == RecoveryAction.RADIO_RESTART_WITH_PROP));
}
}
public int getRecoveryAction() {
int action = Settings.System.getInt(mPhone.getContext().getContentResolver(),
"radio.data.stall.recovery.action", RecoveryAction.GET_DATA_CALL_LIST);
if (VDBG_STALL) log("getRecoveryAction: " + action);
return action;
}
public void putRecoveryAction(int action) {
Settings.System.putInt(mPhone.getContext().getContentResolver(),
"radio.data.stall.recovery.action", action);
if (VDBG_STALL) log("putRecoveryAction: " + action);
}
protected boolean isConnected() {
return false;
}
protected void doRecovery() {
if (getOverallState() == DctConstants.State.CONNECTED) {
// Go through a series of recovery steps, each action transitions to the next action
int recoveryAction = getRecoveryAction();
switch (recoveryAction) {
case RecoveryAction.GET_DATA_CALL_LIST:
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_GET_DATA_CALL_LIST,
mSentSinceLastRecv);
if (DBG) log("doRecovery() get data call list");
mPhone.mCi.getDataCallList(obtainMessage(DctConstants.EVENT_DATA_STATE_CHANGED));
putRecoveryAction(RecoveryAction.CLEANUP);
break;
case RecoveryAction.CLEANUP:
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_CLEANUP, mSentSinceLastRecv);
if (DBG) log("doRecovery() cleanup all connections");
cleanUpAllConnections(Phone.REASON_PDP_RESET);
putRecoveryAction(RecoveryAction.REREGISTER);
break;
case RecoveryAction.REREGISTER:
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_REREGISTER,
mSentSinceLastRecv);
if (DBG) log("doRecovery() re-register");
mPhone.getServiceStateTracker().reRegisterNetwork(null);
putRecoveryAction(RecoveryAction.RADIO_RESTART);
break;
case RecoveryAction.RADIO_RESTART:
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
mSentSinceLastRecv);
if (DBG) log("restarting radio");
putRecoveryAction(RecoveryAction.RADIO_RESTART_WITH_PROP);
restartRadio();
break;
case RecoveryAction.RADIO_RESTART_WITH_PROP:
// This is in case radio restart has not recovered the data.
// It will set an additional "gsm.radioreset" property to tell
// RIL or system to take further action.
// The implementation of hard reset recovery action is up to OEM product.
// Once RADIO_RESET property is consumed, it is expected to set back
// to false by RIL.
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART_WITH_PROP, -1);
if (DBG) log("restarting radio with gsm.radioreset to true");
SystemProperties.set(RADIO_RESET_PROPERTY, "true");
// give 1 sec so property change can be notified.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
restartRadio();
putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
break;
default:
throw new RuntimeException("doRecovery: Invalid recoveryAction=" +
recoveryAction);
}
mSentSinceLastRecv = 0;
}
}
private void updateDataStallInfo() {
long sent, received;
TxRxSum preTxRxSum = new TxRxSum(mDataStallTxRxSum);
mDataStallTxRxSum.updateTxRxSum();
if (VDBG_STALL) {
log("updateDataStallInfo: mDataStallTxRxSum=" + mDataStallTxRxSum +
" preTxRxSum=" + preTxRxSum);
}
sent = mDataStallTxRxSum.txPkts - preTxRxSum.txPkts;
received = mDataStallTxRxSum.rxPkts - preTxRxSum.rxPkts;
if (RADIO_TESTS) {
if (SystemProperties.getBoolean("radio.test.data.stall", false)) {
log("updateDataStallInfo: radio.test.data.stall true received = 0;");
received = 0;
}
}
if ( sent > 0 && received > 0 ) {
if (VDBG_STALL) log("updateDataStallInfo: IN/OUT");
mSentSinceLastRecv = 0;
putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
} else if (sent > 0 && received == 0) {
if (mPhone.getState() == PhoneConstants.State.IDLE) {
mSentSinceLastRecv += sent;
} else {
mSentSinceLastRecv = 0;
}
if (DBG) {
log("updateDataStallInfo: OUT sent=" + sent +
" mSentSinceLastRecv=" + mSentSinceLastRecv);
}
} else if (sent == 0 && received > 0) {
if (VDBG_STALL) log("updateDataStallInfo: IN");
mSentSinceLastRecv = 0;
putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
} else {
if (VDBG_STALL) log("updateDataStallInfo: NONE");
}
}
protected void onDataStallAlarm(int tag) {
if (mDataStallAlarmTag != tag) {
if (DBG) {
log("onDataStallAlarm: ignore, tag=" + tag + " expecting " + mDataStallAlarmTag);
}
return;
}
updateDataStallInfo();
int hangWatchdogTrigger = Settings.Global.getInt(mResolver,
Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
NUMBER_SENT_PACKETS_OF_HANG);
boolean suspectedStall = DATA_STALL_NOT_SUSPECTED;
if (mSentSinceLastRecv >= hangWatchdogTrigger) {
if (DBG) {
log("onDataStallAlarm: tag=" + tag + " do recovery action=" + getRecoveryAction());
}
suspectedStall = DATA_STALL_SUSPECTED;
sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
} else {
if (VDBG_STALL) {
log("onDataStallAlarm: tag=" + tag + " Sent " + String.valueOf(mSentSinceLastRecv) +
" pkts since last received, < watchdogTrigger=" + hangWatchdogTrigger);
}
}
startDataStallAlarm(suspectedStall);
}
protected void startDataStallAlarm(boolean suspectedStall) {
int nextAction = getRecoveryAction();
int delayInMs;
if (mDataStallDetectionEnabled && getOverallState() == DctConstants.State.CONNECTED) {
// If screen is on or data stall is currently suspected, set the alarm
// with an aggresive timeout.
if (mIsScreenOn || suspectedStall || RecoveryAction.isAggressiveRecovery(nextAction)) {
delayInMs = Settings.Global.getInt(mResolver,
Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT);
} else {
delayInMs = Settings.Global.getInt(mResolver,
Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS,
DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT);
}
mDataStallAlarmTag += 1;
if (VDBG_STALL) {
log("startDataStallAlarm: tag=" + mDataStallAlarmTag +
" delay=" + (delayInMs / 1000) + "s");
}
Intent intent = new Intent(INTENT_DATA_STALL_ALARM);
intent.putExtra(DATA_STALL_ALARM_TAG_EXTRA, mDataStallAlarmTag);
mDataStallAlarmIntent = PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + delayInMs, mDataStallAlarmIntent);
} else {
if (VDBG_STALL) {
log("startDataStallAlarm: NOT started, no connection tag=" + mDataStallAlarmTag);
}
}
}
protected void stopDataStallAlarm() {
if (VDBG_STALL) {
log("stopDataStallAlarm: current tag=" + mDataStallAlarmTag +
" mDataStallAlarmIntent=" + mDataStallAlarmIntent);
}
mDataStallAlarmTag += 1;
if (mDataStallAlarmIntent != null) {
mAlarmManager.cancel(mDataStallAlarmIntent);
mDataStallAlarmIntent = null;
}
}
protected void restartDataStallAlarm() {
if (isConnected() == false) return;
// To be called on screen status change.
// Do not cancel the alarm if it is set with aggressive timeout.
int nextAction = getRecoveryAction();
if (RecoveryAction.isAggressiveRecovery(nextAction)) {
if (DBG) log("restartDataStallAlarm: action is pending. not resetting the alarm.");
return;
}
if (VDBG_STALL) log("restartDataStallAlarm: stop then start.");
stopDataStallAlarm();
startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
}
void sendCleanUpConnection(boolean tearDown, ApnContext apnContext) {
if (DBG)log("sendCleanUpConnection: tearDown=" + tearDown + " apnContext=" + apnContext);
Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_CONNECTION);
msg.arg1 = tearDown ? 1 : 0;
msg.arg2 = 0;
msg.obj = apnContext;
sendMessage(msg);
}
void sendRestartRadio() {
if (DBG)log("sendRestartRadio:");
Message msg = obtainMessage(DctConstants.EVENT_RESTART_RADIO);
sendMessage(msg);
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("DataConnectionTrackerBase:");
pw.println(" RADIO_TESTS=" + RADIO_TESTS);
pw.println(" mInternalDataEnabled=" + mInternalDataEnabled);
pw.println(" mUserDataEnabled=" + mUserDataEnabled);
pw.println(" sPolicyDataEnabed=" + sPolicyDataEnabled);
pw.println(" mDataEnabled:");
for(int i=0; i < mDataEnabled.length; i++) {
pw.printf(" mDataEnabled[%d]=%b\n", i, mDataEnabled[i]);
}
pw.flush();
pw.println(" mEnabledCount=" + mEnabledCount);
pw.println(" mRequestedApnType=" + mRequestedApnType);
pw.println(" mPhone=" + mPhone.getPhoneName());
pw.println(" mActivity=" + mActivity);
pw.println(" mState=" + mState);
pw.println(" mTxPkts=" + mTxPkts);
pw.println(" mRxPkts=" + mRxPkts);
pw.println(" mNetStatPollPeriod=" + mNetStatPollPeriod);
pw.println(" mNetStatPollEnabled=" + mNetStatPollEnabled);
pw.println(" mDataStallTxRxSum=" + mDataStallTxRxSum);
pw.println(" mDataStallAlarmTag=" + mDataStallAlarmTag);
pw.println(" mDataStallDetectionEanbled=" + mDataStallDetectionEnabled);
pw.println(" mSentSinceLastRecv=" + mSentSinceLastRecv);
pw.println(" mNoRecvPollCount=" + mNoRecvPollCount);
pw.println(" mResolver=" + mResolver);
pw.println(" mIsWifiConnected=" + mIsWifiConnected);
pw.println(" mReconnectIntent=" + mReconnectIntent);
pw.println(" mCidActive=" + mCidActive);
pw.println(" mAutoAttachOnCreation=" + mAutoAttachOnCreation);
pw.println(" mIsScreenOn=" + mIsScreenOn);
pw.println(" mUniqueIdGenerator=" + mUniqueIdGenerator);
pw.flush();
pw.println(" ***************************************");
DcController dcc = mDcc;
if (dcc != null) {
dcc.dump(fd, pw, args);
} else {
pw.println(" mDcc=null");
}
pw.println(" ***************************************");
HashMap<Integer, DataConnection> dcs = mDataConnections;
if (dcs != null) {
Set<Entry<Integer, DataConnection> > mDcSet = mDataConnections.entrySet();
pw.println(" mDataConnections: count=" + mDcSet.size());
for (Entry<Integer, DataConnection> entry : mDcSet) {
pw.printf(" *** mDataConnection[%d] \n", entry.getKey());
entry.getValue().dump(fd, pw, args);
}
} else {
pw.println("mDataConnections=null");
}
pw.println(" ***************************************");
pw.flush();
HashMap<String, Integer> apnToDcId = mApnToDataConnectionId;
if (apnToDcId != null) {
Set<Entry<String, Integer>> apnToDcIdSet = apnToDcId.entrySet();
pw.println(" mApnToDataConnectonId size=" + apnToDcIdSet.size());
for (Entry<String, Integer> entry : apnToDcIdSet) {
pw.printf(" mApnToDataConnectonId[%s]=%d\n", entry.getKey(), entry.getValue());
}
} else {
pw.println("mApnToDataConnectionId=null");
}
pw.println(" ***************************************");
pw.flush();
ConcurrentHashMap<String, ApnContext> apnCtxs = mApnContexts;
if (apnCtxs != null) {
Set<Entry<String, ApnContext>> apnCtxsSet = apnCtxs.entrySet();
pw.println(" mApnContexts size=" + apnCtxsSet.size());
for (Entry<String, ApnContext> entry : apnCtxsSet) {
entry.getValue().dump(fd, pw, args);
}
pw.println(" ***************************************");
} else {
pw.println(" mApnContexts=null");
}
pw.flush();
pw.println(" mActiveApn=" + mActiveApn);
ArrayList<ApnSetting> apnSettings = mAllApnSettings;
if (apnSettings != null) {
pw.println(" mAllApnSettings size=" + apnSettings.size());
for (int i=0; i < apnSettings.size(); i++) {
pw.printf(" mAllApnSettings[%d]: %s\n", i, apnSettings.get(i));
}
pw.flush();
} else {
pw.println(" mAllApnSettings=null");
}
pw.println(" mPreferredApn=" + mPreferredApn);
pw.println(" mIsPsRestricted=" + mIsPsRestricted);
pw.println(" mIsDisposed=" + mIsDisposed);
pw.println(" mIntentReceiver=" + mIntentReceiver);
pw.println(" mDataRoamingSettingObserver=" + mDataRoamingSettingObserver);
pw.flush();
}
}