diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 5e3decda77..7a1c988ba0 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -16,6 +16,7 @@ package android.net; +import android.net.NetworkBoundURLFactory; import android.net.NetworkUtils; import android.os.Parcelable; import android.os.Parcel; @@ -29,6 +30,8 @@ import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.net.URL; +import java.net.URLStreamHandler; +import java.util.concurrent.atomic.AtomicReference; import javax.net.SocketFactory; import com.android.okhttp.HostResolver; @@ -52,8 +55,8 @@ public class Network implements Parcelable { // Objects used to perform per-network operations such as getSocketFactory // and getBoundURL, and a lock to protect access to them. - private NetworkBoundSocketFactory mNetworkBoundSocketFactory = null; - private OkHttpClient mOkHttpClient = null; + private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null; + private volatile OkHttpClient mOkHttpClient = null; private Object mLock = new Object(); /** @@ -174,36 +177,83 @@ public class Network implements Parcelable { * {@code Network}. */ public SocketFactory getSocketFactory() { - synchronized (mLock) { - if (mNetworkBoundSocketFactory == null) { - mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(netId); + if (mNetworkBoundSocketFactory == null) { + synchronized (mLock) { + if (mNetworkBoundSocketFactory == null) { + mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(netId); + } } } 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); + } + } + } + + 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 sNetworkBoundURLFactory = + new AtomicReference (DEFAULT_URL_FACTORY); + /** - * Returns a {@link URL} based on the given URL but bound to this {@code Network}. - * Note that if this {@code Network} ever disconnects, this factory and any URL object it - * produced in the past or future will cease to work. + * 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 { - 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); - } + 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. + */ + public static void setNetworkBoundURLFactory(NetworkBoundURLFactory factory) { + if (factory == null) { + factory = DEFAULT_URL_FACTORY; } - return new URL(url, "", mOkHttpClient.createURLStreamHandler(url.getProtocol())); + sNetworkBoundURLFactory.set(factory); } // implement the Parcelable interface