diff options
author | Wink Saville <wink@google.com> | 2012-07-11 15:41:29 -0700 |
---|---|---|
committer | Jean-Baptiste Queru <jbq@google.com> | 2012-07-17 12:23:13 -0700 |
commit | c38bb60d867c5d61d90b7179a9ed2b2d1848124f (patch) | |
tree | 1d22c162b785b6e6663419f28d2c4d7afd97086d /src/java/com/android/internal/telephony/DataConnectionTracker.java | |
parent | f4ba68aa88637e9df034fce94971535852cc0a7b (diff) | |
download | frameworks_opt_telephony-c38bb60d867c5d61d90b7179a9ed2b2d1848124f.zip frameworks_opt_telephony-c38bb60d867c5d61d90b7179a9ed2b2d1848124f.tar.gz frameworks_opt_telephony-c38bb60d867c5d61d90b7179a9ed2b2d1848124f.tar.bz2 |
Create telephony-common - DO NOT MERGE
telephony-common was created by moving some of
frameworks/base/telephony
to:
frameworks/opt/telephony
Change-Id: I32cbb5eec1fa239c1587e055c8f7ef4fc48fb62c
Diffstat (limited to 'src/java/com/android/internal/telephony/DataConnectionTracker.java')
-rw-r--r-- | src/java/com/android/internal/telephony/DataConnectionTracker.java | 1203 |
1 files changed, 1203 insertions, 0 deletions
diff --git a/src/java/com/android/internal/telephony/DataConnectionTracker.java b/src/java/com/android/internal/telephony/DataConnectionTracker.java new file mode 100644 index 0000000..89a02d3 --- /dev/null +++ b/src/java/com/android/internal/telephony/DataConnectionTracker.java @@ -0,0 +1,1203 @@ +/* + * 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; + +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.Bundle; +import android.os.Handler; +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.ServiceState; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.R; +import com.android.internal.telephony.DataConnection.FailCause; +import com.android.internal.telephony.DctConstants; +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; + +/** + * {@hide} + */ +public abstract class DataConnectionTracker extends Handler { + protected static final boolean DBG = true; + protected static final boolean VDBG = false; + + + /** Delay between APN attempts. + Note the property override mechanism is there just for testing purpose only. */ + protected static final int APN_DELAY_MILLIS = + SystemProperties.getInt("persist.radio.apn_delay", 5000); + + 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[] dataEnabled = new boolean[DctConstants.APN_NUM_TYPES]; + + private int enabledCount = 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"; + + // TODO: See if we can remove INTENT_RECONNECT_ALARM + // having to have different values for GSM and + // CDMA. If so we can then remove the need for + // getActionIntentReconnectAlarm. + protected static final String INTENT_RECONNECT_ALARM_EXTRA_REASON = + "reconnect_alarm_extra_reason"; + + // Used for debugging. Send the INTENT with an optional counter value with the number + // of times the setup is to fail before succeeding. If the counter isn't passed the + // setup will fail once. Example fail two times with FailCause.SIGNAL_LOST(-3) + // adb shell am broadcast \ + // -a com.android.internal.telephony.dataconnectiontracker.intent_set_fail_data_setup_counter \ + // --ei fail_data_setup_counter 3 --ei fail_data_setup_fail_cause -3 + protected static final String INTENT_SET_FAIL_DATA_SETUP_COUNTER = + "com.android.internal.telephony.dataconnectiontracker.intent_set_fail_data_setup_counter"; + protected static final String FAIL_DATA_SETUP_COUNTER = "fail_data_setup_counter"; + protected int mFailDataSetupCounter = 0; + protected static final String FAIL_DATA_SETUP_FAIL_CAUSE = "fail_data_setup_fail_cause"; + protected FailCause mFailDataSetupFailCause = FailCause.ERROR_UNSPECIFIED; + + protected static final String DEFALUT_DATA_ON_BOOT_PROP = "net.def_data_on_boot"; + + // member variables + protected PhoneBase mPhone; + 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; + + // 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, DataConnectionAc> mDataConnectionAsyncChannels = + new HashMap<Integer, DataConnectionAc>(); + + /** 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> mAllApns = 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 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(getActionIntentReconnectAlarm())) { + log("Reconnect alarm. Previous state was " + mState); + onActionIntentReconnectAlarm(intent); + } else if (action.equals(getActionIntentDataStallAlarm())) { + 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()); + } 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; + } + } else if (action.equals(INTENT_SET_FAIL_DATA_SETUP_COUNTER)) { + mFailDataSetupCounter = intent.getIntExtra(FAIL_DATA_SETUP_COUNTER, 1); + mFailDataSetupFailCause = FailCause.fromInt( + intent.getIntExtra(FAIL_DATA_SETUP_FAIL_CAUSE, + FailCause.ERROR_UNSPECIFIED.getErrorCode())); + if (DBG) log("set mFailDataSetupCounter=" + mFailDataSetupCounter + + " mFailDataSetupFailCause=" + mFailDataSetupFailCause); + } + } + }; + + private final DataRoamingSettingObserver mDataRoamingSettingObserver; + + private class DataRoamingSettingObserver extends ContentObserver { + public DataRoamingSettingObserver(Handler handler) { + super(handler); + } + + public void register(Context context) { + final ContentResolver resolver = context.getContentResolver(); + resolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.DATA_ROAMING), false, this); + } + + public void unregister(Context context) { + final ContentResolver resolver = context.getContentResolver(); + resolver.unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange) { + // already running on mPhone handler thread + handleDataOnRoamingChange(); + } + } + + /** + * Maintian the sum of transmit and receive packets. + * + * The packet counts are initizlied 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; + } + + public String toString() { + return "{txSum=" + txPkts + " rxSum=" + rxPkts + "}"; + } + + public void updateTxRxSum() { + boolean txUpdated = false, rxUpdated = false; + long txSum = 0, rxSum = 0; + for (ApnContext apnContext : mApnContexts.values()) { + if (apnContext.getState() == DctConstants.State.CONNECTED) { + DataConnectionAc dcac = apnContext.getDataConnectionAc(); + if (dcac == null) continue; + + LinkProperties linkProp = dcac.getLinkPropertiesSync(); + if (linkProp == null) continue; + + String iface = linkProp.getInterfaceName(); + + if (iface != null) { + long stats = TrafficStats.getTxPackets(iface); + if (stats > 0) { + txUpdated = true; + txSum += stats; + } + stats = TrafficStats.getRxPackets(iface); + if (stats > 0) { + rxUpdated = true; + rxSum += stats; + } + } + } + } + if (txUpdated) this.txPkts = txSum; + if (rxUpdated) this.rxPkts = rxSum; + } + } + + protected boolean isDataSetupCompleteOk(AsyncResult ar) { + if (ar.exception != null) { + if (DBG) log("isDataSetupCompleteOk return false, ar.result=" + ar.result); + return false; + } + if (mFailDataSetupCounter <= 0) { + if (DBG) log("isDataSetupCompleteOk return true"); + return true; + } + ar.result = mFailDataSetupFailCause; + if (DBG) { + log("isDataSetupCompleteOk return false" + + " mFailDataSetupCounter=" + mFailDataSetupCounter + + " mFailDataSetupFailCause=" + mFailDataSetupFailCause); + } + mFailDataSetupCounter -= 1; + return false; + } + + protected void onActionIntentReconnectAlarm(Intent intent) { + String reason = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON); + if (mState == DctConstants.State.FAILED) { + Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_CONNECTION); + msg.arg1 = 0; // tearDown is false + msg.arg2 = 0; + msg.obj = reason; + sendMessage(msg); + } + sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA)); + } + + protected void onActionIntentDataStallAlarm(Intent intent) { + if (VDBG) 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 DataConnectionTracker(PhoneBase phone) { + super(); + if (DBG) log("DCT.constructor"); + mPhone = phone; + + IntentFilter filter = new IntentFilter(); + filter.addAction(getActionIntentReconnectAlarm()); + 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_SET_FAIL_DATA_SETUP_COUNTER); + + mUserDataEnabled = Settings.Secure.getInt( + mPhone.getContext().getContentResolver(), Settings.Secure.MOBILE_DATA, 1) == 1; + + // TODO: Why is this registering the phone as the receiver of the intent + // and not its own handler? + 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. + + dataEnabled[DctConstants.APN_DEFAULT_ID] = + SystemProperties.getBoolean(DEFALUT_DATA_ON_BOOT_PROP,true); + if (dataEnabled[DctConstants.APN_DEFAULT_ID]) { + enabledCount++; + } + + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext()); + mAutoAttachOnCreation = sp.getBoolean(PhoneBase.DATA_DISABLED_ON_BOOT_KEY, false); + + // watch for changes to Settings.Secure.DATA_ROAMING + mDataRoamingSettingObserver = new DataRoamingSettingObserver(mPhone); + mDataRoamingSettingObserver.register(mPhone.getContext()); + } + + public void dispose() { + if (DBG) log("DCT.dispose"); + for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) { + dcac.disconnect(); + } + mDataConnectionAsyncChannels.clear(); + mIsDisposed = true; + mPhone.getContext().unregisterReceiver(this.mIntentReceiver); + mDataRoamingSettingObserver.unregister(mPhone.getContext()); + } + + protected void broadcastMessenger() { + Intent intent = new Intent(DctConstants.ACTION_DATA_CONNECTION_TRACKER_MESSENGER); + intent.putExtra(DctConstants.EXTRA_MESSENGER, new Messenger(this)); + mPhone.getContext().sendBroadcast(intent); + } + + 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.Secure.getString(c.getContentResolver(), + Settings.Secure.TETHER_DUN_APN); + ApnSetting dunSetting = ApnSetting.fromString(apnData); + if (dunSetting != null) { + if (VDBG) log("fetchDunApn: secure 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 Settings.Secure#DATA_ROAMING} value. + */ + public void setDataOnRoamingEnabled(boolean enabled) { + if (getDataOnRoamingEnabled() != enabled) { + final ContentResolver resolver = mPhone.getContext().getContentResolver(); + Settings.Secure.putInt(resolver, Settings.Secure.DATA_ROAMING, enabled ? 1 : 0); + // will trigger handleDataOnRoamingChange() through observer + } + } + + /** + * Return current {@link Settings.Secure#DATA_ROAMING} value. + */ + public boolean getDataOnRoamingEnabled() { + try { + final ContentResolver resolver = mPhone.getContext().getContentResolver(); + return Settings.Secure.getInt(resolver, Settings.Secure.DATA_ROAMING) != 0; + } catch (SettingNotFoundException snfe) { + return false; + } + } + + private void handleDataOnRoamingChange() { + if (mPhone.getServiceState().getRoaming()) { + if (getDataOnRoamingEnabled()) { + resetAllRetryCounts(); + } + sendMessage(obtainMessage(DctConstants.EVENT_ROAMING_ON)); + } + } + + // abstract methods + protected abstract String getActionIntentReconnectAlarm(); + protected abstract String getActionIntentDataStallAlarm(); + protected abstract void startNetStatPoll(); + protected abstract void stopNetStatPoll(); + protected abstract void restartDataStallAlarm(); + 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 onDisconnectDone(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); + protected abstract boolean isDataPossible(String apnType); + + protected void onDataStallAlarm(int tag) { + loge("onDataStallAlarm: not impleted tag=" + tag); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { + log("DISCONNECTED_CONNECTED: msg=" + msg); + DataConnectionAc dcac = (DataConnectionAc) msg.obj; + mDataConnectionAsyncChannels.remove(dcac.dataConnection.getDataConnectionId()); + 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: + if (getDataOnRoamingEnabled() == false) { + resetAllRetryCounts(); + } + 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_DISCONNECT_DONE: + log("DataConnectoinTracker.handleMessage: EVENT_DISCONNECT_DONE msg=" + msg); + onDisconnectDone(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; + } + default: + Log.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 + && (enabledCount != 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; + } + } + + protected LinkProperties getLinkProperties(String apnType) { + int id = apnTypeToId(apnType); + + if (isApnIdEnabled(id)) { + // TODO - remove this cdma-only hack and support multiple DCs. + DataConnectionAc dcac = mDataConnectionAsyncChannels.get(0); + return dcac.getLinkPropertiesSync(); + } else { + return new LinkProperties(); + } + } + + protected LinkCapabilities getLinkCapabilities(String apnType) { + int id = apnTypeToId(apnType); + if (isApnIdEnabled(id)) { + // TODO - remove this cdma-only hack and support multiple DCs. + DataConnectionAc dcac = mDataConnectionAsyncChannels.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 (dataEnabled[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: + case INITING: + break; + 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; + } + } + + // 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 dataEnabled[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 Phone#APN_TYPE_MMS} and {@link Phone#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 Phone#APN_TYPE_MMS} and {@link Phone#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 (dataEnabled[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 = " + dataEnabled[id] + + " and enabledCount = " + enabledCount); + } + 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 = " + dataEnabled[apnId] + + ", enabledCount = " + enabledCount + ", isApnTypeActive = " + + isApnTypeActive(apnIdToType(apnId))); + } + if (enabled == DctConstants.ENABLED) { + synchronized (this) { + if (!dataEnabled[apnId]) { + dataEnabled[apnId] = true; + enabledCount++; + } + } + 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 (dataEnabled[apnId]) { + dataEnabled[apnId] = false; + enabledCount--; + didDisable = true; + } + } + if (didDisable) { + if ((enabledCount == 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 (dataEnabled[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"); + resetAllRetryCounts(); + 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.Secure.putInt(mPhone.getContext().getContentResolver(), + Settings.Secure.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) { + resetAllRetryCounts(); + 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) { + resetAllRetryCounts(); + 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 resetAllRetryCounts() { + for (ApnContext ac : mApnContexts.values()) { + ac.setRetryCount(0); + } + for (DataConnection dc : mDataConnections.values()) { + dc.resetRetryCount(); + } + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("DataConnectionTracker:"); + pw.println(" mInternalDataEnabled=" + mInternalDataEnabled); + pw.println(" mUserDataEnabled=" + mUserDataEnabled); + pw.println(" sPolicyDataEnabed=" + sPolicyDataEnabled); + pw.println(" dataEnabled:"); + for(int i=0; i < dataEnabled.length; i++) { + pw.printf(" dataEnabled[%d]=%b\n", i, dataEnabled[i]); + } + pw.flush(); + pw.println(" enabledCount=" + enabledCount); + 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(" mSentSinceLastRecv=" + mSentSinceLastRecv); + pw.println(" mNoRecvPollCount=" + mNoRecvPollCount); + 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(" ***************************************"); + 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); + } + pw.println(" ***************************************"); + pw.flush(); + Set<Entry<String, Integer>> mApnToDcIdSet = mApnToDataConnectionId.entrySet(); + pw.println(" mApnToDataConnectonId size=" + mApnToDcIdSet.size()); + for (Entry<String, Integer> entry : mApnToDcIdSet) { + pw.printf(" mApnToDataConnectonId[%s]=%d\n", entry.getKey(), entry.getValue()); + } + pw.println(" ***************************************"); + pw.flush(); + if (mApnContexts != null) { + Set<Entry<String, ApnContext>> mApnContextsSet = mApnContexts.entrySet(); + pw.println(" mApnContexts size=" + mApnContextsSet.size()); + for (Entry<String, ApnContext> entry : mApnContextsSet) { + entry.getValue().dump(fd, pw, args); + } + pw.println(" ***************************************"); + } else { + pw.println(" mApnContexts=null"); + } + pw.flush(); + pw.println(" mActiveApn=" + mActiveApn); + if (mAllApns != null) { + pw.println(" mAllApns size=" + mAllApns.size()); + for (int i=0; i < mAllApns.size(); i++) { + pw.printf(" mAllApns[%d]: %s\n", i, mAllApns.get(i)); + } + pw.flush(); + } else { + pw.println(" mAllApns=null"); + } + pw.println(" mPreferredApn=" + mPreferredApn); + pw.println(" mIsPsRestricted=" + mIsPsRestricted); + pw.println(" mIsDisposed=" + mIsDisposed); + pw.println(" mIntentReceiver=" + mIntentReceiver); + pw.println(" mDataRoamingSettingObserver=" + mDataRoamingSettingObserver); + pw.flush(); + } +} |