eclair snapshot
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 1429bc1..a127df0 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.os.Binder;
 import android.os.RemoteException;
 
 /**
@@ -114,15 +115,64 @@
     public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
             "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
 
-    public static final int TYPE_MOBILE = 0;
-    public static final int TYPE_WIFI   = 1;
+    /**
+     * The Default Mobile data connection.  When active, all data traffic
+     * will use this connection by default.  Should not coexist with other
+     * default connections.
+     */
+    public static final int TYPE_MOBILE      = 0;
+    /**
+     * The Default WIFI data connection.  When active, all data traffic
+     * will use this connection by default.  Should not coexist with other
+     * default connections.
+     */
+    public static final int TYPE_WIFI        = 1;
+    /**
+     * An MMS-specific Mobile data connection.  This connection may be the
+     * same as {@link #TYPEMOBILE} but it may be different.  This is used
+     * by applications needing to talk to the carrier's Multimedia Messaging
+     * Service servers.  It may coexist with default data connections.
+     * {@hide}
+     */
+    public static final int TYPE_MOBILE_MMS  = 2;
+    /**
+     * A SUPL-specific Mobile data connection.  This connection may be the
+     * same as {@link #TYPEMOBILE} but it may be different.  This is used
+     * by applications needing to talk to the carrier's Secure User Plane
+     * Location servers for help locating the device.  It may coexist with
+     * default data connections.
+     * {@hide}
+     */
+    public static final int TYPE_MOBILE_SUPL = 3;
+    /**
+     * A DUN-specific Mobile data connection.  This connection may be the
+     * same as {@link #TYPEMOBILE} but it may be different.  This is used
+     * by applicaitons performing a Dial Up Networking bridge so that
+     * the carrier is aware of DUN traffic.  It may coexist with default data
+     * connections.
+     * {@hide}
+     */
+    public static final int TYPE_MOBILE_DUN  = 4;
+    /**
+     * A High Priority Mobile data connection.  This connection is typically
+     * the same as {@link #TYPEMOBILE} but the routing setup is different.
+     * Only requesting processes will have access to the Mobile DNS servers
+     * and only IP's explicitly requested via {@link #requestRouteToHost}
+     * will route over this interface.
+     *{@hide}
+     */
+    public static final int TYPE_MOBILE_HIPRI = 5;
+    /** {@hide} */
+    public static final int MAX_RADIO_TYPE   = TYPE_WIFI;
+    /** {@hide} */
+    public static final int MAX_NETWORK_TYPE = TYPE_MOBILE_HIPRI;
 
     public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
 
     private IConnectivityManager mService;
 
     static public boolean isNetworkTypeValid(int networkType) {
-        return networkType == TYPE_WIFI || networkType == TYPE_MOBILE;
+        return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
     }
 
     public void setNetworkPreference(int preference) {
@@ -195,7 +245,8 @@
      */
     public int startUsingNetworkFeature(int networkType, String feature) {
         try {
-            return mService.startUsingNetworkFeature(networkType, feature);
+            return mService.startUsingNetworkFeature(networkType, feature,
+                    new Binder());
         } catch (RemoteException e) {
             return -1;
         }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index de68598..9f59cce 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.net.NetworkInfo;
+import android.os.IBinder;
 
 /**
  * Interface that answers queries about, and allows changing, the
@@ -39,7 +40,8 @@
 
     boolean setRadio(int networkType, boolean turnOn);
 
-    int startUsingNetworkFeature(int networkType, in String feature);
+    int startUsingNetworkFeature(int networkType, in String feature,
+            in IBinder binder);
 
     int stopUsingNetworkFeature(int networkType, in String feature);
 
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 9f53937..649cb8c 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -131,7 +131,7 @@
         mSubtypeName = subtypeName;
         setDetailedState(DetailedState.IDLE, null, null);
         mState = State.UNKNOWN;
-        mIsAvailable = true;
+        mIsAvailable = false; // until we're told otherwise, assume unavailable
         mIsRoaming = false;
     }
 
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 1153648..a3ae01b 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -25,6 +25,9 @@
  * {@hide}
  */
 public class NetworkUtils {
+    /** Bring the named network interface up. */
+    public native static int enableInterface(String interfaceName);
+
     /** Bring the named network interface down. */
     public native static int disableInterface(String interfaceName);
 
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 8383deb..feb0dad 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -23,6 +23,7 @@
 #include <arpa/inet.h>
 
 extern "C" {
+int ifc_enable(const char *ifname);
 int ifc_disable(const char *ifname);
 int ifc_add_host_route(const char *ifname, uint32_t addr);
 int ifc_remove_host_routes(const char *ifname);
@@ -66,6 +67,16 @@
     jfieldID leaseDuration;
 } dhcpInfoFieldIds;
 
+static jint android_net_utils_enableInterface(JNIEnv* env, jobject clazz, jstring ifname)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::ifc_enable(nameStr);
+    env->ReleaseStringUTFChars(ifname, nameStr);
+    return (jint)result;
+}
+
 static jint android_net_utils_disableInterface(JNIEnv* env, jobject clazz, jstring ifname)
 {
     int result;
@@ -209,6 +220,7 @@
 static JNINativeMethod gNetworkUtilMethods[] = {
     /* name, signature, funcPtr */
 
+    { "enableInterface", "(Ljava/lang/String;)I",  (void *)android_net_utils_enableInterface },
     { "disableInterface", "(Ljava/lang/String;)I",  (void *)android_net_utils_disableInterface },
     { "addHostRoute", "(Ljava/lang/String;I)I",  (void *)android_net_utils_addHostRoute },
     { "removeHostRoutes", "(Ljava/lang/String;)I",  (void *)android_net_utils_removeHostRoutes },
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 493bd09..78215b0 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -30,51 +30,136 @@
 import android.net.wifi.WifiStateTracker;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
 
+import com.android.internal.telephony.Phone;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @hide
  */
 public class ConnectivityService extends IConnectivityManager.Stub {
 
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final String TAG = "ConnectivityService";
 
     // Event log tags (must be in sync with event-log-tags)
     private static final int EVENTLOG_CONNECTIVITY_STATE_CHANGED = 50020;
 
+    // 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
+    private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
+            "android.telephony.apn-restore";
+
     /**
      * Sometimes we want to refer to the individual network state
      * trackers separately, and sometimes we just want to treat them
      * abstractly.
      */
     private NetworkStateTracker mNetTrackers[];
-    private WifiStateTracker mWifiStateTracker;
-    private MobileDataStateTracker mMobileDataStateTracker;
+
+    /**
+     * A per Net list of the PID's that requested access to the net
+     * used both as a refcount and for per-PID DNS selection
+     */
+    private List mNetRequestersPids[];
+
     private WifiWatchdogService mWifiWatchdogService;
 
+    // priority order of the nettrackers
+    // (excluding dynamically set mNetworkPreference)
+    // TODO - move mNetworkTypePreference into this
+    private int[] mPriorityList;
+
     private Context mContext;
     private int mNetworkPreference;
-    private NetworkStateTracker mActiveNetwork;
+    private int mActiveDefaultNetwork = -1;
 
     private int mNumDnsEntries;
-    private static int sDnsChangeCounter;
 
     private boolean mTestMode;
     private static ConnectivityService sServiceInstance;
 
+    private Handler mHandler;
+
+    // list of DeathRecipients used to make sure features are turned off when
+    // a process dies
+    private List mFeatureUsers;
+
+    private boolean mSystemReady;
+    private ArrayList<Intent> mDeferredBroadcasts;
+
+    private class NetworkAttributes {
+        /**
+         * Class for holding settings read from resources.
+         */
+        public String mName;
+        public int mType;
+        public int mRadio;
+        public int mPriority;
+        public NetworkAttributes(String init) {
+            String fragments[] = init.split(",");
+            mName = fragments[0].toLowerCase();
+            if (fragments[1].toLowerCase().equals("wifi")) {
+                mRadio = ConnectivityManager.TYPE_WIFI;
+            } else {
+                mRadio = ConnectivityManager.TYPE_MOBILE;
+            }
+            if (mName.equals("default")) {
+                mType = mRadio;
+            } else if (mName.equals("mms")) {
+                mType = ConnectivityManager.TYPE_MOBILE_MMS;
+            } else if (mName.equals("supl")) {
+                mType = ConnectivityManager.TYPE_MOBILE_SUPL;
+            } else if (mName.equals("dun")) {
+                mType = ConnectivityManager.TYPE_MOBILE_DUN;
+            } else if (mName.equals("hipri")) {
+                mType = ConnectivityManager.TYPE_MOBILE_HIPRI;
+            }
+            mPriority = Integer.parseInt(fragments[2]);
+        }
+        public boolean isDefault() {
+            return (mType == mRadio);
+        }
+    }
+    NetworkAttributes[] mNetAttributes;
+
+    private class RadioAttributes {
+        public String mName;
+        public int mPriority;
+        public int mSimultaneity;
+        public int mType;
+        public RadioAttributes(String init) {
+            String fragments[] = init.split(",");
+            mName = fragments[0].toLowerCase();
+            mPriority = Integer.parseInt(fragments[1]);
+            mSimultaneity = Integer.parseInt(fragments[2]);
+            if (mName.equals("wifi")) {
+                mType = ConnectivityManager.TYPE_WIFI;
+            } else {
+                mType = ConnectivityManager.TYPE_MOBILE;
+            }
+        }
+    }
+    RadioAttributes[] mRadioAttributes;
+
     private static class ConnectivityThread extends Thread {
         private Context mContext;
-        
+
         private ConnectivityThread(Context context) {
             super("ConnectivityThread");
             mContext = context;
@@ -89,11 +174,11 @@
             }
             Looper.loop();
         }
-        
+
         public static ConnectivityService getServiceInstance(Context context) {
             ConnectivityThread thread = new ConnectivityThread(context);
             thread.start();
-            
+
             synchronized (thread) {
                 while (sServiceInstance == null) {
                     try {
@@ -101,27 +186,71 @@
                         thread.wait();
                     } catch (InterruptedException ignore) {
                         Log.e(TAG,
-                            "Unexpected InterruptedException while waiting for ConnectivityService thread");
+                            "Unexpected InterruptedException while waiting"+
+                            " for ConnectivityService thread");
                     }
                 }
             }
-            
+
             return sServiceInstance;
         }
     }
-    
+
     public static ConnectivityService getInstance(Context context) {
         return ConnectivityThread.getServiceInstance(context);
     }
-    
+
     private ConnectivityService(Context context) {
         if (DBG) Log.v(TAG, "ConnectivityService starting up");
         mContext = context;
-        mNetTrackers = new NetworkStateTracker[2];
-        Handler handler = new MyHandler();
-        
+        mNetTrackers = new NetworkStateTracker[
+                ConnectivityManager.MAX_NETWORK_TYPE+1];
+        mHandler = new MyHandler();
+
         mNetworkPreference = getPersistedNetworkPreference();
-                
+
+        // Load device network attributes from resources
+        mNetAttributes = new NetworkAttributes[
+                ConnectivityManager.MAX_NETWORK_TYPE+1];
+        mRadioAttributes = new RadioAttributes[
+                ConnectivityManager.MAX_RADIO_TYPE+1];
+        String[] naStrings = context.getResources().getStringArray(
+                com.android.internal.R.array.networkAttributes);
+        // TODO - what if the setting has gaps/unknown types?
+        for (String a : naStrings) {
+            NetworkAttributes n = new NetworkAttributes(a);
+            mNetAttributes[n.mType] = n;
+        }
+        String[] raStrings = context.getResources().getStringArray(
+                com.android.internal.R.array.radioAttributes);
+        for (String a : raStrings) {
+            RadioAttributes r = new RadioAttributes(a);
+            mRadioAttributes[r.mType] = r;
+        }
+
+        // high priority first
+        mPriorityList = new int[naStrings.length];
+        {
+            int priority = 0; //lowest
+            int nextPos = naStrings.length-1;
+            while (nextPos>-1) {
+                for (int i = 0; i < mNetAttributes.length; i++) {
+                    if(mNetAttributes[i].mPriority == priority) {
+                        mPriorityList[nextPos--] = i;
+                    }
+                }
+                priority++;
+            }
+        }
+
+        mNetRequestersPids =
+                new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
+        for (int i=0; i<=ConnectivityManager.MAX_NETWORK_TYPE; i++) {
+            mNetRequestersPids[i] = new ArrayList();
+        }
+
+        mFeatureUsers = new ArrayList();
+
         /*
          * Create the network state trackers for Wi-Fi and mobile
          * data. Maybe this could be done with a factory class,
@@ -130,15 +259,36 @@
          * to change very often.
          */
         if (DBG) Log.v(TAG, "Starting Wifi Service.");
-        mWifiStateTracker = new WifiStateTracker(context, handler);
-        WifiService wifiService = new WifiService(context, mWifiStateTracker);
+        WifiStateTracker wst = new WifiStateTracker(context, mHandler);
+        WifiService wifiService = new WifiService(context, wst);
         ServiceManager.addService(Context.WIFI_SERVICE, wifiService);
-        mNetTrackers[ConnectivityManager.TYPE_WIFI] = mWifiStateTracker;
+        mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
 
-        mMobileDataStateTracker = new MobileDataStateTracker(context, handler);
-        mNetTrackers[ConnectivityManager.TYPE_MOBILE] = mMobileDataStateTracker;
-        
-        mActiveNetwork = null;
+        mNetTrackers[ConnectivityManager.TYPE_MOBILE] =
+                new MobileDataStateTracker(context, mHandler,
+                ConnectivityManager.TYPE_MOBILE, Phone.APN_TYPE_DEFAULT,
+                "MOBILE");
+
+        mNetTrackers[ConnectivityManager.TYPE_MOBILE_MMS] =
+                new MobileDataStateTracker(context, mHandler,
+                ConnectivityManager.TYPE_MOBILE_MMS, Phone.APN_TYPE_MMS,
+                "MOBILE_MMS");
+
+        mNetTrackers[ConnectivityManager.TYPE_MOBILE_SUPL] =
+                new MobileDataStateTracker(context, mHandler,
+                ConnectivityManager.TYPE_MOBILE_SUPL, Phone.APN_TYPE_SUPL,
+                "MOBILE_SUPL");
+
+        mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] =
+                new MobileDataStateTracker(context, mHandler,
+                ConnectivityManager.TYPE_MOBILE_DUN, Phone.APN_TYPE_DUN,
+                "MOBILE_DUN");
+
+        mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI] =
+                new MobileDataStateTracker(context, mHandler,
+                ConnectivityManager.TYPE_MOBILE_HIPRI, Phone.APN_TYPE_HIPRI,
+                "MOBILE_HIPRI");
+
         mNumDnsEntries = 0;
 
         mTestMode = SystemProperties.get("cm.test.mode").equals("true")
@@ -148,16 +298,17 @@
             t.startMonitoring();
 
         // Constructing this starts it too
-        mWifiWatchdogService = new WifiWatchdogService(context, mWifiStateTracker);
+        mWifiWatchdogService = new WifiWatchdogService(context, wst);
     }
 
     /**
-     * Sets the preferred network. 
+     * Sets the preferred network.
      * @param preference the new preference
      */
     public synchronized void setNetworkPreference(int preference) {
         enforceChangePermission();
-        if (ConnectivityManager.isNetworkTypeValid(preference)) {
+        if (ConnectivityManager.isNetworkTypeValid(preference) &&
+                mNetAttributes[preference].isDefault()) {
             if (mNetworkPreference != preference) {
                 persistNetworkPreference(preference);
                 mNetworkPreference = preference;
@@ -173,9 +324,10 @@
 
     private void persistNetworkPreference(int networkPreference) {
         final ContentResolver cr = mContext.getContentResolver();
-        Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, networkPreference);
+        Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE,
+                networkPreference);
     }
-    
+
     private int getPersistedNetworkPreference() {
         final ContentResolver cr = mContext.getContentResolver();
 
@@ -187,31 +339,30 @@
 
         return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE;
     }
-    
+
     /**
-     * Make the state of network connectivity conform to the preference settings.
+     * Make the state of network connectivity conform to the preference settings
      * In this method, we only tear down a non-preferred network. Establishing
      * a connection to the preferred network is taken care of when we handle
      * the disconnect event from the non-preferred network
      * (see {@link #handleDisconnect(NetworkInfo)}).
      */
     private void enforcePreference() {
-        if (mActiveNetwork == null)
+        if (mNetTrackers[mNetworkPreference].getNetworkInfo().isConnected())
             return;
 
-        for (NetworkStateTracker t : mNetTrackers) {
-            if (t == mActiveNetwork) {
-                int netType = t.getNetworkInfo().getType();
-                int otherNetType = ((netType == ConnectivityManager.TYPE_WIFI) ?
-                        ConnectivityManager.TYPE_MOBILE :
-                        ConnectivityManager.TYPE_WIFI);
+        if (!mNetTrackers[mNetworkPreference].isAvailable())
+            return;
 
-                if (t.getNetworkInfo().getType() != mNetworkPreference) {
-                    NetworkStateTracker otherTracker = mNetTrackers[otherNetType];
-                    if (otherTracker.isAvailable()) {
-                        teardown(t);
-                    }
+        for (int t=0; t <= ConnectivityManager.MAX_RADIO_TYPE; t++) {
+            if (t != mNetworkPreference &&
+                    mNetTrackers[t].getNetworkInfo().isConnected()) {
+                if (DBG) {
+                    Log.d(TAG, "tearing down " +
+                            mNetTrackers[t].getNetworkInfo() +
+                            " in enforcePreference");
                 }
+                teardown(mNetTrackers[t]);
             }
         }
     }
@@ -229,13 +380,21 @@
      * Return NetworkInfo for the active (i.e., connected) network interface.
      * It is assumed that at most one network is active at a time. If more
      * than one is active, it is indeterminate which will be returned.
-     * @return the info for the active network, or {@code null} if none is active
+     * @return the info for the active network, or {@code null} if none is
+     * active
      */
     public NetworkInfo getActiveNetworkInfo() {
         enforceAccessPermission();
-        for (NetworkStateTracker t : mNetTrackers) {
+        for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
+            if (!mNetAttributes[type].isDefault()) {
+                continue;
+            }
+            NetworkStateTracker t = mNetTrackers[type];
             NetworkInfo info = t.getNetworkInfo();
             if (info.isConnected()) {
+                if (DBG && type != mActiveDefaultNetwork) Log.e(TAG,
+                        "connected default network is not " +
+                        "mActiveDefaultNetwork!");
                 return info;
             }
         }
@@ -280,36 +439,253 @@
         return tracker != null && tracker.setRadio(turnOn);
     }
 
-    public int startUsingNetworkFeature(int networkType, String feature) {
-        enforceChangePermission();
-        if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
-            return -1;
+    /**
+     * Used to notice when the calling process dies so we can self-expire
+     *
+     * Also used to know if the process has cleaned up after itself when
+     * our auto-expire timer goes off.  The timer has a link to an object.
+     *
+     */
+    private class FeatureUser implements IBinder.DeathRecipient {
+        int mNetworkType;
+        String mFeature;
+        IBinder mBinder;
+        int mPid;
+        int mUid;
+
+        FeatureUser(int type, String feature, IBinder binder) {
+            super();
+            mNetworkType = type;
+            mFeature = feature;
+            mBinder = binder;
+            mPid = getCallingPid();
+            mUid = getCallingUid();
+
+            try {
+                mBinder.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                binderDied();
+            }
         }
-        NetworkStateTracker tracker = mNetTrackers[networkType];
-        if (tracker != null) {
-            return tracker.startUsingNetworkFeature(feature, getCallingPid(), getCallingUid());
+
+        void unlinkDeathRecipient() {
+            mBinder.unlinkToDeath(this, 0);
         }
-        return -1;
+
+        public void binderDied() {
+            Log.d(TAG, "ConnectivityService FeatureUser binderDied(" +
+                    mNetworkType + ", " + mFeature + ", " + mBinder);
+            stopUsingNetworkFeature(this, false);
+        }
+
+        public void expire() {
+            Log.d(TAG, "ConnectivityService FeatureUser expire(" +
+                    mNetworkType + ", " + mFeature + ", " + mBinder);
+            stopUsingNetworkFeature(this, false);
+        }
     }
 
+    // javadoc from interface
+    public int startUsingNetworkFeature(int networkType, String feature,
+            IBinder binder) {
+        if (DBG) {
+            Log.d(TAG, "startUsingNetworkFeature for net " + networkType +
+                    ": " + feature);
+        }
+        enforceChangePermission();
+        if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
+            return Phone.APN_REQUEST_FAILED;
+        }
+
+        FeatureUser f = new FeatureUser(networkType, feature, binder);
+
+        // TODO - move this into the MobileDataStateTracker
+        int usedNetworkType = networkType;
+        if(networkType == ConnectivityManager.TYPE_MOBILE) {
+            if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
+            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL;
+            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN;
+            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
+            }
+        }
+        NetworkStateTracker network = mNetTrackers[usedNetworkType];
+        if (network != null) {
+            if (usedNetworkType != networkType) {
+                Integer currentPid = new Integer(getCallingPid());
+
+                NetworkStateTracker radio = mNetTrackers[networkType];
+                NetworkInfo ni = network.getNetworkInfo();
+
+                if (ni.isAvailable() == false) {
+                    if (DBG) Log.d(TAG, "special network not available");
+                    return Phone.APN_TYPE_NOT_AVAILABLE;
+                }
+
+                synchronized(this) {
+                    mFeatureUsers.add(f);
+                    if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) {
+                        // this gets used for per-pid dns when connected
+                        mNetRequestersPids[usedNetworkType].add(currentPid);
+                    }
+                }
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                        NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK,
+                        f), getRestoreDefaultNetworkDelay());
+
+
+                if ((ni.isConnectedOrConnecting() == true) &&
+                        !network.isTeardownRequested()) {
+                    if (ni.isConnected() == true) {
+                        // add the pid-specific dns
+                        handleDnsConfigurationChange();
+                        if (DBG) Log.d(TAG, "special network already active");
+                        return Phone.APN_ALREADY_ACTIVE;
+                    }
+                    if (DBG) Log.d(TAG, "special network already connecting");
+                    return Phone.APN_REQUEST_STARTED;
+                }
+
+                // check if the radio in play can make another contact
+                // assume if cannot for now
+
+                if (DBG) Log.d(TAG, "reconnecting to special network");
+                network.reconnect();
+                return Phone.APN_REQUEST_STARTED;
+            } else {
+                synchronized(this) {
+                    mFeatureUsers.add(f);
+                }
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                        NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK,
+                        f), getRestoreDefaultNetworkDelay());
+
+                return network.startUsingNetworkFeature(feature,
+                        getCallingPid(), getCallingUid());
+            }
+        }
+        return Phone.APN_TYPE_NOT_AVAILABLE;
+    }
+
+    // javadoc from interface
     public int stopUsingNetworkFeature(int networkType, String feature) {
+        int pid = getCallingPid();
+        int uid = getCallingUid();
+
+        FeatureUser u = null;
+        boolean found = false;
+
+        synchronized(this) {
+            for (int i = 0; i < mFeatureUsers.size() ; i++) {
+                u = (FeatureUser)mFeatureUsers.get(i);
+                if (uid == u.mUid && pid == u.mPid &&
+                        networkType == u.mNetworkType &&
+                        TextUtils.equals(feature, u.mFeature)) {
+                    found = true;
+                    break;
+                }
+            }
+        }
+        if (found && u != null) {
+            // stop regardless of how many other time this proc had called start
+            return stopUsingNetworkFeature(u, true);
+        } else {
+            // none found!
+            return 1;
+        }
+    }
+
+    private int stopUsingNetworkFeature(FeatureUser u, boolean ignoreDups) {
+        int networkType = u.mNetworkType;
+        String feature = u.mFeature;
+        int pid = u.mPid;
+        int uid = u.mUid;
+
+        NetworkStateTracker tracker = null;
+        boolean callTeardown = false;  // used to carry our decision outside of sync block
+
+        if (DBG) {
+            Log.d(TAG, "stopUsingNetworkFeature for net " + networkType +
+                    ": " + feature);
+        }
         enforceChangePermission();
         if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
             return -1;
         }
-        NetworkStateTracker tracker = mNetTrackers[networkType];
-        if (tracker != null) {
-            return tracker.stopUsingNetworkFeature(feature, getCallingPid(), getCallingUid());
+
+        // need to link the mFeatureUsers list with the mNetRequestersPids state in this
+        // sync block
+        synchronized(this) {
+            // check if this process still has an outstanding start request
+            if (!mFeatureUsers.contains(u)) {
+                return 1;
+            }
+            u.unlinkDeathRecipient();
+            mFeatureUsers.remove(mFeatureUsers.indexOf(u));
+            // If we care about duplicate requests, check for that here.
+            //
+            // This is done to support the extension of a request - the app
+            // can request we start the network feature again and renew the
+            // auto-shutoff delay.  Normal "stop" calls from the app though
+            // do not pay attention to duplicate requests - in effect the
+            // API does not refcount and a single stop will counter multiple starts.
+            if (ignoreDups == false) {
+                for (int i = 0; i < mFeatureUsers.size() ; i++) {
+                    FeatureUser x = (FeatureUser)mFeatureUsers.get(i);
+                    if (x.mUid == u.mUid && x.mPid == u.mPid &&
+                            x.mNetworkType == u.mNetworkType &&
+                            TextUtils.equals(x.mFeature, u.mFeature)) {
+                        return 1;
+                    }
+                }
+            }
+
+            // TODO - move to MobileDataStateTracker
+            int usedNetworkType = networkType;
+            if (networkType == ConnectivityManager.TYPE_MOBILE) {
+                if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
+                    usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
+                } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
+                    usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL;
+                } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) {
+                    usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN;
+                } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) {
+                    usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
+                }
+            }
+            tracker =  mNetTrackers[usedNetworkType];
+            if(usedNetworkType != networkType) {
+                Integer currentPid = new Integer(pid);
+                reassessPidDns(pid, true);
+                mNetRequestersPids[usedNetworkType].remove(currentPid);
+                if (mNetRequestersPids[usedNetworkType].size() != 0) {
+                    if (DBG) Log.d(TAG, "not tearing down special network - " +
+                           "others still using it");
+                    return 1;
+                }
+                callTeardown = true;
+            }
         }
-        return -1;
+
+        if (callTeardown) {
+            tracker.teardown();
+            return 1;
+        } else {
+            // do it the old fashioned way
+            return tracker.stopUsingNetworkFeature(feature, pid, uid);
+        }
     }
 
     /**
      * Ensure that a network route exists to deliver traffic to the specified
      * host via the specified network interface.
-     * @param networkType the type of the network over which traffic to the specified
-     * host is to be routed
-     * @param hostAddress the IP address of the host to which the route is desired
+     * @param networkType the type of the network over which traffic to the
+     * specified host is to be routed
+     * @param hostAddress the IP address of the host to which the route is
+     * desired
      * @return {@code true} on success, {@code false} on failure
      */
     public boolean requestRouteToHost(int networkType, int hostAddress) {
@@ -318,19 +694,14 @@
             return false;
         }
         NetworkStateTracker tracker = mNetTrackers[networkType];
-        /*
-         * If there's only one connected network, and it's the one requested,
-         * then we don't have to do anything - the requested route already
-         * exists. If it's not the requested network, then it's not possible
-         * to establish the requested route. Finally, if there is more than
-         * one connected network, then we must insert an entry in the routing
-         * table.
-         */
-        if (getNumConnectedNetworks() > 1) {
-            return tracker.requestRouteToHost(hostAddress);
-        } else {
-            return tracker.getNetworkInfo().getType() == networkType;
+
+        if (!tracker.getNetworkInfo().isConnected() || tracker.isTeardownRequested()) {
+            if (DBG) {
+                Log.d(TAG, "requestRouteToHost on down network (" + networkType + " - dropped");
+            }
+            return false;
         }
+        return tracker.requestRouteToHost(hostAddress);
     }
 
     /**
@@ -340,7 +711,7 @@
         return Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.BACKGROUND_DATA, 1) == 1;
     }
-    
+
     /**
      * @see ConnectivityManager#setBackgroundDataSetting(boolean)
      */
@@ -348,22 +719,24 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING,
                 "ConnectivityService");
-        
+
         if (getBackgroundDataSetting() == allowBackgroundDataUsage) return;
 
         Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.BACKGROUND_DATA, allowBackgroundDataUsage ? 1 : 0);
-        
+                Settings.Secure.BACKGROUND_DATA,
+                allowBackgroundDataUsage ? 1 : 0);
+
         Intent broadcast = new Intent(
                 ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
         mContext.sendBroadcast(broadcast);
-    }    
-    
+    }
+
     private int getNumConnectedNetworks() {
         int numConnectedNets = 0;
 
         for (NetworkStateTracker nt : mNetTrackers) {
-            if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
+            if (nt.getNetworkInfo().isConnected() &&
+                    !nt.isTeardownRequested()) {
                 ++numConnectedNets;
             }
         }
@@ -371,64 +744,46 @@
     }
 
     private void enforceAccessPermission() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE,
-                                          "ConnectivityService");
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_NETWORK_STATE,
+                "ConnectivityService");
     }
 
     private void enforceChangePermission() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE,
-                                          "ConnectivityService");
-
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CHANGE_NETWORK_STATE,
+                "ConnectivityService");
     }
 
     /**
-     * Handle a {@code DISCONNECTED} event. If this pertains to the non-active network,
-     * we ignore it. If it is for the active network, we send out a broadcast.
-     * But first, we check whether it might be possible to connect to a different
-     * network.
+     * Handle a {@code DISCONNECTED} event. If this pertains to the non-active
+     * network, we ignore it. If it is for the active network, we send out a
+     * broadcast. But first, we check whether it might be possible to connect
+     * to a different network.
      * @param info the {@code NetworkInfo} for the network
      */
     private void handleDisconnect(NetworkInfo info) {
 
-        if (DBG) Log.v(TAG, "Handle DISCONNECT for " + info.getTypeName());
+        int prevNetType = info.getType();
 
-        mNetTrackers[info.getType()].setTeardownRequested(false);
+        mNetTrackers[prevNetType].setTeardownRequested(false);
         /*
          * 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
          * getting the disconnect for a network that we explicitly disabled
          * in accordance with network preference policies.
          */
-        if (mActiveNetwork == null ||  info.getType() != mActiveNetwork.getNetworkInfo().getType())
-            return;
-
-        NetworkStateTracker newNet;
-        if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
-            newNet = mWifiStateTracker;
-        } else /* info().getType() == TYPE_WIFI */ {
-            newNet = mMobileDataStateTracker;
-        }
-
-        /**
-         * See if the other network is available to fail over to.
-         * If is not available, we enable it anyway, so that it
-         * will be able to connect when it does become available,
-         * but we report a total loss of connectivity rather than
-         * report that we are attempting to fail over.
-         */
-        NetworkInfo switchTo = null;
-        if (newNet.isAvailable()) {
-            mActiveNetwork = newNet;
-            switchTo = newNet.getNetworkInfo();
-            switchTo.setFailover(true);
-            if (!switchTo.isConnectedOrConnecting()) {
-                newNet.reconnect();
+        if (!mNetAttributes[prevNetType].isDefault()) {
+            List pids = mNetRequestersPids[prevNetType];
+            for (int i = 0; i<pids.size(); i++) {
+                Integer pid = (Integer)pids.get(i);
+                // will remove them because the net's no longer connected
+                // need to do this now as only now do we know the pids and
+                // can properly null things that are no longer referenced.
+                reassessPidDns(pid.intValue(), false);
             }
-        } else {
-            newNet.reconnect();
         }
 
-        boolean otherNetworkConnected = false;
         Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
         intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
         if (info.isFailover()) {
@@ -439,31 +794,92 @@
             intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
         }
         if (info.getExtraInfo() != null) {
-            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo());
+            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
+                    info.getExtraInfo());
         }
-        if (switchTo != null) {
-            otherNetworkConnected = switchTo.isConnected();
-            if (DBG) {
-                if (otherNetworkConnected) {
-                    Log.v(TAG, "Switching to already connected " + switchTo.getTypeName());
-                } else {
-                    Log.v(TAG, "Attempting to switch to " + switchTo.getTypeName());
+
+        /*
+         * If this is a default network, check if other defaults are available
+         * or active
+         */
+        NetworkStateTracker newNet = null;
+        if (mNetAttributes[prevNetType].isDefault()) {
+            if (mActiveDefaultNetwork == prevNetType) {
+                mActiveDefaultNetwork = -1;
+            }
+
+            int newType = -1;
+            int newPriority = -1;
+            for (int checkType=0; checkType <=
+                    ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
+                if (checkType == prevNetType) {
+                    continue;
+                }
+                if (mNetAttributes[checkType].isDefault()) {
+                    /* TODO - if we have multiple nets we could use
+                     * we may want to put more thought into which we choose
+                     */
+                    if (checkType == mNetworkPreference) {
+                        newType = checkType;
+                        break;
+                    }
+                    if (mRadioAttributes[mNetAttributes[checkType].mRadio].
+                            mPriority > newPriority) {
+                        newType = checkType;
+                        newPriority = mRadioAttributes[mNetAttributes[newType].
+                                mRadio].mPriority;
+                    }
                 }
             }
-            intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
-        } else {
-            intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
-        }
-        if (DBG) Log.v(TAG, "Sending DISCONNECT bcast for " + info.getTypeName() +
-                (switchTo == null ? "" : " other=" + switchTo.getTypeName()));
 
-        mContext.sendStickyBroadcast(intent);
+            if (newType != -1) {
+                newNet = mNetTrackers[newType];
+                /**
+                 * See if the other network is available to fail over to.
+                 * If is not available, we enable it anyway, so that it
+                 * will be able to connect when it does become available,
+                 * but we report a total loss of connectivity rather than
+                 * report that we are attempting to fail over.
+                 */
+                if (newNet.isAvailable()) {
+                    NetworkInfo switchTo = newNet.getNetworkInfo();
+                    switchTo.setFailover(true);
+                    if (!switchTo.isConnectedOrConnecting() ||
+                            newNet.isTeardownRequested()) {
+                        newNet.reconnect();
+                    }
+                    if (DBG) {
+                        if (switchTo.isConnected()) {
+                            Log.v(TAG, "Switching to already connected " +
+                                    switchTo.getTypeName());
+                        } else {
+                            Log.v(TAG, "Attempting to switch to " +
+                                    switchTo.getTypeName());
+                        }
+                    }
+                    intent.putExtra(ConnectivityManager.
+                            EXTRA_OTHER_NETWORK_INFO, switchTo);
+                } else {
+                    intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,
+                            true);
+                    newNet.reconnect();
+                }
+            } else {
+                intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,
+                        true);
+            }
+        }
+
+        // do this before we broadcast the change
+        handleConnectivityChange();
+
+        sendStickyBroadcast(intent);
         /*
-         * If the failover network is already connected, then immediately send out
-         * a followup broadcast indicating successful failover
+         * If the failover network is already connected, then immediately send
+         * out a followup broadcast indicating successful failover
          */
-        if (switchTo != null && otherNetworkConnected)
-            sendConnectedBroadcast(switchTo);
+        if (newNet != null && newNet.getNetworkInfo().isConnected())
+            sendConnectedBroadcast(newNet.getNetworkInfo());
     }
 
     private void sendConnectedBroadcast(NetworkInfo info) {
@@ -477,9 +893,10 @@
             intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
         }
         if (info.getExtraInfo() != null) {
-            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo());
+            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
+                    info.getExtraInfo());
         }
-        mContext.sendStickyBroadcast(intent);
+        sendStickyBroadcast(intent);
     }
 
     /**
@@ -488,106 +905,124 @@
      */
     private void handleConnectionFailure(NetworkInfo info) {
         mNetTrackers[info.getType()].setTeardownRequested(false);
-        if (getActiveNetworkInfo() == null) {
-            String reason = info.getReason();
-            String extraInfo = info.getExtraInfo();
 
-            if (DBG) {
-                String reasonText;
-                if (reason == null) {
-                    reasonText = ".";
-                } else {
-                    reasonText = " (" + reason + ").";
-                }
-                Log.v(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText);
+        String reason = info.getReason();
+        String extraInfo = info.getExtraInfo();
+
+        if (DBG) {
+            String reasonText;
+            if (reason == null) {
+                reasonText = ".";
+            } else {
+                reasonText = " (" + reason + ").";
             }
-            
-            Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
-            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
+            Log.v(TAG, "Attempt to connect to " + info.getTypeName() +
+                    " failed" + reasonText);
+        }
+
+        Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
+        if (getActiveNetworkInfo() == null) {
             intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
-            if (reason != null) {
-                intent.putExtra(ConnectivityManager.EXTRA_REASON, reason);
+        }
+        if (reason != null) {
+            intent.putExtra(ConnectivityManager.EXTRA_REASON, reason);
+        }
+        if (extraInfo != null) {
+            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo);
+        }
+        if (info.isFailover()) {
+            intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
+            info.setFailover(false);
+        }
+        sendStickyBroadcast(intent);
+    }
+
+    private void sendStickyBroadcast(Intent intent) {
+        synchronized(this) {
+            if (mSystemReady) {
+                mContext.sendStickyBroadcast(intent);
+            } else {
+                if (mDeferredBroadcasts == null) {
+                    mDeferredBroadcasts = new ArrayList<Intent>();
+                }
+                mDeferredBroadcasts.add(intent);
             }
-            if (extraInfo != null) {
-                intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo);
+        }
+    }
+
+    void systemReady() {
+        synchronized(this) {
+            mSystemReady = true;
+            if (mDeferredBroadcasts != null) {
+                int count = mDeferredBroadcasts.size();
+                for (int i = 0; i < count; i++) {
+                    mContext.sendStickyBroadcast(mDeferredBroadcasts.get(i));
+                }
+                mDeferredBroadcasts = null;
             }
-            if (info.isFailover()) {
-                intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
-                info.setFailover(false);
-            }
-            mContext.sendStickyBroadcast(intent);
         }
     }
 
     private void handleConnect(NetworkInfo info) {
-        if (DBG) Log.v(TAG, "Handle CONNECT for " + info.getTypeName());
+        int type = info.getType();
 
         // snapshot isFailover, because sendConnectedBroadcast() resets it
         boolean isFailover = info.isFailover();
-        NetworkStateTracker thisNet = mNetTrackers[info.getType()];
-        NetworkStateTracker deadnet = null;
-        NetworkStateTracker otherNet;
-        if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
-            otherNet = mWifiStateTracker;
-        } else /* info().getType() == TYPE_WIFI */ {
-            otherNet = mMobileDataStateTracker;
-        }
-        /*
-         * Check policy to see whether we are connected to a non-preferred
-         * network that now needs to be torn down.
-         */
-        NetworkInfo wifiInfo = mWifiStateTracker.getNetworkInfo();
-        NetworkInfo mobileInfo = mMobileDataStateTracker.getNetworkInfo();
-        if (wifiInfo.isConnected() && mobileInfo.isConnected()) {
-            if (mNetworkPreference == ConnectivityManager.TYPE_WIFI)
-                deadnet = mMobileDataStateTracker;
-            else
-                deadnet = mWifiStateTracker;
-        }
+        NetworkStateTracker thisNet = mNetTrackers[type];
 
-        boolean toredown = false;
+        // if this is a default net and other default is running
+        // kill the one not preferred
+        if (mNetAttributes[type].isDefault()) {
+            if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
+                if ((type != mNetworkPreference &&
+                        mNetAttributes[mActiveDefaultNetwork].mPriority >
+                        mNetAttributes[type].mPriority) ||
+                        mNetworkPreference == mActiveDefaultNetwork) {
+                        // don't accept this one
+                        if (DBG) Log.v(TAG, "Not broadcasting CONNECT_ACTION " +
+                                "to torn down network " + info.getTypeName());
+                        teardown(thisNet);
+                        return;
+                } else {
+                    // tear down the other
+                    NetworkStateTracker otherNet =
+                            mNetTrackers[mActiveDefaultNetwork];
+                    if (DBG) Log.v(TAG, "Policy requires " +
+                            otherNet.getNetworkInfo().getTypeName() +
+                            " teardown");
+                    if (!teardown(otherNet)) {
+                        Log.e(TAG, "Network declined teardown request");
+                        return;
+                    }
+                    if (isFailover) {
+                        otherNet.releaseWakeLock();
+                    }
+                }
+            }
+            mActiveDefaultNetwork = type;
+        }
         thisNet.setTeardownRequested(false);
-        if (!mTestMode && deadnet != null) {
-            if (DBG) Log.v(TAG, "Policy requires " +
-                  deadnet.getNetworkInfo().getTypeName() + " teardown");
-            toredown = teardown(deadnet);
-            if (DBG && !toredown) {
-                Log.d(TAG, "Network declined teardown request");
-            }
-        }
-
-        /*
-         * Note that if toredown is true, deadnet cannot be null, so there is
-         * no danger of a null pointer exception here..
-         */
-        if (!toredown || deadnet.getNetworkInfo().getType() != info.getType()) {
-            mActiveNetwork = thisNet;
-            if (DBG) Log.v(TAG, "Sending CONNECT bcast for " + info.getTypeName());
-            thisNet.updateNetworkSettings();
-            sendConnectedBroadcast(info);
-            if (isFailover) {
-                otherNet.releaseWakeLock();
-            }
-        } else {
-            if (DBG) Log.v(TAG, "Not broadcasting CONNECT_ACTION to torn down network " +
-                info.getTypeName());
-        }
+        thisNet.updateNetworkSettings();
+        handleConnectivityChange();
+        sendConnectedBroadcast(info);
     }
 
     private void handleScanResultsAvailable(NetworkInfo info) {
         int networkType = info.getType();
         if (networkType != ConnectivityManager.TYPE_WIFI) {
-            if (DBG) Log.v(TAG, "Got ScanResultsAvailable for " + info.getTypeName() + " network."
-                + " Don't know how to handle.");
+            if (DBG) Log.v(TAG, "Got ScanResultsAvailable for " +
+                    info.getTypeName() + " network. Don't know how to handle.");
         }
-        
+
         mNetTrackers[networkType].interpretScanResultsAvailable();
     }
 
-    private void handleNotificationChange(boolean visible, int id, Notification notification) {
+    private void handleNotificationChange(boolean visible, int id,
+            Notification notification) {
         NotificationManager notificationManager = (NotificationManager) mContext
                 .getSystemService(Context.NOTIFICATION_SERVICE);
-        
+
         if (visible) {
             notificationManager.notify(id, notification);
         } else {
@@ -605,79 +1040,173 @@
      */
     private void handleConnectivityChange() {
         /*
+         * If a non-default network is enabled, add the host routes that
+         * will allow it's DNS servers to be accessed.  Only
          * If both mobile and wifi are enabled, add the host routes that
          * will allow MMS traffic to pass on the mobile network. But
          * remove the default route for the mobile network, so that there
          * will be only one default route, to ensure that all traffic
          * except MMS will travel via Wi-Fi.
          */
-        int numConnectedNets = handleConfigurationChange();
-        if (numConnectedNets > 1) {
-            mMobileDataStateTracker.addPrivateRoutes();
-            mMobileDataStateTracker.removeDefaultRoute();
-        } else if (mMobileDataStateTracker.getNetworkInfo().isConnected()) {
-            mMobileDataStateTracker.removePrivateRoutes();
-            mMobileDataStateTracker.restoreDefaultRoute();
+        handleDnsConfigurationChange();
+
+        for (int netType : mPriorityList) {
+            if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
+                if (mNetAttributes[netType].isDefault()) {
+                    mNetTrackers[netType].addDefaultRoute();
+                } else {
+                    mNetTrackers[netType].addPrivateDnsRoutes();
+                }
+            } else {
+                if (mNetAttributes[netType].isDefault()) {
+                    mNetTrackers[netType].removeDefaultRoute();
+                } else {
+                    mNetTrackers[netType].removePrivateDnsRoutes();
+                }
+            }
         }
     }
 
-    private int handleConfigurationChange() {
-        /*
-         * Set DNS properties. Always put Wi-Fi entries at the front of
-         * the list if it is active.
-         */
-        int index = 1;
-        String lastDns = "";
-        int numConnectedNets = 0;
-        int incrValue = ConnectivityManager.TYPE_MOBILE - ConnectivityManager.TYPE_WIFI;
-        int stopValue = ConnectivityManager.TYPE_MOBILE + incrValue;
+    /**
+     * Adjust the per-process dns entries (net.dns<x>.<pid>) based
+     * on the highest priority active net which this process requested.
+     * If there aren't any, clear it out
+     */
+    private void reassessPidDns(int myPid, boolean doBump)
+    {
+        if (DBG) Log.d(TAG, "reassessPidDns for pid " + myPid);
+        for(int i : mPriorityList) {
+            if (mNetAttributes[i].isDefault()) {
+                continue;
+            }
+            NetworkStateTracker nt = mNetTrackers[i];
+            if (nt.getNetworkInfo().isConnected() &&
+                    !nt.isTeardownRequested()) {
+                List pids = mNetRequestersPids[i];
+                for (int j=0; j<pids.size(); j++) {
+                    Integer pid = (Integer)pids.get(j);
+                    if (pid.intValue() == myPid) {
+                        String[] dnsList = nt.getNameServers();
+                        writePidDns(dnsList, myPid);
+                        if (doBump) {
+                            bumpDns();
+                        }
+                        return;
+                    }
+                }
+           }
+        }
+        // nothing found - delete
+        for (int i = 1; ; i++) {
+            String prop = "net.dns" + i + "." + myPid;
+            if (SystemProperties.get(prop).length() == 0) {
+                if (doBump) {
+                    bumpDns();
+                }
+                return;
+            }
+            SystemProperties.set(prop, "");
+        }
+    }
 
-        for (int netType = ConnectivityManager.TYPE_WIFI; netType != stopValue; netType += incrValue) {
+    private void writePidDns(String[] dnsList, int pid) {
+        int j = 1;
+        for (String dns : dnsList) {
+            if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) {
+                SystemProperties.set("net.dns" + j++ + "." + pid, dns);
+            }
+        }
+    }
+
+    private void bumpDns() {
+        /*
+         * Bump the property that tells the name resolver library to reread
+         * the DNS server list from the properties.
+         */
+        String propVal = SystemProperties.get("net.dnschange");
+        int n = 0;
+        if (propVal.length() != 0) {
+            try {
+                n = Integer.parseInt(propVal);
+            } catch (NumberFormatException e) {}
+        }
+        SystemProperties.set("net.dnschange", "" + (n+1));
+    }
+
+    private void handleDnsConfigurationChange() {
+        // add default net's dns entries
+        for (int x = mPriorityList.length-1; x>= 0; x--) {
+            int netType = mPriorityList[x];
             NetworkStateTracker nt = mNetTrackers[netType];
-            if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
-                ++numConnectedNets;
+            if (nt != null && nt.getNetworkInfo().isConnected() &&
+                    !nt.isTeardownRequested()) {
                 String[] dnsList = nt.getNameServers();
-                for (int i = 0; i < dnsList.length && dnsList[i] != null; i++) {
-                    // skip duplicate entries
-                    if (!dnsList[i].equals(lastDns)) {
-                        SystemProperties.set("net.dns" + index++, dnsList[i]);
-                        lastDns = dnsList[i];
+                if (mNetAttributes[netType].isDefault()) {
+                    int j = 1;
+                    for (String dns : dnsList) {
+                        if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) {
+                            if (DBG) {
+                                Log.d(TAG, "adding dns " + dns + " for " +
+                                        nt.getNetworkInfo().getTypeName());
+                            }
+                            SystemProperties.set("net.dns" + j++, dns);
+                        }
+                    }
+                    for (int k=j ; k<mNumDnsEntries; k++) {
+                        if (DBG) Log.d(TAG, "erasing net.dns" + k);
+                        SystemProperties.set("net.dns" + k, "");
+                    }
+                    mNumDnsEntries = j;
+                } else {
+                    // set per-pid dns for attached secondary nets
+                    List pids = mNetRequestersPids[netType];
+                    for (int y=0; y< pids.size(); y++) {
+                        Integer pid = (Integer)pids.get(y);
+                        writePidDns(dnsList, pid.intValue());
                     }
                 }
             }
         }
-        // Null out any DNS properties that are no longer used
-        for (int i = index; i <= mNumDnsEntries; i++) {
-            SystemProperties.set("net.dns" + i, "");
+
+        bumpDns();
+    }
+
+    private int getRestoreDefaultNetworkDelay() {
+        String restoreDefaultNetworkDelayStr = SystemProperties.get(
+                NETWORK_RESTORE_DELAY_PROP_NAME);
+        if(restoreDefaultNetworkDelayStr != null &&
+                restoreDefaultNetworkDelayStr.length() != 0) {
+            try {
+                return Integer.valueOf(restoreDefaultNetworkDelayStr);
+            } catch (NumberFormatException e) {
+            }
         }
-        mNumDnsEntries = index - 1;
-        // Notify the name resolver library of the change
-        SystemProperties.set("net.dnschange", String.valueOf(sDnsChangeCounter++));
-        return numConnectedNets;
+        return RESTORE_DEFAULT_NETWORK_DELAY;
     }
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
-            pw.println("Permission Denial: can't dump ConnectivityService from from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid());
+            pw.println("Permission Denial: can't dump ConnectivityService " +
+                    "from from pid=" + Binder.getCallingPid() + ", uid=" +
+                    Binder.getCallingUid());
             return;
         }
-        if (mActiveNetwork == null) {
-            pw.println("No active network");
-        } else {
-            pw.println("Active network: " + mActiveNetwork.getNetworkInfo().getTypeName());
-        }
         pw.println();
         for (NetworkStateTracker nst : mNetTrackers) {
+            if (nst.getNetworkInfo().isConnected()) {
+                pw.println("Active network: " + nst.getNetworkInfo().
+                        getTypeName());
+            }
             pw.println(nst.getNetworkInfo());
             pw.println(nst);
             pw.println();
         }
     }
 
+    // must be stateless - things change under us.
     private class MyHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
@@ -685,46 +1214,54 @@
             switch (msg.what) {
                 case NetworkStateTracker.EVENT_STATE_CHANGED:
                     info = (NetworkInfo) msg.obj;
-                    if (DBG) Log.v(TAG, "ConnectivityChange for " + info.getTypeName() + ": " +
+                    if (DBG) Log.d(TAG, "ConnectivityChange for " +
+                            info.getTypeName() + ": " +
                             info.getState() + "/" + info.getDetailedState());
 
                     // Connectivity state changed:
                     // [31-13] Reserved for future use
-                    // [12-9] Network subtype (for mobile network, as defined by TelephonyManager)
-                    // [8-3] Detailed state ordinal (as defined by NetworkInfo.DetailedState)
+                    // [12-9] Network subtype (for mobile network, as defined
+                    //         by TelephonyManager)
+                    // [8-3] Detailed state ordinal (as defined by
+                    //         NetworkInfo.DetailedState)
                     // [2-0] Network type (as defined by ConnectivityManager)
                     int eventLogParam = (info.getType() & 0x7) |
                             ((info.getDetailedState().ordinal() & 0x3f) << 3) |
                             (info.getSubtype() << 9);
-                    EventLog.writeEvent(EVENTLOG_CONNECTIVITY_STATE_CHANGED, eventLogParam);
-                    
-                    if (info.getDetailedState() == NetworkInfo.DetailedState.FAILED) {
+                    EventLog.writeEvent(EVENTLOG_CONNECTIVITY_STATE_CHANGED,
+                            eventLogParam);
+
+                    if (info.getDetailedState() ==
+                            NetworkInfo.DetailedState.FAILED) {
                         handleConnectionFailure(info);
-                    } else if (info.getState() == NetworkInfo.State.DISCONNECTED) {
+                    } else if (info.getState() ==
+                            NetworkInfo.State.DISCONNECTED) {
                         handleDisconnect(info);
                     } else if (info.getState() == NetworkInfo.State.SUSPENDED) {
                         // TODO: need to think this over.
-                        // the logic here is, handle SUSPENDED the same as DISCONNECTED. The
-                        // only difference being we are broadcasting an intent with NetworkInfo
-                        // that's suspended. This allows the applications an opportunity to
-                        // handle DISCONNECTED and SUSPENDED differently, or not.
+                        // the logic here is, handle SUSPENDED the same as
+                        // DISCONNECTED. The only difference being we are
+                        // broadcasting an intent with NetworkInfo that's
+                        // suspended. This allows the applications an
+                        // opportunity to handle DISCONNECTED and SUSPENDED
+                        // differently, or not.
                         handleDisconnect(info);
                     } else if (info.getState() == NetworkInfo.State.CONNECTED) {
                         handleConnect(info);
                     }
-                    handleConnectivityChange();
                     break;
 
                 case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE:
                     info = (NetworkInfo) msg.obj;
                     handleScanResultsAvailable(info);
                     break;
-                    
+
                 case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED:
-                    handleNotificationChange(msg.arg1 == 1, msg.arg2, (Notification) msg.obj);
+                    handleNotificationChange(msg.arg1 == 1, msg.arg2,
+                            (Notification) msg.obj);
 
                 case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
-                    handleConfigurationChange();
+                    handleDnsConfigurationChange();
                     break;
 
                 case NetworkStateTracker.EVENT_ROAMING_CHANGED:
@@ -734,6 +1271,10 @@
                 case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED:
                     // fill me in
                     break;
+                case NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK:
+                    FeatureUser u = (FeatureUser)msg.obj;
+                    u.expire();
+                    break;
             }
         }
     }