Alter Android's connection pool behavior on network config changes

Rather than clearing out and closing the shared connection pool
each time the network changes with evictAll() this ensures that
later Http(s)URLConnections will reference a new ConnectionPool
instance.

This is an improvement over the current implementation:
1) It is obviously non-blocking.
2) It ensures that connections for the old network config
and are currently in use are not put back into the shared
pool.

Bug: 17314604
(cherry-picked from commit 8bced3e769d315a0a81b89de7a5282f1a85acbf7)

Change-Id: I4b2295ebceca93d55c994857de8554e9bb48b4c5
diff --git a/Android.mk b/Android.mk
index 3386dc2..4baf397 100644
--- a/Android.mk
+++ b/Android.mk
@@ -22,6 +22,7 @@
 
 okhttp_test_src_files := $(call all-java-files-under,okhttp-tests/src/test/java)
 okhttp_test_src_files += $(call all-java-files-under,mockwebserver/src/main/java)
+okhttp_test_src_files += $(call all-java-files-under,android/test/java)
 okhttp_test_src_files := $(filter-out mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java, $(okhttp_test_src_files))
 
 include $(CLEAR_VARS)
diff --git a/android/main/java/com/squareup/okhttp/ConfigAwareConnectionPool.java b/android/main/java/com/squareup/okhttp/ConfigAwareConnectionPool.java
new file mode 100644
index 0000000..36c3101
--- /dev/null
+++ b/android/main/java/com/squareup/okhttp/ConfigAwareConnectionPool.java
@@ -0,0 +1,100 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 com.squareup.okhttp;
+
+import libcore.net.event.NetworkEventDispatcher;
+import libcore.net.event.NetworkEventListener;
+
+/**
+ * A provider of the shared Android {@link ConnectionPool}. This class is aware of network
+ * configuration change events: When the network configuration changes the pool object is discarded
+ * and a later calls to {@link #get()} will return a new pool.
+ */
+public class ConfigAwareConnectionPool {
+
+  private static final long CONNECTION_POOL_DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min
+
+  private static final int CONNECTION_POOL_MAX_IDLE_CONNECTIONS;
+  private static final long CONNECTION_POOL_KEEP_ALIVE_DURATION_MS;
+  static {
+    String keepAliveProperty = System.getProperty("http.keepAlive");
+    String keepAliveDurationProperty = System.getProperty("http.keepAliveDuration");
+    String maxIdleConnectionsProperty = System.getProperty("http.maxConnections");
+    CONNECTION_POOL_KEEP_ALIVE_DURATION_MS = (keepAliveDurationProperty != null
+        ? Long.parseLong(keepAliveDurationProperty)
+        : CONNECTION_POOL_DEFAULT_KEEP_ALIVE_DURATION_MS);
+    if (keepAliveProperty != null && !Boolean.parseBoolean(keepAliveProperty)) {
+      CONNECTION_POOL_MAX_IDLE_CONNECTIONS = 0;
+    } else if (maxIdleConnectionsProperty != null) {
+      CONNECTION_POOL_MAX_IDLE_CONNECTIONS = Integer.parseInt(maxIdleConnectionsProperty);
+    } else {
+      CONNECTION_POOL_MAX_IDLE_CONNECTIONS = 5;
+    }
+  }
+
+  private static final ConfigAwareConnectionPool instance = new ConfigAwareConnectionPool();
+
+  private final NetworkEventDispatcher networkEventDispatcher;
+
+  /**
+   * {@code true} if the ConnectionPool reset has been registered with the
+   * {@link NetworkEventDispatcher}.
+   */
+  private boolean networkEventListenerRegistered;
+
+  private ConnectionPool connectionPool;
+
+  /** Visible for testing. Use {@link #getInstance()} */
+  protected ConfigAwareConnectionPool(NetworkEventDispatcher networkEventDispatcher) {
+    this.networkEventDispatcher = networkEventDispatcher;
+  }
+
+  private ConfigAwareConnectionPool() {
+    networkEventDispatcher = NetworkEventDispatcher.getInstance();
+  }
+
+  public static ConfigAwareConnectionPool getInstance() {
+    return instance;
+  }
+
+  /**
+   * Returns the current {@link ConnectionPool} to use.
+   */
+  public synchronized ConnectionPool get() {
+    if (connectionPool == null) {
+      // Only register the listener once the first time a ConnectionPool is created.
+      if (!networkEventListenerRegistered) {
+        networkEventDispatcher.addListener(new NetworkEventListener() {
+          @Override
+          public void onNetworkConfigurationChanged() {
+            synchronized (ConfigAwareConnectionPool.this) {
+              // If the network config has changed then existing pooled connections should not be
+              // re-used. By setting connectionPool to null it ensures that the next time
+              // getConnectionPool() is called a new pool will be created.
+              connectionPool = null;
+            }
+          }
+        });
+        networkEventListenerRegistered = true;
+      }
+      connectionPool = new ConnectionPool(
+          CONNECTION_POOL_MAX_IDLE_CONNECTIONS, CONNECTION_POOL_KEEP_ALIVE_DURATION_MS);
+    }
+    return connectionPool;
+  }
+}
diff --git a/android/main/java/com/squareup/okhttp/HttpHandler.java b/android/main/java/com/squareup/okhttp/HttpHandler.java
index 3b21c33..bcb47b0 100644
--- a/android/main/java/com/squareup/okhttp/HttpHandler.java
+++ b/android/main/java/com/squareup/okhttp/HttpHandler.java
@@ -23,11 +23,11 @@
 import java.net.URL;
 import java.net.URLConnection;
 import java.net.URLStreamHandler;
-import libcore.net.event.NetworkEventDispatcher;
-import libcore.net.event.NetworkEventListener;
 
 public class HttpHandler extends URLStreamHandler {
-    private static ConnectionPool connectionPool;
+
+    private final ConfigAwareConnectionPool configAwareConnectionPool =
+            ConfigAwareConnectionPool.getInstance();
 
     @Override protected URLConnection openConnection(URL url) throws IOException {
         return newOkHttpClient(null /* proxy */).open(url);
@@ -45,7 +45,9 @@
     }
 
     protected OkHttpClient newOkHttpClient(Proxy proxy) {
-        return createHttpOkHttpClient(proxy);
+        OkHttpClient okHttpClient = createHttpOkHttpClient(proxy);
+        okHttpClient.setConnectionPool(configAwareConnectionPool.get());
+        return okHttpClient;
     }
 
     /**
@@ -65,24 +67,7 @@
             client.setResponseCache(responseCache);
         }
 
-        client.setConnectionPool(getConnectionPool());
         return client;
     }
 
-    private static synchronized ConnectionPool getConnectionPool() {
-        if (connectionPool == null) {
-            // We assume the default com.android.okhttp.ConnectionPool instance is only used by
-            // us.
-            final ConnectionPool defaultInstance = ConnectionPool.getDefault();
-            NetworkEventDispatcher.getInstance().addListener(new NetworkEventListener() {
-                @Override
-                public void onNetworkConfigurationChanged() {
-                    defaultInstance.evictAll();
-                }
-            });
-            connectionPool = defaultInstance;
-        }
-        return connectionPool;
-    }
-
 }
diff --git a/android/main/java/com/squareup/okhttp/HttpsHandler.java b/android/main/java/com/squareup/okhttp/HttpsHandler.java
index 16a6ddd..e3372f2 100644
--- a/android/main/java/com/squareup/okhttp/HttpsHandler.java
+++ b/android/main/java/com/squareup/okhttp/HttpsHandler.java
@@ -18,7 +18,6 @@
 package com.squareup.okhttp;
 
 import java.net.Proxy;
-import java.net.ResponseCache;
 import java.util.Arrays;
 import java.util.List;
 
@@ -28,6 +27,8 @@
 
 public final class HttpsHandler extends HttpHandler {
     private static final List<Protocol> ENABLED_PROTOCOLS = Arrays.asList(Protocol.HTTP_11);
+    private final ConfigAwareConnectionPool configAwareConnectionPool =
+            ConfigAwareConnectionPool.getInstance();
 
     @Override protected int getDefaultPort() {
         return 443;
@@ -35,7 +36,9 @@
 
     @Override
     protected OkHttpClient newOkHttpClient(Proxy proxy) {
-        return createHttpsOkHttpClient(proxy);
+        OkHttpClient okHttpClient = createHttpsOkHttpClient(proxy);
+        okHttpClient.setConnectionPool(configAwareConnectionPool.get());
+        return okHttpClient;
     }
 
     /**
diff --git a/android/test/java/com.squareup.okhttp/ConfigAwareConnectionPoolTest.java b/android/test/java/com.squareup.okhttp/ConfigAwareConnectionPoolTest.java
new file mode 100644
index 0000000..6477e10
--- /dev/null
+++ b/android/test/java/com.squareup.okhttp/ConfigAwareConnectionPoolTest.java
@@ -0,0 +1,49 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 com.squareup.okhttp;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import libcore.net.event.NetworkEventDispatcher;
+
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+/**
+ * Tests for {@link ConfigAwareConnectionPool}.
+ */
+public class ConfigAwareConnectionPoolTest {
+
+  @Test
+  public void getInstance() {
+    assertSame(ConfigAwareConnectionPool.getInstance(), ConfigAwareConnectionPool.getInstance());
+  }
+
+  @Test
+  public void get() throws Exception {
+    NetworkEventDispatcher networkEventDispatcher = new NetworkEventDispatcher() {};
+    ConfigAwareConnectionPool instance = new ConfigAwareConnectionPool(networkEventDispatcher) {};
+    assertSame(instance.get(), instance.get());
+
+    ConnectionPool beforeEventInstance = instance.get();
+    networkEventDispatcher.onNetworkConfigurationChanged();
+
+    assertNotSame(beforeEventInstance, instance.get());
+  }
+}