am 0e90f738: am cd2a20f2: am 5a2772e2: Merge "Fix code problem in writePidDns"
* commit '0e90f738aa82d9b30e6b4f43cd321e3f9cad2fc1':
Fix code problem in writePidDns
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 5f8793c..a570473 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -89,11 +89,21 @@
* should always obtain network information through
* {@link #getActiveNetworkInfo()} or
* {@link #getAllNetworkInfo()}.
+ * @see #EXTRA_NETWORK_TYPE
*/
@Deprecated
public static final String EXTRA_NETWORK_INFO = "networkInfo";
/**
+ * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast.
+ * Can be used with {@link #getNetworkInfo(int)} to get {@link NetworkInfo}
+ * state based on the calling application.
+ *
+ * @see android.content.Intent#getIntExtra(String, int)
+ */
+ public static final String EXTRA_NETWORK_TYPE = "networkType";
+
+ /**
* The lookup key for a boolean that indicates whether a connect event
* is for a network to which the connectivity manager was failing over
* following a disconnect on another network.
@@ -137,6 +147,28 @@
public static final String EXTRA_INET_CONDITION = "inetCondition";
/**
+ * Broadcast action to indicate the change of data activity status
+ * (idle or active) on a network in a recent period.
+ * The network becomes active when data transimission is started, or
+ * idle if there is no data transimition for a period of time.
+ * {@hide}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DATA_ACTIVITY_CHANGE = "android.net.conn.DATA_ACTIVITY_CHANGE";
+ /**
+ * The lookup key for an enum that indicates the network device type on which this data activity
+ * change happens.
+ * {@hide}
+ */
+ public static final String EXTRA_DEVICE_TYPE = "deviceType";
+ /**
+ * The lookup key for a boolean that indicates the device is active or not. {@code true} means
+ * it is actively sending or receiving data and {@code false} means it is idle.
+ * {@hide}
+ */
+ public static final String EXTRA_IS_ACTIVE = "isActive";
+
+ /**
* Broadcast Action: The setting for background data usage has changed
* values. Use {@link #getBackgroundDataSetting()} to get the current value.
* <p>
@@ -880,4 +912,24 @@
return false;
}
}
+
+ /** {@hide} */
+ public boolean updateLockdownVpn() {
+ try {
+ return mService.updateLockdownVpn();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public void captivePortalCheckComplete(NetworkInfo info) {
+ try {
+ mService.captivePortalCheckComplete(info);
+ } catch (RemoteException e) {
+ }
+ }
+
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 92aeff2..056fa03 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -26,6 +26,7 @@
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
/**
* Interface that answers queries about, and allows changing, the
@@ -118,7 +119,11 @@
ParcelFileDescriptor establishVpn(in VpnConfig config);
- void startLegacyVpn(in VpnConfig config, in String[] racoon, in String[] mtpd);
+ void startLegacyVpn(in VpnProfile profile);
LegacyVpnInfo getLegacyVpnInfo();
+
+ boolean updateLockdownVpn();
+
+ void captivePortalCheckComplete(in NetworkInfo info);
}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 0bc6b58..0b23cb7 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -79,7 +79,9 @@
/** Access to this network is blocked. */
BLOCKED,
/** Link has poor connectivity. */
- VERIFYING_POOR_LINK
+ VERIFYING_POOR_LINK,
+ /** Checking if network is a captive portal */
+ CAPTIVE_PORTAL_CHECK,
}
/**
@@ -97,6 +99,7 @@
stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING);
stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING);
stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING);
+ stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING);
stateMap.put(DetailedState.CONNECTED, State.CONNECTED);
stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED);
stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 86ada40..04991bb 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,25 +16,38 @@
package com.android.server;
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
+import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_DUMMY;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import android.bluetooth.BluetoothTetheringDataTracker;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.net.CaptivePortalTracker;
import android.net.ConnectivityManager;
import android.net.DummyDataStateTracker;
import android.net.EthernetDataTracker;
import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
@@ -68,7 +81,10 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.provider.Settings;
+import android.security.Credentials;
+import android.security.KeyStore;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Slog;
@@ -76,21 +92,23 @@
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
+import com.android.server.net.BaseNetworkObserver;
+import com.android.server.net.LockdownVpnTracker;
import com.google.android.collect.Lists;
import com.google.android.collect.Sets;
+
import dalvik.system.DexClassLoader;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.InvocationTargetException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -106,13 +124,15 @@
* @hide
*/
public class ConnectivityService extends IConnectivityManager.Stub {
+ private static final String TAG = "ConnectivityService";
private static final boolean DBG = true;
private static final boolean VDBG = false;
- private static final String TAG = "ConnectivityService";
private static final boolean LOGD_RULES = false;
+ // TODO: create better separation between radio types and network types
+
// how long to wait before switching back to a radio's default network
private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
// system property that can override the above value
@@ -126,7 +146,13 @@
private Tethering mTethering;
private boolean mTetheringConfigValid = false;
+ private KeyStore mKeyStore;
+
private Vpn mVpn;
+ private VpnCallback mVpnCallback = new VpnCallback();
+
+ private boolean mLockdownEnabled;
+ private LockdownVpnTracker mLockdownTracker;
/** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
private Object mRulesLock = new Object();
@@ -142,6 +168,9 @@
*/
private NetworkStateTracker mNetTrackers[];
+ /* Handles captive portal check on a network */
+ private CaptivePortalTracker mCaptivePortalTracker;
+
/**
* The link properties that define the current links
*/
@@ -186,95 +215,83 @@
private static final boolean TO_DEFAULT_TABLE = true;
private static final boolean TO_SECONDARY_TABLE = false;
- // Share the event space with NetworkStateTracker (which can't see this
- // internal class but sends us events). If you change these, change
- // NetworkStateTracker.java too.
- private static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1;
- private static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100;
-
/**
* used internally as a delayed event to make us switch back to the
* default network
*/
- private static final int EVENT_RESTORE_DEFAULT_NETWORK =
- MAX_NETWORK_STATE_TRACKER_EVENT + 1;
+ private static final int EVENT_RESTORE_DEFAULT_NETWORK = 1;
/**
* used internally to change our mobile data enabled flag
*/
- private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED =
- MAX_NETWORK_STATE_TRACKER_EVENT + 2;
+ private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = 2;
/**
* used internally to change our network preference setting
* arg1 = networkType to prefer
*/
- private static final int EVENT_SET_NETWORK_PREFERENCE =
- MAX_NETWORK_STATE_TRACKER_EVENT + 3;
+ private static final int EVENT_SET_NETWORK_PREFERENCE = 3;
/**
* used internally to synchronize inet condition reports
* arg1 = networkType
* arg2 = condition (0 bad, 100 good)
*/
- private static final int EVENT_INET_CONDITION_CHANGE =
- MAX_NETWORK_STATE_TRACKER_EVENT + 4;
+ private static final int EVENT_INET_CONDITION_CHANGE = 4;
/**
* used internally to mark the end of inet condition hold periods
* arg1 = networkType
*/
- private static final int EVENT_INET_CONDITION_HOLD_END =
- MAX_NETWORK_STATE_TRACKER_EVENT + 5;
+ private static final int EVENT_INET_CONDITION_HOLD_END = 5;
/**
* used internally to set enable/disable cellular data
* arg1 = ENBALED or DISABLED
*/
- private static final int EVENT_SET_MOBILE_DATA =
- MAX_NETWORK_STATE_TRACKER_EVENT + 7;
+ private static final int EVENT_SET_MOBILE_DATA = 7;
/**
* used internally to clear a wakelock when transitioning
* from one net to another
*/
- private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK =
- MAX_NETWORK_STATE_TRACKER_EVENT + 8;
+ private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 8;
/**
* used internally to reload global proxy settings
*/
- private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY =
- MAX_NETWORK_STATE_TRACKER_EVENT + 9;
+ private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = 9;
/**
* used internally to set external dependency met/unmet
* arg1 = ENABLED (met) or DISABLED (unmet)
* arg2 = NetworkType
*/
- private static final int EVENT_SET_DEPENDENCY_MET =
- MAX_NETWORK_STATE_TRACKER_EVENT + 10;
+ private static final int EVENT_SET_DEPENDENCY_MET = 10;
/**
* used internally to restore DNS properties back to the
* default network
*/
- private static final int EVENT_RESTORE_DNS =
- MAX_NETWORK_STATE_TRACKER_EVENT + 11;
+ private static final int EVENT_RESTORE_DNS = 11;
/**
* used internally to send a sticky broadcast delayed.
*/
- private static final int EVENT_SEND_STICKY_BROADCAST_INTENT =
- MAX_NETWORK_STATE_TRACKER_EVENT + 12;
+ private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 12;
/**
* Used internally to
* {@link NetworkStateTracker#setPolicyDataEnable(boolean)}.
*/
- private static final int EVENT_SET_POLICY_DATA_ENABLE = MAX_NETWORK_STATE_TRACKER_EVENT + 13;
+ private static final int EVENT_SET_POLICY_DATA_ENABLE = 13;
- private Handler mHandler;
+ private static final int EVENT_VPN_STATE_CHANGED = 14;
+
+ /** Handler used for internal events. */
+ private InternalHandler mHandler;
+ /** Handler used for incoming {@link NetworkStateTracker} events. */
+ private NetworkStateTrackerHandler mTrackerHandler;
// list of DeathRecipients used to make sure features are turned off when
// a process dies
@@ -328,11 +345,24 @@
public ConnectivityService(Context context, INetworkManagementService netd,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
+ // Currently, omitting a NetworkFactory will create one internally
+ // TODO: create here when we have cleaner WiMAX support
+ this(context, netd, statsService, policyManager, null);
+ }
+
+ public ConnectivityService(Context context, INetworkManagementService netManager,
+ INetworkStatsService statsService, INetworkPolicyManager policyManager,
+ NetworkFactory netFactory) {
if (DBG) log("ConnectivityService starting up");
HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
handlerThread.start();
- mHandler = new MyHandler(handlerThread.getLooper());
+ mHandler = new InternalHandler(handlerThread.getLooper());
+ mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper());
+
+ if (netFactory == null) {
+ netFactory = new DefaultNetworkFactory(context, mTrackerHandler);
+ }
// setup our unique device name
if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) {
@@ -358,8 +388,9 @@
}
mContext = checkNotNull(context, "missing Context");
- mNetd = checkNotNull(netd, "missing INetworkManagementService");
+ mNetd = checkNotNull(netManager, "missing INetworkManagementService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
+ mKeyStore = KeyStore.getInstance();
try {
mPolicyManager.registerListener(mPolicyListener);
@@ -472,69 +503,38 @@
mTestMode = SystemProperties.get("cm.test.mode").equals("true")
&& SystemProperties.get("ro.build.type").equals("eng");
- /*
- * Create the network state trackers for Wi-Fi and mobile
- * data. Maybe this could be done with a factory class,
- * but it's not clear that it's worth it, given that
- * the number of different network types is not going
- * to change very often.
- */
- for (int netType : mPriorityList) {
- switch (mNetConfigs[netType].radio) {
- case ConnectivityManager.TYPE_WIFI:
- mNetTrackers[netType] = new WifiStateTracker(netType,
- mNetConfigs[netType].name);
- mNetTrackers[netType].startMonitoring(context, mHandler);
- break;
- case ConnectivityManager.TYPE_MOBILE:
- mNetTrackers[netType] = new MobileDataStateTracker(netType,
- mNetConfigs[netType].name);
- mNetTrackers[netType].startMonitoring(context, mHandler);
- break;
- case ConnectivityManager.TYPE_DUMMY:
- mNetTrackers[netType] = new DummyDataStateTracker(netType,
- mNetConfigs[netType].name);
- mNetTrackers[netType].startMonitoring(context, mHandler);
- break;
- case ConnectivityManager.TYPE_BLUETOOTH:
- mNetTrackers[netType] = BluetoothTetheringDataTracker.getInstance();
- mNetTrackers[netType].startMonitoring(context, mHandler);
- break;
- case ConnectivityManager.TYPE_WIMAX:
- mNetTrackers[netType] = makeWimaxStateTracker();
- if (mNetTrackers[netType]!= null) {
- mNetTrackers[netType].startMonitoring(context, mHandler);
- }
- break;
- case ConnectivityManager.TYPE_ETHERNET:
- mNetTrackers[netType] = EthernetDataTracker.getInstance();
- mNetTrackers[netType].startMonitoring(context, mHandler);
- break;
- default:
- loge("Trying to create a DataStateTracker for an unknown radio type " +
- mNetConfigs[netType].radio);
+
+ // Create and start trackers for hard-coded networks
+ for (int targetNetworkType : mPriorityList) {
+ final NetworkConfig config = mNetConfigs[targetNetworkType];
+ final NetworkStateTracker tracker;
+ try {
+ tracker = netFactory.createTracker(targetNetworkType, config);
+ mNetTrackers[targetNetworkType] = tracker;
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Problem creating " + getNetworkTypeName(targetNetworkType)
+ + " tracker: " + e);
continue;
}
- mCurrentLinkProperties[netType] = null;
- if (mNetTrackers[netType] != null && mNetConfigs[netType].isDefault()) {
- mNetTrackers[netType].reconnect();
+
+ tracker.startMonitoring(context, mTrackerHandler);
+ if (config.isDefault()) {
+ tracker.reconnect();
}
}
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b);
-
- mTethering = new Tethering(mContext, nmService, statsService, this, mHandler.getLooper());
+ mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper());
mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 ||
mTethering.getTetherableWifiRegexs().length != 0 ||
mTethering.getTetherableBluetoothRegexs().length != 0) &&
mTethering.getUpstreamIfaceTypes().length != 0);
- mVpn = new Vpn(mContext, new VpnCallback());
+ mVpn = new Vpn(mContext, mVpnCallback, mNetd);
+ mVpn.startMonitoring(mContext, mTrackerHandler);
try {
- nmService.registerObserver(mTethering);
- nmService.registerObserver(mVpn);
+ mNetd.registerObserver(mTethering);
+ mNetd.registerObserver(mDataActivityObserver);
} catch (RemoteException e) {
loge("Error registering observer :" + e);
}
@@ -548,8 +548,55 @@
loadGlobalProxy();
}
-private NetworkStateTracker makeWimaxStateTracker() {
- //Initialize Wimax
+
+ /**
+ * Factory that creates {@link NetworkStateTracker} instances using given
+ * {@link NetworkConfig}.
+ */
+ public interface NetworkFactory {
+ public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config);
+ }
+
+ private static class DefaultNetworkFactory implements NetworkFactory {
+ private final Context mContext;
+ private final Handler mTrackerHandler;
+
+ public DefaultNetworkFactory(Context context, Handler trackerHandler) {
+ mContext = context;
+ mTrackerHandler = trackerHandler;
+ }
+
+ @Override
+ public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config) {
+ switch (config.radio) {
+ case TYPE_WIFI:
+ return new WifiStateTracker(targetNetworkType, config.name);
+ case TYPE_MOBILE:
+ return new MobileDataStateTracker(targetNetworkType, config.name);
+ case TYPE_DUMMY:
+ return new DummyDataStateTracker(targetNetworkType, config.name);
+ case TYPE_BLUETOOTH:
+ return BluetoothTetheringDataTracker.getInstance();
+ case TYPE_WIMAX:
+ return makeWimaxStateTracker(mContext, mTrackerHandler);
+ case TYPE_ETHERNET:
+ return EthernetDataTracker.getInstance();
+ default:
+ throw new IllegalArgumentException(
+ "Trying to create a NetworkStateTracker for an unknown radio type: "
+ + config.radio);
+ }
+ }
+ }
+
+ /**
+ * Loads external WiMAX library and registers as system service, returning a
+ * {@link NetworkStateTracker} for WiMAX. Caller is still responsible for
+ * invoking {@link NetworkStateTracker#startMonitoring(Context, Handler)}.
+ */
+ private static NetworkStateTracker makeWimaxStateTracker(
+ Context context, Handler trackerHandler) {
+ // Initialize Wimax
DexClassLoader wimaxClassLoader;
Class wimaxStateTrackerClass = null;
Class wimaxServiceClass = null;
@@ -562,25 +609,25 @@
NetworkStateTracker wimaxStateTracker = null;
- boolean isWimaxEnabled = mContext.getResources().getBoolean(
+ boolean isWimaxEnabled = context.getResources().getBoolean(
com.android.internal.R.bool.config_wimaxEnabled);
if (isWimaxEnabled) {
try {
- wimaxJarLocation = mContext.getResources().getString(
+ wimaxJarLocation = context.getResources().getString(
com.android.internal.R.string.config_wimaxServiceJarLocation);
- wimaxLibLocation = mContext.getResources().getString(
+ wimaxLibLocation = context.getResources().getString(
com.android.internal.R.string.config_wimaxNativeLibLocation);
- wimaxManagerClassName = mContext.getResources().getString(
+ wimaxManagerClassName = context.getResources().getString(
com.android.internal.R.string.config_wimaxManagerClassname);
- wimaxServiceClassName = mContext.getResources().getString(
+ wimaxServiceClassName = context.getResources().getString(
com.android.internal.R.string.config_wimaxServiceClassname);
- wimaxStateTrackerClassName = mContext.getResources().getString(
+ wimaxStateTrackerClassName = context.getResources().getString(
com.android.internal.R.string.config_wimaxStateTrackerClassname);
log("wimaxJarLocation: " + wimaxJarLocation);
wimaxClassLoader = new DexClassLoader(wimaxJarLocation,
- new ContextWrapper(mContext).getCacheDir().getAbsolutePath(),
+ new ContextWrapper(context).getCacheDir().getAbsolutePath(),
wimaxLibLocation, ClassLoader.getSystemClassLoader());
try {
@@ -601,13 +648,13 @@
Constructor wmxStTrkrConst = wimaxStateTrackerClass.getConstructor
(new Class[] {Context.class, Handler.class});
- wimaxStateTracker = (NetworkStateTracker)wmxStTrkrConst.newInstance(mContext,
- mHandler);
+ wimaxStateTracker = (NetworkStateTracker) wmxStTrkrConst.newInstance(
+ context, trackerHandler);
Constructor wmxSrvConst = wimaxServiceClass.getDeclaredConstructor
(new Class[] {Context.class, wimaxStateTrackerClass});
wmxSrvConst.setAccessible(true);
- IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(mContext, wimaxStateTracker);
+ IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(context, wimaxStateTracker);
wmxSrvConst.setAccessible(false);
ServiceManager.addService(WimaxManagerConstants.WIMAX_SERVICE, svcInvoker);
@@ -623,6 +670,7 @@
return wimaxStateTracker;
}
+
/**
* Sets the preferred network.
* @param preference the new preference
@@ -630,7 +678,8 @@
public void setNetworkPreference(int preference) {
enforceChangePermission();
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0));
+ mHandler.sendMessage(
+ mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0));
}
public int getNetworkPreference() {
@@ -749,6 +798,9 @@
info = new NetworkInfo(info);
info.setDetailedState(DetailedState.BLOCKED, null, null);
}
+ if (mLockdownTracker != null) {
+ info = mLockdownTracker.augmentNetworkInfo(info);
+ }
return info;
}
@@ -766,6 +818,17 @@
return getNetworkInfo(mActiveDefaultNetwork, uid);
}
+ public NetworkInfo getActiveNetworkInfoUnfiltered() {
+ enforceAccessPermission();
+ if (isNetworkTypeValid(mActiveDefaultNetwork)) {
+ final NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork];
+ if (tracker != null) {
+ return tracker.getNetworkInfo();
+ }
+ }
+ return null;
+ }
+
@Override
public NetworkInfo getActiveNetworkInfoForUid(int uid) {
enforceConnectivityInternalPermission();
@@ -923,6 +986,14 @@
return tracker != null && tracker.setRadio(turnOn);
}
+ private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() {
+ @Override
+ public void interfaceClassDataActivityChanged(String label, boolean active) {
+ int deviceType = Integer.parseInt(label);
+ sendDataActivityBroadcast(deviceType, active);
+ }
+ };
+
/**
* Used to notice when the calling process dies so we can self-expire
*
@@ -1017,6 +1088,12 @@
// TODO - move this into individual networktrackers
int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
+ if (mLockdownEnabled) {
+ // Since carrier APNs usually aren't available from VPN
+ // endpoint, mark them as unavailable.
+ return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+ }
+
if (mProtectedNetworks.contains(usedNetworkType)) {
enforceConnectivityInternalPermission();
}
@@ -1291,8 +1368,10 @@
return false;
}
NetworkStateTracker tracker = mNetTrackers[networkType];
+ DetailedState netState = tracker.getNetworkInfo().getDetailedState();
- if (tracker == null || !tracker.getNetworkInfo().isConnected() ||
+ if (tracker == null || (netState != DetailedState.CONNECTED &&
+ netState != DetailedState.CAPTIVE_PORTAL_CHECK) ||
tracker.isTeardownRequested()) {
if (VDBG) {
log("requestRouteToHostAddress on down network " +
@@ -1591,6 +1670,10 @@
int prevNetType = info.getType();
mNetTrackers[prevNetType].setTeardownRequested(false);
+
+ // Remove idletimer previously setup in {@code handleConnect}
+ removeDataActivityTracking(prevNetType);
+
/*
* If the disconnected network is not the active one, then don't report
* this as a loss of connectivity. What probably happened is that we're
@@ -1610,6 +1693,7 @@
Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
if (info.isFailover()) {
intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
info.setFailover(false);
@@ -1719,7 +1803,7 @@
}
}
- private void sendConnectedBroadcast(NetworkInfo info) {
+ public void sendConnectedBroadcast(NetworkInfo info) {
sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
@@ -1734,8 +1818,13 @@
}
private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
+ if (mLockdownTracker != null) {
+ info = mLockdownTracker.augmentNetworkInfo(info);
+ }
+
Intent intent = new Intent(bcastType);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
if (info.isFailover()) {
intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
info.setFailover(false);
@@ -1759,6 +1848,14 @@
sendStickyBroadcastDelayed(makeGeneralIntent(info, bcastType), delayMs);
}
+ private void sendDataActivityBroadcast(int deviceType, boolean active) {
+ Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE);
+ intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType);
+ intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active);
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL,
+ RECEIVE_DATA_ACTIVITY_CHANGE, null, null, 0, null, null);
+ }
+
/**
* Called when an attempt to fail over to another network has failed.
* @param info the {@link NetworkInfo} for the failed network
@@ -1779,6 +1876,7 @@
Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
if (getActiveNetworkInfo() == null) {
intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
}
@@ -1829,7 +1927,7 @@
log("sendStickyBroadcast: action=" + intent.getAction());
}
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
}
@@ -1850,37 +1948,55 @@
synchronized(this) {
mSystemReady = true;
if (mInitialBroadcast != null) {
- mContext.sendStickyBroadcast(mInitialBroadcast);
+ mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL);
mInitialBroadcast = null;
}
}
// load the global proxy at startup
mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
+
+ // Try bringing up tracker, but if KeyStore isn't ready yet, wait
+ // for user to unlock device.
+ if (!updateLockdownVpn()) {
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
+ mContext.registerReceiver(mUserPresentReceiver, filter);
+ }
+ }
+
+ private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Try creating lockdown tracker, since user present usually means
+ // unlocked keystore.
+ if (updateLockdownVpn()) {
+ mContext.unregisterReceiver(this);
+ }
+ }
+ };
+
+ private boolean isNewNetTypePreferredOverCurrentNetType(int type) {
+ if ((type != mNetworkPreference &&
+ mNetConfigs[mActiveDefaultNetwork].priority >
+ mNetConfigs[type].priority) ||
+ mNetworkPreference == mActiveDefaultNetwork) return false;
+ return true;
}
private void handleConnect(NetworkInfo info) {
- final int type = info.getType();
+ final int newNetType = info.getType();
+
+ setupDataActivityTracking(newNetType);
// snapshot isFailover, because sendConnectedBroadcast() resets it
boolean isFailover = info.isFailover();
- final NetworkStateTracker thisNet = mNetTrackers[type];
+ final NetworkStateTracker thisNet = mNetTrackers[newNetType];
+ final String thisIface = thisNet.getLinkProperties().getInterfaceName();
// if this is a default net and other default is running
// kill the one not preferred
- if (mNetConfigs[type].isDefault()) {
- if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
- if ((type != mNetworkPreference &&
- mNetConfigs[mActiveDefaultNetwork].priority >
- mNetConfigs[type].priority) ||
- mNetworkPreference == mActiveDefaultNetwork) {
- // don't accept this one
- if (VDBG) {
- log("Not broadcasting CONNECT_ACTION " +
- "to torn down network " + info.getTypeName());
- }
- teardown(thisNet);
- return;
- } else {
+ if (mNetConfigs[newNetType].isDefault()) {
+ if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) {
+ if (isNewNetTypePreferredOverCurrentNetType(newNetType)) {
// tear down the other
NetworkStateTracker otherNet =
mNetTrackers[mActiveDefaultNetwork];
@@ -1893,6 +2009,14 @@
teardown(thisNet);
return;
}
+ } else {
+ // don't accept this one
+ if (VDBG) {
+ log("Not broadcasting CONNECT_ACTION " +
+ "to torn down network " + info.getTypeName());
+ }
+ teardown(thisNet);
+ return;
}
}
synchronized (ConnectivityService.this) {
@@ -1906,7 +2030,7 @@
1000);
}
}
- mActiveDefaultNetwork = type;
+ mActiveDefaultNetwork = newNetType;
// this will cause us to come up initially as unconnected and switching
// to connected after our normal pause unless somebody reports us as reall
// disconnected
@@ -1918,20 +2042,99 @@
}
thisNet.setTeardownRequested(false);
updateNetworkSettings(thisNet);
- handleConnectivityChange(type, false);
+ handleConnectivityChange(newNetType, false);
sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
// notify battery stats service about this network
- final String iface = thisNet.getLinkProperties().getInterfaceName();
- if (iface != null) {
+ if (thisIface != null) {
try {
- BatteryStatsService.getService().noteNetworkInterfaceType(iface, type);
+ BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, newNetType);
} catch (RemoteException e) {
// ignored; service lives in system_server
}
}
}
+ private void handleCaptivePortalTrackerCheck(NetworkInfo info) {
+ if (DBG) log("Captive portal check " + info);
+ int type = info.getType();
+ final NetworkStateTracker thisNet = mNetTrackers[type];
+ if (mNetConfigs[type].isDefault()) {
+ if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
+ if (isNewNetTypePreferredOverCurrentNetType(type)) {
+ if (DBG) log("Captive check on " + info.getTypeName());
+ mCaptivePortalTracker = CaptivePortalTracker.detect(mContext, info,
+ ConnectivityService.this);
+ return;
+ } else {
+ if (DBG) log("Tear down low priority net " + info.getTypeName());
+ teardown(thisNet);
+ return;
+ }
+ }
+ }
+
+ thisNet.captivePortalCheckComplete();
+ }
+
+ /** @hide */
+ public void captivePortalCheckComplete(NetworkInfo info) {
+ mNetTrackers[info.getType()].captivePortalCheckComplete();
+ mCaptivePortalTracker = null;
+ }
+
+ /**
+ * Setup data activity tracking for the given network interface.
+ *
+ * Every {@code setupDataActivityTracking} should be paired with a
+ * {@link removeDataActivityTracking} for cleanup.
+ */
+ private void setupDataActivityTracking(int type) {
+ final NetworkStateTracker thisNet = mNetTrackers[type];
+ final String iface = thisNet.getLinkProperties().getInterfaceName();
+
+ final int timeout;
+
+ if (ConnectivityManager.isNetworkTypeMobile(type)) {
+ timeout = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.DATA_ACTIVITY_TIMEOUT_MOBILE,
+ 0);
+ // Canonicalize mobile network type
+ type = ConnectivityManager.TYPE_MOBILE;
+ } else if (ConnectivityManager.TYPE_WIFI == type) {
+ timeout = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.DATA_ACTIVITY_TIMEOUT_WIFI,
+ 0);
+ } else {
+ // do not track any other networks
+ timeout = 0;
+ }
+
+ if (timeout > 0 && iface != null) {
+ try {
+ mNetd.addIdleTimer(iface, timeout, Integer.toString(type));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Remove data activity tracking when network disconnects.
+ */
+ private void removeDataActivityTracking(int type) {
+ final NetworkStateTracker net = mNetTrackers[type];
+ final String iface = net.getLinkProperties().getInterfaceName();
+
+ if (iface != null && (ConnectivityManager.isNetworkTypeMobile(type) ||
+ ConnectivityManager.TYPE_WIFI == type)) {
+ try {
+ // the call fails silently if no idletimer setup for this interface
+ mNetd.removeIdleTimer(iface);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
/**
* After a change in the connectivity state of a network. We're mainly
* concerned with making sure that the list of DNS servers is set up
@@ -2136,9 +2339,9 @@
*/
public void updateNetworkSettings(NetworkStateTracker nt) {
String key = nt.getTcpBufferSizesPropName();
- String bufferSizes = SystemProperties.get(key);
+ String bufferSizes = key == null ? null : SystemProperties.get(key);
- if (bufferSizes.length() == 0) {
+ if (TextUtils.isEmpty(bufferSizes)) {
if (VDBG) log(key + " not found in system properties. Using defaults");
// Setting to default values so we won't be stuck to previous values
@@ -2264,7 +2467,7 @@
* Connectivity events can happen before boot has completed ...
*/
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
// Caller must grab mDnsLock.
@@ -2431,8 +2634,8 @@
}
// must be stateless - things change under us.
- private class MyHandler extends Handler {
- public MyHandler(Looper looper) {
+ private class NetworkStateTrackerHandler extends Handler {
+ public NetworkStateTrackerHandler(Looper looper) {
super(looper);
}
@@ -2468,6 +2671,9 @@
if (info.getDetailedState() ==
NetworkInfo.DetailedState.FAILED) {
handleConnectionFailure(info);
+ } else if (info.getDetailedState() ==
+ DetailedState.CAPTIVE_PORTAL_CHECK) {
+ handleCaptivePortalTrackerCheck(info);
} else if (state == NetworkInfo.State.DISCONNECTED) {
handleDisconnect(info);
} else if (state == NetworkInfo.State.SUSPENDED) {
@@ -2482,6 +2688,9 @@
} else if (state == NetworkInfo.State.CONNECTED) {
handleConnect(info);
}
+ if (mLockdownTracker != null) {
+ mLockdownTracker.onNetworkInfoChanged(info);
+ }
break;
case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
info = (NetworkInfo) msg.obj;
@@ -2495,6 +2704,19 @@
type = info.getType();
updateNetworkSettings(mNetTrackers[type]);
break;
+ }
+ }
+ }
+
+ private class InternalHandler extends Handler {
+ public InternalHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ NetworkInfo info;
+ switch (msg.what) {
case EVENT_CLEAR_NET_TRANSITION_WAKELOCK:
String causedBy = null;
synchronized (ConnectivityService.this) {
@@ -2566,6 +2788,13 @@
final int networkType = msg.arg1;
final boolean enabled = msg.arg2 == ENABLED;
handleSetPolicyDataEnable(networkType, enabled);
+ break;
+ }
+ case EVENT_VPN_STATE_CHANGED: {
+ if (mLockdownTracker != null) {
+ mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj);
+ }
+ break;
}
}
}
@@ -2883,7 +3112,7 @@
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy);
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private static class SettingsObserver extends ContentObserver {
@@ -2907,11 +3136,11 @@
}
}
- private void log(String s) {
+ private static void log(String s) {
Slog.d(TAG, s);
}
- private void loge(String s) {
+ private static void loge(String s) {
Slog.e(TAG, s);
}
@@ -2964,6 +3193,7 @@
*/
@Override
public boolean protectVpn(ParcelFileDescriptor socket) {
+ throwIfLockdownEnabled();
try {
int type = mActiveDefaultNetwork;
if (ConnectivityManager.isNetworkTypeValid(type)) {
@@ -2990,6 +3220,7 @@
*/
@Override
public boolean prepareVpn(String oldPackage, String newPackage) {
+ throwIfLockdownEnabled();
return mVpn.prepare(oldPackage, newPackage);
}
@@ -3002,18 +3233,22 @@
*/
@Override
public ParcelFileDescriptor establishVpn(VpnConfig config) {
+ throwIfLockdownEnabled();
return mVpn.establish(config);
}
/**
- * Start legacy VPN and return an intent to VpnDialogs. This method is
- * used by VpnSettings and not available in ConnectivityManager.
- * Permissions are checked in Vpn class.
- * @hide
+ * Start legacy VPN, controlling native daemons as needed. Creates a
+ * secondary thread to perform connection work, returning quickly.
*/
@Override
- public void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
- mVpn.startLegacyVpn(config, racoon, mtpd);
+ public void startLegacyVpn(VpnProfile profile) {
+ throwIfLockdownEnabled();
+ final LinkProperties egress = getActiveLinkProperties();
+ if (egress == null) {
+ throw new IllegalStateException("Missing active network connection");
+ }
+ mVpn.startLegacyVpn(profile, mKeyStore, egress);
}
/**
@@ -3024,6 +3259,7 @@
*/
@Override
public LegacyVpnInfo getLegacyVpnInfo() {
+ throwIfLockdownEnabled();
return mVpn.getLegacyVpnInfo();
}
@@ -3038,10 +3274,13 @@
* be done whenever a better abstraction is developed.
*/
public class VpnCallback {
-
private VpnCallback() {
}
+ public void onStateChanged(NetworkInfo info) {
+ mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget();
+ }
+
public void override(List<String> dnsServers, List<String> searchDomains) {
if (dnsServers == null) {
restore();
@@ -3108,4 +3347,58 @@
}
}
}
+
+ @Override
+ public boolean updateLockdownVpn() {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ // Tear down existing lockdown if profile was removed
+ mLockdownEnabled = LockdownVpnTracker.isEnabled();
+ if (mLockdownEnabled) {
+ if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
+ Slog.w(TAG, "KeyStore locked; unable to create LockdownTracker");
+ return false;
+ }
+
+ final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
+ final VpnProfile profile = VpnProfile.decode(
+ profileName, mKeyStore.get(Credentials.VPN + profileName));
+ setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpn, profile));
+ } else {
+ setLockdownTracker(null);
+ }
+
+ return true;
+ }
+
+ /**
+ * Internally set new {@link LockdownVpnTracker}, shutting down any existing
+ * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
+ */
+ private void setLockdownTracker(LockdownVpnTracker tracker) {
+ // Shutdown any existing tracker
+ final LockdownVpnTracker existing = mLockdownTracker;
+ mLockdownTracker = null;
+ if (existing != null) {
+ existing.shutdown();
+ }
+
+ try {
+ if (tracker != null) {
+ mNetd.setFirewallEnabled(true);
+ mLockdownTracker = tracker;
+ mLockdownTracker.init();
+ } else {
+ mNetd.setFirewallEnabled(false);
+ }
+ } catch (RemoteException e) {
+ // ignored; NMS lives inside system_server
+ }
+ }
+
+ private void throwIfLockdownEnabled() {
+ if (mLockdownEnabled) {
+ throw new IllegalStateException("Unavailable in lockdown mode");
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
new file mode 100644
index 0000000..93ea6a2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2012 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.server;
+
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.NetworkStateTracker.EVENT_STATE_CHANGED;
+import static com.google.testing.littlemock.LittleMock.anyInt;
+import static com.google.testing.littlemock.LittleMock.createCaptor;
+import static com.google.testing.littlemock.LittleMock.doNothing;
+import static com.google.testing.littlemock.LittleMock.doReturn;
+import static com.google.testing.littlemock.LittleMock.doThrow;
+import static com.google.testing.littlemock.LittleMock.eq;
+import static com.google.testing.littlemock.LittleMock.isA;
+import static com.google.testing.littlemock.LittleMock.mock;
+import static com.google.testing.littlemock.LittleMock.reset;
+import static com.google.testing.littlemock.LittleMock.verify;
+
+import android.content.Context;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.net.LinkProperties;
+import android.net.NetworkConfig;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkStateTracker;
+import android.net.RouteInfo;
+import android.os.Handler;
+import android.os.INetworkManagementService;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.util.LogPrinter;
+
+import com.google.testing.littlemock.ArgumentCaptor;
+
+import java.net.InetAddress;
+import java.util.concurrent.Future;
+
+/**
+ * Tests for {@link ConnectivityService}.
+ */
+@LargeTest
+public class ConnectivityServiceTest extends AndroidTestCase {
+ private static final String TAG = "ConnectivityServiceTest";
+
+ private static final String MOBILE_IFACE = "rmnet3";
+ private static final String WIFI_IFACE = "wlan6";
+
+ private static final RouteInfo MOBILE_ROUTE_V4 = RouteInfo.makeHostRoute(parse("10.0.0.33"));
+ private static final RouteInfo MOBILE_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::33"));
+
+ private static final RouteInfo WIFI_ROUTE_V4 = RouteInfo.makeHostRoute(
+ parse("192.168.0.66"), parse("192.168.0.1"));
+ private static final RouteInfo WIFI_ROUTE_V6 = RouteInfo.makeHostRoute(
+ parse("fd00::66"), parse("fd00::"));
+
+ private INetworkManagementService mNetManager;
+ private INetworkStatsService mStatsService;
+ private INetworkPolicyManager mPolicyService;
+ private ConnectivityService.NetworkFactory mNetFactory;
+
+ private BroadcastInterceptingContext mServiceContext;
+ private ConnectivityService mService;
+
+ private MockNetwork mMobile;
+ private MockNetwork mWifi;
+
+ private Handler mTrackerHandler;
+
+ private static class MockNetwork {
+ public NetworkStateTracker tracker;
+ public NetworkInfo info;
+ public LinkProperties link;
+
+ public MockNetwork(int type) {
+ tracker = mock(NetworkStateTracker.class);
+ info = new NetworkInfo(type, -1, getNetworkTypeName(type), null);
+ link = new LinkProperties();
+ }
+
+ public void doReturnDefaults() {
+ // TODO: eventually CS should make defensive copies
+ doReturn(new NetworkInfo(info)).when(tracker).getNetworkInfo();
+ doReturn(new LinkProperties(link)).when(tracker).getLinkProperties();
+
+ // fallback to default TCP buffers
+ doReturn("").when(tracker).getTcpBufferSizesPropName();
+ }
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mServiceContext = new BroadcastInterceptingContext(getContext());
+
+ mNetManager = mock(INetworkManagementService.class);
+ mStatsService = mock(INetworkStatsService.class);
+ mPolicyService = mock(INetworkPolicyManager.class);
+ mNetFactory = mock(ConnectivityService.NetworkFactory.class);
+
+ mMobile = new MockNetwork(TYPE_MOBILE);
+ mWifi = new MockNetwork(TYPE_WIFI);
+
+ // omit most network trackers
+ doThrow(new IllegalArgumentException("Not supported in test environment"))
+ .when(mNetFactory).createTracker(anyInt(), isA(NetworkConfig.class));
+
+ doReturn(mMobile.tracker)
+ .when(mNetFactory).createTracker(eq(TYPE_MOBILE), isA(NetworkConfig.class));
+ doReturn(mWifi.tracker)
+ .when(mNetFactory).createTracker(eq(TYPE_WIFI), isA(NetworkConfig.class));
+
+ final ArgumentCaptor<Handler> trackerHandler = createCaptor();
+ doNothing().when(mMobile.tracker)
+ .startMonitoring(isA(Context.class), trackerHandler.capture());
+
+ mService = new ConnectivityService(
+ mServiceContext, mNetManager, mStatsService, mPolicyService, mNetFactory);
+ mService.systemReady();
+
+ mTrackerHandler = trackerHandler.getValue();
+ mTrackerHandler.getLooper().setMessageLogging(new LogPrinter(Log.INFO, TAG));
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testMobileConnectedAddedRoutes() throws Exception {
+ Future<?> nextConnBroadcast;
+
+ // bring up mobile network
+ mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null);
+ mMobile.link.setInterfaceName(MOBILE_IFACE);
+ mMobile.link.addRoute(MOBILE_ROUTE_V4);
+ mMobile.link.addRoute(MOBILE_ROUTE_V6);
+ mMobile.doReturnDefaults();
+
+ nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+ mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
+ nextConnBroadcast.get();
+
+ // verify that both routes were added and DNS was flushed
+ verify(mNetManager).addRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V4));
+ verify(mNetManager).addRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V6));
+ verify(mNetManager).flushInterfaceDnsCache(MOBILE_IFACE);
+
+ }
+
+ public void testMobileWifiHandoff() throws Exception {
+ Future<?> nextConnBroadcast;
+
+ // bring up mobile network
+ mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null);
+ mMobile.link.setInterfaceName(MOBILE_IFACE);
+ mMobile.link.addRoute(MOBILE_ROUTE_V4);
+ mMobile.link.addRoute(MOBILE_ROUTE_V6);
+ mMobile.doReturnDefaults();
+
+ nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+ mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
+ nextConnBroadcast.get();
+
+ reset(mNetManager);
+
+ // now bring up wifi network
+ mWifi.info.setDetailedState(DetailedState.CONNECTED, null, null);
+ mWifi.link.setInterfaceName(WIFI_IFACE);
+ mWifi.link.addRoute(WIFI_ROUTE_V4);
+ mWifi.link.addRoute(WIFI_ROUTE_V6);
+ mWifi.doReturnDefaults();
+
+ // expect that mobile will be torn down
+ doReturn(true).when(mMobile.tracker).teardown();
+
+ nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+ mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget();
+ nextConnBroadcast.get();
+
+ // verify that wifi routes added, and teardown requested
+ verify(mNetManager).addRoute(eq(WIFI_IFACE), eq(WIFI_ROUTE_V4));
+ verify(mNetManager).addRoute(eq(WIFI_IFACE), eq(WIFI_ROUTE_V6));
+ verify(mNetManager).flushInterfaceDnsCache(WIFI_IFACE);
+ verify(mMobile.tracker).teardown();
+
+ reset(mNetManager, mMobile.tracker);
+
+ // tear down mobile network, as requested
+ mMobile.info.setDetailedState(DetailedState.DISCONNECTED, null, null);
+ mMobile.link.clear();
+ mMobile.doReturnDefaults();
+
+ nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+ mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
+ nextConnBroadcast.get();
+
+ verify(mNetManager).removeRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V4));
+ verify(mNetManager).removeRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V6));
+
+ }
+
+ private static InetAddress parse(String addr) {
+ return InetAddress.parseNumericAddress(addr);
+ }
+}