Add net type to mobile for mobile-required traffic

This also refactors ConnectivityService a bit towards supporting multiple simultaneous connections by making each a seem like a seperate Network with it's own stateTracker, etc.
Also adds tracking of process death to clean orphaned startUsingNetworkFeature features.
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/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 62b839d..1c60058 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -30,48 +30,130 @@
 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 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;
 
@@ -118,11 +200,54 @@
     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,
@@ -131,15 +256,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;
+        mNetTrackers[ConnectivityManager.TYPE_MOBILE] =
+                new MobileDataStateTracker(context, mHandler,
+                ConnectivityManager.TYPE_MOBILE, Phone.APN_TYPE_DEFAULT,
+                "MOBILE");
 
-        mActiveNetwork = null;
+        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")
@@ -149,8 +295,7 @@
             t.startMonitoring();
 
         // Constructing this starts it too
-        mWifiWatchdogService = new WifiWatchdogService(context,
-                mWifiStateTracker);
+        mWifiWatchdogService = new WifiWatchdogService(context, wst);
     }
 
     /**
@@ -159,7 +304,8 @@
      */
     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;
@@ -199,23 +345,16 @@
      * (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()) {
+                teardown(mNetTrackers[t]);
             }
         }
     }
@@ -238,9 +377,16 @@
      */
     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;
             }
         }
@@ -285,30 +431,189 @@
         return tracker != null && tracker.setRadio(turnOn);
     }
 
-    public int startUsingNetworkFeature(int networkType, String feature) {
+    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();
+            }
+        }
+
+        void unlinkDeathRecipient() {
+            mBinder.unlinkToDeath(this, 0);
+        }
+
+        public void binderDied() {
+            Log.d(TAG, "ConnectivityService FeatureUser binderDied(" +
+                    mNetworkType + ", " + mFeature + ", " + mBinder);
+            stopUsingNetworkFeature(mNetworkType, mFeature, mPid, mUid);
+        }
+
+    }
+
+    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 -1;
+            return Phone.APN_REQUEST_FAILED;
         }
-        NetworkStateTracker tracker = mNetTrackers[networkType];
-        if (tracker != null) {
-            return tracker.startUsingNetworkFeature(feature, getCallingPid(),
-                    getCallingUid());
+
+        synchronized (mFeatureUsers) {
+            mFeatureUsers.add(new FeatureUser(networkType, feature, binder));
         }
-        return -1;
+
+        // 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;
+                }
+
+                if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) {
+                    // this gets used for per-pid dns when connected
+                    mNetRequestersPids[usedNetworkType].add(currentPid);
+                }
+
+                if (ni.isConnectedOrConnecting() == true) {
+                    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
+
+                // since we have to drop the default on this radio, setup
+                // an automatic event to switch back
+                if(mHandler.hasMessages(NetworkStateTracker.
+                        EVENT_RESTORE_DEFAULT_NETWORK, radio) ||
+                        radio.getNetworkInfo().isConnectedOrConnecting()) {
+                    mHandler.removeMessages(NetworkStateTracker.
+                            EVENT_RESTORE_DEFAULT_NETWORK,
+                            radio);
+                    mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                            NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK,
+                            radio), getRestoreDefaultNetworkDelay());
+                }
+                if (DBG) Log.d(TAG, "reconnecting to special network");
+                network.reconnect();
+                return Phone.APN_REQUEST_STARTED;
+            } else {
+                return network.startUsingNetworkFeature(feature,
+                        getCallingPid(), getCallingUid());
+            }
+        }
+        return Phone.APN_TYPE_NOT_AVAILABLE;
     }
 
     public int stopUsingNetworkFeature(int networkType, String feature) {
+        return stopUsingNetworkFeature(networkType, feature, getCallingPid(),
+                getCallingUid());
+    }
+
+    private int stopUsingNetworkFeature(int networkType, String feature,
+            int pid, int uid) {
+        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());
+
+        synchronized (mFeatureUsers) {
+            for (int i=0; i < mFeatureUsers.size(); i++) {
+                FeatureUser u = (FeatureUser)mFeatureUsers.get(i);
+                if (uid == u.mUid && pid == u.mPid &&
+                        networkType == u.mNetworkType &&
+                        TextUtils.equals(feature, u.mFeature)) {
+                    u.unlinkDeathRecipient();
+                    mFeatureUsers.remove(i);
+                    break;
+                }
+            }
         }
-        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;
+            }
+        }
+        NetworkStateTracker tracker =  mNetTrackers[usedNetworkType];
+        if(usedNetworkType != networkType) {
+            Integer currentPid = new Integer(pid);
+            if (mNetRequestersPids[usedNetworkType].remove(currentPid)) {
+                reassessPidDns(pid, true);
+            }
+            if (mNetRequestersPids[usedNetworkType].size() != 0) {
+                if (DBG) Log.d(TAG, "not tearing down special network - " +
+                        "others still using it");
+                return 1;
+            }
+
+            tracker.teardown();
+            NetworkStateTracker radio = mNetTrackers[networkType];
+            // Check if we want to revert to the default
+            if (mHandler.hasMessages(NetworkStateTracker.
+                    EVENT_RESTORE_DEFAULT_NETWORK, radio)) {
+                mHandler.removeMessages(NetworkStateTracker.
+                        EVENT_RESTORE_DEFAULT_NETWORK, radio);
+                radio.reconnect();
+            }
+            return 1;
+        } else {
+            return tracker.stopUsingNetworkFeature(feature, pid, uid);
+        }
     }
 
     /**
@@ -337,7 +642,8 @@
         if (getNumConnectedNetworks() > 1) {
             return tracker.requestRouteToHost(hostAddress);
         } else {
-            return tracker.getNetworkInfo().getType() == networkType;
+            return (mNetAttributes[networkType].isDefault() &&
+                    tracker.getNetworkInfo().isConnected());
         }
     }
 
@@ -402,45 +708,26 @@
     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()) {
@@ -454,33 +741,92 @@
             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 (DBG) Log.d(TAG, "disconnecting a default network");
+            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 (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.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 {
+                    newNet.reconnect();
+                }
+            } else {
+                intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,
+                        true);
+            }
         }
+
+        // do this before we broadcast the change
+        handleConnectivityChange();
+
         if (DBG) Log.v(TAG, "Sending DISCONNECT bcast for " +
                 info.getTypeName() +
-                (switchTo == null ? "" : " other=" + switchTo.getTypeName()));
+                (newNet == null || !newNet.isAvailable() ? "" : " other=" +
+                newNet.getNetworkInfo().getTypeName()));
 
         mContext.sendStickyBroadcast(intent);
         /*
          * 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) {
@@ -506,93 +852,85 @@
      */
     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();
 
-            Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
-            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
-            intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
-            if (reason != null) {
-                intent.putExtra(ConnectivityManager.EXTRA_REASON, reason);
+        if (DBG) {
+            String reasonText;
+            if (reason == null) {
+                reasonText = ".";
+            } else {
+                reasonText = " (" + reason + ").";
             }
-            if (extraInfo != null) {
-                intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
-                        extraInfo);
-            }
-            if (info.isFailover()) {
-                intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
-                info.setFailover(false);
-            }
-            mContext.sendStickyBroadcast(intent);
+            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 (extraInfo != null) {
+            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo);
+        }
+        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());
+        if (DBG) Log.d(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 (DBG) Log.d(TAG, "connecting to a default network");
+            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());
-        }
+        if (DBG) Log.d(TAG, "Sending CONNECT bcast for " + info.getTypeName());
+        thisNet.updateNetworkSettings();
+        handleConnectivityChange();
+        sendConnectedBroadcast(info);
     }
 
     private void handleScanResultsAvailable(NetworkInfo info) {
@@ -626,60 +964,148 @@
      * table entries exist.
      */
     private void handleConnectivityChange() {
+        if (DBG) Log.d(TAG, "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;
-
-        for (int netType = ConnectivityManager.TYPE_WIFI; netType != stopValue;
-                netType += incrValue) {
-            NetworkStateTracker nt = mNetTrackers[netType];
+    /**
+     * 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()) {
-                ++numConnectedNets;
+                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, "");
+        }
+    }
+
+    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() {
+        if (DBG) Log.d(TAG, "handleDnsConfig Change");
+        // add default net's dns entries
+        for (int x = mPriorityList.length-1; x>= 0; x--) {
+            int netType = mPriorityList[x];
+            NetworkStateTracker nt = mNetTrackers[netType];
+            if (DBG) Log.d(TAG, " checking " + nt);
+            if (nt != null && nt.getNetworkInfo().isConnected() &&
+                    !nt.isTeardownRequested()) {
+                if (DBG) Log.d(TAG, "  connected");
                 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")) {
+                            SystemProperties.set("net.dns" + j++, dns);
+                        }
+                    }
+                    for (int k=j ; k<mNumDnsEntries; k++) {
+                        SystemProperties.set("net.dns" + j, "");
+                    }
+                    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
@@ -692,20 +1118,19 @@
                     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) {
@@ -713,7 +1138,7 @@
             switch (msg.what) {
                 case NetworkStateTracker.EVENT_STATE_CHANGED:
                     info = (NetworkInfo) msg.obj;
-                    if (DBG) Log.v(TAG, "ConnectivityChange for " +
+                    if (DBG) Log.d(TAG, "ConnectivityChange for " +
                             info.getTypeName() + ": " +
                             info.getState() + "/" + info.getDetailedState());
 
@@ -748,7 +1173,6 @@
                     } else if (info.getState() == NetworkInfo.State.CONNECTED) {
                         handleConnect(info);
                     }
-                    handleConnectivityChange();
                     break;
 
                 case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE:
@@ -761,7 +1185,7 @@
                             (Notification) msg.obj);
 
                 case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
-                    handleConfigurationChange();
+                    handleDnsConfigurationChange();
                     break;
 
                 case NetworkStateTracker.EVENT_ROAMING_CHANGED:
@@ -771,6 +1195,15 @@
                 case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED:
                     // fill me in
                     break;
+                case NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK:
+                    for (NetworkStateTracker net : mNetTrackers) {
+                        NetworkInfo i = net.getNetworkInfo();
+                        if (i.isConnected() &&
+                                !mNetAttributes[i.getType()].isDefault()) {
+                            teardown(net);
+                        }
+                    }
+                    break;
             }
         }
     }