Merge commit '052a0da' into merge2
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 6359f08..c531e7e 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -25,7 +25,6 @@
 import android.net.NetworkUtils;
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -38,7 +37,6 @@
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -312,6 +310,14 @@
     public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal";
 
     /**
+     * Action used to display a dialog that asks the user whether to connect to a network that is
+     * not validated. This intent is used to start the dialog in settings via startActivity.
+     *
+     * @hide
+     */
+    public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED";
+
+    /**
      * The absence of a connection type.
      * @hide
      */
@@ -334,6 +340,9 @@
      * same network interface as {@link #TYPE_MOBILE} or it may use a different
      * one.  This is used by applications needing to talk to the carrier's
      * Multimedia Messaging Service servers.
+     *
+     * @deprecated Applications should instead use {@link #requestNetwork} to request a network that
+     *         provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability.
      */
     public static final int TYPE_MOBILE_MMS  = 2;
     /**
@@ -341,6 +350,9 @@
      * same network interface as {@link #TYPE_MOBILE} or it may use a different
      * one.  This is used by applications needing to talk to the carrier's
      * Secure User Plane Location servers for help locating the device.
+     *
+     * @deprecated Applications should instead use {@link #requestNetwork} to request a network that
+     *         provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability.
      */
     public static final int TYPE_MOBILE_SUPL = 3;
     /**
@@ -353,9 +365,10 @@
     /**
      * A High Priority Mobile data connection.  This network type uses the
      * same network interface as {@link #TYPE_MOBILE} 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 if no default route exists.
+     * is different.
+     *
+     * @deprecated Applications should instead use {@link #requestNetwork} to request a network that
+     *         uses the {@link NetworkCapabilities#TRANSPORT_CELLULAR} transport.
      */
     public static final int TYPE_MOBILE_HIPRI = 5;
     /**
@@ -415,7 +428,7 @@
      */
     public static final int TYPE_MOBILE_IA = 14;
 
-/**
+    /**
      * Emergency PDN connection for emergency calls
      * {@hide}
      */
@@ -529,6 +542,8 @@
                 return "MOBILE_EMERGENCY";
             case TYPE_PROXY:
                 return "PROXY";
+            case TYPE_VPN:
+                return "VPN";
             default:
                 return Integer.toString(type);
         }
@@ -774,7 +789,7 @@
     }
 
     /**
-     * Returns an array of of {@link NetworkCapabilities} objects, representing
+     * Returns an array of {@link android.net.NetworkCapabilities} objects, representing
      * the Networks that applications run by the given user will use by default.
      * @hide
      */
@@ -868,11 +883,11 @@
     }
 
     /**
-     * Get the {@link NetworkCapabilities} for the given {@link Network}.  This
+     * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}.  This
      * will return {@code null} if the network is unknown.
      *
      * @param network The {@link Network} object identifying the network in question.
-     * @return The {@link NetworkCapabilities} for the network, or {@code null}.
+     * @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}.
      */
     public NetworkCapabilities getNetworkCapabilities(Network network) {
         try {
@@ -896,6 +911,7 @@
      * always indicates failure.
      *
      * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
+     * @removed
      */
     public int startUsingNetworkFeature(int networkType, String feature) {
         NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
@@ -943,6 +959,7 @@
      * always indicates failure.
      *
      * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
+     * @removed
      */
     public int stopUsingNetworkFeature(int networkType, String feature) {
         NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
@@ -1221,6 +1238,7 @@
      *
      * @deprecated Deprecated in favor of the {@link #requestNetwork},
      *             {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} api.
+     * @removed
      */
     public boolean requestRouteToHost(int networkType, int hostAddress) {
         return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress));
@@ -1239,6 +1257,7 @@
      * @hide
      * @deprecated Deprecated in favor of the {@link #requestNetwork} and
      *             {@link #bindProcessToNetwork} api.
+     * @removed
      */
     public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
         try {
@@ -2176,7 +2195,7 @@
          * changes capabilities but still satisfies the stated need.
          *
          * @param network The {@link Network} whose capabilities have changed.
-         * @param networkCapabilities The new {@link NetworkCapabilities} for this network.
+         * @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this network.
          */
         public void onCapabilitiesChanged(Network network,
                 NetworkCapabilities networkCapabilities) {}
@@ -2418,7 +2437,7 @@
     }
 
     /**
-     * Request a network to satisfy a set of {@link NetworkCapabilities}.
+     * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
      *
      * This {@link NetworkRequest} will live until released via
      * {@link #unregisterNetworkCallback} or the calling application exits.
@@ -2437,7 +2456,7 @@
     }
 
     /**
-     * Request a network to satisfy a set of {@link NetworkCapabilities}, limited
+     * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
      * by a timeout.
      *
      * This function behaves identically to the non-timedout version, but if a suitable
@@ -2484,7 +2503,7 @@
 
 
     /**
-     * Request a network to satisfy a set of {@link NetworkCapabilities}.
+     * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
      *
      * This function behaves identically to the version that takes a NetworkCallback, but instead
      * of {@link NetworkCallback} a {@link PendingIntent} is used.  This means
@@ -2560,17 +2579,21 @@
     }
 
     /**
-     * Request connectivityservice to refresh network capabilities for the given
-     * {@link network}. This method returns true if the network is still active, false
-     * otherwise. Notice the method call assumes the caller has registered for
-     * listening NetworkCapabilities updates.
+     * Requests bandwidth update for a given {@link Network} and returns whether the update request
+     * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying
+     * network connection for updated bandwidth information. The caller will be notified via
+     * {@link ConnectivityManager.NetworkCallback} if there is an update. Notice that this
+     * method assumes that the caller has previously called {@link #registerNetworkCallback} to
+     * listen for network changes.
      *
      * @param network{@link Network} specifying which network you're interested.
+     * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
+     *
      * @hide
      */
-    public boolean requestBwUpdate(Network network) {
+    public boolean requestBandwidthUpdate(Network network) {
         try {
-            return mService.requestBwUpdate(network);
+            return mService.requestBandwidthUpdate(network);
         } catch (RemoteException e) {
             return false;
         }
@@ -2596,6 +2619,40 @@
     }
 
     /**
+     * Informs the system whether it should switch to {@code network} regardless of whether it is
+     * validated or not. If {@code accept} is true, and the network was explicitly selected by the
+     * user (e.g., by selecting a Wi-Fi network in the Settings app), then the network will become
+     * the system default network regardless of any other network that's currently connected. If
+     * {@code always} is true, then the choice is remembered, so that the next time the user
+     * connects to this network, the system will switch to it.
+     *
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
+     *
+     * @param network The network to accept.
+     * @param accept Whether to accept the network even if unvalidated.
+     * @param always Whether to remember this choice in the future.
+     *
+     * @hide
+     */
+    public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
+        try {
+            mService.setAcceptUnvalidated(network, accept, always);
+        } catch (RemoteException e) {}
+    }
+
+    /**
+     * Resets all connectivity manager settings back to factory defaults.
+     * @hide
+     */
+    public void factoryReset() {
+        try {
+            mService.factoryReset();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Binds the current process to {@code network}.  All Sockets created in the future
      * (and not explicitly bound via a bound SocketFactory from
      * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to
@@ -2648,7 +2705,12 @@
         if (NetworkUtils.bindProcessToNetwork(netId)) {
             // Set HTTP proxy system properties to match network.
             // TODO: Deprecate this static method and replace it with a non-static version.
-            Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
+            try {
+                Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
+            } catch (SecurityException e) {
+                // The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy.
+                Log.e(TAG, "Can't set proxy properties", e);
+            }
             // Must flush DNS cache as new network may have different DNS resolutions.
             InetAddress.clearDnsCache();
             // Must flush socket pool as idle sockets will be bound to previous network and may
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 6e06aa5..77200a5 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -33,6 +33,7 @@
 
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 
 /**
@@ -117,6 +118,8 @@
 
     LegacyVpnInfo getLegacyVpnInfo();
 
+    VpnInfo[] getAllVpnInfo();
+
     boolean updateLockdownVpn();
 
     void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
@@ -133,7 +136,7 @@
 
     void registerNetworkFactory(in Messenger messenger, in String name);
 
-    boolean requestBwUpdate(in Network network);
+    boolean requestBandwidthUpdate(in Network network);
 
     void unregisterNetworkFactory(in Messenger messenger);
 
@@ -156,9 +159,13 @@
 
     void releaseNetworkRequest(in NetworkRequest networkRequest);
 
+    void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
+
     int getRestoreDefaultNetworkDelay(int networkType);
 
     boolean addVpnAddress(String address, int prefixLength);
     boolean removeVpnAddress(String address, int prefixLength);
     boolean setUnderlyingNetworksForVpn(in Network[] networks);
+
+    void factoryReset();
 }
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index b268986..6b4f2d5 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -170,6 +170,21 @@
     }
 
     /**
+     * Determines whether the prefix contains the specified address.
+     *
+     * @param address An {@link InetAddress} to test.
+     * @return {@code true} if the prefix covers the given address.
+     */
+    public boolean contains(InetAddress address) {
+        byte[] addrBytes = (address == null) ? null : address.getAddress();
+        if (addrBytes == null || addrBytes.length != this.address.length) {
+            return false;
+        }
+        NetworkUtils.maskRawAddress(addrBytes, prefixLength);
+        return Arrays.equals(this.address, addrBytes);
+    }
+
+    /**
      * Returns a string representation of this {@code IpPrefix}.
      *
      * @return a string such as {@code "192.0.2.0/24"} or {@code "2001:db8:1:2::/64"}.
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 4ce9e68..67ecb5d 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -16,7 +16,6 @@
 
 package android.net;
 
-import android.net.NetworkUtils;
 import android.os.Parcelable;
 import android.os.Parcel;
 import android.system.ErrnoException;
@@ -27,22 +26,20 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
-import java.net.ProxySelector;
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.net.URL;
 import java.net.URLConnection;
-import java.net.URLStreamHandler;
-import java.util.concurrent.atomic.AtomicReference;
 import javax.net.SocketFactory;
 
 import com.android.okhttp.ConnectionPool;
-import com.android.okhttp.HostResolver;
 import com.android.okhttp.HttpHandler;
 import com.android.okhttp.HttpsHandler;
 import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.OkUrlFactory;
+import com.android.okhttp.internal.Internal;
 
 /**
  * Identifies a {@code Network}.  This is supplied to applications via
@@ -63,10 +60,10 @@
     // Objects used to perform per-network operations such as getSocketFactory
     // and openConnection, and a lock to protect access to them.
     private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null;
-    // mLock should be used to control write access to mConnectionPool and mHostResolver.
+    // mLock should be used to control write access to mConnectionPool and mNetwork.
     // maybeInitHttpClient() must be called prior to reading either variable.
     private volatile ConnectionPool mConnectionPool = null;
-    private volatile HostResolver mHostResolver = null;
+    private volatile com.android.okhttp.internal.Network mNetwork = null;
     private Object mLock = new Object();
 
     // Default connection pool values. These are evaluated at startup, just
@@ -220,10 +217,10 @@
     // out) ConnectionPools.
     private void maybeInitHttpClient() {
         synchronized (mLock) {
-            if (mHostResolver == null) {
-                mHostResolver = new HostResolver() {
+            if (mNetwork == null) {
+                mNetwork = new com.android.okhttp.internal.Network() {
                     @Override
-                    public InetAddress[] getAllByName(String host) throws UnknownHostException {
+                    public InetAddress[] resolveInetAddresses(String host) throws UnknownHostException {
                         return Network.this.getAllByName(host);
                     }
                 };
@@ -280,22 +277,25 @@
         if (proxy == null) throw new IllegalArgumentException("proxy is null");
         maybeInitHttpClient();
         String protocol = url.getProtocol();
-        OkHttpClient client;
-        // TODO: HttpHandler creates OkHttpClients that share the default ResponseCache.
+        OkUrlFactory okUrlFactory;
+        // TODO: HttpHandler creates OkUrlFactory instances that share the default ResponseCache.
         // Could this cause unexpected behavior?
         if (protocol.equals("http")) {
-            client = HttpHandler.createHttpOkHttpClient(proxy);
+            okUrlFactory = HttpHandler.createHttpOkUrlFactory(proxy);
         } else if (protocol.equals("https")) {
-            client = HttpsHandler.createHttpsOkHttpClient(proxy);
+            okUrlFactory = HttpsHandler.createHttpsOkUrlFactory(proxy);
         } else {
-            // OkHttpClient only supports HTTP and HTTPS and returns a null URLStreamHandler if
+            // OkHttp only supports HTTP and HTTPS and returns a null URLStreamHandler if
             // passed another protocol.
             throw new MalformedURLException("Invalid URL or unrecognized protocol " + protocol);
         }
-        return client.setSocketFactory(getSocketFactory())
-                .setHostResolver(mHostResolver)
-                .setConnectionPool(mConnectionPool)
-                .open(url);
+        OkHttpClient client = okUrlFactory.client();
+        client.setSocketFactory(getSocketFactory()).setConnectionPool(mConnectionPool);
+
+        // Use internal APIs to change the Network.
+        Internal.instance.setNetwork(client, mNetwork);
+
+        return okUrlFactory.open(url);
     }
 
     /**
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index c33aa2f..9c3a623 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -21,15 +21,12 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.Log;
 
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 
 import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * A Utility class for handling for communicating between bearer-specific
@@ -113,7 +110,7 @@
     public static final int EVENT_UID_RANGES_REMOVED = BASE + 6;
 
     /**
-     * Sent by ConnectivitySerice to the NetworkAgent to inform the agent of the
+     * Sent by ConnectivityService to the NetworkAgent to inform the agent of the
      * networks status - whether we could use the network or could not, due to
      * either a bad network configuration (no internet link) or captive portal.
      *
@@ -128,15 +125,25 @@
      * Sent by the NetworkAgent to ConnectivityService to indicate this network was
      * explicitly selected.  This should be sent before the NetworkInfo is marked
      * CONNECTED so it can be given special treatment at that time.
+     *
+     * obj = boolean indicating whether to use this network even if unvalidated
      */
     public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8;
 
     /**
-     * Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
+     * Sent by ConnectivityService to the NetworkAgent to inform the agent of
+     * whether the network should in the future be used even if not validated.
+     * This decision is made by the user, but it is the network transport's
+     * responsibility to remember it.
+     *
+     * arg1 = 1 if true, 0 if false
+     */
+    public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
+
+    /** Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
      * the underlying network connection for updated bandwidth information.
      */
-    public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 9;
-
+    public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
 
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score) {
@@ -216,6 +223,9 @@
                 networkStatus(msg.arg1);
                 break;
             }
+            case CMD_SAVE_ACCEPT_UNVALIDATED: {
+                saveAcceptUnvalidated(msg.arg1 != 0);
+            }
         }
     }
 
@@ -284,10 +294,16 @@
     /**
      * Called by the bearer to indicate this network was manually selected by the user.
      * This should be called before the NetworkInfo is marked CONNECTED so that this
-     * Network can be given special treatment at that time.
+     * Network can be given special treatment at that time. If {@code acceptUnvalidated} is
+     * {@code true}, then the system will switch to this network. If it is {@code false} and the
+     * network cannot be validated, the system will ask the user whether to switch to this network.
+     * If the user confirms and selects "don't ask again", then the system will call
+     * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
+     * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
+     * {@link #saveAcceptUnvalidated} to respect the user's choice.
      */
-    public void explicitlySelected() {
-        queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, 0);
+    public void explicitlySelected(boolean acceptUnvalidated) {
+        queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, acceptUnvalidated);
     }
 
     /**
@@ -323,6 +339,16 @@
     protected void networkStatus(int status) {
     }
 
+    /**
+     * Called when the user asks to remember the choice to use this network even if unvalidated.
+     * The transport is responsible for remembering the choice, and the next time the user connects
+     * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}.
+     * This method will only be called if {@link #explicitlySelected} was called with
+     * {@code acceptUnvalidated} set to {@code false}.
+     */
+    protected void saveAcceptUnvalidated(boolean accept) {
+    }
+
     protected void log(String s) {
         Log.d(LOG_TAG, "NetworkAgent: " + s);
     }
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index b92c9e3..5511a24 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -45,6 +45,13 @@
     public boolean explicitlySelected;
 
     /**
+     * Set if the user desires to use this network even if it is unvalidated. This field has meaning
+     * only if {#link explicitlySelected} is true. If it is, this field must also be set to the
+     * appropriate value based on previous user choice.
+     */
+    public boolean acceptUnvalidated;
+
+    /**
      * For mobile networks, this is the subscriber ID (such as IMSI).
      */
     public String subscriberId;
@@ -56,6 +63,7 @@
         if (nm != null) {
             allowBypass = nm.allowBypass;
             explicitlySelected = nm.explicitlySelected;
+            acceptUnvalidated = nm.acceptUnvalidated;
             subscriberId = nm.subscriberId;
         }
     }
@@ -69,6 +77,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(allowBypass ? 1 : 0);
         out.writeInt(explicitlySelected ? 1 : 0);
+        out.writeInt(acceptUnvalidated ? 1 : 0);
         out.writeString(subscriberId);
     }
 
@@ -78,6 +87,7 @@
             NetworkMisc networkMisc = new NetworkMisc();
             networkMisc.allowBypass = in.readInt() != 0;
             networkMisc.explicitlySelected = in.readInt() != 0;
+            networkMisc.acceptUnvalidated = in.readInt() != 0;
             networkMisc.subscriberId = in.readString();
             return networkMisc;
         }
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 5a09b46..7838b47 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -19,8 +19,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.concurrent.atomic.AtomicInteger;
-
 /**
  * Defines a request for a network, made through {@link NetworkRequest.Builder} and used
  * to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index a3cad77..2c90909 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -21,8 +21,6 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
-import org.apache.http.client.HttpClient;
-
 import java.net.InetSocketAddress;
 import java.net.URLConnection;
 import java.util.List;
@@ -31,8 +29,9 @@
 /**
  * Describes a proxy configuration.
  *
- * Proxy configurations are already integrated within the Apache HTTP stack.
- * So {@link URLConnection} and {@link HttpClient} will use them automatically.
+ * Proxy configurations are already integrated within the {@code java.net} and
+ * Apache HTTP stack. So {@link URLConnection} and Apache's {@code HttpClient} will use
+ * them automatically.
  *
  * Other HTTP stacks will need to obtain the proxy info from
  * {@link Proxy#PROXY_CHANGE_ACTION} broadcast as the extra {@link Proxy#EXTRA_PROXY_INFO}.
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index cfd20a0..90a2460 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -367,13 +367,7 @@
      * @return {@code true} if the destination and prefix length cover the given address.
      */
     public boolean matches(InetAddress destination) {
-        if (destination == null) return false;
-
-        // match the route destination and destination with prefix length
-        InetAddress dstNet = NetworkUtils.getNetworkPart(destination,
-                mDestination.getPrefixLength());
-
-        return mDestination.getAddress().equals(dstNet);
+        return mDestination.contains(destination);
     }
 
     /**
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index 365f2b6..7f1b179 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -21,7 +21,6 @@
 import android.os.Parcel;
 
 import java.net.InetAddress;
-import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -188,6 +187,7 @@
         for (InetAddress dnsServer : dnsServers) {
             NetworkUtils.parcelInetAddress(dest, dnsServer, flags);
         }
+        dest.writeString(domains);
     }
 
     protected static void readFromParcel(StaticIpConfiguration s, Parcel in) {
@@ -198,5 +198,6 @@
         for (int i = 0; i < size; i++) {
             s.dnsServers.add(NetworkUtils.unparcelInetAddress(in));
         }
+        s.domains = in.readString();
     }
 }
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 52da137..60e8ed0 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -19,7 +19,6 @@
 #include "jni.h"
 #include "JNIHelp.h"
 #include "NetdClient.h"
-#include "resolv_netid.h"
 #include <utils/misc.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
@@ -34,6 +33,8 @@
 #include <netinet/udp.h>
 #include <cutils/properties.h>
 
+#include "core_jni_helpers.h"
+
 extern "C" {
 int ifc_enable(const char *ifname);
 int ifc_disable(const char *ifname);
@@ -315,27 +316,26 @@
 
 int register_android_net_NetworkUtils(JNIEnv* env)
 {
-    jclass dhcpResultsClass = env->FindClass("android/net/DhcpResults");
-    LOG_FATAL_IF(dhcpResultsClass == NULL, "Unable to find class android/net/DhcpResults");
-    dhcpResultsFieldIds.clear =
-            env->GetMethodID(dhcpResultsClass, "clear", "()V");
-    dhcpResultsFieldIds.setIpAddress =
-            env->GetMethodID(dhcpResultsClass, "setIpAddress", "(Ljava/lang/String;I)Z");
-    dhcpResultsFieldIds.setGateway =
-            env->GetMethodID(dhcpResultsClass, "setGateway", "(Ljava/lang/String;)Z");
-    dhcpResultsFieldIds.addDns =
-            env->GetMethodID(dhcpResultsClass, "addDns", "(Ljava/lang/String;)Z");
-    dhcpResultsFieldIds.setDomains =
-            env->GetMethodID(dhcpResultsClass, "setDomains", "(Ljava/lang/String;)V");
-    dhcpResultsFieldIds.setServerAddress =
-            env->GetMethodID(dhcpResultsClass, "setServerAddress", "(Ljava/lang/String;)Z");
-    dhcpResultsFieldIds.setLeaseDuration =
-            env->GetMethodID(dhcpResultsClass, "setLeaseDuration", "(I)V");
-    dhcpResultsFieldIds.setVendorInfo =
-            env->GetMethodID(dhcpResultsClass, "setVendorInfo", "(Ljava/lang/String;)V");
+    jclass dhcpResultsClass = FindClassOrDie(env, "android/net/DhcpResults");
 
-    return AndroidRuntime::registerNativeMethods(env,
-            NETUTILS_PKG_NAME, gNetworkUtilMethods, NELEM(gNetworkUtilMethods));
+    dhcpResultsFieldIds.clear = GetMethodIDOrDie(env, dhcpResultsClass, "clear", "()V");
+    dhcpResultsFieldIds.setIpAddress =GetMethodIDOrDie(env, dhcpResultsClass, "setIpAddress",
+            "(Ljava/lang/String;I)Z");
+    dhcpResultsFieldIds.setGateway = GetMethodIDOrDie(env, dhcpResultsClass, "setGateway",
+            "(Ljava/lang/String;)Z");
+    dhcpResultsFieldIds.addDns = GetMethodIDOrDie(env, dhcpResultsClass, "addDns",
+            "(Ljava/lang/String;)Z");
+    dhcpResultsFieldIds.setDomains = GetMethodIDOrDie(env, dhcpResultsClass, "setDomains",
+            "(Ljava/lang/String;)V");
+    dhcpResultsFieldIds.setServerAddress = GetMethodIDOrDie(env, dhcpResultsClass,
+            "setServerAddress", "(Ljava/lang/String;)Z");
+    dhcpResultsFieldIds.setLeaseDuration = GetMethodIDOrDie(env, dhcpResultsClass,
+            "setLeaseDuration", "(I)V");
+    dhcpResultsFieldIds.setVendorInfo = GetMethodIDOrDie(env, dhcpResultsClass, "setVendorInfo",
+            "(Ljava/lang/String;)V");
+
+    return RegisterMethodsOrDie(env, NETUTILS_PKG_NAME, gNetworkUtilMethods,
+                                NELEM(gNetworkUtilMethods));
 }
 
 }; // namespace android
diff --git a/core/tests/coretests/src/android/net/IpPrefixTest.java b/core/tests/coretests/src/android/net/IpPrefixTest.java
index cf278fb..fcc6389 100644
--- a/core/tests/coretests/src/android/net/IpPrefixTest.java
+++ b/core/tests/coretests/src/android/net/IpPrefixTest.java
@@ -29,6 +29,10 @@
 
 public class IpPrefixTest extends TestCase {
 
+    private static InetAddress Address(String addr) {
+        return InetAddress.parseNumericAddress(addr);
+    }
+
     // Explicitly cast everything to byte because "error: possible loss of precision".
     private static final byte[] IPV4_BYTES = { (byte) 192, (byte) 0, (byte) 2, (byte) 4};
     private static final byte[] IPV6_BYTES = {
@@ -209,6 +213,34 @@
     }
 
     @SmallTest
+    public void testContains() {
+        IpPrefix p = new IpPrefix("2001:db8:f00::ace:d00d/127");
+        assertTrue(p.contains(Address("2001:db8:f00::ace:d00c")));
+        assertTrue(p.contains(Address("2001:db8:f00::ace:d00d")));
+        assertFalse(p.contains(Address("2001:db8:f00::ace:d00e")));
+        assertFalse(p.contains(Address("2001:db8:f00::bad:d00d")));
+        assertFalse(p.contains(Address("2001:4868:4860::8888")));
+        assertFalse(p.contains(null));
+        assertFalse(p.contains(Address("8.8.8.8")));
+
+        p = new IpPrefix("192.0.2.0/23");
+        assertTrue(p.contains(Address("192.0.2.43")));
+        assertTrue(p.contains(Address("192.0.3.21")));
+        assertFalse(p.contains(Address("192.0.0.21")));
+        assertFalse(p.contains(Address("8.8.8.8")));
+        assertFalse(p.contains(Address("2001:4868:4860::8888")));
+
+        IpPrefix ipv6Default = new IpPrefix("::/0");
+        assertTrue(ipv6Default.contains(Address("2001:db8::f00")));
+        assertFalse(ipv6Default.contains(Address("192.0.2.1")));
+
+        IpPrefix ipv4Default = new IpPrefix("0.0.0.0/0");
+        assertTrue(ipv4Default.contains(Address("255.255.255.255")));
+        assertTrue(ipv4Default.contains(Address("192.0.2.1")));
+        assertFalse(ipv4Default.contains(Address("2001:db8::f00")));
+    }
+
+    @SmallTest
     public void testHashCode() {
         IpPrefix p;
         int oldCode = -1;
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 9ee4e20..a470de1 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -18,6 +18,8 @@
 
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.SET_DBG_VPN_IN;
+import static android.net.NetworkStats.SET_DBG_VPN_OUT;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.TAG_NONE;
@@ -320,6 +322,92 @@
         red.combineAllValues(blue);
     }
 
+    public void testMigrateTun() throws Exception {
+        final int tunUid = 10030;
+        final String tunIface = "tun0";
+        final String underlyingIface = "wlan0";
+        final int testTag1 = 8888;
+        NetworkStats delta = new NetworkStats(TEST_START, 17)
+            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, 39605L, 46L, 12259L, 55L, 0L)
+            .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+            .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, 72667L, 197L, 43909L, 241L, 0L)
+            .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, 9297L, 17L, 4128L, 21L, 0L)
+            // VPN package also uses some traffic through unprotected network.
+            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, 4983L, 10L, 1801L, 12L, 0L)
+            .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+            // Tag entries
+            .addValues(tunIface, 10120, SET_DEFAULT, testTag1, 21691L, 41L, 13820L, 51L, 0L)
+            .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, 1281L, 2L, 665L, 2L, 0L)
+            // Irrelevant entries
+            .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, 1685L, 5L, 2070L, 6L, 0L)
+            // Underlying Iface entries
+            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, 5178L, 8L, 2139L, 11L, 0L)
+            .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, 149873L, 287L,
+                    59217L /* smaller than sum(tun0) */, 299L /* smaller than sum(tun0) */, 0L)
+            .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+
+        assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface));
+        assertEquals(21, delta.size());
+
+        // tunIface and TEST_IFACE entries are not changed.
+        assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE,
+                39605L, 46L, 12259L, 55L, 0L);
+        assertValues(delta, 1, tunIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+        assertValues(delta, 2, tunIface, 10120, SET_DEFAULT, TAG_NONE,
+                72667L, 197L, 43909L, 241L, 0L);
+        assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE,
+                9297L, 17L, 4128L, 21L, 0L);
+        assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE,
+                4983L, 10L, 1801L, 12L, 0L);
+        assertValues(delta, 5, tunIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+        assertValues(delta, 6, tunIface, 10120, SET_DEFAULT, testTag1,
+                21691L, 41L, 13820L, 51L, 0L);
+        assertValues(delta, 7, tunIface, 10120, SET_FOREGROUND, testTag1, 1281L, 2L, 665L, 2L, 0L);
+        assertValues(delta, 8, TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, 1685L, 5L, 2070L, 6L, 0L);
+
+        // Existing underlying Iface entries are updated
+        assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE,
+                44783L, 54L, 13829L, 60L, 0L);
+        assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE,
+                0L, 0L, 0L, 0L, 0L);
+
+        // VPN underlying Iface entries are updated
+        assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
+                28304L, 27L, 1719L, 12L, 0L);
+        assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
+                0L, 0L, 0L, 0L, 0L);
+
+        // New entries are added for new application's underlying Iface traffic
+        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, TAG_NONE,
+                72667L, 197L, 41872l, 219L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE,
+                9297L, 17L, 3936, 19L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, testTag1,
+                21691L, 41L, 13179L, 46L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, testTag1,
+                1281L, 2L, 634L, 1L, 0L);
+
+        // New entries are added for debug purpose
+        assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE,
+                39605L, 46L, 11690, 49, 0);
+        assertContains(delta, underlyingIface, 10120, SET_DBG_VPN_IN, TAG_NONE,
+                81964, 214, 45808, 238, 0);
+        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_IN, TAG_NONE,
+                4983, 10, 1717, 10, 0);
+        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE,
+                126552, 270, 59215, 297, 0);
+
+    }
+
+    private static void assertContains(NetworkStats stats,  String iface, int uid, int set,
+            int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+        int index = stats.findIndex(iface, uid, set, tag);
+        assertTrue(index != -1);
+        assertValues(stats, index, iface, uid, set, tag,
+                rxBytes, rxPackets, txBytes, txPackets, operations);
+    }
+
     private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
             int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
         final NetworkStats.Entry entry = stats.getValues(index, null);
diff --git a/core/tests/coretests/src/android/net/RouteInfoTest.java b/core/tests/coretests/src/android/net/RouteInfoTest.java
index 0b88bc7..831fefd 100644
--- a/core/tests/coretests/src/android/net/RouteInfoTest.java
+++ b/core/tests/coretests/src/android/net/RouteInfoTest.java
@@ -90,6 +90,7 @@
         assertFalse(r.matches(Address("2001:db8:f00::ace:d00e")));
         assertFalse(r.matches(Address("2001:db8:f00::bad:d00d")));
         assertFalse(r.matches(Address("2001:4868:4860::8888")));
+        assertFalse(r.matches(Address("8.8.8.8")));
 
         r = new PatchedRouteInfo(Prefix("192.0.2.0/23"), null, "wlan0");
         assertTrue(r.matches(Address("192.0.2.43")));
diff --git a/core/tests/coretests/src/android/net/StaticIpConfigurationTest.java b/core/tests/coretests/src/android/net/StaticIpConfigurationTest.java
new file mode 100644
index 0000000..59f780f
--- /dev/null
+++ b/core/tests/coretests/src/android/net/StaticIpConfigurationTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+import android.net.StaticIpConfiguration;
+import android.os.Parcel;
+
+import java.net.InetAddress;
+import java.util.HashSet;
+
+import junit.framework.TestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static org.junit.Assert.*;
+
+
+public class StaticIpConfigurationTest extends TestCase {
+
+    private static final String ADDRSTR = "192.0.2.2/25";
+    private static final LinkAddress ADDR = new LinkAddress(ADDRSTR);
+    private static final InetAddress GATEWAY = IpAddress("192.0.2.1");
+    private static final InetAddress OFFLINKGATEWAY = IpAddress("192.0.2.129");
+    private static final InetAddress DNS1 = IpAddress("8.8.8.8");
+    private static final InetAddress DNS2 = IpAddress("8.8.4.4");
+    private static final InetAddress DNS3 = IpAddress("4.2.2.2");
+    private static final String IFACE = "eth0";
+
+    private static InetAddress IpAddress(String addr) {
+        return InetAddress.parseNumericAddress(addr);
+    }
+
+    private void checkEmpty(StaticIpConfiguration s) {
+        assertNull(s.ipAddress);
+        assertNull(s.gateway);
+        assertNull(s.domains);
+        assertEquals(0, s.dnsServers.size());
+    }
+
+    private boolean isEqual(StaticIpConfiguration s1, StaticIpConfiguration s2) {
+        return s1.equals(s2);
+    }
+
+    private void assertEquals(StaticIpConfiguration s1, StaticIpConfiguration s2) {
+        assertTrue(isEqual(s1, s2));
+    }
+
+    private void assertNotEquals(StaticIpConfiguration s1, StaticIpConfiguration s2) {
+        assertFalse(isEqual(s1, s2));
+    }
+
+    private StaticIpConfiguration makeTestObject() {
+        StaticIpConfiguration s = new StaticIpConfiguration();
+        s.ipAddress = ADDR;
+        s.gateway = GATEWAY;
+        s.dnsServers.add(DNS1);
+        s.dnsServers.add(DNS2);
+        s.dnsServers.add(DNS3);
+        s.domains = "google.com";
+        return s;
+    }
+
+    @SmallTest
+    public void testConstructor() {
+        StaticIpConfiguration s = new StaticIpConfiguration();
+        checkEmpty(s);
+    }
+
+    @SmallTest
+    public void testCopyAndClear() {
+        StaticIpConfiguration empty = new StaticIpConfiguration((StaticIpConfiguration) null);
+        checkEmpty(empty);
+
+        StaticIpConfiguration s1 = makeTestObject();
+        StaticIpConfiguration s2 = new StaticIpConfiguration(s1);
+        assertEquals(s1, s2);
+        s2.clear();
+        assertEquals(empty, s2);
+    }
+
+    @SmallTest
+    public void testHashCodeAndEquals() {
+        HashSet<Integer> hashCodes = new HashSet();
+        hashCodes.add(0);
+
+        StaticIpConfiguration s = new StaticIpConfiguration();
+        // Check that this hash code is nonzero and different from all the ones seen so far.
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.ipAddress = ADDR;
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.gateway = GATEWAY;
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.dnsServers.add(DNS1);
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.dnsServers.add(DNS2);
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.dnsServers.add(DNS3);
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.domains = "example.com";
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        assertFalse(s.equals(null));
+        assertEquals(s, s);
+
+        StaticIpConfiguration s2 = new StaticIpConfiguration(s);
+        assertEquals(s, s2);
+
+        s.ipAddress = new LinkAddress(DNS1, 32);
+        assertNotEquals(s, s2);
+
+        s2 = new StaticIpConfiguration(s);
+        s.domains = "foo";
+        assertNotEquals(s, s2);
+
+        s2 = new StaticIpConfiguration(s);
+        s.gateway = DNS2;
+        assertNotEquals(s, s2);
+
+        s2 = new StaticIpConfiguration(s);
+        s.dnsServers.add(DNS3);
+        assertNotEquals(s, s2);
+    }
+
+    @SmallTest
+    public void testToLinkProperties() {
+        LinkProperties expected = new LinkProperties();
+        expected.setInterfaceName(IFACE);
+
+        StaticIpConfiguration s = new StaticIpConfiguration();
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        final RouteInfo connectedRoute = new RouteInfo(new IpPrefix(ADDRSTR), null, IFACE);
+        s.ipAddress = ADDR;
+        expected.addLinkAddress(ADDR);
+        expected.addRoute(connectedRoute);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.gateway = GATEWAY;
+        RouteInfo defaultRoute = new RouteInfo(new IpPrefix("0.0.0.0/0"), GATEWAY, IFACE);
+        expected.addRoute(defaultRoute);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.gateway = OFFLINKGATEWAY;
+        expected.removeRoute(defaultRoute);
+        defaultRoute = new RouteInfo(new IpPrefix("0.0.0.0/0"), OFFLINKGATEWAY, IFACE);
+        expected.addRoute(defaultRoute);
+
+        RouteInfo gatewayRoute = new RouteInfo(new IpPrefix("192.0.2.129/32"), null, IFACE);
+        expected.addRoute(gatewayRoute);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.dnsServers.add(DNS1);
+        expected.addDnsServer(DNS1);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.dnsServers.add(DNS2);
+        s.dnsServers.add(DNS3);
+        expected.addDnsServer(DNS2);
+        expected.addDnsServer(DNS3);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.domains = "google.com";
+        expected.setDomains("google.com");
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.gateway = null;
+        expected.removeRoute(defaultRoute);
+        expected.removeRoute(gatewayRoute);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        // Without knowing the IP address, we don't have a directly-connected route, so we can't
+        // tell if the gateway is off-link or not and we don't add a host route. This isn't a real
+        // configuration, but we should at least not crash.
+        s.gateway = OFFLINKGATEWAY;
+        s.ipAddress = null;
+        expected.removeLinkAddress(ADDR);
+        expected.removeRoute(connectedRoute);
+        expected.addRoute(defaultRoute);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+    }
+
+    private StaticIpConfiguration passThroughParcel(StaticIpConfiguration s) {
+        Parcel p = Parcel.obtain();
+        StaticIpConfiguration s2 = null;
+        try {
+            s.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            s2 = StaticIpConfiguration.CREATOR.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
+        assertNotNull(s2);
+        return s2;
+    }
+
+    @SmallTest
+    public void testParceling() {
+        StaticIpConfiguration s = makeTestObject();
+        StaticIpConfiguration s2 = passThroughParcel(s);
+        assertEquals(s, s2);
+    }
+}
+
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 64a924a..12a99b0 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -16,7 +16,6 @@
 
 package com.android.server;
 
-import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.NETID_UNSET;
@@ -27,6 +26,7 @@
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -99,6 +99,7 @@
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.NetworkStatsFactory;
 import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 import com.android.internal.telephony.DctConstants;
 import com.android.internal.util.AsyncChannel;
@@ -161,6 +162,10 @@
     private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
             "android.telephony.apn-restore";
 
+    // How long to wait before putting up a "This network doesn't have an Internet connection,
+    // connect anyway?" dialog after the user selects a network that doesn't validate.
+    private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
+
     // How long to delay to removal of a pending intent based request.
     // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
     private final int mReleasePendingIntentDelayMs;
@@ -324,6 +329,19 @@
      */
     private static final int EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT = 27;
 
+    /**
+     * used to specify whether a network should be used even if unvalidated.
+     * arg1 = whether to accept the network if it's unvalidated (1 or 0)
+     * arg2 = whether to remember this choice in the future (1 or 0)
+     * obj  = network
+     */
+    private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
+
+    /**
+     * used to ask the user to confirm a connection to an unvalidated network.
+     * obj  = network
+     */
+    private static final int EVENT_PROMPT_UNVALIDATED = 29;
 
     /** Handler used for internal events. */
     final private InternalHandler mHandler;
@@ -1511,7 +1529,7 @@
                 NetworkCapabilities.TRANSPORT_WIFI)) {
             timeout = Settings.Global.getInt(mContext.getContentResolver(),
                                              Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
-                                             0);
+                                             5);
             type = ConnectivityManager.TYPE_WIFI;
         } else {
             // do not track any other networks
@@ -1867,6 +1885,7 @@
                         loge("ERROR: created network explicitly selected.");
                     }
                     nai.networkMisc.explicitlySelected = true;
+                    nai.networkMisc.acceptUnvalidated = (boolean) msg.obj;
                     break;
                 }
                 case NetworkMonitor.EVENT_NETWORK_TESTED: {
@@ -1890,6 +1909,9 @@
                                 android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS,
                                 (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
                                 0, null);
+
+                        // TODO: trigger a NetworkCapabilities update so that the dialog can know
+                        // that the network is now validated and close itself.
                     }
                     break;
                 }
@@ -2049,6 +2071,8 @@
                         ReapUnvalidatedNetworks.DONT_REAP);
             }
         }
+        NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
+        if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
     }
 
     // If this method proves to be too slow then we can maintain a separate
@@ -2251,6 +2275,91 @@
         }
     }
 
+    public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
+        enforceConnectivityInternalPermission();
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
+                accept ? 1 : 0, always ? 1: 0, network));
+    }
+
+    private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
+        if (DBG) log("handleSetAcceptUnvalidated network=" + network +
+                " accept=" + accept + " always=" + always);
+
+        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+        if (nai == null) {
+            // Nothing to do.
+            return;
+        }
+
+        if (nai.everValidated) {
+            // The network validated while the dialog box was up. Don't make any changes. There's a
+            // TODO in the dialog code to make it go away if the network validates; once that's
+            // implemented, taking action here will be confusing.
+            return;
+        }
+
+        if (!nai.networkMisc.explicitlySelected) {
+            Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network");
+        }
+
+        if (accept != nai.networkMisc.acceptUnvalidated) {
+            int oldScore = nai.getCurrentScore();
+            nai.networkMisc.acceptUnvalidated = accept;
+            rematchAllNetworksAndRequests(nai, oldScore);
+            sendUpdatedScoreToFactories(nai);
+        }
+
+        if (always) {
+            nai.asyncChannel.sendMessage(
+                    NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0);
+        }
+
+        // TODO: should we also disconnect from the network if accept is false?
+    }
+
+    private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network),
+                PROMPT_UNVALIDATED_DELAY_MS);
+    }
+
+    private void handlePromptUnvalidated(Network network) {
+        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+
+        // Only prompt if the network is unvalidated and was explicitly selected by the user, and if
+        // we haven't already been told to switch to it regardless of whether it validated or not.
+        if (nai == null || nai.everValidated ||
+                !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
+            return;
+        }
+
+        // TODO: What should we do if we've already switched to this network because we had no
+        // better option? There are two obvious alternatives.
+        //
+        // 1. Decide that there's no point prompting because this is our only usable network.
+        //    However, because we didn't prompt, if later on a validated network comes along, we'll
+        //    either a) silently switch to it - bad if the user wanted to connect to stay on this
+        //    unvalidated network - or b) prompt the user at that later time - bad because the user
+        //    might not understand why they are now being prompted.
+        //
+        // 2. Always prompt the user, even if we have no other network to use. The user could then
+        //    try to find an alternative network to join (remember, if we got here, then the user
+        //    selected this network manually). This is bad because the prompt isn't really very
+        //    useful.
+        //
+        // For now we do #1, but we can revisit that later.
+        if (isDefaultNetwork(nai)) {
+            return;
+        }
+
+        Intent intent = new Intent(ConnectivityManager.ACTION_PROMPT_UNVALIDATED);
+        intent.putExtra(ConnectivityManager.EXTRA_NETWORK, network);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClassName("com.android.settings",
+                "com.android.settings.wifi.WifiNoInternetDialog");
+        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+    }
+
     private class InternalHandler extends Handler {
         public InternalHandler(Looper looper) {
             super(looper);
@@ -2321,6 +2430,14 @@
                     handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
                     break;
                 }
+                case EVENT_SET_ACCEPT_UNVALIDATED: {
+                    handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0);
+                    break;
+                }
+                case EVENT_PROMPT_UNVALIDATED: {
+                    handlePromptUnvalidated((Network) msg.obj);
+                    break;
+                }
                 case EVENT_SYSTEM_READY: {
                     for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
                         nai.networkMonitor.systemReady = true;
@@ -2812,7 +2929,6 @@
      * Return the information of the ongoing legacy VPN. This method is used
      * by VpnSettings and not available in ConnectivityManager. Permissions
      * are checked in Vpn class.
-     * @hide
      */
     @Override
     public LegacyVpnInfo getLegacyVpnInfo() {
@@ -2824,6 +2940,56 @@
     }
 
     /**
+     * Return the information of all ongoing VPNs. This method is used by NetworkStatsService
+     * and not available in ConnectivityManager.
+     */
+    @Override
+    public VpnInfo[] getAllVpnInfo() {
+        enforceConnectivityInternalPermission();
+        if (mLockdownEnabled) {
+            return new VpnInfo[0];
+        }
+
+        synchronized(mVpns) {
+            List<VpnInfo> infoList = new ArrayList<>();
+            for (int i = 0; i < mVpns.size(); i++) {
+                VpnInfo info = createVpnInfo(mVpns.valueAt(i));
+                if (info != null) {
+                    infoList.add(info);
+                }
+            }
+            return infoList.toArray(new VpnInfo[infoList.size()]);
+        }
+    }
+
+    /**
+     * @return VPN information for accounting, or null if we can't retrieve all required
+     *         information, e.g primary underlying iface.
+     */
+    @Nullable
+    private VpnInfo createVpnInfo(Vpn vpn) {
+        VpnInfo info = vpn.getVpnInfo();
+        if (info == null) {
+            return null;
+        }
+        Network[] underlyingNetworks = vpn.getUnderlyingNetworks();
+        // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
+        // the underlyingNetworks list.
+        if (underlyingNetworks == null) {
+            NetworkAgentInfo defaultNetwork = getDefaultNetwork();
+            if (defaultNetwork != null && defaultNetwork.linkProperties != null) {
+                info.primaryUnderlyingIface = getDefaultNetwork().linkProperties.getInterfaceName();
+            }
+        } else if (underlyingNetworks.length > 0) {
+            LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]);
+            if (linkProperties != null) {
+                info.primaryUnderlyingIface = linkProperties.getInterfaceName();
+            }
+        }
+        return info.primaryUnderlyingIface == null ? null : info;
+    }
+
+    /**
      * Returns the information of the ongoing VPN. This method is used by VpnDialogs and
      * not available in ConnectivityManager.
      * Permissions are checked in Vpn class.
@@ -2970,7 +3136,7 @@
             notification.icon = icon;
             notification.flags = Notification.FLAG_AUTO_CANCEL;
             notification.tickerText = title;
-            notification.color = mContext.getResources().getColor(
+            notification.color = mContext.getColor(
                     com.android.internal.R.color.system_notification_accent_color);
             notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
             notification.contentIntent = intent;
@@ -3298,7 +3464,7 @@
     }
 
     @Override
-    public boolean requestBwUpdate(Network network) {
+    public boolean requestBandwidthUpdate(Network network) {
         enforceAccessPermission();
         NetworkAgentInfo nai = null;
         if (network == null) {
@@ -3362,10 +3528,34 @@
                 getCallingUid(), 0, operation));
     }
 
+    // In order to implement the compatibility measure for pre-M apps that call
+    // WifiManager.enableNetwork(..., true) without also binding to that network explicitly,
+    // WifiManager registers a network listen for the purpose of calling setProcessDefaultNetwork.
+    // This ensures it has permission to do so.
+    private boolean hasWifiNetworkListenPermission(NetworkCapabilities nc) {
+        if (nc == null) {
+            return false;
+        }
+        int[] transportTypes = nc.getTransportTypes();
+        if (transportTypes.length != 1 || transportTypes[0] != NetworkCapabilities.TRANSPORT_WIFI) {
+            return false;
+        }
+        try {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.ACCESS_WIFI_STATE,
+                    "ConnectivityService");
+        } catch (SecurityException e) {
+            return false;
+        }
+        return true;
+    }
+
     @Override
     public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
             Messenger messenger, IBinder binder) {
-        enforceAccessPermission();
+        if (!hasWifiNetworkListenPermission(networkCapabilities)) {
+            enforceAccessPermission();
+        }
 
         NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities(
                 networkCapabilities), TYPE_NONE, nextNetworkRequestId());
@@ -4097,8 +4287,10 @@
             networkAgent.created = true;
             updateLinkProperties(networkAgent, null);
             notifyIfacesChanged();
-            notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
+
             networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+            scheduleUnvalidatedPrompt(networkAgent);
+
             if (networkAgent.isVPN()) {
                 // Temporarily disable the default proxy (not global).
                 synchronized (mProxyLock) {
@@ -4111,9 +4303,13 @@
                 }
                 // TODO: support proxy per network.
             }
+
             // Consider network even though it is not yet validated.
             rematchNetworkAndRequests(networkAgent, NascentState.NOT_JUST_VALIDATED,
                     ReapUnvalidatedNetworks.REAP);
+
+            // This has to happen after matching the requests, because callbacks are just requests.
+            notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
         } else if (state == NetworkInfo.State.DISCONNECTED ||
                 state == NetworkInfo.State.SUSPENDED) {
             networkAgent.asyncChannel.disconnect();
@@ -4268,8 +4464,39 @@
     public boolean setUnderlyingNetworksForVpn(Network[] networks) {
         throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
+        boolean success;
         synchronized (mVpns) {
-            return mVpns.get(user).setUnderlyingNetworks(networks);
+            success = mVpns.get(user).setUnderlyingNetworks(networks);
+        }
+        if (success) {
+            notifyIfacesChanged();
+        }
+        return success;
+    }
+
+    @Override
+    public void factoryReset() {
+        enforceConnectivityInternalPermission();
+        // Turn airplane mode off
+        setAirplaneMode(false);
+
+        // Untether
+        for (String tether : getTetheredIfaces()) {
+            untether(tether);
+        }
+
+        // Turn VPN off
+        VpnConfig vpnConfig = getVpnConfig();
+        if (vpnConfig != null) {
+            if (vpnConfig.legacy) {
+                prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+            } else {
+                // Prevent this app from initiating VPN connections in the future without
+                // user intervention.
+                setVpnPackageAuthorization(false);
+
+                prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 3fa21d0..a9eaeee 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -22,16 +22,13 @@
 import java.net.Inet4Address;
 
 import android.content.Context;
-import android.net.IConnectivityManager;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkAgent;
-import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.os.Handler;
 import android.os.Message;
-import android.os.Messenger;
 import android.os.INetworkManagementService;
 import android.os.RemoteException;
 import android.util.Slog;
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 2d1f939..8a7c902 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -71,7 +71,10 @@
     private static final int UNVALIDATED_SCORE_PENALTY = 40;
 
     // Score for explicitly connected network.
-    private static final int EXPLICITLY_SELECTED_NETWORK_SCORE = 100;
+    //
+    // This ensures that a) the explicitly selected network is never trumped by anything else, and
+    // b) the explicitly selected network is never torn down.
+    private static final int MAXIMUM_NETWORK_SCORE = 100;
 
     // The list of NetworkRequests being satisfied by this Network.
     public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>();
@@ -123,13 +126,18 @@
         // score.  The NetworkScore class would provide a nice place to centralize score constants
         // so they are not scattered about the transports.
 
-        int score = currentScore;
+        // If this network is explicitly selected and the user has decided to use it even if it's
+        // unvalidated, give it the maximum score. Also give it the maximum score if it's explicitly
+        // selected and we're trying to see what its score could be. This ensures that we don't tear
+        // down an explicitly selected network before the user gets a chance to prefer it when
+        // a higher-scoring network (e.g., Ethernet) is available.
+        if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
+            return MAXIMUM_NETWORK_SCORE;
+        }
 
+        int score = currentScore;
         if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY;
         if (score < 0) score = 0;
-
-        if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE;
-
         return score;
     }
 
@@ -156,7 +164,9 @@
                 networkCapabilities + "}  Score{" + getCurrentScore() + "}  " +
                 "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  " +
                 "created{" + created + "}  " +
-                "explicitlySelected{" + networkMisc.explicitlySelected + "} }";
+                "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
+                "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
+                "}";
     }
 
     public String name() {
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 00af71d..90b4f43 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -39,6 +39,7 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static org.easymock.EasyMock.anyInt;
 import static org.easymock.EasyMock.anyLong;
 import static org.easymock.EasyMock.capture;
 import static org.easymock.EasyMock.createMock;
@@ -879,7 +880,7 @@
         expectLastCall().anyTimes();
 
         mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
-                isA(PendingIntent.class), isA(WorkSource.class),
+                anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
                 isA(AlarmManager.AlarmClockInfo.class));
         expectLastCall().atLeastOnce();