am 89ab6757: (-s ours) am 6626398a: Merge "Fix for updating TCP buffer size when switching network type"

* commit '89ab67578e4e06c41f0e96d840de952d10895233':
  Fix for updating TCP buffer size when switching network type
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 5f8793c..d30ef04 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>
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 86ada40..375ba68 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -17,8 +17,16 @@
 package com.android.server;
 
 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;
@@ -35,6 +43,7 @@
 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;
@@ -81,16 +90,16 @@
 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.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;
@@ -186,95 +195,81 @@
     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;
+    /** 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 +323,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 netd,
+            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"))) {
@@ -472,59 +480,27 @@
 
         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) &&
@@ -533,8 +509,9 @@
         mVpn = new Vpn(mContext, new VpnCallback());
 
         try {
-            nmService.registerObserver(mTethering);
-            nmService.registerObserver(mVpn);
+            mNetd.registerObserver(mTethering);
+            mNetd.registerObserver(mVpn);
+            mNetd.registerObserver(mDataActivityObserver);
         } catch (RemoteException e) {
             loge("Error registering observer :" + e);
         }
@@ -548,8 +525,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 +586,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 +625,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 +647,7 @@
 
         return wimaxStateTracker;
     }
+
     /**
      * Sets the preferred network.
      * @param preference the new preference
@@ -630,7 +655,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() {
@@ -923,6 +949,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
      *
@@ -1591,6 +1625,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 +1648,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);
@@ -1736,6 +1775,7 @@
     private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
         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 +1799,13 @@
         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.sendOrderedBroadcast(intent, RECEIVE_DATA_ACTIVITY_CHANGE);
+    }
+
     /**
      * Called when an attempt to fail over to another network has failed.
      * @param info the {@link NetworkInfo} for the failed network
@@ -1779,6 +1826,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);
         }
@@ -1861,9 +1909,12 @@
     private void handleConnect(NetworkInfo info) {
         final int type = info.getType();
 
+        setupDataActivityTracking(type);
+
         // snapshot isFailover, because sendConnectedBroadcast() resets it
         boolean isFailover = info.isFailover();
         final NetworkStateTracker thisNet = mNetTrackers[type];
+        final String thisIface = thisNet.getLinkProperties().getInterfaceName();
 
         // if this is a default net and other default is running
         // kill the one not preferred
@@ -1922,10 +1973,9 @@
         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, type);
             } catch (RemoteException e) {
                 // ignored; service lives in system_server
             }
@@ -1933,6 +1983,58 @@
     }
 
     /**
+     * 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
      * according to which networks are connected, and ensuring that the
@@ -2431,8 +2533,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);
         }
 
@@ -2490,11 +2592,19 @@
                     //       @see bug/4455071
                     handleConnectivityChange(info.getType(), false);
                     break;
-                case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED:
-                    info = (NetworkInfo) msg.obj;
-                    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) {
@@ -2907,11 +3017,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);
     }
 
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);
+    }
+}