OSDN Git Service

Allow using third-party HTTP/... stacks for per-network URLs.
authorLorenzo Colitti <lorenzo@google.com>
Tue, 29 Jul 2014 13:51:27 +0000 (22:51 +0900)
committerLorenzo Colitti <lorenzo@google.com>
Thu, 31 Jul 2014 02:17:28 +0000 (02:17 +0000)
Also switch to double-checked locking for
mNetworkBoundSocketFactory and OkHttpClient.

Change-Id: Ic52776ee760036ad5623b7496156b8909dc282fa

api/current.txt
core/java/android/net/Network.java
core/java/android/net/NetworkBoundURLFactory.java [new file with mode: 0644]

index b4c2e11..ba49d1e 100644 (file)
@@ -17474,10 +17474,15 @@ package android.net {
     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 void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
   }
 
+  public abstract interface NetworkBoundURLFactory {
+    method public abstract java.net.URL getBoundURL(android.net.Network, java.net.URL) throws java.net.MalformedURLException;
+  }
+
   public final class NetworkCapabilities implements android.os.Parcelable {
     ctor public NetworkCapabilities(android.net.NetworkCapabilities);
     method public int describeContents();
index 5e3decd..7a1c988 100644 (file)
@@ -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<NetworkBoundURLFactory> sNetworkBoundURLFactory =
+            new AtomicReference <NetworkBoundURLFactory>(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
diff --git a/core/java/android/net/NetworkBoundURLFactory.java b/core/java/android/net/NetworkBoundURLFactory.java
new file mode 100644 (file)
index 0000000..356100e
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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 java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * An interface that describes a factory for network-specific {@link URL} objects.
+ */
+public interface NetworkBoundURLFactory {
+    /**
+     * Returns a {@link URL} based on the given URL but bound to the specified {@code Network},
+     * such that opening the URL will send all network traffic on the specified Network.
+     *
+     * @return a {@link URL} bound to this {@code Network}.
+     * @throws MalformedURLException if the URL was not valid, or this factory cannot handle the
+     *         specified URL (e.g., if it does not support the protocol of the URL).
+     */
+    public URL getBoundURL(Network network, URL url) throws MalformedURLException;
+}