Merge "Remove CONNECTIVITY_CHANGE_DELAY and friends." into lmp-mr1-dev
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index bdc1c7f..4fe418a 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -71,7 +71,6 @@
  */
 public class ConnectivityManager {
     private static final String TAG = "ConnectivityManager";
-    private static final boolean LEGACY_DBG = true; // STOPSHIP
 
     /**
      * A change in network connectivity has occurred. A default connection has either
@@ -710,6 +709,19 @@
     }
 
     /**
+     * Returns an array of of {@link NetworkCapabilities} objects, representing
+     * the Networks that applications run by the given user will use by default.
+     * @hide
+     */
+    public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
+        try {
+            return mService.getDefaultNetworkCapabilitiesForUser(userId);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Returns details about the Provisioning or currently active default data network. When
      * connected, this network is the default route for outgoing connections.
      * You should always check {@link NetworkInfo#isConnected()} before initiating
@@ -868,14 +880,6 @@
 
         NetworkRequest request = null;
         synchronized (sLegacyRequests) {
-            if (LEGACY_DBG) {
-                Log.d(TAG, "Looking for legacyRequest for netCap with hash: " + netCap + " (" +
-                        netCap.hashCode() + ")");
-                Log.d(TAG, "sLegacyRequests has:");
-                for (NetworkCapabilities nc : sLegacyRequests.keySet()) {
-                    Log.d(TAG, "  " + nc + " (" + nc.hashCode() + ")");
-                }
-            }
             LegacyRequest l = sLegacyRequests.get(netCap);
             if (l != null) {
                 Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
@@ -1000,60 +1004,57 @@
         return null;
     }
 
+    /**
+     * Guess what the network request was trying to say so that the resulting
+     * network is accessible via the legacy (deprecated) API such as
+     * requestRouteToHost.
+     * This means we should try to be fairly preceise about transport and
+     * capability but ignore things such as networkSpecifier.
+     * If the request has more than one transport or capability it doesn't
+     * match the old legacy requests (they selected only single transport/capability)
+     * so this function cannot map the request to a single legacy type and
+     * the resulting network will not be available to the legacy APIs.
+     *
+     * TODO - This should be removed when the legacy APIs are removed.
+     */
     private int inferLegacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
         if (netCap == null) {
             return TYPE_NONE;
         }
+
         if (!netCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
             return TYPE_NONE;
         }
+
+        String type = null;
+        int result = TYPE_NONE;
+
         if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableCBS"))) {
-                return TYPE_MOBILE_CBS;
-            } else {
-                return TYPE_NONE;
-            }
+            type = "enableCBS";
+            result = TYPE_MOBILE_CBS;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+            type = "enableIMS";
+            result = TYPE_MOBILE_IMS;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
+            type = "enableFOTA";
+            result = TYPE_MOBILE_FOTA;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+            type = "enableDUN";
+            result = TYPE_MOBILE_DUN;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+            type = "enableSUPL";
+            result = TYPE_MOBILE_SUPL;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+            type = "enableMMS";
+            result = TYPE_MOBILE_MMS;
+        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+            type = "enableHIPRI";
+            result = TYPE_MOBILE_HIPRI;
         }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableIMS"))) {
-                return TYPE_MOBILE_IMS;
-            } else {
-                return TYPE_NONE;
-            }
-        }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableFOTA"))) {
-                return TYPE_MOBILE_FOTA;
-            } else {
-                return TYPE_NONE;
-            }
-        }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableDUN"))) {
-                return TYPE_MOBILE_DUN;
-            } else {
-                return TYPE_NONE;
-            }
-        }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableSUPL"))) {
-                return TYPE_MOBILE_SUPL;
-            } else {
-                return TYPE_NONE;
-            }
-        }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableMMS"))) {
-                return TYPE_MOBILE_MMS;
-            } else {
-                return TYPE_NONE;
-            }
-        }
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-            if (netCap.equals(networkCapabilitiesForFeature(TYPE_MOBILE, "enableHIPRI"))) {
-                return TYPE_MOBILE_HIPRI;
-            } else {
-                return TYPE_NONE;
+        if (type != null) {
+            NetworkCapabilities testCap = networkCapabilitiesForFeature(TYPE_MOBILE, type);
+            if (testCap.equalsNetCapabilities(netCap) && testCap.equalsTransportTypes(netCap)) {
+                return result;
             }
         }
         return TYPE_NONE;
@@ -1912,45 +1913,6 @@
     }
 
     /**
-     * get the information about a specific network link
-     * @hide
-     */
-    public LinkQualityInfo getLinkQualityInfo(int networkType) {
-        try {
-            LinkQualityInfo li = mService.getLinkQualityInfo(networkType);
-            return li;
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    /**
-     * get the information of currently active network link
-     * @hide
-     */
-    public LinkQualityInfo getActiveLinkQualityInfo() {
-        try {
-            LinkQualityInfo li = mService.getActiveLinkQualityInfo();
-            return li;
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    /**
-     * get the information of all network links
-     * @hide
-     */
-    public LinkQualityInfo[] getAllLinkQualityInfo() {
-        try {
-            LinkQualityInfo[] li = mService.getAllLinkQualityInfo();
-            return li;
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    /**
      * Set sign in error notification to visible or in visible
      *
      * @param visible
@@ -2369,26 +2331,23 @@
 
     /**
      * The lookup key for a {@link Network} object included with the intent after
-     * succesfully finding a network for the applications request.  Retrieve it with
+     * successfully finding a network for the applications request.  Retrieve it with
      * {@link android.content.Intent#getParcelableExtra(String)}.
-     * @hide
      */
-    public static final String EXTRA_NETWORK_REQUEST_NETWORK = "networkRequestNetwork";
+    public static final String EXTRA_NETWORK = "android.net.extra.NETWORK";
 
     /**
      * The lookup key for a {@link NetworkRequest} object included with the intent after
-     * succesfully finding a network for the applications request.  Retrieve it with
+     * successfully finding a network for the applications request.  Retrieve it with
      * {@link android.content.Intent#getParcelableExtra(String)}.
-     * @hide
      */
-    public static final String EXTRA_NETWORK_REQUEST_NETWORK_REQUEST =
-            "networkRequestNetworkRequest";
+    public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST";
 
 
     /**
      * Request a network to satisfy a set of {@link NetworkCapabilities}.
      *
-     * This function behavies identically to the version that takes a NetworkCallback, but instead
+     * This function behaves identically to the version that takes a NetworkCallback, but instead
      * of {@link NetworkCallback} a {@link PendingIntent} is used.  This means
      * the request may outlive the calling application and get called back when a suitable
      * network is found.
@@ -2398,8 +2357,8 @@
      * <receiver> tag in an AndroidManifest.xml file
      * <p>
      * The operation Intent is delivered with two extras, a {@link Network} typed
-     * extra called {@link #EXTRA_NETWORK_REQUEST_NETWORK} and a {@link NetworkRequest}
-     * typed extra called {@link #EXTRA_NETWORK_REQUEST_NETWORK_REQUEST} containing
+     * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest}
+     * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing
      * the original requests parameters.  It is important to create a new,
      * {@link NetworkCallback} based request before completing the processing of the
      * Intent to reserve the network or it will be released shortly after the Intent
@@ -2409,21 +2368,46 @@
      * two Intents defined by {@link Intent#filterEquals}), then it will be removed and
      * replaced by this one, effectively releasing the previous {@link NetworkRequest}.
      * <p>
-     * The request may be released normally by calling {@link #unregisterNetworkCallback}.
+     * The request may be released normally by calling
+     * {@link #releaseNetworkRequest(android.app.PendingIntent)}.
      *
      * @param request {@link NetworkRequest} describing this request.
      * @param operation Action to perform when the network is available (corresponds
      *                  to the {@link NetworkCallback#onAvailable} call.  Typically
-     *                  comes from {@link PendingIntent#getBroadcast}.
-     * @hide
+     *                  comes from {@link PendingIntent#getBroadcast}. Cannot be null.
      */
     public void requestNetwork(NetworkRequest request, PendingIntent operation) {
+        checkPendingIntent(operation);
         try {
             mService.pendingRequestForNetwork(request.networkCapabilities, operation);
         } catch (RemoteException e) {}
     }
 
     /**
+     * Removes a request made via {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)}
+     * <p>
+     * This method has the same behavior as {@link #unregisterNetworkCallback} with respect to
+     * releasing network resources and disconnecting.
+     *
+     * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the
+     *                  PendingIntent passed to
+     *                  {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the
+     *                  corresponding NetworkRequest you'd like to remove. Cannot be null.
+     */
+    public void releaseNetworkRequest(PendingIntent operation) {
+        checkPendingIntent(operation);
+        try {
+            mService.releasePendingNetworkRequest(operation);
+        } catch (RemoteException e) {}
+    }
+
+    private void checkPendingIntent(PendingIntent intent) {
+        if (intent == null) {
+            throw new IllegalArgumentException("PendingIntent cannot be null.");
+        }
+    }
+
+    /**
      * Registers to receive notifications about all networks which satisfy the given
      * {@link NetworkRequest}.  The callbacks will continue to be called until
      * either the application exits or {@link #unregisterNetworkCallback} is called
@@ -2439,7 +2423,7 @@
     /**
      * Unregisters callbacks about and possibly releases networks originating from
      * {@link #requestNetwork} and {@link #registerNetworkCallback} calls.  If the
-     * given {@code NetworkCallback} had previosuly been used with {@code #requestNetwork},
+     * given {@code NetworkCallback} had previously been used with {@code #requestNetwork},
      * any networks that had been connected to only to satisfy that request will be
      * disconnected.
      *
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index a983d88..8021210 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -17,7 +17,6 @@
 package android.net;
 
 import android.app.PendingIntent;
-import android.net.LinkQualityInfo;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -50,6 +49,7 @@
     NetworkInfo[] getAllNetworkInfo();
     Network getNetworkForType(int networkType);
     Network[] getAllNetworks();
+    NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId);
 
     NetworkInfo getProvisioningOrActiveNetworkInfo();
 
@@ -133,12 +133,6 @@
 
     String getMobileRedirectedProvisioningUrl();
 
-    LinkQualityInfo getLinkQualityInfo(int networkType);
-
-    LinkQualityInfo getActiveLinkQualityInfo();
-
-    LinkQualityInfo[] getAllLinkQualityInfo();
-
     void setProvisioningNotificationVisible(boolean visible, int networkType, in String action);
 
     void setAirplaneMode(boolean enable);
@@ -156,6 +150,8 @@
     NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
             in PendingIntent operation);
 
+    void releasePendingNetworkRequest(in PendingIntent operation);
+
     NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
             in Messenger messenger, in IBinder binder);
 
@@ -168,4 +164,5 @@
 
     boolean addVpnAddress(String address, int prefixLength);
     boolean removeVpnAddress(String address, int prefixLength);
+    boolean setUnderlyingNetworksForVpn(in Network[] networks);
 }
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 1efe478..a7f9c5b 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -154,9 +154,16 @@
      */
     public static final int NET_CAPABILITY_NOT_VPN        = 15;
 
+    /**
+     * Indicates that connectivity on this network was successfully validated. For example, for a
+     * network with NET_CAPABILITY_INTERNET, it means that Internet connectivity was successfully
+     * detected.
+     * @hide
+     */
+    public static final int NET_CAPABILITY_VALIDATED      = 16;
 
     private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
-    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VPN;
+    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_VALIDATED;
 
     /**
      * Adds the given capability to this {@code NetworkCapability} instance.
@@ -235,7 +242,8 @@
         return ((nc.mNetworkCapabilities & this.mNetworkCapabilities) == this.mNetworkCapabilities);
     }
 
-    private boolean equalsNetCapabilities(NetworkCapabilities nc) {
+    /** @hide */
+    public boolean equalsNetCapabilities(NetworkCapabilities nc) {
         return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
     }
 
@@ -344,7 +352,8 @@
         return ((this.mTransportTypes == 0) ||
                 ((this.mTransportTypes & nc.mTransportTypes) != 0));
     }
-    private boolean equalsTransportTypes(NetworkCapabilities nc) {
+    /** @hide */
+    public boolean equalsTransportTypes(NetworkCapabilities nc) {
         return (nc.mTransportTypes == this.mTransportTypes);
     }
 
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index 2e0e9e4..d26c70d 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -29,20 +29,23 @@
     public final NetworkInfo networkInfo;
     public final LinkProperties linkProperties;
     public final NetworkCapabilities networkCapabilities;
+    public final Network network;
     /** Currently only used by testing. */
     public final String subscriberId;
     public final String networkId;
 
     public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
-            NetworkCapabilities networkCapabilities) {
-        this(networkInfo, linkProperties, networkCapabilities, null, null);
+            NetworkCapabilities networkCapabilities, Network network) {
+        this(networkInfo, linkProperties, networkCapabilities, network, null, null);
     }
 
     public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
-            NetworkCapabilities networkCapabilities, String subscriberId, String networkId) {
+            NetworkCapabilities networkCapabilities, Network network, String subscriberId,
+            String networkId) {
         this.networkInfo = networkInfo;
         this.linkProperties = linkProperties;
         this.networkCapabilities = networkCapabilities;
+        this.network = network;
         this.subscriberId = subscriberId;
         this.networkId = networkId;
     }
@@ -51,6 +54,7 @@
         networkInfo = in.readParcelable(null);
         linkProperties = in.readParcelable(null);
         networkCapabilities = in.readParcelable(null);
+        network = in.readParcelable(null);
         subscriberId = in.readString();
         networkId = in.readString();
     }
@@ -65,6 +69,7 @@
         out.writeParcelable(networkInfo, flags);
         out.writeParcelable(linkProperties, flags);
         out.writeParcelable(networkCapabilities, flags);
+        out.writeParcelable(network, flags);
         out.writeString(subscriberId);
         out.writeString(networkId);
     }
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index 5a273cf..598a503 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -107,6 +107,7 @@
         for (InetAddress dns : dnsServers) {
             lp.addDnsServer(dns);
         }
+        lp.setDomains(domains);
         return lp;
     }
 
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
index b181122..9a08f41 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
@@ -451,6 +451,40 @@
         assertIndexBeforeAfter(stats, 4, 4, Long.MAX_VALUE);
     }
 
+    public void testIntersects() throws Exception {
+        final long BUCKET_SIZE = HOUR_IN_MILLIS;
+        stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+        final long FIRST_START = TEST_START;
+        final long FIRST_END = FIRST_START + (2 * HOUR_IN_MILLIS);
+        final long SECOND_START = TEST_START + WEEK_IN_MILLIS;
+        final long SECOND_END = SECOND_START + HOUR_IN_MILLIS;
+        final long THIRD_START = TEST_START + (2 * WEEK_IN_MILLIS);
+        final long THIRD_END = THIRD_START + (2 * HOUR_IN_MILLIS);
+
+        stats.recordData(FIRST_START, FIRST_END,
+                new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+        stats.recordData(SECOND_START, SECOND_END,
+                new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+        stats.recordData(THIRD_START, THIRD_END,
+                new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+
+        assertFalse(stats.intersects(10, 20));
+        assertFalse(stats.intersects(TEST_START + YEAR_IN_MILLIS, TEST_START + YEAR_IN_MILLIS + 1));
+        assertFalse(stats.intersects(Long.MAX_VALUE, Long.MIN_VALUE));
+
+        assertTrue(stats.intersects(Long.MIN_VALUE, Long.MAX_VALUE));
+        assertTrue(stats.intersects(10, TEST_START + YEAR_IN_MILLIS));
+        assertTrue(stats.intersects(TEST_START, TEST_START));
+        assertTrue(stats.intersects(TEST_START + DAY_IN_MILLIS, TEST_START + DAY_IN_MILLIS + 1));
+        assertTrue(stats.intersects(TEST_START + DAY_IN_MILLIS, Long.MAX_VALUE));
+        assertTrue(stats.intersects(TEST_START + 1, Long.MAX_VALUE));
+
+        assertFalse(stats.intersects(Long.MIN_VALUE, TEST_START - 1));
+        assertTrue(stats.intersects(Long.MIN_VALUE, TEST_START));
+        assertTrue(stats.intersects(Long.MIN_VALUE, TEST_START + 1));
+    }
+
     private static void assertIndexBeforeAfter(
             NetworkStatsHistory stats, int before, int after, long time) {
         assertEquals("unexpected before", before, stats.getIndexBefore(time));
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index c3f321f..84fddd7 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -23,18 +23,19 @@
 import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
 import static android.net.ConnectivityManager.TYPE_DUMMY;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
-import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
+import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
-import static android.net.ConnectivityManager.TYPE_MOBILE_IMS;
-import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
-import static android.net.ConnectivityManager.TYPE_MOBILE_IA;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
 import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_PROXY;
+import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivityManager.TYPE_WIMAX;
-import static android.net.ConnectivityManager.TYPE_PROXY;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
@@ -64,7 +65,6 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LinkProperties.CompareResult;
-import android.net.LinkQualityInfo;
 import android.net.MobileDataStateTracker;
 import android.net.Network;
 import android.net.NetworkAgent;
@@ -184,7 +184,8 @@
 /**
  * @hide
  */
-public class ConnectivityService extends IConnectivityManager.Stub {
+public class ConnectivityService extends IConnectivityManager.Stub
+        implements PendingIntent.OnFinished {
     private static final String TAG = "ConnectivityService";
 
     private static final boolean DBG = true;
@@ -254,7 +255,6 @@
 
     private Context mContext;
     private int mNetworkPreference;
-    private int mActiveDefaultNetwork = TYPE_NONE;
     // 0 is full bad, 100 is full good
     private int mDefaultInetConditionPublished = 0;
 
@@ -382,6 +382,19 @@
      */
     private static final int EVENT_SYSTEM_READY = 25;
 
+    /**
+     * used to add a network request with a pending intent
+     * includes a NetworkRequestInfo
+     */
+    private static final int EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT = 26;
+
+    /**
+     * used to remove a pending intent and its associated network request.
+     * arg1 = UID of caller
+     * obj  = PendingIntent
+     */
+    private static final int EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT = 27;
+
 
     /** Handler used for internal events. */
     final private InternalHandler mHandler;
@@ -395,6 +408,7 @@
     private String mNetTransitionWakeLockCausedBy = "";
     private int mNetTransitionWakeLockSerialNumber;
     private int mNetTransitionWakeLockTimeout;
+    private final PowerManager.WakeLock mPendingIntentWakeLock;
 
     private InetAddress mDefaultDns;
 
@@ -649,6 +663,7 @@
         mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_networkTransitionTimeout);
+        mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
         mNetTrackers = new NetworkStateTracker[
                 ConnectivityManager.MAX_NETWORK_TYPE+1];
@@ -687,6 +702,15 @@
                 // ignore it - leave the entry null
             }
         }
+
+        // Forcibly add TYPE_VPN as a supported type, if it has not already been added via config.
+        if (mNetConfigs[TYPE_VPN] == null) {
+            // mNetConfigs is used only for "restore time", which isn't applicable to VPNs, so we
+            // don't need to add TYPE_VPN to mNetConfigs.
+            mLegacyTypeTracker.addSupportedType(TYPE_VPN);
+            mNetworksDefined++;  // used only in the log() statement below.
+        }
+
         if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
 
         mProtectedNetworks = new ArrayList<Integer>();
@@ -782,20 +806,85 @@
         }
     }
 
-    /**
-     * Check if UID should be blocked from using the network represented by the given networkType.
-     * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type.
-     */
-    private boolean isNetworkBlocked(int networkType, int uid) {
-        return isNetworkWithLinkPropertiesBlocked(getLinkPropertiesForType(networkType), uid);
+    private NetworkState getFilteredNetworkState(int networkType, int uid) {
+        NetworkInfo info = null;
+        LinkProperties lp = null;
+        NetworkCapabilities nc = null;
+        Network network = null;
+
+        if (mLegacyTypeTracker.isTypeSupported(networkType)) {
+            NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+            if (nai != null) {
+                synchronized (nai) {
+                    info = new NetworkInfo(nai.networkInfo);
+                    lp = new LinkProperties(nai.linkProperties);
+                    nc = new NetworkCapabilities(nai.networkCapabilities);
+                    network = new Network(nai.network);
+                }
+                info.setType(networkType);
+            } else {
+                info = new NetworkInfo(networkType, 0, getNetworkTypeName(networkType), "");
+                info.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
+                info.setIsAvailable(true);
+                lp = new LinkProperties();
+                nc = new NetworkCapabilities();
+                network = null;
+            }
+            info = getFilteredNetworkInfo(info, lp, uid);
+        }
+
+        return new NetworkState(info, lp, nc, network);
     }
 
-    /**
-     * Check if UID should be blocked from using the network represented by the given
-     * NetworkAgentInfo.
-     */
-    private boolean isNetworkBlocked(NetworkAgentInfo nai, int uid) {
-        return isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid);
+    private NetworkAgentInfo getNetworkAgentInfoForNetwork(Network network) {
+        if (network == null) {
+            return null;
+        }
+        synchronized (mNetworkForNetId) {
+            return mNetworkForNetId.get(network.netId);
+        }
+    };
+
+    private NetworkState getUnfilteredActiveNetworkState(int uid) {
+        NetworkInfo info = null;
+        LinkProperties lp = null;
+        NetworkCapabilities nc = null;
+        Network network = null;
+
+        NetworkAgentInfo nai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+
+        if (!mLockdownEnabled) {
+            int user = UserHandle.getUserId(uid);
+            synchronized (mVpns) {
+                Vpn vpn = mVpns.get(user);
+                if (vpn != null && vpn.appliesToUid(uid)) {
+                    // getUnderlyingNetworks() returns:
+                    // null => the VPN didn't specify anything, so we use the default.
+                    // empty array => the VPN explicitly said "no default network".
+                    // non-empty array => the VPN specified one or more default networks; we use the
+                    //                    first one.
+                    Network[] networks = vpn.getUnderlyingNetworks();
+                    if (networks != null) {
+                        if (networks.length > 0) {
+                            nai = getNetworkAgentInfoForNetwork(networks[0]);
+                        } else {
+                            nai = null;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (nai != null) {
+            synchronized (nai) {
+                info = new NetworkInfo(nai.networkInfo);
+                lp = new LinkProperties(nai.linkProperties);
+                nc = new NetworkCapabilities(nai.networkCapabilities);
+                network = new Network(nai.network);
+            }
+        }
+
+        return new NetworkState(info, lp, nc, network);
     }
 
     /**
@@ -822,40 +911,16 @@
     /**
      * Return a filtered {@link NetworkInfo}, potentially marked
      * {@link DetailedState#BLOCKED} based on
-     * {@link #isNetworkBlocked}.
-     * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type.
+     * {@link #isNetworkWithLinkPropertiesBlocked}.
      */
-    private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) {
-        NetworkInfo info = getNetworkInfoForType(networkType);
-        return getFilteredNetworkInfo(info, networkType, uid);
-    }
-
-    /*
-     * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type.
-     */
-    private NetworkInfo getFilteredNetworkInfo(NetworkInfo info, int networkType, int uid) {
-        if (isNetworkBlocked(networkType, uid)) {
-            // network is blocked; clone and override state
-            info = new NetworkInfo(info);
-            info.setDetailedState(DetailedState.BLOCKED, null, null);
-            if (VDBG) log("returning Blocked NetworkInfo");
-        }
-        if (mLockdownTracker != null) {
-            info = mLockdownTracker.augmentNetworkInfo(info);
-            if (VDBG) log("returning Locked NetworkInfo");
-        }
-        return info;
-    }
-
-    private NetworkInfo getFilteredNetworkInfo(NetworkAgentInfo nai, int uid) {
-        NetworkInfo info = nai.networkInfo;
-        if (isNetworkBlocked(nai, uid)) {
+    private NetworkInfo getFilteredNetworkInfo(NetworkInfo info, LinkProperties lp, int uid) {
+        if (info != null && isNetworkWithLinkPropertiesBlocked(lp, uid)) {
             // network is blocked; clone and override state
             info = new NetworkInfo(info);
             info.setDetailedState(DetailedState.BLOCKED, null, null);
             if (DBG) log("returning Blocked NetworkInfo");
         }
-        if (mLockdownTracker != null) {
+        if (info != null && mLockdownTracker != null) {
             info = mLockdownTracker.augmentNetworkInfo(info);
             if (DBG) log("returning Locked NetworkInfo");
         }
@@ -873,7 +938,8 @@
     public NetworkInfo getActiveNetworkInfo() {
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
-        return getNetworkInfo(mActiveDefaultNetwork, uid);
+        NetworkState state = getUnfilteredActiveNetworkState(uid);
+        return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
     }
 
     /**
@@ -908,8 +974,7 @@
 
         NetworkInfo provNi = getProvisioningNetworkInfo();
         if (provNi == null) {
-            final int uid = Binder.getCallingUid();
-            provNi = getNetworkInfo(mActiveDefaultNetwork, uid);
+            provNi = getActiveNetworkInfo();
         }
         if (DBG) log("getProvisioningOrActiveNetworkInfo: X provNi=" + provNi);
         return provNi;
@@ -917,62 +982,50 @@
 
     public NetworkInfo getActiveNetworkInfoUnfiltered() {
         enforceAccessPermission();
-        if (isNetworkTypeValid(mActiveDefaultNetwork)) {
-            return getNetworkInfoForType(mActiveDefaultNetwork);
-        }
-        return null;
+        final int uid = Binder.getCallingUid();
+        NetworkState state = getUnfilteredActiveNetworkState(uid);
+        return state.networkInfo;
     }
 
     @Override
     public NetworkInfo getActiveNetworkInfoForUid(int uid) {
         enforceConnectivityInternalPermission();
-        return getNetworkInfo(mActiveDefaultNetwork, uid);
+        NetworkState state = getUnfilteredActiveNetworkState(uid);
+        return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
     }
 
     @Override
     public NetworkInfo getNetworkInfo(int networkType) {
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
-        return getNetworkInfo(networkType, uid);
+        NetworkState state = getFilteredNetworkState(networkType, uid);
+        return state.networkInfo;
     }
 
-    private NetworkInfo getNetworkInfo(int networkType, int uid) {
+    @Override
+    public NetworkInfo getNetworkInfoForNetwork(Network network) {
+        enforceAccessPermission();
+        final int uid = Binder.getCallingUid();
         NetworkInfo info = null;
-        if (isNetworkTypeValid(networkType)) {
-            if (getNetworkInfoForType(networkType) != null) {
-                info = getFilteredNetworkInfo(networkType, uid);
+        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+        if (nai != null) {
+            synchronized (nai) {
+                info = new NetworkInfo(nai.networkInfo);
+                info = getFilteredNetworkInfo(info, nai.linkProperties, uid);
             }
         }
         return info;
     }
 
     @Override
-    public NetworkInfo getNetworkInfoForNetwork(Network network) {
-        enforceAccessPermission();
-        if (network == null) return null;
-
-        final int uid = Binder.getCallingUid();
-        NetworkAgentInfo nai = null;
-        synchronized (mNetworkForNetId) {
-            nai = mNetworkForNetId.get(network.netId);
-        }
-        if (nai == null) return null;
-        synchronized (nai) {
-            if (nai.networkInfo == null) return null;
-
-            return getFilteredNetworkInfo(nai, uid);
-        }
-    }
-
-    @Override
     public NetworkInfo[] getAllNetworkInfo() {
         enforceAccessPermission();
-        final int uid = Binder.getCallingUid();
         final ArrayList<NetworkInfo> result = Lists.newArrayList();
         for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
                 networkType++) {
-            if (getNetworkInfoForType(networkType) != null) {
-                result.add(getFilteredNetworkInfo(networkType, uid));
+            NetworkInfo info = getNetworkInfo(networkType);
+            if (info != null) {
+                result.add(info);
             }
         }
         return result.toArray(new NetworkInfo[result.size()]);
@@ -982,11 +1035,11 @@
     public Network getNetworkForType(int networkType) {
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
-        if (isNetworkBlocked(networkType, uid)) {
-            return null;
+        NetworkState state = getFilteredNetworkState(networkType, uid);
+        if (!isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid)) {
+            return state.network;
         }
-        NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
-        return (nai == null) ? null : nai.network;
+        return null;
     }
 
     @Override
@@ -1001,10 +1054,76 @@
         return result.toArray(new Network[result.size()]);
     }
 
+    private NetworkCapabilities getNetworkCapabilitiesAndValidation(NetworkAgentInfo nai) {
+        if (nai != null) {
+            synchronized (nai) {
+                if (nai.created) {
+                    NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
+                    if (nai.validated) {
+                        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+                    } else {
+                        nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+                    }
+                    return nc;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
+        // The basic principle is: if an app's traffic could possibly go over a
+        // network, without the app doing anything multinetwork-specific,
+        // (hence, by "default"), then include that network's capabilities in
+        // the array.
+        //
+        // In the normal case, app traffic only goes over the system's default
+        // network connection, so that's the only network returned.
+        //
+        // With a VPN in force, some app traffic may go into the VPN, and thus
+        // over whatever underlying networks the VPN specifies, while other app
+        // traffic may go over the system default network (e.g.: a split-tunnel
+        // VPN, or an app disallowed by the VPN), so the set of networks
+        // returned includes the VPN's underlying networks and the system
+        // default.
+        enforceAccessPermission();
+
+        HashMap<Network, NetworkCapabilities> result = new HashMap<Network, NetworkCapabilities>();
+
+        NetworkAgentInfo nai = getDefaultNetwork();
+        NetworkCapabilities nc = getNetworkCapabilitiesAndValidation(getDefaultNetwork());
+        if (nc != null) {
+            result.put(nai.network, nc);
+        }
+
+        if (!mLockdownEnabled) {
+            synchronized (mVpns) {
+                Vpn vpn = mVpns.get(userId);
+                if (vpn != null) {
+                    Network[] networks = vpn.getUnderlyingNetworks();
+                    if (networks != null) {
+                        for (Network network : networks) {
+                            nai = getNetworkAgentInfoForNetwork(network);
+                            nc = getNetworkCapabilitiesAndValidation(nai);
+                            if (nc != null) {
+                                result.put(nai.network, nc);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        NetworkCapabilities[] out = new NetworkCapabilities[result.size()];
+        out = result.values().toArray(out);
+        return out;
+    }
+
     @Override
     public boolean isNetworkSupported(int networkType) {
         enforceAccessPermission();
-        return (isNetworkTypeValid(networkType) && (getNetworkInfoForType(networkType) != null));
+        return mLegacyTypeTracker.isTypeSupported(networkType);
     }
 
     /**
@@ -1017,14 +1136,20 @@
      */
     @Override
     public LinkProperties getActiveLinkProperties() {
-        return getLinkPropertiesForType(mActiveDefaultNetwork);
+        enforceAccessPermission();
+        final int uid = Binder.getCallingUid();
+        NetworkState state = getUnfilteredActiveNetworkState(uid);
+        return state.linkProperties;
     }
 
     @Override
     public LinkProperties getLinkPropertiesForType(int networkType) {
         enforceAccessPermission();
-        if (isNetworkTypeValid(networkType)) {
-            return getLinkPropertiesForTypeInternal(networkType);
+        NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+        if (nai != null) {
+            synchronized (nai) {
+                return new LinkProperties(nai.linkProperties);
+            }
         }
         return null;
     }
@@ -1033,11 +1158,7 @@
     @Override
     public LinkProperties getLinkProperties(Network network) {
         enforceAccessPermission();
-        NetworkAgentInfo nai = null;
-        synchronized (mNetworkForNetId) {
-            nai = mNetworkForNetId.get(network.netId);
-        }
-
+        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
         if (nai != null) {
             synchronized (nai) {
                 return new LinkProperties(nai.linkProperties);
@@ -1049,10 +1170,7 @@
     @Override
     public NetworkCapabilities getNetworkCapabilities(Network network) {
         enforceAccessPermission();
-        NetworkAgentInfo nai = null;
-        synchronized (mNetworkForNetId) {
-            nai = mNetworkForNetId.get(network.netId);
-        }
+        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
         if (nai != null) {
             synchronized (nai) {
                 return new NetworkCapabilities(nai.networkCapabilities);
@@ -1068,36 +1186,22 @@
         final ArrayList<NetworkState> result = Lists.newArrayList();
         for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
                 networkType++) {
-            if (getNetworkInfoForType(networkType) != null) {
-                final NetworkInfo info = getFilteredNetworkInfo(networkType, uid);
-                final LinkProperties lp = getLinkPropertiesForTypeInternal(networkType);
-                final NetworkCapabilities netcap = getNetworkCapabilitiesForType(networkType);
-                result.add(new NetworkState(info, lp, netcap));
+            NetworkState state = getFilteredNetworkState(networkType, uid);
+            if (state.networkInfo != null) {
+                result.add(state);
             }
         }
         return result.toArray(new NetworkState[result.size()]);
     }
 
-    private NetworkState getNetworkStateUnchecked(int networkType) {
-        if (isNetworkTypeValid(networkType)) {
-            NetworkInfo info = getNetworkInfoForType(networkType);
-            if (info != null) {
-                return new NetworkState(info,
-                        getLinkPropertiesForTypeInternal(networkType),
-                        getNetworkCapabilitiesForType(networkType));
-            }
-        }
-        return null;
-    }
-
     @Override
     public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
         enforceAccessPermission();
-
+        final int uid = Binder.getCallingUid();
         final long token = Binder.clearCallingIdentity();
         try {
-            final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork);
-            if (state != null) {
+            final NetworkState state = getUnfilteredActiveNetworkState(uid);
+            if (state.networkInfo != null) {
                 try {
                     return mPolicyManager.getNetworkQuotaInfo(state);
                 } catch (RemoteException e) {
@@ -1112,17 +1216,18 @@
     @Override
     public boolean isActiveNetworkMetered() {
         enforceAccessPermission();
+        final int uid = Binder.getCallingUid();
         final long token = Binder.clearCallingIdentity();
         try {
-            return isNetworkMeteredUnchecked(mActiveDefaultNetwork);
+            return isActiveNetworkMeteredUnchecked(uid);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
-    private boolean isNetworkMeteredUnchecked(int networkType) {
-        final NetworkState state = getNetworkStateUnchecked(networkType);
-        if (state != null) {
+    private boolean isActiveNetworkMeteredUnchecked(int uid) {
+        final NetworkState state = getUnfilteredActiveNetworkState(uid);
+        if (state.networkInfo != null) {
             try {
                 return mPolicyManager.isNetworkMetered(state);
             } catch (RemoteException e) {
@@ -1293,16 +1398,18 @@
 
             // kick off connectivity change broadcast for active network, since
             // global background policy change is radical.
-            final int networkType = mActiveDefaultNetwork;
-            if (isNetworkTypeValid(networkType)) {
-                final NetworkStateTracker tracker = mNetTrackers[networkType];
-                if (tracker != null) {
-                    final NetworkInfo info = tracker.getNetworkInfo();
-                    if (info != null && info.isConnected()) {
-                        sendConnectedBroadcast(info);
-                    }
-                }
-            }
+            // TODO: Dead code; remove.
+            //
+            // final int networkType = mActiveDefaultNetwork;
+            // if (isNetworkTypeValid(networkType)) {
+            //     final NetworkStateTracker tracker = mNetTrackers[networkType];
+            //     if (tracker != null) {
+            //         final NetworkInfo info = tracker.getNetworkInfo();
+            //         if (info != null && info.isConnected()) {
+            //             sendConnectedBroadcast(info);
+            //         }
+            //     }
+            // }
         }
     };
 
@@ -1707,16 +1814,6 @@
         pw.println();
         pw.decreaseIndent();
 
-        pw.print("mActiveDefaultNetwork: " + mActiveDefaultNetwork);
-        if (mActiveDefaultNetwork != TYPE_NONE) {
-            NetworkInfo activeNetworkInfo = getActiveNetworkInfo();
-            if (activeNetworkInfo != null) {
-                pw.print(" " + activeNetworkInfo.getState() +
-                         "/" + activeNetworkInfo.getDetailedState());
-            }
-        }
-        pw.println();
-
         pw.println("mLegacyTypeTracker:");
         pw.increaseIndent();
         mLegacyTypeTracker.dump(pw);
@@ -1745,10 +1842,7 @@
 
     private boolean isLiveNetworkAgent(NetworkAgentInfo nai, String msg) {
         if (nai.network == null) return false;
-        final NetworkAgentInfo officialNai;
-        synchronized (mNetworkForNetId) {
-            officialNai = mNetworkForNetId.get(nai.network.netId);
-        }
+        final NetworkAgentInfo officialNai = getNetworkAgentInfoForNetwork(nai.network);
         if (officialNai != null && officialNai.equals(nai)) return true;
         if (officialNai != null || VDBG) {
             loge(msg + " - isLiveNetworkAgent found mismatched netId: " + officialNai +
@@ -1944,7 +2038,7 @@
                          * to the link that may have incorrectly setup by the lower
                          * levels.
                          */
-                        LinkProperties lp = getLinkPropertiesForTypeInternal(info.getType());
+                        LinkProperties lp = getLinkPropertiesForType(info.getType());
                         if (DBG) {
                             log("EVENT_STATE_CHANGED: connected to provisioning network, lp=" + lp);
                         }
@@ -2086,7 +2180,6 @@
             }
             if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
                 removeDataActivityTracking(nai);
-                mActiveDefaultNetwork = ConnectivityManager.TYPE_NONE;
                 notifyLockdownVpn(nai);
                 requestNetworkTransitionWakelock(nai.name());
             }
@@ -2098,11 +2191,40 @@
         }
     }
 
+    // If this method proves to be too slow then we can maintain a separate
+    // pendingIntent => NetworkRequestInfo map.
+    // This method assumes that every non-null PendingIntent maps to exactly 1 NetworkRequestInfo.
+    private NetworkRequestInfo findExistingNetworkRequestInfo(PendingIntent pendingIntent) {
+        Intent intent = pendingIntent.getIntent();
+        for (Map.Entry<NetworkRequest, NetworkRequestInfo> entry : mNetworkRequests.entrySet()) {
+            PendingIntent existingPendingIntent = entry.getValue().mPendingIntent;
+            if (existingPendingIntent != null &&
+                    existingPendingIntent.getIntent().filterEquals(intent)) {
+                return entry.getValue();
+            }
+        }
+        return null;
+    }
+
+    private void handleRegisterNetworkRequestWithIntent(Message msg) {
+        final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
+
+        NetworkRequestInfo existingRequest = findExistingNetworkRequestInfo(nri.mPendingIntent);
+        if (existingRequest != null) { // remove the existing request.
+            if (DBG) log("Replacing " + existingRequest.request + " with "
+                    + nri.request + " because their intents matched.");
+            handleReleaseNetworkRequest(existingRequest.request, getCallingUid());
+        }
+        handleRegisterNetworkRequest(msg);
+    }
+
     private void handleRegisterNetworkRequest(Message msg) {
         final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
         final NetworkCapabilities newCap = nri.request.networkCapabilities;
         int score = 0;
 
+        mNetworkRequests.put(nri.request, nri);
+
         // Check for the best currently alive network that satisfies this request
         NetworkAgentInfo bestNetwork = null;
         for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
@@ -2140,7 +2262,7 @@
                 mLegacyTypeTracker.add(nri.request.legacyType, bestNetwork);
             }
         }
-        mNetworkRequests.put(nri.request, nri);
+
         if (nri.isRequest) {
             if (DBG) log("sending new NetworkRequest to factories");
             for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
@@ -2150,6 +2272,14 @@
         }
     }
 
+    private void handleReleaseNetworkRequestWithIntent(PendingIntent pendingIntent,
+            int callingUid) {
+        NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
+        if (nri != null) {
+            handleReleaseNetworkRequest(nri.request, callingUid);
+        }
+    }
+
     private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
         NetworkRequestInfo nri = mNetworkRequests.get(request);
         if (nri != null) {
@@ -2185,11 +2315,11 @@
                     }
                 }
 
-                // Maintain the illusion.  When this request arrived, we might have preteneded
+                // Maintain the illusion.  When this request arrived, we might have pretended
                 // that a network connected to serve it, even though the network was already
                 // connected.  Now that this request has gone away, we might have to pretend
                 // that the network disconnected.  LegacyTypeTracker will generate that
-                // phatom disconnect for this type.
+                // phantom disconnect for this type.
                 NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
                 if (nai != null) {
                     mNetworkForRequestId.remove(nri.request.requestId);
@@ -2220,7 +2350,6 @@
 
         @Override
         public void handleMessage(Message msg) {
-            NetworkInfo info;
             switch (msg.what) {
                 case EVENT_EXPIRE_NET_TRANSITION_WAKELOCK:
                 case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: {
@@ -2301,6 +2430,14 @@
                     handleRegisterNetworkRequest(msg);
                     break;
                 }
+                case EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT: {
+                    handleRegisterNetworkRequestWithIntent(msg);
+                    break;
+                }
+                case EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT: {
+                    handleReleaseNetworkRequestWithIntent((PendingIntent) msg.obj, msg.arg1);
+                    break;
+                }
                 case EVENT_RELEASE_NETWORK_REQUEST: {
                     handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
                     break;
@@ -2453,10 +2590,7 @@
         if (network == null) return;
 
         final int uid = Binder.getCallingUid();
-        NetworkAgentInfo nai = null;
-        synchronized (mNetworkForNetId) {
-            nai = mNetworkForNetId.get(network.netId);
-        }
+        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
         if (nai == null) return;
         if (DBG) log("reportBadNetwork(" + nai.name() + ") by " + uid);
         synchronized (nai) {
@@ -2464,7 +2598,7 @@
             // which isn't meant to work on uncreated networks.
             if (!nai.created) return;
 
-            if (isNetworkBlocked(nai, uid)) return;
+            if (isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid)) return;
 
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid);
         }
@@ -2495,7 +2629,7 @@
             String exclList = "";
             String pacFileUrl = "";
             if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) ||
-                    (proxyProperties.getPacFileUrl() != null))) {
+                    !Uri.EMPTY.equals(proxyProperties.getPacFileUrl()))) {
                 if (!proxyProperties.isValid()) {
                     if (DBG)
                         log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
@@ -2505,7 +2639,7 @@
                 host = mGlobalProxy.getHost();
                 port = mGlobalProxy.getPort();
                 exclList = mGlobalProxy.getExclusionListAsString();
-                if (proxyProperties.getPacFileUrl() != null) {
+                if (!Uri.EMPTY.equals(proxyProperties.getPacFileUrl())) {
                     pacFileUrl = proxyProperties.getPacFileUrl().toString();
                 }
             } else {
@@ -2567,7 +2701,7 @@
 
     private void handleApplyDefaultProxy(ProxyInfo proxy) {
         if (proxy != null && TextUtils.isEmpty(proxy.getHost())
-                && (proxy.getPacFileUrl() == null)) {
+                && Uri.EMPTY.equals(proxy.getPacFileUrl())) {
             proxy = null;
         }
         synchronized (mProxyLock) {
@@ -2583,7 +2717,8 @@
             // global (to get the correct local port), and send a broadcast.
             // TODO: Switch PacManager to have its own message to send back rather than
             // reusing EVENT_HAS_CHANGED_PROXY and this call to handleApplyDefaultProxy.
-            if ((mGlobalProxy != null) && (proxy != null) && (proxy.getPacFileUrl() != null)
+            if ((mGlobalProxy != null) && (proxy != null)
+                    && (!Uri.EMPTY.equals(proxy.getPacFileUrl()))
                     && proxy.getPacFileUrl().equals(mGlobalProxy.getPacFileUrl())) {
                 mGlobalProxy = proxy;
                 sendProxyBroadcast(mGlobalProxy);
@@ -2666,42 +2801,6 @@
         Slog.e(TAG, s);
     }
 
-    int convertFeatureToNetworkType(int networkType, String feature) {
-        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) ||
-                    TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
-                usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN;
-            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) {
-                usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
-            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_FOTA)) {
-                usedNetworkType = ConnectivityManager.TYPE_MOBILE_FOTA;
-            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_IMS)) {
-                usedNetworkType = ConnectivityManager.TYPE_MOBILE_IMS;
-            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_CBS)) {
-                usedNetworkType = ConnectivityManager.TYPE_MOBILE_CBS;
-            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_EMERGENCY)) {
-                usedNetworkType = ConnectivityManager.TYPE_MOBILE_EMERGENCY;
-            } else {
-                Slog.e(TAG, "Can't match any mobile netTracker!");
-            }
-        } else if (networkType == ConnectivityManager.TYPE_WIFI) {
-            if (TextUtils.equals(feature, "p2p")) {
-                usedNetworkType = ConnectivityManager.TYPE_WIFI_P2P;
-            } else {
-                Slog.e(TAG, "Can't match any wifi netTracker!");
-            }
-        } else {
-            Slog.e(TAG, "Unexpected network type");
-        }
-        return usedNetworkType;
-    }
-
     private static <T> T checkNotNull(T value, String message) {
         if (value == null) {
             throw new NullPointerException(message);
@@ -2710,9 +2809,8 @@
     }
 
     /**
-     * Prepare for a VPN application. This method is used by VpnDialogs
-     * and not available in ConnectivityManager. Permissions are checked
-     * in Vpn class.
+     * Prepare for a VPN application.
+     * Permissions are checked in Vpn class.
      * @hide
      */
     @Override
@@ -2726,8 +2824,8 @@
 
     /**
      * Set whether the current VPN package has the ability to launch VPNs without
-     * user intervention. This method is used by system UIs and not available
-     * in ConnectivityManager. Permissions are checked in Vpn class.
+     * user intervention. This method is used by system-privileged apps.
+     * Permissions are checked in Vpn class.
      * @hide
      */
     @Override
@@ -3144,7 +3242,7 @@
             Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0);
             Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
             intent.putExtra("state", enable);
-            mContext.sendBroadcast(intent);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -3188,43 +3286,6 @@
         }
     };
 
-    @Override
-    public LinkQualityInfo getLinkQualityInfo(int networkType) {
-        enforceAccessPermission();
-        if (isNetworkTypeValid(networkType) && mNetTrackers[networkType] != null) {
-            return mNetTrackers[networkType].getLinkQualityInfo();
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public LinkQualityInfo getActiveLinkQualityInfo() {
-        enforceAccessPermission();
-        if (isNetworkTypeValid(mActiveDefaultNetwork) &&
-                mNetTrackers[mActiveDefaultNetwork] != null) {
-            return mNetTrackers[mActiveDefaultNetwork].getLinkQualityInfo();
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public LinkQualityInfo[] getAllLinkQualityInfo() {
-        enforceAccessPermission();
-        final ArrayList<LinkQualityInfo> result = Lists.newArrayList();
-        for (NetworkStateTracker tracker : mNetTrackers) {
-            if (tracker != null) {
-                LinkQualityInfo li = tracker.getLinkQualityInfo();
-                if (li != null) {
-                    result.add(li);
-                }
-            }
-        }
-
-        return result.toArray(new LinkQualityInfo[result.size()]);
-    }
-
     /* Infrastructure for network sampling */
 
     private void handleNetworkSamplingTimeout() {
@@ -3314,12 +3375,23 @@
         static final boolean LISTEN = false;
 
         final NetworkRequest request;
-        IBinder mBinder;
+        final PendingIntent mPendingIntent;
+        private final IBinder mBinder;
         final int mPid;
         final int mUid;
         final Messenger messenger;
         final boolean isRequest;
 
+        NetworkRequestInfo(NetworkRequest r, PendingIntent pi, boolean isRequest) {
+            request = r;
+            mPendingIntent = pi;
+            messenger = null;
+            mBinder = null;
+            mPid = getCallingPid();
+            mUid = getCallingUid();
+            this.isRequest = isRequest;
+        }
+
         NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, boolean isRequest) {
             super();
             messenger = m;
@@ -3328,6 +3400,7 @@
             mPid = getCallingPid();
             mUid = getCallingUid();
             this.isRequest = isRequest;
+            mPendingIntent = null;
 
             try {
                 mBinder.linkToDeath(this, 0);
@@ -3337,7 +3410,9 @@
         }
 
         void unlinkDeathRecipient() {
-            mBinder.unlinkToDeath(this, 0);
+            if (mBinder != null) {
+                mBinder.unlinkToDeath(this, 0);
+            }
         }
 
         public void binderDied() {
@@ -3348,40 +3423,22 @@
 
         public String toString() {
             return (isRequest ? "Request" : "Listen") + " from uid/pid:" + mUid + "/" +
-                    mPid + " for " + request;
+                    mPid + " for " + request +
+                    (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
         }
     }
 
     @Override
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
             Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
-        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                == false) {
-            enforceConnectivityInternalPermission();
-        } else {
-            enforceChangePermission();
-        }
-
         networkCapabilities = new NetworkCapabilities(networkCapabilities);
-
-        // if UID is restricted, don't allow them to bring up metered APNs
-        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
-                == false) {
-            final int uidRules;
-            final int uid = Binder.getCallingUid();
-            synchronized(mRulesLock) {
-                uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
-            }
-            if ((uidRules & RULE_REJECT_METERED) != 0) {
-                // we could silently fail or we can filter the available nets to only give
-                // them those they have access to.  Chose the more useful
-                networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-            }
-        }
+        enforceNetworkRequestPermissions(networkCapabilities);
+        enforceMeteredApnPolicy(networkCapabilities);
 
         if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) {
             throw new IllegalArgumentException("Bad timeout specified");
         }
+
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId());
         if (DBG) log("requestNetwork for " + networkRequest);
@@ -3396,11 +3453,54 @@
         return networkRequest;
     }
 
+    private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities) {
+        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                == false) {
+            enforceConnectivityInternalPermission();
+        } else {
+            enforceChangePermission();
+        }
+    }
+
+    private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) {
+        // if UID is restricted, don't allow them to bring up metered APNs
+        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                == false) {
+            final int uidRules;
+            final int uid = Binder.getCallingUid();
+            synchronized(mRulesLock) {
+                uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+            }
+            if ((uidRules & RULE_REJECT_METERED) != 0) {
+                // we could silently fail or we can filter the available nets to only give
+                // them those they have access to.  Chose the more useful
+                networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+            }
+        }
+    }
+
     @Override
     public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities,
             PendingIntent operation) {
-        // TODO
-        return null;
+        checkNotNull(operation, "PendingIntent cannot be null.");
+        networkCapabilities = new NetworkCapabilities(networkCapabilities);
+        enforceNetworkRequestPermissions(networkCapabilities);
+        enforceMeteredApnPolicy(networkCapabilities);
+
+        NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
+                nextNetworkRequestId());
+        if (DBG) log("pendingRequest for " + networkRequest + " to trigger " + operation);
+        NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
+                NetworkRequestInfo.REQUEST);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
+                nri));
+        return networkRequest;
+    }
+
+    @Override
+    public void releasePendingNetworkRequest(PendingIntent operation) {
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT,
+                getCallingUid(), 0, operation));
     }
 
     @Override
@@ -3474,10 +3574,15 @@
     private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos =
             new HashMap<Messenger, NetworkAgentInfo>();
 
+    // Note: if mDefaultRequest is changed, NetworkMonitor needs to be updated.
     private final NetworkRequest mDefaultRequest;
 
+    private NetworkAgentInfo getDefaultNetwork() {
+        return mNetworkForRequestId.get(mDefaultRequest.requestId);
+    }
+
     private boolean isDefaultNetwork(NetworkAgentInfo nai) {
-        return mNetworkForRequestId.get(mDefaultRequest.requestId) == nai;
+        return nai == getDefaultNetwork();
     }
 
     public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
@@ -3485,10 +3590,12 @@
             int currentScore, NetworkMisc networkMisc) {
         enforceConnectivityInternalPermission();
 
+        // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network
+        // satisfies mDefaultRequest.
         NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
             new NetworkInfo(networkInfo), new LinkProperties(linkProperties),
             new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler,
-            new NetworkMisc(networkMisc));
+            new NetworkMisc(networkMisc), mDefaultRequest);
         synchronized (this) {
             nai.networkMonitor.systemReady = mSystemReady;
         }
@@ -3523,8 +3630,14 @@
 //            updateMtu(lp, null);
 //        }
         updateTcpBufferSizes(networkAgent);
+
+        // TODO: deprecate and remove mDefaultDns when we can do so safely.
+        // For now, use it only when the network has Internet access. http://b/18327075
+        final boolean useDefaultDns = networkAgent.networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET);
         final boolean flushDns = updateRoutes(newLp, oldLp, netId);
-        updateDnses(newLp, oldLp, netId, flushDns);
+        updateDnses(newLp, oldLp, netId, flushDns, useDefaultDns);
+
         updateClat(newLp, oldLp, networkAgent);
         if (isDefaultNetwork(networkAgent)) handleApplyDefaultProxy(newLp.getHttpProxy());
         // TODO - move this check to cover the whole function
@@ -3618,10 +3731,11 @@
         }
         return !routeDiff.added.isEmpty() || !routeDiff.removed.isEmpty();
     }
-    private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId, boolean flush) {
+    private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId,
+                             boolean flush, boolean useDefaultDns) {
         if (oldLp == null || (newLp.isIdenticalDnses(oldLp) == false)) {
             Collection<InetAddress> dnses = newLp.getDnsServers();
-            if (dnses.size() == 0 && mDefaultDns != null) {
+            if (dnses.size() == 0 && mDefaultDns != null && useDefaultDns) {
                 dnses = new ArrayList();
                 dnses.add(mDefaultDns);
                 if (DBG) {
@@ -3668,12 +3782,11 @@
 
     private void updateCapabilities(NetworkAgentInfo networkAgent,
             NetworkCapabilities networkCapabilities) {
-        //  TODO - turn this on in MR1 when we have more dogfooding time.
-        // rematchAllNetworksAndRequests();
         if (!Objects.equals(networkAgent.networkCapabilities, networkCapabilities)) {
             synchronized (networkAgent) {
                 networkAgent.networkCapabilities = networkCapabilities;
             }
+            rematchAllNetworksAndRequests(networkAgent, networkAgent.getCurrentScore());
             notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
     }
@@ -3695,6 +3808,38 @@
         }
     }
 
+    private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent,
+            int notificationType) {
+        if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE) {
+            Intent intent = new Intent();
+            intent.putExtra(ConnectivityManager.EXTRA_NETWORK, networkAgent.network);
+            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, nri.request);
+            sendIntent(nri.mPendingIntent, intent);
+        }
+        // else not handled
+    }
+
+    private void sendIntent(PendingIntent pendingIntent, Intent intent) {
+        mPendingIntentWakeLock.acquire();
+        try {
+            if (DBG) log("Sending " + pendingIntent);
+            pendingIntent.send(mContext, 0, intent, this /* onFinished */, null /* Handler */);
+        } catch (PendingIntent.CanceledException e) {
+            if (DBG) log(pendingIntent + " was not sent, it had been canceled.");
+            mPendingIntentWakeLock.release();
+            releasePendingNetworkRequest(pendingIntent);
+        }
+        // ...otherwise, mPendingIntentWakeLock.release() gets called by onSendFinished()
+    }
+
+    @Override
+    public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
+            String resultData, Bundle resultExtras) {
+        if (DBG) log("Finished sending " + pendingIntent);
+        mPendingIntentWakeLock.release();
+        releasePendingNetworkRequest(pendingIntent);
+    }
+
     private void callCallbackForRequest(NetworkRequestInfo nri,
             NetworkAgentInfo networkAgent, int notificationType) {
         if (nri.messenger == null) return;  // Default request has no msgr
@@ -3758,7 +3903,6 @@
 
     private void makeDefault(NetworkAgentInfo newNetwork) {
         if (DBG) log("Switching to new default network: " + newNetwork);
-        mActiveDefaultNetwork = newNetwork.networkInfo.getType();
         setupDataActivityTracking(newNetwork);
         try {
             mNetd.setDefaultNetId(newNetwork.network.netId);
@@ -3800,7 +3944,7 @@
     //                 another higher scoring network by another call to rematchNetworkAndRequests()
     //                 and this other call also lingered newNetwork.
     private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, boolean nascent) {
-        if (!newNetwork.created) loge("ERROR: uncreated network being rematched.");
+        if (!newNetwork.created) return;
         if (nascent && !newNetwork.validated) loge("ERROR: nascent network not validated.");
         boolean keep = newNetwork.isVPN();
         boolean isNewDefault = false;
@@ -3863,7 +4007,6 @@
                     if (mDefaultRequest.requestId == nri.request.requestId) {
                         isNewDefault = true;
                         // TODO: Remove following line.  It's redundant with makeDefault call.
-                        mActiveDefaultNetwork = newNetwork.networkInfo.getType();
                         if (newNetwork.linkProperties != null) {
                             updateTcpBufferSizes(newNetwork);
                             setDefaultDnsSystemProperties(
@@ -3947,6 +4090,14 @@
             }
 
             notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_AVAILABLE);
+
+            // A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above,
+            // because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest
+            // wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the
+            // newNetwork to the tracker explicitly (it's a no-op if it has already been added).
+            if (newNetwork.isVPN()) {
+                mLegacyTypeTracker.add(TYPE_VPN, newNetwork);
+            }
         } else if (nascent) {
             // Only tear down newly validated networks here.  Leave unvalidated to either become
             // validated (and get evaluated against peers, one losing here) or
@@ -4091,7 +4242,7 @@
         final int oldScore = nai.getCurrentScore();
         nai.setCurrentScore(score);
 
-        if (nai.created) rematchAllNetworksAndRequests(nai, oldScore);
+        rematchAllNetworksAndRequests(nai, oldScore);
 
         sendUpdatedScoreToFactories(nai);
     }
@@ -4105,7 +4256,11 @@
 //        } else if (nai.networkMonitor.isEvaluating()) {
 //            notifyType = NetworkCallbacks.callCallbackForRequest(request, nai, notifyType);
 //        }
-        callCallbackForRequest(nri, nai, notifyType);
+        if (nri.mPendingIntent == null) {
+            callCallbackForRequest(nri, nai, notifyType);
+        } else {
+            sendPendingIntentForRequest(nri, nai, notifyType);
+        }
     }
 
     private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, boolean connected, int type) {
@@ -4163,7 +4318,11 @@
             NetworkRequest nr = networkAgent.networkRequests.valueAt(i);
             NetworkRequestInfo nri = mNetworkRequests.get(nr);
             if (VDBG) log(" sending notification for " + nr);
-            callCallbackForRequest(nri, networkAgent, notifyType);
+            if (nri.mPendingIntent == null) {
+                callCallbackForRequest(nri, networkAgent, notifyType);
+            } else {
+                sendPendingIntentForRequest(nri, networkAgent, notifyType);
+            }
         }
     }
 
@@ -4181,44 +4340,6 @@
         return "UNKNOWN";
     }
 
-    private LinkProperties getLinkPropertiesForTypeInternal(int networkType) {
-        NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
-        if (nai != null) {
-            synchronized (nai) {
-                return new LinkProperties(nai.linkProperties);
-            }
-        }
-        return new LinkProperties();
-    }
-
-    private NetworkInfo getNetworkInfoForType(int networkType) {
-        if (!mLegacyTypeTracker.isTypeSupported(networkType))
-            return null;
-
-        NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
-        if (nai != null) {
-            NetworkInfo result = new NetworkInfo(nai.networkInfo);
-            result.setType(networkType);
-            return result;
-        } else {
-            NetworkInfo result = new NetworkInfo(
-                    networkType, 0, ConnectivityManager.getNetworkTypeName(networkType), "");
-            result.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
-            result.setIsAvailable(true);
-            return result;
-        }
-    }
-
-    private NetworkCapabilities getNetworkCapabilitiesForType(int networkType) {
-        NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
-        if (nai != null) {
-            synchronized (nai) {
-                return new NetworkCapabilities(nai.networkCapabilities);
-            }
-        }
-        return new NetworkCapabilities();
-    }
-
     @Override
     public boolean addVpnAddress(String address, int prefixLength) {
         throwIfLockdownEnabled();
@@ -4236,4 +4357,13 @@
             return mVpns.get(user).removeAddress(address, prefixLength);
         }
     }
+
+    @Override
+    public boolean setUnderlyingNetworksForVpn(Network[] networks) {
+        throwIfLockdownEnabled();
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized (mVpns) {
+            return mVpns.get(user).setUnderlyingNetworks(networks);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 576556b..3fa21d0 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -63,8 +63,8 @@
     //  - Idle: start() not called. Everything is null.
     //  - Starting: start() called. Interfaces are non-null. isStarted() returns true.
     //    mIsRunning is false.
-    //  - Running: start() called, and interfaceAdded() told us that mIface is up. Clat IP address
-    //    is non-null. mIsRunning is true.
+    //  - Running: start() called, and interfaceLinkStateChanged() told us that mIface is up.
+    //    mIsRunning is true.
     //
     // Once mIface is non-null and isStarted() is true, methods called by ConnectivityService on
     // its handler thread must not modify any internal state variables; they are only updated by the
@@ -236,10 +236,10 @@
     }
 
     @Override
-    public void interfaceAdded(String iface) {
+    public void interfaceLinkStateChanged(String iface, boolean up) {
         // Called by the InterfaceObserver on its own thread, so can race with stop().
-        if (isStarted() && mIface.equals(iface)) {
-            Slog.i(TAG, "interface " + iface + " added, mIsRunning " + mIsRunning + "->true");
+        if (isStarted() && up && mIface.equals(iface)) {
+            Slog.i(TAG, "interface " + iface + " is up, mIsRunning " + mIsRunning + "->true");
 
             if (!mIsRunning) {
                 LinkAddress clatAddress = getLinkAddress(iface);
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 4cf2a4a..4af920a 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -45,7 +45,14 @@
     public NetworkCapabilities networkCapabilities;
     public final NetworkMonitor networkMonitor;
     public final NetworkMisc networkMisc;
+    // Indicates if netd has been told to create this Network.  Once created the appropriate routing
+    // rules are setup and routes are added so packets can begin flowing over the Network.
+    // NOTE: This is a sticky bit; once set it is never cleared.
     public boolean created;
+    // Set to true if this Network successfully passed validation or if it did not satisfy the
+    // default NetworkRequest in which case validation will not be attempted.
+    // NOTE: This is a sticky bit; once set it is never cleared even if future validation attempts
+    // fail.
     public boolean validated;
 
     // This represents the last score received from the NetworkAgent.
@@ -58,6 +65,9 @@
 
     // The list of NetworkRequests being satisfied by this Network.
     public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>();
+    // The list of NetworkRequests that this Network previously satisfied with the highest
+    // score.  A non-empty list indicates that if this Network was validated it is lingered.
+    // NOTE: This list is only used for debugging.
     public final ArrayList<NetworkRequest> networkLingered = new ArrayList<NetworkRequest>();
 
     public final Messenger messenger;
@@ -68,7 +78,7 @@
 
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, NetworkInfo info,
             LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
-            NetworkMisc misc) {
+            NetworkMisc misc, NetworkRequest defaultRequest) {
         this.messenger = messenger;
         asyncChannel = ac;
         network = null;
@@ -76,7 +86,7 @@
         linkProperties = lp;
         networkCapabilities = nc;
         currentScore = score;
-        networkMonitor = new NetworkMonitor(context, handler, this);
+        networkMonitor = new NetworkMonitor(context, handler, this, defaultRequest);
         networkMisc = misc;
         created = false;
         validated = false;
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 238402f..debda14 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -191,8 +191,8 @@
         }
         try {
             if (add) {
-                mNetd.setPermission(CHANGE_NETWORK_STATE, toIntArray(network));
-                mNetd.setPermission(CONNECTIVITY_INTERNAL, toIntArray(system));
+                mNetd.setPermission("NETWORK", toIntArray(network));
+                mNetd.setPermission("SYSTEM", toIntArray(system));
             } else {
                 mNetd.clearPermission(toIntArray(network));
                 mNetd.clearPermission(toIntArray(system));
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index c115339..f9a03fc 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -1006,7 +1006,7 @@
         info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
-        return new NetworkState(info, prop, null, null, TEST_SSID);
+        return new NetworkState(info, prop, null, null, null, TEST_SSID);
     }
 
     private static NetworkState buildMobile3gState(String subscriberId) {
@@ -1015,7 +1015,7 @@
         info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
-        return new NetworkState(info, prop, null, subscriberId, null);
+        return new NetworkState(info, prop, null, null, subscriberId, null);
     }
 
     private static NetworkState buildMobile4gState(String iface) {
@@ -1023,7 +1023,7 @@
         info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(iface);
-        return new NetworkState(info, prop, null);
+        return new NetworkState(info, prop, null, null);
     }
 
     private NetworkStats buildEmptyStats() {