Rework the per-network URL API.

This addresses API council comments.

Bug: 17112978
Change-Id: I698b243b2b685d1f25414cee72450be3ae0c2bf0
diff --git a/api/current.txt b/api/current.txt
index 9a9d5f6..a09fc3b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -17212,12 +17212,12 @@
   }
 
   public class Network implements android.os.Parcelable {
+    method public void bindSocket(java.net.Socket) throws java.io.IOException;
     method public int describeContents();
     method public java.net.InetAddress[] getAllByName(java.lang.String) throws java.net.UnknownHostException;
-    method public java.net.URL getBoundURL(java.net.URL) throws java.net.MalformedURLException;
     method public java.net.InetAddress getByName(java.lang.String) throws java.net.UnknownHostException;
     method public javax.net.SocketFactory getSocketFactory();
-    method public static void setNetworkBoundURLFactory(android.net.NetworkBoundURLFactory);
+    method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException;
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
   }
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 0de3f26..d2a4728 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -16,10 +16,10 @@
 
 package android.net;
 
-import android.net.NetworkBoundURLFactory;
 import android.net.NetworkUtils;
 import android.os.Parcelable;
 import android.os.Parcel;
+import android.system.ErrnoException;
 
 import java.io.IOException;
 import java.net.InetAddress;
@@ -30,6 +30,7 @@
 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;
@@ -54,7 +55,7 @@
     public final int netId;
 
     // Objects used to perform per-network operations such as getSocketFactory
-    // and getBoundURL, and a lock to protect access to them.
+    // and openConnection, and a lock to protect access to them.
     private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null;
     private volatile OkHttpClient mOkHttpClient = null;
     private Object mLock = new Object();
@@ -157,12 +158,7 @@
         @Override
         public Socket createSocket() throws IOException {
             Socket socket = new Socket();
-            // Query a property of the underlying socket to ensure the underlying
-            // socket exists so a file descriptor is available to bind to a network.
-            socket.getReuseAddress();
-            if (!NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), mNetId)) {
-                throw new SocketException("Failed to bind socket to network.");
-            }
+            bindSocket(socket);
             return socket;
         }
     }
@@ -187,73 +183,63 @@
         return mNetworkBoundSocketFactory;
     }
 
-    /** The default NetworkBoundURLFactory, used if setNetworkBoundURLFactory is never called. */
-    private static final NetworkBoundURLFactory DEFAULT_URL_FACTORY = new NetworkBoundURLFactory() {
-        public URL getBoundURL(final Network network, URL url) throws MalformedURLException {
-            if (network.mOkHttpClient == null) {
-                synchronized (network.mLock) {
-                    if (network.mOkHttpClient == null) {
-                        HostResolver hostResolver = new HostResolver() {
-                            @Override
-                            public InetAddress[] getAllByName(String host)
-                                    throws UnknownHostException {
-                                return network.getAllByName(host);
-                            }
-                        };
-                        network.mOkHttpClient = new OkHttpClient()
-                                .setSocketFactory(network.getSocketFactory())
-                                .setHostResolver(hostResolver);
-                    }
+    private void maybeInitHttpClient() {
+        if (mOkHttpClient == null) {
+            synchronized (mLock) {
+                if (mOkHttpClient == null) {
+                    HostResolver hostResolver = new HostResolver() {
+                        @Override
+                        public InetAddress[] getAllByName(String host) throws UnknownHostException {
+                            return Network.this.getAllByName(host);
+                        }
+                    };
+                    mOkHttpClient = new OkHttpClient()
+                            .setSocketFactory(getSocketFactory())
+                            .setHostResolver(hostResolver);
                 }
             }
-
-            String protocol = url.getProtocol();
-            URLStreamHandler handler = network.mOkHttpClient.createURLStreamHandler(protocol);
-            if (handler == null) {
-                // OkHttpClient only supports HTTP and HTTPS and returns a null URLStreamHandler if
-                // passed another protocol.
-                throw new MalformedURLException("Invalid URL or unrecognized protocol " + protocol);
-            }
-            return new URL(url, "", handler);
         }
-    };
-
-    private static AtomicReference<NetworkBoundURLFactory> sNetworkBoundURLFactory =
-            new AtomicReference <NetworkBoundURLFactory>(DEFAULT_URL_FACTORY);
-
-    /**
-     * Returns a {@link URL} based on the given URL but bound to this {@code Network},
-     * such that opening the URL will send all network traffic on this Network.
-     *
-     * Note that if this {@code Network} ever disconnects, any URL object generated by this method
-     * in the past or future will cease to work.
-     *
-     * The returned URL may have a {@link URLStreamHandler} explicitly set, which may not be the
-     * handler generated by the factory set with {@link java.net.URL#setURLStreamHandlerFactory}. To
-     * affect the {@code URLStreamHandler}s of URLs returned by this method, call
-     * {@link #setNetworkBoundURLFactory}.
-     *
-     * Because the returned URLs may have an explicit {@code URLStreamHandler} set, using them as a
-     * context when constructing other URLs and explicitly specifying a {@code URLStreamHandler} may
-     * result in URLs that are no longer bound to the same {@code Network}.
-     *
-     * The default implementation only supports {@code HTTP} and {@code HTTPS} URLs.
-     *
-     * @return a {@link URL} bound to this {@code Network}.
-     */
-    public URL getBoundURL(URL url) throws MalformedURLException {
-        return sNetworkBoundURLFactory.get().getBoundURL(this, url);
     }
 
     /**
-     * Sets the {@link NetworkBoundURLFactory} to be used by future {@link #getBoundURL} calls.
-     * If {@code null}, clears any factory that was previously specified.
+     * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
+     * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
+     *
+     * @return a {@code URLConnection} to the resource referred to by this URL.
+     * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
+     * @throws IOException if an error occurs while opening the connection.
+     * @see java.net.URL#openConnection()
      */
-    public static void setNetworkBoundURLFactory(NetworkBoundURLFactory factory) {
-        if (factory == null) {
-            factory = DEFAULT_URL_FACTORY;
+    public URLConnection openConnection(URL url) throws IOException {
+        maybeInitHttpClient();
+        String protocol = url.getProtocol();
+        URLStreamHandler handler = mOkHttpClient.createURLStreamHandler(protocol);
+        if (handler == null) {
+            // OkHttpClient only supports HTTP and HTTPS and returns a null URLStreamHandler if
+            // passed another protocol.
+            throw new MalformedURLException("Invalid URL or unrecognized protocol " + protocol);
         }
-        sNetworkBoundURLFactory.set(factory);
+        return new URL(url, "", handler).openConnection();
+    }
+
+    /**
+     * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
+     * will be sent on this {@code Network}, irrespective of any process-wide network binding set by
+     * {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be connected.
+     */
+    public void bindSocket(Socket socket) throws IOException {
+        if (socket.isConnected()) {
+            throw new SocketException("Socket is connected");
+        }
+        // Query a property of the underlying socket to ensure the underlying
+        // socket exists so a file descriptor is available to bind to a network.
+        socket.getReuseAddress();
+        int err = NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), netId);
+        if (err != 0) {
+            // bindSocketToNetwork returns negative errno.
+            throw new ErrnoException("Binding socket to network " + netId, -err)
+                    .rethrowAsSocketException();
+        }
     }
 
     // implement the Parcelable interface
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 54d8676..d2a2997 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -128,8 +128,9 @@
     /**
      * Explicitly binds {@code socketfd} to the network designated by {@code netId}.  This
      * overrides any binding via {@link #bindProcessToNetwork}.
+     * @return 0 on success or negative errno on failure.
      */
-    public native static boolean bindSocketToNetwork(int socketfd, int netId);
+    public native static int bindSocketToNetwork(int socketfd, int netId);
 
     /**
      * Protect {@code socketfd} from VPN connections.  After protecting, data sent through
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 5bd38f3..8b9f574 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -236,10 +236,10 @@
     return (jboolean) !setNetworkForResolv(netId);
 }
 
-static jboolean android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket,
+static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket,
         jint netId)
 {
-    return (jboolean) !setNetworkForSocket(netId, socket);
+    return setNetworkForSocket(netId, socket);
 }
 
 static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint socket)
@@ -263,7 +263,7 @@
     { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
     { "getNetworkBoundToProcess", "()I", (void*) android_net_utils_getNetworkBoundToProcess },
     { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
-    { "bindSocketToNetwork", "(II)Z", (void*) android_net_utils_bindSocketToNetwork },
+    { "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork },
     { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
 };
 
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index ddd0865..9f4ed89 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -639,8 +639,7 @@
                 log("Checking " + url.toString() + " on " +
                         mNetworkAgentInfo.networkInfo.getExtraInfo());
             }
-            url = mNetworkAgentInfo.network.getBoundURL(url);
-            urlConnection = (HttpURLConnection) url.openConnection();
+            urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url);
             urlConnection.setInstanceFollowRedirects(false);
             urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
             urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);