Merge "Use generated source not jarjar"
am: fbdaa4eb83

Change-Id: Ia6ff9f4b8e8cdd7d2035c2d8d6bc16e06b9cd58c
diff --git a/Android.bp b/Android.bp
index eb6d503..720d605 100644
--- a/Android.bp
+++ b/Android.bp
@@ -17,7 +17,9 @@
 // The source files that contribute to Android's core library APIs.
 filegroup {
     name: "okhttp_api_files",
-    srcs: ["android/src/main/java/com/android/okhttp/internalandroidapi/**/*.java"],
+    // Use the repackaged version of android as that is what is used by Android core library
+    // APIs.
+    srcs: ["repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/**/*.java"],
 }
 
 // non-jarjar'd version of okhttp to compile the tests against
@@ -44,8 +46,16 @@
 
 java_library {
     name: "okhttp",
-    static_libs: ["okhttp-nojarjar"],
-    jarjar_rules: "jarjar-rules.txt",
+    srcs: [
+        // Although some of the classes in the android/ directory are already in the correct
+        // package and do not need to be moved to another package they are transformed as they
+        // reference other classes that do require repackaging.
+        "repackaged/android/src/main/java/**/*.java",
+        "repackaged/okhttp/src/main/java/**/*.java",
+        "repackaged/okhttp-urlconnection/src/main/java/**/*.java",
+        "repackaged/okhttp-android-support/src/main/java/**/*.java",
+        "repackaged/okio/okio/src/main/java/**/*.java",
+    ],
 
     hostdex: true,
     installable: true,
diff --git a/jarjar-rules.txt b/jarjar-rules.txt
deleted file mode 100644
index c84813d..0000000
--- a/jarjar-rules.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-rule com.squareup.** com.android.@1
-rule okio.** com.android.okhttp.okio.@1
diff --git a/repackaged/android/src/main/java/com/android/okhttp/ConfigAwareConnectionPool.java b/repackaged/android/src/main/java/com/android/okhttp/ConfigAwareConnectionPool.java
new file mode 100644
index 0000000..1414133
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/ConfigAwareConnectionPool.java
@@ -0,0 +1,102 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.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.
+ * @hide This class is not part of the Android public SDK API
+ */
+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/repackaged/android/src/main/java/com/android/okhttp/ConnectionSpecs.java b/repackaged/android/src/main/java/com/android/okhttp/ConnectionSpecs.java
new file mode 100644
index 0000000..2684dd6
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/ConnectionSpecs.java
@@ -0,0 +1,35 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2017 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 com.android.okhttp;
+
+/**
+ * Exposes nonpublic {@link ConnectionSpec} API for internal use by libcore.
+ *
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+public class ConnectionSpecs {
+    /** uninstantiable */
+    private ConnectionSpecs() {
+    }
+
+    public static ConnectionSpec.Builder builder(boolean tls) {
+        return new ConnectionSpec.Builder(tls);
+    }
+
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/HttpHandler.java b/repackaged/android/src/main/java/com/android/okhttp/HttpHandler.java
new file mode 100644
index 0000000..920824b
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/HttpHandler.java
@@ -0,0 +1,123 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp;
+
+import com.android.okhttp.internal.URLFilter;
+import libcore.net.NetworkSecurityPolicy;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.ResponseCache;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public class HttpHandler extends URLStreamHandler {
+
+    private final static List<ConnectionSpec> CLEARTEXT_ONLY =
+        Collections.singletonList(ConnectionSpec.CLEARTEXT);
+
+    private static final CleartextURLFilter CLEARTEXT_FILTER = new CleartextURLFilter();
+
+    private final ConfigAwareConnectionPool configAwareConnectionPool =
+            ConfigAwareConnectionPool.getInstance();
+
+    @Override protected URLConnection openConnection(URL url) throws IOException {
+        return newOkUrlFactory(null /* proxy */).open(url);
+    }
+
+    @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
+        if (url == null || proxy == null) {
+            throw new IllegalArgumentException("url == null || proxy == null");
+        }
+        return newOkUrlFactory(proxy).open(url);
+    }
+
+    @Override protected int getDefaultPort() {
+        return 80;
+    }
+
+    protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
+        OkUrlFactory okUrlFactory = createHttpOkUrlFactory(proxy);
+        // For HttpURLConnections created through java.net.URL Android uses a connection pool that
+        // is aware when the default network changes so that pooled connections are not re-used when
+        // the default network changes.
+        okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get());
+        return okUrlFactory;
+    }
+
+    /**
+     * Creates an OkHttpClient suitable for creating {@link java.net.HttpURLConnection} instances on
+     * Android.
+     */
+    // Visible for android.net.Network.
+    public static OkUrlFactory createHttpOkUrlFactory(Proxy proxy) {
+        OkHttpClient client = new OkHttpClient();
+
+        // Explicitly set the timeouts to infinity.
+        client.setConnectTimeout(0, TimeUnit.MILLISECONDS);
+        client.setReadTimeout(0, TimeUnit.MILLISECONDS);
+        client.setWriteTimeout(0, TimeUnit.MILLISECONDS);
+
+        // Set the default (same protocol) redirect behavior. The default can be overridden for
+        // each instance using HttpURLConnection.setInstanceFollowRedirects().
+        client.setFollowRedirects(HttpURLConnection.getFollowRedirects());
+
+        // Do not permit http -> https and https -> http redirects.
+        client.setFollowSslRedirects(false);
+
+        // Permit cleartext traffic only (this is a handler for HTTP, not for HTTPS).
+        client.setConnectionSpecs(CLEARTEXT_ONLY);
+
+        // When we do not set the Proxy explicitly OkHttp picks up a ProxySelector using
+        // ProxySelector.getDefault().
+        if (proxy != null) {
+            client.setProxy(proxy);
+        }
+
+        // OkHttp requires that we explicitly set the response cache.
+        OkUrlFactory okUrlFactory = new OkUrlFactory(client);
+
+        // Use the installed NetworkSecurityPolicy to determine which requests are permitted over
+        // http.
+        OkUrlFactories.setUrlFilter(okUrlFactory, CLEARTEXT_FILTER);
+
+        ResponseCache responseCache = ResponseCache.getDefault();
+        if (responseCache != null) {
+            AndroidInternal.setResponseCache(okUrlFactory, responseCache);
+        }
+        return okUrlFactory;
+    }
+
+    private static final class CleartextURLFilter implements URLFilter {
+        @Override
+        public void checkURLPermitted(URL url) throws IOException {
+            String host = url.getHost();
+            if (!NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(host)) {
+                throw new IOException("Cleartext HTTP traffic to " + host + " not permitted");
+            }
+        }
+    }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/HttpsHandler.java b/repackaged/android/src/main/java/com/android/okhttp/HttpsHandler.java
new file mode 100644
index 0000000..299274d
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/HttpsHandler.java
@@ -0,0 +1,100 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp;
+
+import java.net.Proxy;
+import java.util.Collections;
+import java.util.List;
+
+import javax.net.ssl.HttpsURLConnection;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpsHandler extends HttpHandler {
+
+    /**
+     * The connection spec to use when connecting to an https:// server. Note that Android does
+     * not set the cipher suites or TLS versions to use so the socket's defaults will be used
+     * instead. When the SSLSocketFactory is provided by the app or GMS core we will not
+     * override the enabled ciphers or TLS versions set on the sockets it produces with a
+     * list hardcoded at release time. This is deliberate.
+     */
+    private static final ConnectionSpec TLS_CONNECTION_SPEC = ConnectionSpecs.builder(true)
+            .allEnabledCipherSuites()
+            .allEnabledTlsVersions()
+            .supportsTlsExtensions(true)
+            .build();
+
+    private static final List<Protocol> HTTP_1_1_ONLY =
+            Collections.singletonList(Protocol.HTTP_1_1);
+
+    private final ConfigAwareConnectionPool configAwareConnectionPool =
+            ConfigAwareConnectionPool.getInstance();
+
+    @Override protected int getDefaultPort() {
+        return 443;
+    }
+
+    @Override
+    protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
+        OkUrlFactory okUrlFactory = createHttpsOkUrlFactory(proxy);
+        // For HttpsURLConnections created through java.net.URL Android uses a connection pool that
+        // is aware when the default network changes so that pooled connections are not re-used when
+        // the default network changes.
+        okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get());
+        return okUrlFactory;
+    }
+
+    /**
+     * Creates an OkHttpClient suitable for creating {@link HttpsURLConnection} instances on
+     * Android.
+     */
+    // Visible for android.net.Network.
+    public static OkUrlFactory createHttpsOkUrlFactory(Proxy proxy) {
+        // The HTTPS OkHttpClient is an HTTP OkHttpClient with extra configuration.
+        OkUrlFactory okUrlFactory = HttpHandler.createHttpOkUrlFactory(proxy);
+
+        // All HTTPS requests are allowed.
+        OkUrlFactories.setUrlFilter(okUrlFactory, null);
+
+        OkHttpClient okHttpClient = okUrlFactory.client();
+
+        // Only enable HTTP/1.1 (implies HTTP/1.0). Disable SPDY / HTTP/2.0.
+        okHttpClient.setProtocols(HTTP_1_1_ONLY);
+
+        okHttpClient.setConnectionSpecs(Collections.singletonList(TLS_CONNECTION_SPEC));
+
+        // Android support certificate pinning via NetworkSecurityConfig so there is no need to
+        // also expose OkHttp's mechanism. The OkHttpClient underlying https HttpsURLConnections
+        // in Android should therefore always use the default certificate pinner, whose set of
+        // {@code hostNamesToPin} is empty.
+        okHttpClient.setCertificatePinner(CertificatePinner.DEFAULT);
+
+        // OkHttp does not automatically honor the system-wide HostnameVerifier set with
+        // HttpsURLConnection.setDefaultHostnameVerifier().
+        okUrlFactory.client().setHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());
+        // OkHttp does not automatically honor the system-wide SSLSocketFactory set with
+        // HttpsURLConnection.setDefaultSSLSocketFactory().
+        // See https://github.com/square/okhttp/issues/184 for details.
+        okHttpClient.setSslSocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
+
+        return okUrlFactory;
+    }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/OkUrlFactories.java b/repackaged/android/src/main/java/com/android/okhttp/OkUrlFactories.java
new file mode 100644
index 0000000..1b01f44
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/OkUrlFactories.java
@@ -0,0 +1,46 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2017 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 com.android.okhttp;
+
+import com.android.okhttp.internal.URLFilter;
+
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+
+/**
+ * Exposes nonpublic {@link OkUrlFactory} API for internal use by libcore.
+ *
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+public class OkUrlFactories {
+
+    /** uninstantiable */
+    private OkUrlFactories() {
+    }
+
+    public static HttpURLConnection open(OkUrlFactory okUrlFactory, URL url, Proxy proxy) {
+        return okUrlFactory.open(url, proxy);
+    }
+
+    public static void setUrlFilter(OkUrlFactory okUrlFactory, URLFilter urlFilter) {
+        okUrlFactory.setUrlFilter(urlFilter);
+    }
+
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internal/Platform.java b/repackaged/android/src/main/java/com/android/okhttp/internal/Platform.java
new file mode 100644
index 0000000..ad2559a
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internal/Platform.java
@@ -0,0 +1,219 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 Square, Inc.
+ * Copyright (C) 2012 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 com.android.okhttp.internal;
+
+import com.android.okhttp.Protocol;
+import com.android.okhttp.internal.tls.RealTrustRootIndex;
+import com.android.okhttp.internal.tls.TrustRootIndex;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+
+import dalvik.system.SocketTagger;
+import com.android.okhttp.okio.Buffer;
+
+/**
+ * Access to proprietary Android APIs. Avoids use of reflection where possible.
+ * @hide This class is not part of the Android public SDK API
+ */
+// only tests should extend this class
+public class Platform {
+    private static final AtomicReference<Platform> INSTANCE_HOLDER
+            = new AtomicReference<>(new Platform());
+
+    // only for private use and in tests
+    protected Platform() {
+    }
+
+    public static Platform get() {
+        return INSTANCE_HOLDER.get();
+    }
+
+    /**
+     * Atomically replaces the Platform instance returned by future
+     * invocations of {@link #get()}, for use in tests.
+     * Invocations of this method should typically be followed by
+     * a try/finally block to reset the previous value:
+     *
+     * <pre>
+     * Platform p = getAndSetForTest(...);
+     * try {
+     *   ...
+     * } finally {
+     *   getAndSetForTest(p);
+     * }
+     * </pre>
+     *
+     * @return the previous value of {@link #get()}.
+     */
+    public static Platform getAndSetForTest(Platform platform) {
+        if (platform == null) {
+            throw new NullPointerException();
+        }
+        return INSTANCE_HOLDER.getAndSet(platform);
+    }
+
+    /** setUseSessionTickets(boolean) */
+    private static final OptionalMethod<Socket> SET_USE_SESSION_TICKETS =
+            new OptionalMethod<Socket>(null, "setUseSessionTickets", Boolean.TYPE);
+    /** setHostname(String) */
+    private static final OptionalMethod<Socket> SET_HOSTNAME =
+            new OptionalMethod<Socket>(null, "setHostname", String.class);
+    /** byte[] getAlpnSelectedProtocol() */
+    private static final OptionalMethod<Socket> GET_ALPN_SELECTED_PROTOCOL =
+            new OptionalMethod<Socket>(byte[].class, "getAlpnSelectedProtocol");
+    /** setAlpnSelectedProtocol(byte[]) */
+    private static final OptionalMethod<Socket> SET_ALPN_PROTOCOLS =
+            new OptionalMethod<Socket>(null, "setAlpnProtocols", byte[].class );
+
+    public void logW(String warning) {
+        System.logW(warning);
+    }
+
+    public void tagSocket(Socket socket) throws SocketException {
+        SocketTagger.get().tag(socket);
+    }
+
+    public void untagSocket(Socket socket) throws SocketException {
+        SocketTagger.get().untag(socket);
+    }
+
+    public void configureTlsExtensions(
+            SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
+        // Enable SNI and session tickets.
+        if (hostname != null) {
+            SET_USE_SESSION_TICKETS.invokeOptionalWithoutCheckedException(sslSocket, true);
+            SET_HOSTNAME.invokeOptionalWithoutCheckedException(sslSocket, hostname);
+        }
+
+        // Enable ALPN.
+        boolean alpnSupported = SET_ALPN_PROTOCOLS.isSupported(sslSocket);
+        if (!alpnSupported) {
+            return;
+        }
+
+        Object[] parameters = { concatLengthPrefixed(protocols) };
+        if (alpnSupported) {
+            SET_ALPN_PROTOCOLS.invokeWithoutCheckedException(sslSocket, parameters);
+        }
+    }
+
+    /**
+     * Called after the TLS handshake to release resources allocated by {@link
+     * #configureTlsExtensions}.
+     */
+    public void afterHandshake(SSLSocket sslSocket) {
+    }
+
+    public String getSelectedProtocol(SSLSocket socket) {
+        boolean alpnSupported = GET_ALPN_SELECTED_PROTOCOL.isSupported(socket);
+        if (!alpnSupported) {
+            return null;
+        }
+
+        byte[] alpnResult =
+                (byte[]) GET_ALPN_SELECTED_PROTOCOL.invokeWithoutCheckedException(socket);
+        if (alpnResult != null) {
+            return new String(alpnResult, Util.UTF_8);
+        }
+        return null;
+    }
+
+    public void connectSocket(Socket socket, InetSocketAddress address,
+              int connectTimeout) throws IOException {
+        socket.connect(address, connectTimeout);
+    }
+
+    /** Prefix used on custom headers. */
+    public String getPrefix() {
+        return "X-Android";
+    }
+
+    /**
+     * Stripped down/adapted from OkHttp's {@code Platform.Android.trustManager()}.
+     * OkHttp 2.7.5 uses this only for certificate pinning logic that we don't use
+     * on Android, so this method should never be called outside of OkHttp's tests.
+     * This method has been stripped down to the minimum for OkHttp's tests to pass.
+     */
+    public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
+        Class sslParametersClass;
+        try {
+            sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl");
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        Object context = readFieldOrNull(sslSocketFactory, sslParametersClass, "sslParameters");
+        return readFieldOrNull(context, X509TrustManager.class, "x509TrustManager");
+    }
+
+    /**
+     * Stripped down from OkHttp's implementation to the minimum to get OkHttp's tests
+     * to pass. OkHttp 2.7.5 uses this for certificate pinning logic which is unused
+     * in Android. This method should never be called outside of OkHttp's tests.
+     */
+    public TrustRootIndex trustRootIndex(X509TrustManager trustManager) {
+        return new RealTrustRootIndex(trustManager.getAcceptedIssuers());
+    }
+
+    // Helper method from OkHttp's Platform.java
+    private static <T> T readFieldOrNull(Object instance, Class<T> fieldType, String fieldName) {
+        for (Class<?> c = instance.getClass(); c != Object.class; c = c.getSuperclass()) {
+            try {
+                Field field = c.getDeclaredField(fieldName);
+                field.setAccessible(true);
+                Object value = field.get(instance);
+                if (value == null || !fieldType.isInstance(value)) return null;
+                return fieldType.cast(value);
+            } catch (NoSuchFieldException ignored) {
+            } catch (IllegalAccessException e) {
+                throw new AssertionError();
+            }
+        }
+
+        // Didn't find the field we wanted. As a last gasp attempt, try to find the value on a delegate.
+        if (!fieldName.equals("delegate")) {
+            Object delegate = readFieldOrNull(instance, Object.class, "delegate");
+            if (delegate != null) return readFieldOrNull(delegate, fieldType, fieldName);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the concatenation of 8-bit, length prefixed protocol names.
+     * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
+     */
+    static byte[] concatLengthPrefixed(List<Protocol> protocols) {
+        Buffer result = new Buffer();
+        for (int i = 0, size = protocols.size(); i < size; i++) {
+            Protocol protocol = protocols.get(i);
+            if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
+            result.writeByte(protocol.toString().length());
+            result.writeUtf8(protocol.toString());
+        }
+        return result.readByteArray();
+    }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internal/Version.java b/repackaged/android/src/main/java/com/android/okhttp/internal/Version.java
new file mode 100644
index 0000000..f186a62
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internal/Version.java
@@ -0,0 +1,30 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Version {
+  public static String userAgent() {
+    String agent = System.getProperty("http.agent");
+    return agent != null ? agent : ("Java" + System.getProperty("java.version"));
+  }
+
+  private Version() {
+  }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java
new file mode 100644
index 0000000..b7f5002
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java
@@ -0,0 +1,172 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp.internalandroidapi;
+
+import com.android.okhttp.internalandroidapi.HasCacheHolder.CacheHolder;
+import com.android.okhttp.Cache;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.internal.huc.JavaApiConverter;
+
+import java.io.IOException;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.URI;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A modified copy of {@link com.android.okhttp.AndroidShimResponseCache} that exposes a
+ * {@link CacheHolder} instead of a {@link Cache}. We want to keep the framework code that interacts
+ * with OkHttp at arms length. By delegating to this class the Android HttpResponseCache class has
+ * no knowledge of OkHttp internal classes at class resolution time and there are no internal
+ * classes appearing on method signatures.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+@libcore.api.CorePlatformApi
+public final class AndroidResponseCacheAdapter {
+
+    private final CacheHolder cacheHolder;
+    private final Cache okHttpCache;
+
+    @libcore.api.CorePlatformApi
+    public AndroidResponseCacheAdapter(CacheHolder cacheHolder) {
+        this.cacheHolder = cacheHolder;
+        // Avoid one level of dereferencing by storing the reference to the OkHttp cache for later.
+        this.okHttpCache = cacheHolder.getCache();
+    }
+
+    /**
+     * Returns the {@link CacheHolder} associated with this instance and can be used by OkHttp
+     * internal code to obtain the underlying OkHttp Cache object.
+     */
+    @libcore.api.CorePlatformApi
+    public CacheHolder getCacheHolder() {
+        return cacheHolder;
+    }
+
+    /**
+     * Used to implement {@link java.net.ResponseCache#get(URI, String, Map)}. See that method for
+     * details.
+     */
+    @libcore.api.CorePlatformApi
+    public CacheResponse get(URI uri, String requestMethod,
+            Map<String, List<String>> requestHeaders) throws IOException {
+        Request okRequest = JavaApiConverter.createOkRequest(uri, requestMethod, requestHeaders);
+        Response okResponse = okHttpCache.internalCache.get(okRequest);
+        if (okResponse == null) {
+            return null;
+        }
+        return JavaApiConverter.createJavaCacheResponse(okResponse);
+    }
+
+    /**
+     * Used to implement {@link java.net.ResponseCache#put(URI, URLConnection)}. See that method for
+     * details.
+     */
+    @libcore.api.CorePlatformApi
+    public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
+        Response okResponse = JavaApiConverter.createOkResponseForCachePut(uri, urlConnection);
+        if (okResponse == null) {
+            // The URLConnection is not cacheable or could not be converted. Stop.
+            return null;
+        }
+        com.android.okhttp.internal.http.CacheRequest okCacheRequest =
+                okHttpCache.internalCache.put(okResponse);
+        if (okCacheRequest == null) {
+            return null;
+        }
+        return JavaApiConverter.createJavaCacheRequest(okCacheRequest);
+    }
+
+    /**
+     * Returns the number of bytes currently being used to store the values in
+     * this cache. This may be greater than the {@link #getMaxSize()} if a background
+     * deletion is pending. IOException is thrown if the size cannot be determined.
+     */
+    @libcore.api.CorePlatformApi
+    public long getSize() throws IOException {
+        return okHttpCache.getSize();
+    }
+
+    /**
+     * Returns the maximum number of bytes that this cache should use to store
+     * its data.
+     */
+    @libcore.api.CorePlatformApi
+    public long getMaxSize() {
+        return okHttpCache.getMaxSize();
+    }
+
+    /**
+     * Force buffered operations to the filesystem. This ensures that responses
+     * written to the cache will be available the next time the cache is opened,
+     * even if this process is killed. IOException is thrown if the flush fails.
+     */
+    @libcore.api.CorePlatformApi
+    public void flush() throws IOException {
+        okHttpCache.flush();
+    }
+
+    /**
+     * Returns the number of HTTP requests that required the network to either
+     * supply a response or validate a locally cached response.
+     */
+    @libcore.api.CorePlatformApi
+    public int getNetworkCount() {
+        return okHttpCache.getNetworkCount();
+    }
+
+    /**
+     * Returns the number of HTTP requests whose response was provided by the
+     * cache. This may include conditional {@code GET} requests that were
+     * validated over the network.
+     */
+    @libcore.api.CorePlatformApi
+    public int getHitCount() {
+        return okHttpCache.getHitCount();
+    }
+
+    /**
+     * Returns the total number of HTTP requests that were made. This includes
+     * both client requests and requests that were made on the client's behalf
+     * to handle a redirects and retries.
+     */
+    @libcore.api.CorePlatformApi
+    public int getRequestCount() {
+        return okHttpCache.getRequestCount();
+    }
+
+    /** Closes this cache. Stored values will remain on the filesystem. */
+    @libcore.api.CorePlatformApi
+    public void close() throws IOException {
+        okHttpCache.close();
+    }
+
+    /**
+     * Closes the cache and deletes all of its stored values. This will delete
+     * all files in the cache directory including files that weren't created by
+     * the cache.
+     */
+    @libcore.api.CorePlatformApi
+    public void delete() throws IOException {
+        okHttpCache.delete();
+    }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java
new file mode 100644
index 0000000..859ea80
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java
@@ -0,0 +1,37 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2017 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 com.android.okhttp.internalandroidapi;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+
+/**
+ * A domain name service that resolves IP addresses for host names.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+@libcore.api.CorePlatformApi
+public interface Dns {
+    /**
+     * Returns the IP addresses of {@code hostname}, in the order they should
+     * be attempted.
+     */
+    @libcore.api.CorePlatformApi
+    List<InetAddress> lookup(String hostname) throws UnknownHostException;
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java
new file mode 100644
index 0000000..aa90bf3
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java
@@ -0,0 +1,91 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2018 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 com.android.okhttp.internalandroidapi;
+
+import com.android.okhttp.Cache;
+
+import java.io.File;
+
+/**
+ * An interface used to indicate a class can return a {@link CacheHolder} object.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+@libcore.api.CorePlatformApi
+public interface HasCacheHolder {
+
+    /**
+     * Returns the {@link CacheHolder} object.
+     */
+    @libcore.api.CorePlatformApi
+    CacheHolder getCacheHolder();
+
+    /**
+     * A holder for an OkHttp internal Cache object. This class exists as an opaque layer over
+     * OkHttp internal classes.
+     * @hide
+     */
+    @libcore.api.CorePlatformApi
+    final class CacheHolder {
+
+        private final Cache okHttpCache;
+
+        private CacheHolder(Cache okHttpCache) {
+            this.okHttpCache = okHttpCache;
+        }
+
+        // This constructor is required for @libcore.api.CorePlatformApi stubs generation. Without
+        // it the constructor above is included in the stubs code which adds a dependency on
+        // okhttp.Cache that we don't want.
+        @SuppressWarnings("unused")
+        private CacheHolder() {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Returns the underlying {@link Cache} object.
+         * @hide
+         */
+        public Cache getCache() {
+            return okHttpCache;
+        }
+
+        /**
+         * Returns a new {@link CacheHolder} containing an OKHttp Cache with the specified settings.
+         *
+         * @param directory a writable directory
+         * @param maxSizeBytes the maximum number of bytes this cache should use to store
+         */
+        @libcore.api.CorePlatformApi
+        public static CacheHolder create(File directory, long maxSizeBytes) {
+            Cache cache = new Cache(directory, maxSizeBytes);
+            return new CacheHolder(cache);
+        }
+
+        /**
+         * Returns true if the arguments supplied would result in an equivalent cache to this one
+         * being created if they were passed to {@link #create(File, long)}.
+         */
+        @libcore.api.CorePlatformApi
+        public boolean isEquivalent(File directory, long maxSizeBytes) {
+            return (okHttpCache.getDirectory().equals(directory)
+                    && okHttpCache.getMaxSize() == maxSizeBytes
+                    && !okHttpCache.isClosed());
+        }
+    }
+}
diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java
new file mode 100644
index 0000000..38722fe
--- /dev/null
+++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java
@@ -0,0 +1,181 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2017 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 com.android.okhttp.internalandroidapi;
+
+import com.android.okhttp.ConnectionPool;
+import com.android.okhttp.HttpHandler;
+import com.android.okhttp.HttpsHandler;
+import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.OkUrlFactories;
+import com.android.okhttp.OkUrlFactory;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import javax.net.SocketFactory;
+
+/**
+ * A way to construct {@link java.net.HttpURLConnection}s that supports some
+ * configuration on a per-factory or per-connection basis rather than only via
+ * global static state such as {@link java.net.CookieHandler#setDefault(java.net.CookieHandler)}.
+ * The per-factory configuration is <b>optional</b>; if not set, global
+ * configuration or default behavior is used.
+ *
+ * This facade prevents tight coupling with the underlying implementation (on
+ * top of a particular version of OkHttp). Android code outside of libcore
+ * should never depend directly on OkHttp.
+ *
+ * This abstraction is not suitable for general use. Talk to the maintainers of
+ * this class before modifying it or adding additional dependencies.
+ *
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+@libcore.api.CorePlatformApi
+public final class HttpURLConnectionFactory {
+
+    private ConnectionPool connectionPool;
+    private com.android.okhttp.Dns dns;
+
+    /**
+     * Sets a new ConnectionPool, specific to this URLFactory and not shared with
+     * any other connections, with the given configuration.
+     */
+    @libcore.api.CorePlatformApi
+    public void setNewConnectionPool(int maxIdleConnections, long keepAliveDuration,
+            TimeUnit timeUnit) {
+        this.connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
+    }
+
+    @libcore.api.CorePlatformApi
+    public void setDns(Dns dns) {
+        Objects.requireNonNull(dns);
+        this.dns = new DnsAdapter(dns);
+    }
+
+    /**
+     * Opens a connection that uses the system default proxy settings and SocketFactory.
+     */
+    public URLConnection openConnection(URL url) throws IOException {
+        return internalOpenConnection(url, null /* socketFactory */, null /* proxy */);
+    }
+
+    /**
+     * Opens a connection that uses the system default SocketFactory and the specified
+     * proxy settings.
+     */
+    public URLConnection openConnection(URL url, Proxy proxy) throws IOException {
+        Objects.requireNonNull(proxy);
+        return internalOpenConnection(url, null /* socketFactory */, proxy);
+    }
+
+    /**
+     * Opens a connection that uses the specified SocketFactory and the system default
+     * proxy settings.
+     */
+    public URLConnection openConnection(URL url, SocketFactory socketFactory) throws IOException {
+        Objects.requireNonNull(socketFactory);
+        return internalOpenConnection(url, socketFactory, null /* proxy */);
+    }
+
+    /**
+     * Opens a connection using the specified SocketFactory and the specified proxy
+     * settings, overriding any system wide configuration.
+     */
+    @libcore.api.CorePlatformApi
+    public URLConnection openConnection(URL url, SocketFactory socketFactory, Proxy proxy)
+            throws IOException {
+        Objects.requireNonNull(socketFactory);
+        Objects.requireNonNull(proxy);
+        return internalOpenConnection(url, socketFactory, proxy);
+    }
+
+    private URLConnection internalOpenConnection(URL url, SocketFactory socketFactoryOrNull,
+            Proxy proxyOrNull) throws IOException {
+        String protocol = url.getProtocol();
+        OkUrlFactory okUrlFactory;
+        // TODO: HttpHandler creates OkUrlFactory instances that share the default ResponseCache.
+        // Could this cause unexpected behavior?
+        if (protocol.equals("http")) {
+            okUrlFactory = HttpHandler.createHttpOkUrlFactory(proxyOrNull);
+        } else if (protocol.equals("https")) {
+            okUrlFactory = HttpsHandler.createHttpsOkUrlFactory(proxyOrNull);
+        } else {
+            // OkHttp only supports HTTP and HTTPS.
+            throw new MalformedURLException("Invalid URL or unrecognized protocol " + protocol);
+        }
+
+        OkHttpClient client = okUrlFactory.client();
+        if (connectionPool != null) {
+            client.setConnectionPool(connectionPool);
+        }
+        if (dns != null) {
+            client.setDns(dns);
+        }
+        if (socketFactoryOrNull != null) {
+            client.setSocketFactory(socketFactoryOrNull);
+        }
+        if (proxyOrNull == null) {
+            return okUrlFactory.open(url);
+        } else {
+            return OkUrlFactories.open(okUrlFactory, url, proxyOrNull);
+        }
+    }
+
+    /**
+     * Adapts a {@link Dns} as a {@link com.android.okhttp.Dns}.
+     */
+    static final class DnsAdapter implements com.android.okhttp.Dns {
+        private final Dns adaptee;
+
+        DnsAdapter(Dns adaptee) {
+            this.adaptee = Objects.requireNonNull(adaptee);
+        }
+
+        @Override
+        public List<InetAddress> lookup(String hostname) throws UnknownHostException {
+            return adaptee.lookup(hostname);
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * DnsAdapter.class.hashCode() + adaptee.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof DnsAdapter)) {
+                return false;
+            }
+            return adaptee.equals(((DnsAdapter) obj).adaptee);
+        }
+
+        @Override
+        public String toString() {
+            return adaptee.toString();
+        }
+    }
+
+}
diff --git a/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidInternal.java b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidInternal.java
new file mode 100644
index 0000000..746f63c
--- /dev/null
+++ b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidInternal.java
@@ -0,0 +1,58 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internalandroidapi.HasCacheHolder;
+import com.android.okhttp.internalandroidapi.HasCacheHolder.CacheHolder;
+import com.android.okhttp.internal.huc.CacheAdapter;
+
+import java.net.ResponseCache;
+
+/**
+ * Back doors to enable the use of OkHttp within the Android platform libraries. OkHttp is used to
+ * provide the default {@link java.net.HttpURLConnection} / {@link javax.net.ssl.HttpsURLConnection}
+ * implementation including support for a custom {@link ResponseCache}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class AndroidInternal {
+
+  private AndroidInternal() {
+  }
+
+  /** Sets the response cache to be used to read and write cached responses. */
+  public static void setResponseCache(OkUrlFactory okUrlFactory, ResponseCache responseCache) {
+    OkHttpClient client = okUrlFactory.client();
+    if (responseCache instanceof OkCacheContainer) {
+      // Avoid adding layers of wrappers. Rather than wrap the ResponseCache in yet another layer to
+      // make the ResponseCache look like an InternalCache, we can unwrap the Cache instead.
+      // This means that Cache stats will be correctly updated.
+      OkCacheContainer okCacheContainer = (OkCacheContainer) responseCache;
+      client.setCache(okCacheContainer.getCache());
+    // BEGIN Android-added: Recognize internalapi.HasCacheHolder.
+    } else if (responseCache instanceof HasCacheHolder) {
+      // Avoid adding layers of wrappers. Rather than wrap the ResponseCache in yet another layer to
+      // make the ResponseCache look like an InternalCache using CacheAdapter, we can unwrap the
+      // held Cache instead. This means that Cache stats will be correctly updated by OkHttp.
+      HasCacheHolder hasCacheHolder = (HasCacheHolder) responseCache;
+      CacheHolder cacheHolder = hasCacheHolder.getCacheHolder();
+      client.setCache(cacheHolder.getCache());
+    // END Android-added: Recognize internalapi.HasCacheHolder.
+    } else {
+      client.setInternalCache(responseCache != null ? new CacheAdapter(responseCache) : null);
+    }
+  }
+}
diff --git a/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidShimResponseCache.java b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidShimResponseCache.java
new file mode 100644
index 0000000..b0f504a
--- /dev/null
+++ b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/AndroidShimResponseCache.java
@@ -0,0 +1,151 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.huc.JavaApiConverter;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.ResponseCache;
+import java.net.URI;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class provided for use by Android so that it can continue supporting a {@link ResponseCache}
+ * with stats.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class AndroidShimResponseCache extends ResponseCache {
+
+  private final Cache delegate;
+
+  private AndroidShimResponseCache(Cache delegate) {
+    this.delegate = delegate;
+  }
+
+  public static AndroidShimResponseCache create(File directory, long maxSize) throws IOException {
+    Cache cache = new Cache(directory, maxSize);
+    return new AndroidShimResponseCache(cache);
+  }
+
+  public boolean isEquivalent(File directory, long maxSize) {
+    Cache installedCache = getCache();
+    return (installedCache.getDirectory().equals(directory)
+        && installedCache.getMaxSize() == maxSize
+        && !installedCache.isClosed());
+  }
+
+  public Cache getCache() {
+    return delegate;
+  }
+
+  @Override public CacheResponse get(URI uri, String requestMethod,
+      Map<String, List<String>> requestHeaders) throws IOException {
+    Request okRequest = JavaApiConverter.createOkRequest(uri, requestMethod, requestHeaders);
+    Response okResponse = delegate.internalCache.get(okRequest);
+    if (okResponse == null) {
+      return null;
+    }
+    return JavaApiConverter.createJavaCacheResponse(okResponse);
+  }
+
+  @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
+    Response okResponse = JavaApiConverter.createOkResponseForCachePut(uri, urlConnection);
+    if (okResponse == null) {
+      // The URLConnection is not cacheable or could not be converted. Stop.
+      return null;
+    }
+    com.android.okhttp.internal.http.CacheRequest okCacheRequest =
+        delegate.internalCache.put(okResponse);
+    if (okCacheRequest == null) {
+      return null;
+    }
+    return JavaApiConverter.createJavaCacheRequest(okCacheRequest);
+  }
+
+  /**
+   * Returns the number of bytes currently being used to store the values in
+   * this cache. This may be greater than the {@link #maxSize} if a background
+   * deletion is pending.
+   */
+  public long size() throws IOException {
+    return delegate.getSize();
+  }
+
+  /**
+   * Returns the maximum number of bytes that this cache should use to store
+   * its data.
+   */
+  public long maxSize() {
+    return delegate.getMaxSize();
+  }
+
+  /**
+   * Force buffered operations to the filesystem. This ensures that responses
+   * written to the cache will be available the next time the cache is opened,
+   * even if this process is killed.
+   */
+  public void flush() throws IOException {
+    delegate.flush();
+  }
+
+  /**
+   * Returns the number of HTTP requests that required the network to either
+   * supply a response or validate a locally cached response.
+   */
+  public int getNetworkCount() {
+    return delegate.getNetworkCount();
+  }
+
+  /**
+   * Returns the number of HTTP requests whose response was provided by the
+   * cache. This may include conditional {@code GET} requests that were
+   * validated over the network.
+   */
+  public int getHitCount() {
+    return delegate.getHitCount();
+  }
+
+  /**
+   * Returns the total number of HTTP requests that were made. This includes
+   * both client requests and requests that were made on the client's behalf
+   * to handle a redirects and retries.
+   */
+  public int getRequestCount() {
+    return delegate.getRequestCount();
+  }
+
+  /**
+   * Uninstalls the cache and releases any active resources. Stored contents
+   * will remain on the filesystem.
+   */
+  public void close() throws IOException {
+    delegate.close();
+  }
+
+  /**
+   * Uninstalls the cache and deletes all of its stored contents.
+   */
+  public void delete() throws IOException {
+    delegate.delete();
+  }
+
+}
diff --git a/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/OkCacheContainer.java b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/OkCacheContainer.java
new file mode 100644
index 0000000..72e71b1
--- /dev/null
+++ b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/OkCacheContainer.java
@@ -0,0 +1,26 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+/**
+ * An interface that allows OkHttp to detect that a {@link java.net.ResponseCache} contains a
+ * {@link Cache}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface OkCacheContainer {
+  Cache getCache();
+}
diff --git a/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/CacheAdapter.java b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/CacheAdapter.java
new file mode 100644
index 0000000..97751d2
--- /dev/null
+++ b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/CacheAdapter.java
@@ -0,0 +1,107 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.huc;
+
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.internal.InternalCache;
+import com.android.okhttp.internal.http.CacheRequest;
+import com.android.okhttp.internal.http.CacheStrategy;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ResponseCache;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+
+/** Adapts {@link ResponseCache} to {@link InternalCache}. 
+ * @hide This class is not part of the Android public SDK API*/
+public final class CacheAdapter implements InternalCache {
+  private final ResponseCache delegate;
+
+  public CacheAdapter(ResponseCache delegate) {
+    this.delegate = delegate;
+  }
+
+  public ResponseCache getDelegate() {
+    return delegate;
+  }
+
+  @Override public Response get(Request request) throws IOException {
+    CacheResponse javaResponse = getJavaCachedResponse(request);
+    if (javaResponse == null) {
+      return null;
+    }
+    return JavaApiConverter.createOkResponseForCacheGet(request, javaResponse);
+  }
+
+  @Override public CacheRequest put(Response response) throws IOException {
+    URI uri = response.request().uri();
+    HttpURLConnection connection = JavaApiConverter.createJavaUrlConnectionForCachePut(response);
+    final java.net.CacheRequest request = delegate.put(uri, connection);
+    if (request == null) {
+      return null;
+    }
+    return new CacheRequest() {
+      @Override public Sink body() throws IOException {
+        OutputStream body = request.getBody();
+        return body != null ? Okio.sink(body) : null;
+      }
+
+      @Override public void abort() {
+        request.abort();
+      }
+    };
+  }
+
+  @Override public void remove(Request request) throws IOException {
+    // This method is treated as optional and there is no obvious way of implementing it with
+    // ResponseCache. Removing items from the cache due to modifications made from this client is
+    // not essential given that modifications could be made from any other client. We have to assume
+    // that it's ok to keep using the cached data. Otherwise the server shouldn't declare it as
+    // cacheable or the client should be careful about caching it.
+  }
+
+  @Override public void update(Response cached, Response network) throws IOException {
+    // This method is treated as optional and there is no obvious way of implementing it with
+    // ResponseCache. Updating headers is useful if the server changes the metadata for a resource
+    // (e.g. max age) to extend or truncate the life of that resource in the cache. If the metadata
+    // is not updated the caching behavior may not be optimal, but will obey the metadata sent
+    // with the original cached response.
+  }
+
+  @Override public void trackConditionalCacheHit() {
+    // This method is optional.
+  }
+
+  @Override public void trackResponse(CacheStrategy cacheStrategy) {
+    // This method is optional.
+  }
+
+  /**
+   * Returns the {@link CacheResponse} from the delegate by converting the
+   * OkHttp {@link Request} into the arguments required by the {@link ResponseCache}.
+   */
+  private CacheResponse getJavaCachedResponse(Request request) throws IOException {
+    Map<String, List<String>> headers = JavaApiConverter.extractJavaHeaders(request);
+    return delegate.get(request.uri(), request.method(), headers);
+  }
+}
diff --git a/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/JavaApiConverter.java b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/JavaApiConverter.java
new file mode 100644
index 0000000..cbd3b83
--- /dev/null
+++ b/repackaged/okhttp-android-support/src/main/java/com/android/okhttp/internal/huc/JavaApiConverter.java
@@ -0,0 +1,867 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.huc;
+
+import com.android.okhttp.Handshake;
+import com.android.okhttp.Headers;
+import com.android.okhttp.MediaType;
+import com.android.okhttp.Request;
+import com.android.okhttp.RequestBody;
+import com.android.okhttp.Response;
+import com.android.okhttp.ResponseBody;
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.http.CacheRequest;
+import com.android.okhttp.internal.http.HttpMethod;
+import com.android.okhttp.internal.http.OkHeaders;
+import com.android.okhttp.internal.http.StatusLine;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ProtocolException;
+import java.net.SecureCacheResponse;
+import java.net.URI;
+import java.net.URLConnection;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocketFactory;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+
+/**
+ * Helper methods that convert between Java and OkHttp representations.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class JavaApiConverter {
+  private static final RequestBody EMPTY_REQUEST_BODY = RequestBody.create(null, new byte[0]);
+
+  private JavaApiConverter() {
+  }
+
+  /**
+   * Creates an OkHttp {@link Response} using the supplied {@link URI} and {@link URLConnection}
+   * to supply the data. The URLConnection is assumed to already be connected. If this method
+   * returns {@code null} the response is uncacheable.
+   */
+  public static Response createOkResponseForCachePut(URI uri, URLConnection urlConnection)
+      throws IOException {
+
+    HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection;
+
+    Response.Builder okResponseBuilder = new Response.Builder();
+
+    // Request: Create one from the URL connection.
+    Headers responseHeaders = createHeaders(urlConnection.getHeaderFields());
+    // Some request headers are needed for Vary caching.
+    Headers varyHeaders = varyHeaders(urlConnection, responseHeaders);
+    if (varyHeaders == null) {
+      return null;
+    }
+
+    // OkHttp's Call API requires a placeholder body; the real body will be streamed separately.
+    String requestMethod = httpUrlConnection.getRequestMethod();
+    RequestBody placeholderBody = HttpMethod.requiresRequestBody(requestMethod)
+        ? EMPTY_REQUEST_BODY
+        : null;
+
+    Request okRequest = new Request.Builder()
+        .url(uri.toString())
+        .method(requestMethod, placeholderBody)
+        .headers(varyHeaders)
+        .build();
+    okResponseBuilder.request(okRequest);
+
+    // Status line
+    StatusLine statusLine = StatusLine.parse(extractStatusLine(httpUrlConnection));
+    okResponseBuilder.protocol(statusLine.protocol);
+    okResponseBuilder.code(statusLine.code);
+    okResponseBuilder.message(statusLine.message);
+
+    // A network response is required for the Cache to find any Vary headers it needs.
+    Response networkResponse = okResponseBuilder.build();
+    okResponseBuilder.networkResponse(networkResponse);
+
+    // Response headers
+    Headers okHeaders = extractOkResponseHeaders(httpUrlConnection);
+    okResponseBuilder.headers(okHeaders);
+
+    // Response body
+    ResponseBody okBody = createOkBody(urlConnection);
+    okResponseBuilder.body(okBody);
+
+    // Handle SSL handshake information as needed.
+    if (httpUrlConnection instanceof HttpsURLConnection) {
+      HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) httpUrlConnection;
+
+      Certificate[] peerCertificates;
+      try {
+        peerCertificates = httpsUrlConnection.getServerCertificates();
+      } catch (SSLPeerUnverifiedException e) {
+        peerCertificates = null;
+      }
+
+      Certificate[] localCertificates = httpsUrlConnection.getLocalCertificates();
+
+      Handshake handshake = Handshake.get(
+          httpsUrlConnection.getCipherSuite(), nullSafeImmutableList(peerCertificates),
+          nullSafeImmutableList(localCertificates));
+      okResponseBuilder.handshake(handshake);
+    }
+
+    return okResponseBuilder.build();
+  }
+
+  /**
+   * Returns headers for the header names and values in the {@link Map}.
+   */
+  private static Headers createHeaders(Map<String, List<String>> headers) {
+    Headers.Builder builder = new Headers.Builder();
+    for (Map.Entry<String, List<String>> header : headers.entrySet()) {
+      if (header.getKey() == null || header.getValue() == null) {
+        continue;
+      }
+      String name = header.getKey().trim();
+      for (String value : header.getValue()) {
+        String trimmedValue = value.trim();
+        Internal.instance.addLenient(builder, name, trimmedValue);
+      }
+    }
+    return builder.build();
+  }
+
+  private static Headers varyHeaders(URLConnection urlConnection, Headers responseHeaders) {
+    if (OkHeaders.hasVaryAll(responseHeaders)) {
+      // "*" means that this will be treated as uncacheable anyway.
+      return null;
+    }
+    Set<String> varyFields = OkHeaders.varyFields(responseHeaders);
+    if (varyFields.isEmpty()) {
+      return new Headers.Builder().build();
+    }
+
+    // This probably indicates another HTTP stack is trying to use the shared ResponseCache.
+    // We cannot guarantee this case will work properly because we cannot reliably extract *all*
+    // the request header values, and we can't get multiple Vary request header values.
+    // We also can't be sure about the Accept-Encoding behavior of other stacks.
+    if (!(urlConnection instanceof CacheHttpURLConnection
+        || urlConnection instanceof CacheHttpsURLConnection)) {
+      return null;
+    }
+
+    // This is the case we expect: The URLConnection is from a call to
+    // JavaApiConverter.createJavaUrlConnection() and we have access to the user's request headers.
+    Map<String, List<String>> requestProperties = urlConnection.getRequestProperties();
+    Headers.Builder result = new Headers.Builder();
+    for (String fieldName : varyFields) {
+      List<String> fieldValues = requestProperties.get(fieldName);
+      if (fieldValues == null) {
+        if (fieldName.equals("Accept-Encoding")) {
+          // Accept-Encoding is special. If OkHttp sees Accept-Encoding is unset it will add
+          // "gzip". We don't have access to the request that was actually made so we must do the
+          // same.
+          result.add("Accept-Encoding", "gzip");
+        }
+      } else {
+        for (String fieldValue : fieldValues) {
+          Internal.instance.addLenient(result, fieldName, fieldValue);
+        }
+      }
+    }
+    return result.build();
+  }
+
+  /**
+   * Creates an OkHttp {@link Response} using the supplied {@link Request} and {@link CacheResponse}
+   * to supply the data.
+   */
+  static Response createOkResponseForCacheGet(Request request, CacheResponse javaResponse)
+      throws IOException {
+
+    // Build a cache request for the response to use.
+    Headers responseHeaders = createHeaders(javaResponse.getHeaders());
+    Headers varyHeaders;
+    if (OkHeaders.hasVaryAll(responseHeaders)) {
+      // "*" means that this will be treated as uncacheable anyway.
+      varyHeaders = new Headers.Builder().build();
+    } else {
+      varyHeaders = OkHeaders.varyHeaders(request.headers(), responseHeaders);
+    }
+
+    Request cacheRequest = new Request.Builder()
+        .url(request.httpUrl())
+        .method(request.method(), null)
+        .headers(varyHeaders)
+        .build();
+
+    Response.Builder okResponseBuilder = new Response.Builder();
+
+    // Request: Use the cacheRequest we built.
+    okResponseBuilder.request(cacheRequest);
+
+    // Status line: Java has this as one of the headers.
+    StatusLine statusLine = StatusLine.parse(extractStatusLine(javaResponse));
+    okResponseBuilder.protocol(statusLine.protocol);
+    okResponseBuilder.code(statusLine.code);
+    okResponseBuilder.message(statusLine.message);
+
+    // Response headers
+    Headers okHeaders = extractOkHeaders(javaResponse);
+    okResponseBuilder.headers(okHeaders);
+
+    // Response body
+    ResponseBody okBody = createOkBody(okHeaders, javaResponse);
+    okResponseBuilder.body(okBody);
+
+    // Handle SSL handshake information as needed.
+    if (javaResponse instanceof SecureCacheResponse) {
+      SecureCacheResponse javaSecureCacheResponse = (SecureCacheResponse) javaResponse;
+
+      // Handshake doesn't support null lists.
+      List<Certificate> peerCertificates;
+      try {
+        peerCertificates = javaSecureCacheResponse.getServerCertificateChain();
+      } catch (SSLPeerUnverifiedException e) {
+        peerCertificates = Collections.emptyList();
+      }
+      List<Certificate> localCertificates = javaSecureCacheResponse.getLocalCertificateChain();
+      if (localCertificates == null) {
+        localCertificates = Collections.emptyList();
+      }
+      Handshake handshake = Handshake.get(
+          javaSecureCacheResponse.getCipherSuite(), peerCertificates, localCertificates);
+      okResponseBuilder.handshake(handshake);
+    }
+
+    return okResponseBuilder.build();
+  }
+
+  /**
+   * Creates an OkHttp {@link Request} from the supplied information.
+   *
+   * <p>This method allows a {@code null} value for {@code requestHeaders} for situations
+   * where a connection is already connected and access to the headers has been lost.
+   * See {@link java.net.HttpURLConnection#getRequestProperties()} for details.
+   */
+  public static Request createOkRequest(
+      URI uri, String requestMethod, Map<String, List<String>> requestHeaders) {
+    // OkHttp's Call API requires a placeholder body; the real body will be streamed separately.
+    RequestBody placeholderBody = HttpMethod.requiresRequestBody(requestMethod)
+        ? EMPTY_REQUEST_BODY
+        : null;
+
+    Request.Builder builder = new Request.Builder()
+        .url(uri.toString())
+        .method(requestMethod, placeholderBody);
+
+    if (requestHeaders != null) {
+      Headers headers = extractOkHeaders(requestHeaders);
+      builder.headers(headers);
+    }
+    return builder.build();
+  }
+
+  /**
+   * Creates a {@link java.net.CacheResponse} of the correct (sub)type using information
+   * gathered from the supplied {@link Response}.
+   */
+  public static CacheResponse createJavaCacheResponse(final Response response) {
+    final Headers headers = response.headers();
+    final ResponseBody body = response.body();
+    if (response.request().isHttps()) {
+      final Handshake handshake = response.handshake();
+      return new SecureCacheResponse() {
+        @Override
+        public String getCipherSuite() {
+          return handshake != null ? handshake.cipherSuite() : null;
+        }
+
+        @Override
+        public List<Certificate> getLocalCertificateChain() {
+          if (handshake == null) return null;
+          // Java requires null, not an empty list here.
+          List<Certificate> certificates = handshake.localCertificates();
+          return certificates.size() > 0 ? certificates : null;
+        }
+
+        @Override
+        public List<Certificate> getServerCertificateChain() throws SSLPeerUnverifiedException {
+          if (handshake == null) return null;
+          // Java requires null, not an empty list here.
+          List<Certificate> certificates = handshake.peerCertificates();
+          return certificates.size() > 0 ? certificates : null;
+        }
+
+        @Override
+        public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+          if (handshake == null) return null;
+          return handshake.peerPrincipal();
+        }
+
+        @Override
+        public Principal getLocalPrincipal() {
+          if (handshake == null) return null;
+          return handshake.localPrincipal();
+        }
+
+        @Override
+        public Map<String, List<String>> getHeaders() throws IOException {
+          // Java requires that the entry with a null key be the status line.
+          return OkHeaders.toMultimap(headers, StatusLine.get(response).toString());
+        }
+
+        @Override
+        public InputStream getBody() throws IOException {
+          if (body == null) return null;
+          return body.byteStream();
+        }
+      };
+    } else {
+      return new CacheResponse() {
+        @Override
+        public Map<String, List<String>> getHeaders() throws IOException {
+          // Java requires that the entry with a null key be the status line.
+          return OkHeaders.toMultimap(headers, StatusLine.get(response).toString());
+        }
+
+        @Override
+        public InputStream getBody() throws IOException {
+          if (body == null) return null;
+          return body.byteStream();
+        }
+      };
+    }
+  }
+
+  public static java.net.CacheRequest createJavaCacheRequest(final CacheRequest okCacheRequest) {
+    return new java.net.CacheRequest() {
+      @Override
+      public void abort() {
+        okCacheRequest.abort();
+      }
+      @Override
+      public OutputStream getBody() throws IOException {
+        Sink body = okCacheRequest.body();
+        if (body == null) {
+          return null;
+        }
+        return Okio.buffer(body).outputStream();
+      }
+    };
+  }
+
+  /**
+   * Creates an {@link java.net.HttpURLConnection} of the correct subclass from the supplied OkHttp
+   * {@link Response}.
+   */
+  static HttpURLConnection createJavaUrlConnectionForCachePut(Response okResponse) {
+    Request request = okResponse.request();
+    // Create an object of the correct class in case the ResponseCache uses instanceof.
+    if (request.isHttps()) {
+      return new CacheHttpsURLConnection(new CacheHttpURLConnection(okResponse));
+    } else {
+      return new CacheHttpURLConnection(okResponse);
+    }
+  }
+
+  /**
+   * Extracts an immutable request header map from the supplied {@link com.android.okhttp.Headers}.
+   */
+  static Map<String, List<String>> extractJavaHeaders(Request request) {
+    return OkHeaders.toMultimap(request.headers(), null);
+  }
+
+  /**
+   * Extracts OkHttp headers from the supplied {@link java.net.CacheResponse}. Only real headers are
+   * extracted. See {@link #extractStatusLine(java.net.CacheResponse)}.
+   */
+  private static Headers extractOkHeaders(CacheResponse javaResponse) throws IOException {
+    Map<String, List<String>> javaResponseHeaders = javaResponse.getHeaders();
+    return extractOkHeaders(javaResponseHeaders);
+  }
+
+  /**
+   * Extracts OkHttp headers from the supplied {@link java.net.HttpURLConnection}. Only real headers
+   * are extracted. See {@link #extractStatusLine(java.net.HttpURLConnection)}.
+   */
+  private static Headers extractOkResponseHeaders(HttpURLConnection httpUrlConnection) {
+    Map<String, List<String>> javaResponseHeaders = httpUrlConnection.getHeaderFields();
+    return extractOkHeaders(javaResponseHeaders);
+  }
+
+  /**
+   * Extracts OkHttp headers from the supplied {@link Map}. Only real headers are
+   * extracted. Any entry (one with a {@code null} key) is discarded.
+   */
+  // @VisibleForTesting
+  static Headers extractOkHeaders(Map<String, List<String>> javaHeaders) {
+    Headers.Builder okHeadersBuilder = new Headers.Builder();
+    for (Map.Entry<String, List<String>> javaHeader : javaHeaders.entrySet()) {
+      String name = javaHeader.getKey();
+      if (name == null) {
+        // The Java API uses the null key to store the status line in responses.
+        // Earlier versions of OkHttp would use the null key to store the "request line" in
+        // requests. e.g. "GET / HTTP 1.1". Although this is no longer the case it must be
+        // explicitly ignored because Headers.Builder does not support null keys.
+        continue;
+      }
+      for (String value : javaHeader.getValue()) {
+        Internal.instance.addLenient(okHeadersBuilder, name, value);
+      }
+    }
+    return okHeadersBuilder.build();
+  }
+
+  /**
+   * Extracts the status line from the supplied Java API {@link java.net.HttpURLConnection}.
+   * As per the spec, the status line is held as the header with the null key. Returns {@code null}
+   * if there is no status line.
+   */
+  private static String extractStatusLine(HttpURLConnection httpUrlConnection) {
+    // Java specifies that this will be be response header with a null key.
+    return httpUrlConnection.getHeaderField(null);
+  }
+
+  /**
+   * Extracts the status line from the supplied Java API {@link java.net.CacheResponse}.
+   * As per the spec, the status line is held as the header with the null key. Throws a
+   * {@link ProtocolException} if there is no status line.
+   */
+  private static String extractStatusLine(CacheResponse javaResponse) throws IOException {
+    Map<String, List<String>> javaResponseHeaders = javaResponse.getHeaders();
+    return extractStatusLine(javaResponseHeaders);
+  }
+
+  // VisibleForTesting
+  static String extractStatusLine(Map<String, List<String>> javaResponseHeaders)
+      throws ProtocolException {
+    List<String> values = javaResponseHeaders.get(null);
+    if (values == null || values.size() == 0) {
+      // The status line is missing. This suggests a badly behaving cache.
+      throw new ProtocolException(
+          "CacheResponse is missing a \'null\' header containing the status line. Headers="
+          + javaResponseHeaders);
+    }
+    return values.get(0);
+  }
+
+  /**
+   * Creates an OkHttp Response.Body containing the supplied information.
+   */
+  private static ResponseBody createOkBody(final Headers okHeaders,
+      final CacheResponse cacheResponse) {
+    return new ResponseBody() {
+      private BufferedSource body;
+
+      @Override
+      public MediaType contentType() {
+        String contentTypeHeader = okHeaders.get("Content-Type");
+        return contentTypeHeader == null ? null : MediaType.parse(contentTypeHeader);
+      }
+
+      @Override
+      public long contentLength() {
+        return OkHeaders.contentLength(okHeaders);
+      }
+      @Override public BufferedSource source() throws IOException {
+        if (body == null) {
+          InputStream is = cacheResponse.getBody();
+          body = Okio.buffer(Okio.source(is));
+        }
+        return body;
+      }
+    };
+  }
+
+  /**
+   * Creates an OkHttp Response.Body containing the supplied information.
+   */
+  private static ResponseBody createOkBody(final URLConnection urlConnection) {
+    if (!urlConnection.getDoInput()) {
+      return null;
+    }
+    return new ResponseBody() {
+      private BufferedSource body;
+
+      @Override public MediaType contentType() {
+        String contentTypeHeader = urlConnection.getContentType();
+        return contentTypeHeader == null ? null : MediaType.parse(contentTypeHeader);
+      }
+      @Override public long contentLength() {
+        String s = urlConnection.getHeaderField("Content-Length");
+        return stringToLong(s);
+      }
+      @Override public BufferedSource source() throws IOException {
+        if (body == null) {
+          InputStream is = urlConnection.getInputStream();
+          body = Okio.buffer(Okio.source(is));
+        }
+        return body;
+      }
+    };
+  }
+
+  /**
+   * An {@link java.net.HttpURLConnection} that represents an HTTP request at the point where
+   * the request has been made, and the response headers have been received, but the body content,
+   * if present, has not been read yet. This intended to provide enough information for
+   * {@link java.net.ResponseCache} subclasses and no more.
+   *
+   * <p>Much of the method implementations are overrides to delegate to the OkHttp request and
+   * response, or to deny access to information as a real HttpURLConnection would after connection.
+   */
+  private static final class CacheHttpURLConnection extends HttpURLConnection {
+
+    private final Request request;
+    private final Response response;
+
+    public CacheHttpURLConnection(Response response) {
+      super(response.request().url());
+      this.request = response.request();
+      this.response = response;
+
+      // Configure URLConnection inherited fields.
+      this.connected = true;
+      this.doOutput = request.body() != null;
+      this.doInput = true;
+      this.useCaches = true;
+
+      // Configure HttpUrlConnection inherited fields.
+      this.method = request.method();
+    }
+
+    // HTTP connection lifecycle methods
+
+    @Override
+    public void connect() throws IOException {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public void disconnect() {
+      throw throwRequestModificationException();
+    }
+
+    // HTTP Request methods
+
+    @Override
+    public void setRequestProperty(String key, String value) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public void addRequestProperty(String key, String value) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public String getRequestProperty(String key) {
+      return request.header(key);
+    }
+
+    @Override
+    public Map<String, List<String>> getRequestProperties() {
+      // The RI and OkHttp's HttpURLConnectionImpl fail this call after connect() as required by the
+      // spec. There seems no good reason why this should fail while getRequestProperty() is ok.
+      // We don't fail here, because we need all request header values for caching Vary responses
+      // correctly.
+      return OkHeaders.toMultimap(request.headers(), null);
+    }
+
+    @Override
+    public void setFixedLengthStreamingMode(int contentLength) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public void setFixedLengthStreamingMode(long contentLength) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public void setChunkedStreamingMode(int chunklen) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public void setInstanceFollowRedirects(boolean followRedirects) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public boolean getInstanceFollowRedirects() {
+      // Return the platform default.
+      return super.getInstanceFollowRedirects();
+    }
+
+    @Override
+    public void setRequestMethod(String method) throws ProtocolException {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public String getRequestMethod() {
+      return request.method();
+    }
+
+    // HTTP Response methods
+
+    @Override
+    public String getHeaderFieldKey(int position) {
+      // Deal with index 0 meaning "status line"
+      if (position < 0) {
+        throw new IllegalArgumentException("Invalid header index: " + position);
+      }
+      if (position == 0) {
+        return null;
+      }
+      return response.headers().name(position - 1);
+    }
+
+    @Override
+    public String getHeaderField(int position) {
+      // Deal with index 0 meaning "status line"
+      if (position < 0) {
+        throw new IllegalArgumentException("Invalid header index: " + position);
+      }
+      if (position == 0) {
+        return StatusLine.get(response).toString();
+      }
+      return response.headers().value(position - 1);
+    }
+
+    @Override
+    public String getHeaderField(String fieldName) {
+      return fieldName == null
+          ? StatusLine.get(response).toString()
+          : response.headers().get(fieldName);
+    }
+
+    @Override
+    public Map<String, List<String>> getHeaderFields() {
+      return OkHeaders.toMultimap(response.headers(), StatusLine.get(response).toString());
+    }
+
+    @Override
+    public int getResponseCode() throws IOException {
+      return response.code();
+    }
+
+    @Override
+    public String getResponseMessage() throws IOException {
+      return response.message();
+    }
+
+    @Override
+    public InputStream getErrorStream() {
+      return null;
+    }
+
+    // HTTP miscellaneous methods
+
+    @Override
+    public boolean usingProxy() {
+      // It's safe to return false here, even if a proxy is in use. The problem is we don't
+      // necessarily know if we're going to use a proxy by the time we ask the cache for a response.
+      return false;
+    }
+
+    // URLConnection methods
+
+    @Override
+    public void setConnectTimeout(int timeout) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public int getConnectTimeout() {
+      // Impossible to say.
+      return 0;
+    }
+
+    @Override
+    public void setReadTimeout(int timeout) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public int getReadTimeout() {
+      // Impossible to say.
+      return 0;
+    }
+
+    @Override
+    public Object getContent() throws IOException {
+      throw throwResponseBodyAccessException();
+    }
+
+    @Override
+    public Object getContent(Class[] classes) throws IOException {
+      throw throwResponseBodyAccessException();
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+      throw throwResponseBodyAccessException();
+    }
+
+    @Override
+    public OutputStream getOutputStream() throws IOException {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public void setDoInput(boolean doInput) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public boolean getDoInput() {
+      return doInput;
+    }
+
+    @Override
+    public void setDoOutput(boolean doOutput) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public boolean getDoOutput() {
+      return doOutput;
+    }
+
+    @Override
+    public void setAllowUserInteraction(boolean allowUserInteraction) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public boolean getAllowUserInteraction() {
+      return false;
+    }
+
+    @Override
+    public void setUseCaches(boolean useCaches) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public boolean getUseCaches() {
+      return super.getUseCaches();
+    }
+
+    @Override
+    public void setIfModifiedSince(long ifModifiedSince) {
+      throw throwRequestModificationException();
+    }
+
+    @Override
+    public long getIfModifiedSince() {
+      return stringToLong(request.headers().get("If-Modified-Since"));
+    }
+
+    @Override
+    public boolean getDefaultUseCaches() {
+      return super.getDefaultUseCaches();
+    }
+
+    @Override
+    public void setDefaultUseCaches(boolean defaultUseCaches) {
+      super.setDefaultUseCaches(defaultUseCaches);
+    }
+  }
+
+  /** An HttpsURLConnection to offer to the cache. */
+  private static final class CacheHttpsURLConnection extends DelegatingHttpsURLConnection {
+    private final CacheHttpURLConnection delegate;
+
+    public CacheHttpsURLConnection(CacheHttpURLConnection delegate) {
+      super(delegate);
+      this.delegate = delegate;
+    }
+
+    @Override protected Handshake handshake() {
+      return delegate.response.handshake();
+    }
+
+    @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+      throw throwRequestModificationException();
+    }
+
+    @Override public HostnameVerifier getHostnameVerifier() {
+      throw throwRequestSslAccessException();
+    }
+
+    @Override public void setSSLSocketFactory(SSLSocketFactory socketFactory) {
+      throw throwRequestModificationException();
+    }
+
+    @Override public SSLSocketFactory getSSLSocketFactory() {
+      throw throwRequestSslAccessException();
+    }
+
+    @Override public long getContentLengthLong() {
+      return delegate.getContentLengthLong();
+    }
+
+    @Override public void setFixedLengthStreamingMode(long contentLength) {
+      delegate.setFixedLengthStreamingMode(contentLength);
+    }
+
+    @Override public long getHeaderFieldLong(String field, long defaultValue) {
+      return delegate.getHeaderFieldLong(field, defaultValue);
+    }
+  }
+
+  private static RuntimeException throwRequestModificationException() {
+    throw new UnsupportedOperationException("ResponseCache cannot modify the request.");
+  }
+
+  private static RuntimeException throwRequestHeaderAccessException() {
+    throw new UnsupportedOperationException("ResponseCache cannot access request headers");
+  }
+
+  private static RuntimeException throwRequestSslAccessException() {
+    throw new UnsupportedOperationException("ResponseCache cannot access SSL internals");
+  }
+
+  private static RuntimeException throwResponseBodyAccessException() {
+    throw new UnsupportedOperationException("ResponseCache cannot access the response body.");
+  }
+
+  private static <T> List<T> nullSafeImmutableList(T[] elements) {
+    return elements == null ? Collections.<T>emptyList() : Util.immutableList(elements);
+  }
+
+  private static long stringToLong(String s) {
+    if (s == null) return -1;
+    try {
+      return Long.parseLong(s);
+    } catch (NumberFormatException e) {
+      return -1;
+    }
+  }
+}
diff --git a/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/OkUrlFactory.java b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/OkUrlFactory.java
new file mode 100644
index 0000000..43e8259
--- /dev/null
+++ b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/OkUrlFactory.java
@@ -0,0 +1,100 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.URLFilter;
+import com.android.okhttp.internal.huc.HttpURLConnectionImpl;
+import com.android.okhttp.internal.huc.HttpsURLConnectionImpl;
+
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class OkUrlFactory implements URLStreamHandlerFactory, Cloneable {
+  private final OkHttpClient client;
+  private URLFilter urlFilter;
+
+  public OkUrlFactory(OkHttpClient client) {
+    this.client = client;
+  }
+
+  public OkHttpClient client() {
+    return client;
+  }
+
+  void setUrlFilter(URLFilter filter) {
+    urlFilter = filter;
+  }
+
+  /**
+   * Returns a copy of this stream handler factory that includes a shallow copy
+   * of the internal {@linkplain OkHttpClient HTTP client}.
+   */
+  @Override public OkUrlFactory clone() {
+    return new OkUrlFactory(client.clone());
+  }
+
+  public HttpURLConnection open(URL url) {
+    return open(url, client.getProxy());
+  }
+
+  HttpURLConnection open(URL url, Proxy proxy) {
+    String protocol = url.getProtocol();
+    OkHttpClient copy = client.copyWithDefaults();
+    copy.setProxy(proxy);
+
+    if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy, urlFilter);
+    if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy, urlFilter);
+    throw new IllegalArgumentException("Unexpected protocol: " + protocol);
+  }
+
+  /**
+   * Creates a URLStreamHandler as a {@link java.net.URL#setURLStreamHandlerFactory}.
+   *
+   * <p>This code configures OkHttp to handle all HTTP and HTTPS connections
+   * created with {@link java.net.URL#openConnection()}: <pre>   {@code
+   *
+   *   OkHttpClient okHttpClient = new OkHttpClient();
+   *   URL.setURLStreamHandlerFactory(new OkUrlFactory(okHttpClient));
+   * }</pre>
+   */
+  @Override public URLStreamHandler createURLStreamHandler(final String protocol) {
+    if (!protocol.equals("http") && !protocol.equals("https")) return null;
+
+    return new URLStreamHandler() {
+      @Override protected URLConnection openConnection(URL url) {
+        return open(url);
+      }
+
+      @Override protected URLConnection openConnection(URL url, Proxy proxy) {
+        return open(url, proxy);
+      }
+
+      @Override protected int getDefaultPort() {
+        if (protocol.equals("http")) return 80;
+        if (protocol.equals("https")) return 443;
+        throw new AssertionError();
+      }
+    };
+  }
+}
diff --git a/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/URLFilter.java b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/URLFilter.java
new file mode 100644
index 0000000..9f69a0f
--- /dev/null
+++ b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/URLFilter.java
@@ -0,0 +1,34 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Request filter based on the request's URL.
+ *
+ * @deprecated use {@link okhttp3.Interceptor} for non-HttpURLConnection filtering.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface URLFilter {
+  /**
+   * Check whether request to the provided URL is permitted to be issued.
+   *
+   * @throws IOException if the request to the provided URL is not permitted.
+   */
+  void checkURLPermitted(URL url) throws IOException;
+}
diff --git a/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/DelegatingHttpsURLConnection.java b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/DelegatingHttpsURLConnection.java
new file mode 100644
index 0000000..25cf6fb
--- /dev/null
+++ b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/DelegatingHttpsURLConnection.java
@@ -0,0 +1,293 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp.internal.huc;
+
+import com.android.okhttp.Handshake;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.security.Permission;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Implement an HTTPS connection by delegating to an HTTP connection for
+ * everything but the HTTPS-specific stuff.
+ */
+abstract class DelegatingHttpsURLConnection extends HttpsURLConnection {
+  private final HttpURLConnection delegate;
+
+  public DelegatingHttpsURLConnection(HttpURLConnection delegate) {
+    super(delegate.getURL());
+    this.delegate = delegate;
+  }
+
+  protected abstract Handshake handshake();
+
+  @Override public abstract void setHostnameVerifier(HostnameVerifier hostnameVerifier);
+
+  @Override public abstract HostnameVerifier getHostnameVerifier();
+
+  @Override public abstract void setSSLSocketFactory(SSLSocketFactory sslSocketFactory);
+
+  @Override public abstract SSLSocketFactory getSSLSocketFactory();
+
+  @Override public String getCipherSuite() {
+    Handshake handshake = handshake();
+    return handshake != null ? handshake.cipherSuite() : null;
+  }
+
+  @Override public Certificate[] getLocalCertificates() {
+    Handshake handshake = handshake();
+    if (handshake == null) return null;
+    List<Certificate> result = handshake.localCertificates();
+    return !result.isEmpty() ? result.toArray(new Certificate[result.size()]) : null;
+  }
+
+  @Override public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
+    Handshake handshake = handshake();
+    if (handshake == null) return null;
+    List<Certificate> result = handshake.peerCertificates();
+    return !result.isEmpty() ? result.toArray(new Certificate[result.size()]) : null;
+  }
+
+  @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+    Handshake handshake = handshake();
+    return handshake != null ? handshake.peerPrincipal() : null;
+  }
+
+  @Override public Principal getLocalPrincipal() {
+    Handshake handshake = handshake();
+    return handshake != null ? handshake.localPrincipal() : null;
+  }
+
+  @Override public void connect() throws IOException {
+    connected = true;
+    delegate.connect();
+  }
+
+  @Override public void disconnect() {
+    delegate.disconnect();
+  }
+
+  @Override public InputStream getErrorStream() {
+    return delegate.getErrorStream();
+  }
+
+  @Override public String getRequestMethod() {
+    return delegate.getRequestMethod();
+  }
+
+  @Override public int getResponseCode() throws IOException {
+    return delegate.getResponseCode();
+  }
+
+  @Override public String getResponseMessage() throws IOException {
+    return delegate.getResponseMessage();
+  }
+
+  @Override public void setRequestMethod(String method) throws ProtocolException {
+    delegate.setRequestMethod(method);
+  }
+
+  @Override public boolean usingProxy() {
+    return delegate.usingProxy();
+  }
+
+  @Override public boolean getInstanceFollowRedirects() {
+    return delegate.getInstanceFollowRedirects();
+  }
+
+  @Override public void setInstanceFollowRedirects(boolean followRedirects) {
+    delegate.setInstanceFollowRedirects(followRedirects);
+  }
+
+  @Override public boolean getAllowUserInteraction() {
+    return delegate.getAllowUserInteraction();
+  }
+
+  @Override public Object getContent() throws IOException {
+    return delegate.getContent();
+  }
+
+  @SuppressWarnings("unchecked") // Spec does not generify
+  @Override public Object getContent(Class[] types) throws IOException {
+    return delegate.getContent(types);
+  }
+
+  @Override public String getContentEncoding() {
+    return delegate.getContentEncoding();
+  }
+
+  @Override public int getContentLength() {
+    return delegate.getContentLength();
+  }
+
+  @Override public String getContentType() {
+    return delegate.getContentType();
+  }
+
+  @Override public long getDate() {
+    return delegate.getDate();
+  }
+
+  @Override public boolean getDefaultUseCaches() {
+    return delegate.getDefaultUseCaches();
+  }
+
+  @Override public boolean getDoInput() {
+    return delegate.getDoInput();
+  }
+
+  @Override public boolean getDoOutput() {
+    return delegate.getDoOutput();
+  }
+
+  @Override public long getExpiration() {
+    return delegate.getExpiration();
+  }
+
+  @Override public String getHeaderField(int pos) {
+    return delegate.getHeaderField(pos);
+  }
+
+  @Override public Map<String, List<String>> getHeaderFields() {
+    return delegate.getHeaderFields();
+  }
+
+  @Override public Map<String, List<String>> getRequestProperties() {
+    return delegate.getRequestProperties();
+  }
+
+  @Override public void addRequestProperty(String field, String newValue) {
+    delegate.addRequestProperty(field, newValue);
+  }
+
+  @Override public String getHeaderField(String key) {
+    return delegate.getHeaderField(key);
+  }
+
+  @Override public long getHeaderFieldDate(String field, long defaultValue) {
+    return delegate.getHeaderFieldDate(field, defaultValue);
+  }
+
+  @Override public int getHeaderFieldInt(String field, int defaultValue) {
+    return delegate.getHeaderFieldInt(field, defaultValue);
+  }
+
+  @Override public String getHeaderFieldKey(int position) {
+    return delegate.getHeaderFieldKey(position);
+  }
+
+  @Override public long getIfModifiedSince() {
+    return delegate.getIfModifiedSince();
+  }
+
+  @Override public InputStream getInputStream() throws IOException {
+    return delegate.getInputStream();
+  }
+
+  @Override public long getLastModified() {
+    return delegate.getLastModified();
+  }
+
+  @Override public OutputStream getOutputStream() throws IOException {
+    return delegate.getOutputStream();
+  }
+
+  @Override public Permission getPermission() throws IOException {
+    return delegate.getPermission();
+  }
+
+  @Override public String getRequestProperty(String field) {
+    return delegate.getRequestProperty(field);
+  }
+
+  @Override public URL getURL() {
+    return delegate.getURL();
+  }
+
+  @Override public boolean getUseCaches() {
+    return delegate.getUseCaches();
+  }
+
+  @Override public void setAllowUserInteraction(boolean newValue) {
+    delegate.setAllowUserInteraction(newValue);
+  }
+
+  @Override public void setDefaultUseCaches(boolean newValue) {
+    delegate.setDefaultUseCaches(newValue);
+  }
+
+  @Override public void setDoInput(boolean newValue) {
+    delegate.setDoInput(newValue);
+  }
+
+  @Override public void setDoOutput(boolean newValue) {
+    delegate.setDoOutput(newValue);
+  }
+
+  @Override public void setIfModifiedSince(long newValue) {
+    delegate.setIfModifiedSince(newValue);
+  }
+
+  @Override public void setRequestProperty(String field, String newValue) {
+    delegate.setRequestProperty(field, newValue);
+  }
+
+  @Override public void setUseCaches(boolean newValue) {
+    delegate.setUseCaches(newValue);
+  }
+
+  @Override public void setConnectTimeout(int timeoutMillis) {
+    delegate.setConnectTimeout(timeoutMillis);
+  }
+
+  @Override public int getConnectTimeout() {
+    return delegate.getConnectTimeout();
+  }
+
+  @Override public void setReadTimeout(int timeoutMillis) {
+    delegate.setReadTimeout(timeoutMillis);
+  }
+
+  @Override public int getReadTimeout() {
+    return delegate.getReadTimeout();
+  }
+
+  @Override public String toString() {
+    return delegate.toString();
+  }
+
+  @Override public void setFixedLengthStreamingMode(int contentLength) {
+    delegate.setFixedLengthStreamingMode(contentLength);
+  }
+
+  @Override public void setChunkedStreamingMode(int chunkLength) {
+    delegate.setChunkedStreamingMode(chunkLength);
+  }
+}
diff --git a/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpURLConnectionImpl.java b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpURLConnectionImpl.java
new file mode 100644
index 0000000..962bc99
--- /dev/null
+++ b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpURLConnectionImpl.java
@@ -0,0 +1,641 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp.internal.huc;
+
+import com.android.okhttp.Connection;
+import com.android.okhttp.Handshake;
+import com.android.okhttp.Headers;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.Protocol;
+import com.android.okhttp.Request;
+import com.android.okhttp.RequestBody;
+import com.android.okhttp.Response;
+import com.android.okhttp.Route;
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.Platform;
+import com.android.okhttp.internal.URLFilter;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.Version;
+import com.android.okhttp.internal.http.HttpDate;
+import com.android.okhttp.internal.http.HttpEngine;
+import com.android.okhttp.internal.http.HttpMethod;
+import com.android.okhttp.internal.http.OkHeaders;
+import com.android.okhttp.internal.http.RequestException;
+import com.android.okhttp.internal.http.RetryableSink;
+import com.android.okhttp.internal.http.RouteException;
+import com.android.okhttp.internal.http.StatusLine;
+import com.android.okhttp.internal.http.StreamAllocation;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpRetryException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.net.SocketPermission;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.security.Permission;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.Sink;
+
+/**
+ * This implementation uses HttpEngine to send requests and receive responses.
+ * This class may use multiple HttpEngines to follow redirects, authentication
+ * retries, etc. to retrieve the final response body.
+ *
+ * <h3>What does 'connected' mean?</h3>
+ * This class inherits a {@code connected} field from the superclass. That field
+ * is <strong>not</strong> used to indicate whether this URLConnection is
+ * currently connected. Instead, it indicates whether a connection has ever been
+ * attempted. Once a connection has been attempted, certain properties (request
+ * header fields, request method, etc.) are immutable.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class HttpURLConnectionImpl extends HttpURLConnection {
+  private static final Set<String> METHODS = new LinkedHashSet<>(
+      Arrays.asList("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "PATCH"));
+  private static final RequestBody EMPTY_REQUEST_BODY = RequestBody.create(null, new byte[0]);
+
+  final OkHttpClient client;
+
+  private Headers.Builder requestHeaders = new Headers.Builder();
+
+  /** Like the superclass field of the same name, but a long and available on all platforms. */
+  private long fixedContentLength = -1;
+  private int followUpCount;
+  protected IOException httpEngineFailure;
+  protected HttpEngine httpEngine;
+  /** Lazily created (with synthetic headers) on first call to getHeaders(). */
+  private Headers responseHeaders;
+
+  /**
+   * The most recently attempted route. This will be null if we haven't sent a
+   * request yet, or if the response comes from a cache.
+   */
+  private Route route;
+
+  /**
+   * The most recently received TLS handshake. This will be null if we haven't
+   * connected yet, or if the most recent connection was HTTP (and not HTTPS).
+   */
+  Handshake handshake;
+
+  private URLFilter urlFilter;
+
+  public HttpURLConnectionImpl(URL url, OkHttpClient client) {
+    super(url);
+    this.client = client;
+  }
+
+  public HttpURLConnectionImpl(URL url, OkHttpClient client, URLFilter urlFilter) {
+    this(url, client);
+    this.urlFilter = urlFilter;
+  }
+
+  @Override public final void connect() throws IOException {
+    initHttpEngine();
+    boolean success;
+    do {
+      success = execute(false);
+    } while (!success);
+  }
+
+  @Override public final void disconnect() {
+    // Calling disconnect() before a connection exists should have no effect.
+    if (httpEngine == null) return;
+
+    httpEngine.cancel();
+
+    // This doesn't close the stream because doing so would require all stream
+    // access to be synchronized. It's expected that the thread using the
+    // connection will close its streams directly. If it doesn't, the worst
+    // case is that the GzipSource's Inflater won't be released until it's
+    // finalized. (This logs a warning on Android.)
+  }
+
+  /**
+   * Returns an input stream from the server in the case of error such as the
+   * requested file (txt, htm, html) is not found on the remote server.
+   */
+  @Override public final InputStream getErrorStream() {
+    try {
+      HttpEngine response = getResponse();
+      if (HttpEngine.hasBody(response.getResponse())
+          && response.getResponse().code() >= HTTP_BAD_REQUEST) {
+        return response.getResponse().body().byteStream();
+      }
+      return null;
+    } catch (IOException e) {
+      return null;
+    }
+  }
+
+  private Headers getHeaders() throws IOException {
+    if (responseHeaders == null) {
+      Response response = getResponse().getResponse();
+      Headers headers = response.headers();
+      responseHeaders = headers.newBuilder()
+          .add(OkHeaders.SELECTED_PROTOCOL, response.protocol().toString())
+          .add(OkHeaders.RESPONSE_SOURCE, responseSourceHeader(response))
+          .build();
+    }
+    return responseHeaders;
+  }
+
+  private static String responseSourceHeader(Response response) {
+    if (response.networkResponse() == null) {
+      if (response.cacheResponse() == null) {
+        return "NONE";
+      }
+      return "CACHE " + response.code();
+    }
+    if (response.cacheResponse() == null) {
+      return "NETWORK " + response.code();
+    }
+    return "CONDITIONAL_CACHE " + response.networkResponse().code();
+  }
+
+  /**
+   * Returns the value of the field at {@code position}. Returns null if there
+   * are fewer than {@code position} headers.
+   */
+  @Override public final String getHeaderField(int position) {
+    try {
+      return getHeaders().value(position);
+    } catch (IOException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Returns the value of the field corresponding to the {@code fieldName}, or
+   * null if there is no such field. If the field has multiple values, the
+   * last value is returned.
+   */
+  @Override public final String getHeaderField(String fieldName) {
+    try {
+      return fieldName == null
+          ? StatusLine.get(getResponse().getResponse()).toString()
+          : getHeaders().get(fieldName);
+    } catch (IOException e) {
+      return null;
+    }
+  }
+
+  @Override public final String getHeaderFieldKey(int position) {
+    try {
+      return getHeaders().name(position);
+    } catch (IOException e) {
+      return null;
+    }
+  }
+
+  @Override public final Map<String, List<String>> getHeaderFields() {
+    try {
+      return OkHeaders.toMultimap(getHeaders(),
+          StatusLine.get(getResponse().getResponse()).toString());
+    } catch (IOException e) {
+      return Collections.emptyMap();
+    }
+  }
+
+  @Override public final Map<String, List<String>> getRequestProperties() {
+    if (connected) {
+      throw new IllegalStateException(
+          "Cannot access request header fields after connection is set");
+    }
+
+    return OkHeaders.toMultimap(requestHeaders.build(), null);
+  }
+
+  @Override public final InputStream getInputStream() throws IOException {
+    if (!doInput) {
+      throw new ProtocolException("This protocol does not support input");
+    }
+
+    HttpEngine response = getResponse();
+
+    // if the requested file does not exist, throw an exception formerly the
+    // Error page from the server was returned if the requested file was
+    // text/html this has changed to return FileNotFoundException for all
+    // file types
+    if (getResponseCode() >= HTTP_BAD_REQUEST) {
+      throw new FileNotFoundException(url.toString());
+    }
+
+    return response.getResponse().body().byteStream();
+  }
+
+  @Override public final OutputStream getOutputStream() throws IOException {
+    connect();
+
+    BufferedSink sink = httpEngine.getBufferedRequestBody();
+    if (sink == null) {
+      throw new ProtocolException("method does not support a request body: " + method);
+    } else if (httpEngine.hasResponse()) {
+      throw new ProtocolException("cannot write request body after response has been read");
+    }
+
+    return sink.outputStream();
+  }
+
+  @Override public final Permission getPermission() throws IOException {
+    URL url = getURL();
+    String hostName = url.getHost();
+    int hostPort = url.getPort() != -1
+        ? url.getPort()
+        : HttpUrl.defaultPort(url.getProtocol());
+    if (usingProxy()) {
+      InetSocketAddress proxyAddress = (InetSocketAddress) client.getProxy().address();
+      hostName = proxyAddress.getHostName();
+      hostPort = proxyAddress.getPort();
+    }
+    return new SocketPermission(hostName + ":" + hostPort, "connect, resolve");
+  }
+
+  @Override public final String getRequestProperty(String field) {
+    if (field == null) return null;
+    return requestHeaders.get(field);
+  }
+
+  @Override public void setConnectTimeout(int timeoutMillis) {
+    client.setConnectTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
+  }
+
+  @Override
+  public void setInstanceFollowRedirects(boolean followRedirects) {
+    client.setFollowRedirects(followRedirects);
+  }
+
+  @Override public boolean getInstanceFollowRedirects() {
+    return client.getFollowRedirects();
+  }
+
+  @Override public int getConnectTimeout() {
+    return client.getConnectTimeout();
+  }
+
+  @Override public void setReadTimeout(int timeoutMillis) {
+    client.setReadTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
+  }
+
+  @Override public int getReadTimeout() {
+    return client.getReadTimeout();
+  }
+
+  private void initHttpEngine() throws IOException {
+    if (httpEngineFailure != null) {
+      throw httpEngineFailure;
+    } else if (httpEngine != null) {
+      return;
+    }
+
+    connected = true;
+    try {
+      if (doOutput) {
+        if (method.equals("GET")) {
+          // they are requesting a stream to write to. This implies a POST method
+          method = "POST";
+        } else if (!HttpMethod.permitsRequestBody(method)) {
+          throw new ProtocolException(method + " does not support writing");
+        }
+      }
+      // If the user set content length to zero, we know there will not be a request body.
+      httpEngine = newHttpEngine(method, null, null, null);
+    } catch (IOException e) {
+      httpEngineFailure = e;
+      throw e;
+    }
+  }
+
+  private HttpEngine newHttpEngine(String method, StreamAllocation streamAllocation,
+      RetryableSink requestBody, Response priorResponse)
+      throws MalformedURLException, UnknownHostException {
+    // OkHttp's Call API requires a placeholder body; the real body will be streamed separately.
+    RequestBody placeholderBody = HttpMethod.requiresRequestBody(method)
+        ? EMPTY_REQUEST_BODY
+        : null;
+    URL url = getURL();
+    HttpUrl httpUrl = Internal.instance.getHttpUrlChecked(url.toString());
+    Request.Builder builder = new Request.Builder()
+        .url(httpUrl)
+        .method(method, placeholderBody);
+    Headers headers = requestHeaders.build();
+    for (int i = 0, size = headers.size(); i < size; i++) {
+      builder.addHeader(headers.name(i), headers.value(i));
+    }
+
+    boolean bufferRequestBody = false;
+    if (HttpMethod.permitsRequestBody(method)) {
+      // Specify how the request body is terminated.
+      if (fixedContentLength != -1) {
+        builder.header("Content-Length", Long.toString(fixedContentLength));
+      } else if (chunkLength > 0) {
+        builder.header("Transfer-Encoding", "chunked");
+      } else {
+        bufferRequestBody = true;
+      }
+
+      // Add a content type for the request body, if one isn't already present.
+      if (headers.get("Content-Type") == null) {
+        builder.header("Content-Type", "application/x-www-form-urlencoded");
+      }
+    }
+
+    if (headers.get("User-Agent") == null) {
+      builder.header("User-Agent", defaultUserAgent());
+    }
+
+    Request request = builder.build();
+
+    // If we're currently not using caches, make sure the engine's client doesn't have one.
+    OkHttpClient engineClient = client;
+    if (Internal.instance.internalCache(engineClient) != null && !getUseCaches()) {
+      engineClient = client.clone().setCache(null);
+    }
+
+    return new HttpEngine(engineClient, request, bufferRequestBody, true, false, streamAllocation,
+        requestBody, priorResponse);
+  }
+
+  private String defaultUserAgent() {
+    String agent = System.getProperty("http.agent");
+    return agent != null ? Util.toHumanReadableAscii(agent) : Version.userAgent();
+  }
+
+  /**
+   * Aggressively tries to get the final HTTP response, potentially making
+   * many HTTP requests in the process in order to cope with redirects and
+   * authentication.
+   */
+  private HttpEngine getResponse() throws IOException {
+    initHttpEngine();
+
+    if (httpEngine.hasResponse()) {
+      return httpEngine;
+    }
+
+    while (true) {
+      if (!execute(true)) {
+        continue;
+      }
+
+      Response response = httpEngine.getResponse();
+      Request followUp = httpEngine.followUpRequest();
+
+      if (followUp == null) {
+        httpEngine.releaseStreamAllocation();
+        return httpEngine;
+      }
+
+      if (++followUpCount > HttpEngine.MAX_FOLLOW_UPS) {
+        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
+      }
+
+      // The first request was insufficient. Prepare for another...
+      url = followUp.url();
+      requestHeaders = followUp.headers().newBuilder();
+
+      // Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM redirect
+      // should keep the same method, Chrome, Firefox and the RI all issue GETs
+      // when following any redirect.
+      Sink requestBody = httpEngine.getRequestBody();
+      if (!followUp.method().equals(method)) {
+        requestBody = null;
+      }
+
+      if (requestBody != null && !(requestBody instanceof RetryableSink)) {
+        throw new HttpRetryException("Cannot retry streamed HTTP body", responseCode);
+      }
+
+      StreamAllocation streamAllocation = httpEngine.close();
+      if (!httpEngine.sameConnection(followUp.httpUrl())) {
+        streamAllocation.release();
+        streamAllocation = null;
+      }
+
+      httpEngine = newHttpEngine(followUp.method(), streamAllocation, (RetryableSink) requestBody,
+          response);
+    }
+  }
+
+  /**
+   * Sends a request and optionally reads a response. Returns true if the
+   * request was successfully executed, and false if the request can be
+   * retried. Throws an exception if the request failed permanently.
+   */
+  private boolean execute(boolean readResponse) throws IOException {
+    boolean releaseConnection = true;
+    if (urlFilter != null) {
+      urlFilter.checkURLPermitted(httpEngine.getRequest().url());
+    }
+    try {
+      httpEngine.sendRequest();
+      Connection connection = httpEngine.getConnection();
+      if (connection != null) {
+        route = connection.getRoute();
+        handshake = connection.getHandshake();
+      } else {
+        route = null;
+        handshake = null;
+      }
+      if (readResponse) {
+        httpEngine.readResponse();
+      }
+      releaseConnection = false;
+
+      return true;
+    } catch (RequestException e) {
+      // An attempt to interpret a request failed.
+      IOException toThrow = e.getCause();
+      httpEngineFailure = toThrow;
+      throw toThrow;
+    } catch (RouteException e) {
+      // The attempt to connect via a route failed. The request will not have been sent.
+      HttpEngine retryEngine = httpEngine.recover(e);
+      if (retryEngine != null) {
+        releaseConnection = false;
+        httpEngine = retryEngine;
+        return false;
+      }
+
+      // Give up; recovery is not possible.
+      IOException toThrow = e.getLastConnectException();
+      httpEngineFailure = toThrow;
+      throw toThrow;
+    } catch (IOException e) {
+      // An attempt to communicate with a server failed. The request may have been sent.
+      HttpEngine retryEngine = httpEngine.recover(e);
+      if (retryEngine != null) {
+        releaseConnection = false;
+        httpEngine = retryEngine;
+        return false;
+      }
+
+      // Give up; recovery is not possible.
+      httpEngineFailure = e;
+      throw e;
+    } finally {
+      // We're throwing an unchecked exception. Release any resources.
+      if (releaseConnection) {
+        StreamAllocation streamAllocation = httpEngine.close();
+        streamAllocation.release();
+      }
+    }
+  }
+
+  /**
+   * Returns true if either:
+   * <ul>
+   *   <li>A specific proxy was explicitly configured for this connection.
+   *   <li>The response has already been retrieved, and a proxy was {@link
+   *       java.net.ProxySelector selected} in order to get it.
+   * </ul>
+   *
+   * <p><strong>Warning:</strong> This method may return false before attempting
+   * to connect and true afterwards.
+   */
+  @Override public final boolean usingProxy() {
+    Proxy proxy = route != null
+        ? route.getProxy()
+        : client.getProxy();
+    return proxy != null && proxy.type() != Proxy.Type.DIRECT;
+  }
+
+  @Override public String getResponseMessage() throws IOException {
+    return getResponse().getResponse().message();
+  }
+
+  @Override public final int getResponseCode() throws IOException {
+    return getResponse().getResponse().code();
+  }
+
+  @Override public final void setRequestProperty(String field, String newValue) {
+    if (connected) {
+      throw new IllegalStateException("Cannot set request property after connection is made");
+    }
+    if (field == null) {
+      throw new NullPointerException("field == null");
+    }
+    if (newValue == null) {
+      // Silently ignore null header values for backwards compatibility with older
+      // android versions as well as with other URLConnection implementations.
+      //
+      // Some implementations send a malformed HTTP header when faced with
+      // such requests, we respect the spec and ignore the header.
+      Platform.get().logW("Ignoring header " + field + " because its value was null.");
+      return;
+    }
+
+    // TODO: Deprecate use of X-Android-Transports header?
+    if ("X-Android-Transports".equals(field) || "X-Android-Protocols".equals(field)) {
+      setProtocols(newValue, false /* append */);
+    } else {
+      requestHeaders.set(field, newValue);
+    }
+  }
+
+  @Override public void setIfModifiedSince(long newValue) {
+    super.setIfModifiedSince(newValue);
+    if (ifModifiedSince != 0) {
+      requestHeaders.set("If-Modified-Since", HttpDate.format(new Date(ifModifiedSince)));
+    } else {
+      requestHeaders.removeAll("If-Modified-Since");
+    }
+  }
+
+  @Override public final void addRequestProperty(String field, String value) {
+    if (connected) {
+      throw new IllegalStateException("Cannot add request property after connection is made");
+    }
+    if (field == null) {
+      throw new NullPointerException("field == null");
+    }
+    if (value == null) {
+      // Silently ignore null header values for backwards compatibility with older
+      // android versions as well as with other URLConnection implementations.
+      //
+      // Some implementations send a malformed HTTP header when faced with
+      // such requests, we respect the spec and ignore the header.
+      Platform.get().logW("Ignoring header " + field + " because its value was null.");
+      return;
+    }
+
+    // TODO: Deprecate use of X-Android-Transports header?
+    if ("X-Android-Transports".equals(field) || "X-Android-Protocols".equals(field)) {
+      setProtocols(value, true /* append */);
+    } else {
+      requestHeaders.add(field, value);
+    }
+  }
+
+  /*
+   * Splits and validates a comma-separated string of protocols.
+   * When append == false, we require that the transport list contains "http/1.1".
+   * Throws {@link IllegalStateException} when one of the protocols isn't
+   * defined in {@link Protocol OkHttp's protocol enumeration}.
+   */
+  private void setProtocols(String protocolsString, boolean append) {
+    List<Protocol> protocolsList = new ArrayList<>();
+    if (append) {
+      protocolsList.addAll(client.getProtocols());
+    }
+    for (String protocol : protocolsString.split(",", -1)) {
+      try {
+        protocolsList.add(Protocol.get(protocol));
+      } catch (IOException e) {
+        throw new IllegalStateException(e);
+      }
+    }
+    client.setProtocols(protocolsList);
+  }
+
+  @Override public void setRequestMethod(String method) throws ProtocolException {
+    if (!METHODS.contains(method)) {
+      throw new ProtocolException("Expected one of " + METHODS + " but was " + method);
+    }
+    this.method = method;
+  }
+
+  @Override public void setFixedLengthStreamingMode(int contentLength) {
+    setFixedLengthStreamingMode((long) contentLength);
+  }
+
+  @Override public void setFixedLengthStreamingMode(long contentLength) {
+    if (super.connected) throw new IllegalStateException("Already connected");
+    if (chunkLength > 0) throw new IllegalStateException("Already in chunked mode");
+    if (contentLength < 0) throw new IllegalArgumentException("contentLength < 0");
+    this.fixedContentLength = contentLength;
+    super.fixedContentLength = (int) Math.min(contentLength, Integer.MAX_VALUE);
+  }
+}
diff --git a/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpsURLConnectionImpl.java b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpsURLConnectionImpl.java
new file mode 100644
index 0000000..5d7e738
--- /dev/null
+++ b/repackaged/okhttp-urlconnection/src/main/java/com/android/okhttp/internal/huc/HttpsURLConnectionImpl.java
@@ -0,0 +1,95 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp.internal.huc;
+
+import com.android.okhttp.Handshake;
+import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.internal.URLFilter;
+import java.net.URL;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpsURLConnectionImpl extends DelegatingHttpsURLConnection {
+  private final HttpURLConnectionImpl delegate;
+
+  public HttpsURLConnectionImpl(URL url, OkHttpClient client) {
+    this(new HttpURLConnectionImpl(url, client));
+  }
+
+  public HttpsURLConnectionImpl(URL url, OkHttpClient client, URLFilter filter) {
+    this(new HttpURLConnectionImpl(url, client, filter));
+  }
+
+  public HttpsURLConnectionImpl(HttpURLConnectionImpl delegate) {
+    super(delegate);
+    this.delegate = delegate;
+  }
+
+  @Override protected Handshake handshake() {
+    if (delegate.httpEngine == null) {
+      throw new IllegalStateException("Connection has not yet been established");
+    }
+
+    // If there's a response, get the handshake from there so that caching
+    // works. Otherwise get the handshake from the connection because we might
+    // have not connected yet.
+    return delegate.httpEngine.hasResponse()
+        ? delegate.httpEngine.getResponse().handshake()
+        : delegate.handshake;
+  }
+
+  @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+    delegate.client.setHostnameVerifier(hostnameVerifier);
+  }
+
+  @Override public HostnameVerifier getHostnameVerifier() {
+    return delegate.client.getHostnameVerifier();
+  }
+
+  @Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
+    // BEGIN Android-added: Integrate upstream change: Let setSSLSocketFactory(null) throw.
+    // https://github.com/square/okhttp/commit/f704f9d30e941ebdbdc95843c931cfc9d34bcba6
+    // This method is documented to throw if sslSocketFactory == null. Setting the client's
+    // sslSocketFactory to null instead would cause it to fall back to the default factory.
+    // http://b/73702052
+    if (sslSocketFactory == null) {
+      throw new IllegalArgumentException("sslSocketFactory == null");
+    }
+    // END Android-added: Integrate upstream change: Let setSSLSocketFactory(null) throw.
+    delegate.client.setSslSocketFactory(sslSocketFactory);
+  }
+
+  @Override public SSLSocketFactory getSSLSocketFactory() {
+    return delegate.client.getSslSocketFactory();
+  }
+
+  @Override public long getContentLengthLong() {
+    return delegate.getContentLengthLong();
+  }
+
+  @Override public void setFixedLengthStreamingMode(long contentLength) {
+    delegate.setFixedLengthStreamingMode(contentLength);
+  }
+
+  @Override public long getHeaderFieldLong(String field, long defaultValue) {
+    return delegate.getHeaderFieldLong(field, defaultValue);
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Address.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Address.java
new file mode 100644
index 0000000..5331969
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Address.java
@@ -0,0 +1,205 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.util.List;
+import javax.net.SocketFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+import static com.android.okhttp.internal.Util.equal;
+
+/**
+ * A specification for a connection to an origin server. For simple connections,
+ * this is the server's hostname and port. If an explicit proxy is requested (or
+ * {@linkplain Proxy#NO_PROXY no proxy} is explicitly requested), this also includes
+ * that proxy information. For secure connections the address also includes the
+ * SSL socket factory, hostname verifier, and certificate pinner.
+ *
+ * <p>HTTP requests that share the same {@code Address} may also share the same
+ * {@link Connection}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Address {
+  final HttpUrl url;
+  final Dns dns;
+  final SocketFactory socketFactory;
+  final Authenticator authenticator;
+  final List<Protocol> protocols;
+  final List<ConnectionSpec> connectionSpecs;
+  final ProxySelector proxySelector;
+  final Proxy proxy;
+  final SSLSocketFactory sslSocketFactory;
+  final HostnameVerifier hostnameVerifier;
+  final CertificatePinner certificatePinner;
+
+  public Address(String uriHost, int uriPort, Dns dns, SocketFactory socketFactory,
+      SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier,
+      CertificatePinner certificatePinner, Authenticator authenticator, Proxy proxy,
+      List<Protocol> protocols, List<ConnectionSpec> connectionSpecs, ProxySelector proxySelector) {
+    this.url = new HttpUrl.Builder()
+        .scheme(sslSocketFactory != null ? "https" : "http")
+        .host(uriHost)
+        .port(uriPort)
+        .build();
+
+    if (dns == null) throw new IllegalArgumentException("dns == null");
+    this.dns = dns;
+
+    if (socketFactory == null) throw new IllegalArgumentException("socketFactory == null");
+    this.socketFactory = socketFactory;
+
+    if (authenticator == null) throw new IllegalArgumentException("authenticator == null");
+    this.authenticator = authenticator;
+
+    if (protocols == null) throw new IllegalArgumentException("protocols == null");
+    this.protocols = Util.immutableList(protocols);
+
+    if (connectionSpecs == null) throw new IllegalArgumentException("connectionSpecs == null");
+    this.connectionSpecs = Util.immutableList(connectionSpecs);
+
+    if (proxySelector == null) throw new IllegalArgumentException("proxySelector == null");
+    this.proxySelector = proxySelector;
+
+    this.proxy = proxy;
+    this.sslSocketFactory = sslSocketFactory;
+    this.hostnameVerifier = hostnameVerifier;
+    this.certificatePinner = certificatePinner;
+  }
+
+  /**
+   * Returns a URL with the hostname and port of the origin server. The path, query, and fragment of
+   * this URL are always empty, since they are not significant for planning a route.
+   */
+  public HttpUrl url() {
+    return url;
+  }
+
+  /**
+   * Returns the hostname of the origin server.
+   *
+   * @deprecated prefer {@code address.url().host()}.
+   */
+  @Deprecated
+  public String getUriHost() {
+    return url.host();
+  }
+
+  /**
+   * Returns the port of the origin server; typically 80 or 443. Unlike
+   * may {@code getPort()} accessors, this method never returns -1.
+   *
+   * @deprecated prefer {@code address.url().port()}.
+   */
+  @Deprecated
+  public int getUriPort() {
+    return url.port();
+  }
+
+  /** Returns the service that will be used to resolve IP addresses for hostnames. */
+  public Dns getDns() {
+    return dns;
+  }
+
+  /** Returns the socket factory for new connections. */
+  public SocketFactory getSocketFactory() {
+    return socketFactory;
+  }
+
+  /** Returns the client's authenticator. */
+  public Authenticator getAuthenticator() {
+    return authenticator;
+  }
+
+  /**
+   * Returns the protocols the client supports. This method always returns a
+   * non-null list that contains minimally {@link Protocol#HTTP_1_1}.
+   */
+  public List<Protocol> getProtocols() {
+    return protocols;
+  }
+
+  public List<ConnectionSpec> getConnectionSpecs() {
+    return connectionSpecs;
+  }
+
+  /**
+   * Returns this address's proxy selector. Only used if the proxy is null. If none of this
+   * selector's proxies are reachable, a direct connection will be attempted.
+   */
+  public ProxySelector getProxySelector() {
+    return proxySelector;
+  }
+
+  /**
+   * Returns this address's explicitly-specified HTTP proxy, or null to
+   * delegate to the {@linkplain #getProxySelector proxy selector}.
+   */
+  public Proxy getProxy() {
+    return proxy;
+  }
+
+  /** Returns the SSL socket factory, or null if this is not an HTTPS address. */
+  public SSLSocketFactory getSslSocketFactory() {
+    return sslSocketFactory;
+  }
+
+  /** Returns the hostname verifier, or null if this is not an HTTPS address. */
+  public HostnameVerifier getHostnameVerifier() {
+    return hostnameVerifier;
+  }
+
+  /** Returns this address's certificate pinner, or null if this is not an HTTPS address. */
+  public CertificatePinner getCertificatePinner() {
+    return certificatePinner;
+  }
+
+  @Override public boolean equals(Object other) {
+    if (other instanceof Address) {
+      Address that = (Address) other;
+      return this.url.equals(that.url)
+          && this.dns.equals(that.dns)
+          && this.authenticator.equals(that.authenticator)
+          && this.protocols.equals(that.protocols)
+          && this.connectionSpecs.equals(that.connectionSpecs)
+          && this.proxySelector.equals(that.proxySelector)
+          && equal(this.proxy, that.proxy)
+          && equal(this.sslSocketFactory, that.sslSocketFactory)
+          && equal(this.hostnameVerifier, that.hostnameVerifier)
+          && equal(this.certificatePinner, that.certificatePinner);
+    }
+    return false;
+  }
+
+  @Override public int hashCode() {
+    int result = 17;
+    result = 31 * result + url.hashCode();
+    result = 31 * result + dns.hashCode();
+    result = 31 * result + authenticator.hashCode();
+    result = 31 * result + protocols.hashCode();
+    result = 31 * result + connectionSpecs.hashCode();
+    result = 31 * result + proxySelector.hashCode();
+    result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
+    result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
+    result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
+    result = 31 * result + (certificatePinner != null ? certificatePinner.hashCode() : 0);
+    return result;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Authenticator.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Authenticator.java
new file mode 100644
index 0000000..ae23d30
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Authenticator.java
@@ -0,0 +1,62 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import java.io.IOException;
+import java.net.Proxy;
+
+/**
+ * Responds to authentication challenges from the remote web or proxy server.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Authenticator {
+  /**
+   * Returns a request that includes a credential to satisfy an authentication
+   * challenge in {@code response}. Returns null if the challenge cannot be
+   * satisfied. This method is called in response to an HTTP 401 unauthorized
+   * status code sent by the origin server.
+   *
+   * <p>Typical implementations will look up a credential and create a request
+   * derived from the initial request by setting the "Authorization" header.
+   * <pre>   {@code
+   *
+   *    String credential = Credentials.basic(...)
+   *    return response.request().newBuilder()
+   *        .header("Authorization", credential)
+   *        .build();
+   * }</pre>
+   */
+  Request authenticate(Proxy proxy, Response response) throws IOException;
+
+  /**
+   * Returns a request that includes a credential to satisfy an authentication
+   * challenge made by {@code response}. Returns null if the challenge cannot be
+   * satisfied. This method is called in response to an HTTP 407 unauthorized
+   * status code sent by the proxy server.
+   *
+   * <p>Typical implementations will look up a credential and create a request
+   * derived from the initial request by setting the "Proxy-Authorization"
+   * header. <pre>   {@code
+   *
+   *    String credential = Credentials.basic(...)
+   *    return response.request().newBuilder()
+   *        .header("Proxy-Authorization", credential)
+   *        .build();
+   * }</pre>
+   */
+  Request authenticateProxy(Proxy proxy, Response response) throws IOException;
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Cache.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Cache.java
new file mode 100644
index 0000000..1e8f583
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Cache.java
@@ -0,0 +1,732 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2010 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 com.android.okhttp;
+
+import com.android.okhttp.internal.DiskLruCache;
+import com.android.okhttp.internal.InternalCache;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.http.CacheRequest;
+import com.android.okhttp.internal.http.CacheStrategy;
+import com.android.okhttp.internal.http.HttpMethod;
+import com.android.okhttp.internal.http.OkHeaders;
+import com.android.okhttp.internal.http.StatusLine;
+import com.android.okhttp.internal.io.FileSystem;
+import java.io.File;
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.ForwardingSink;
+import com.android.okhttp.okio.ForwardingSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+
+/**
+ * Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and
+ * bandwidth.
+ *
+ * <h3>Cache Optimization</h3>
+ * To measure cache effectiveness, this class tracks three statistics:
+ * <ul>
+ *   <li><strong>{@linkplain #getRequestCount() Request Count:}</strong> the number of HTTP
+ *     requests issued since this cache was created.
+ *   <li><strong>{@linkplain #getNetworkCount() Network Count:}</strong> the number of those
+ *     requests that required network use.
+ *   <li><strong>{@linkplain #getHitCount() Hit Count:}</strong> the number of those requests whose
+ *     responses were served by the cache.
+ * </ul>
+ *
+ * Sometimes a request will result in a conditional cache hit. If the cache contains a stale copy of
+ * the response, the client will issue a conditional {@code GET}. The server will then send either
+ * the updated response if it has changed, or a short 'not modified' response if the client's copy
+ * is still valid. Such responses increment both the network count and hit count.
+ *
+ * <p>The best way to improve the cache hit rate is by configuring the web server to return
+ * cacheable responses. Although this client honors all <a
+ * href="http://tools.ietf.org/html/rfc7234">HTTP/1.1 (RFC 7234)</a> cache headers, it doesn't cache
+ * partial responses.
+ *
+ * <h3>Force a Network Response</h3>
+ * In some situations, such as after a user clicks a 'refresh' button, it may be necessary to skip
+ * the cache, and fetch data directly from the server. To force a full refresh, add the {@code
+ * no-cache} directive: <pre>   {@code
+ *
+ *   Request request = new Request.Builder()
+ *       .cacheControl(new CacheControl.Builder().noCache().build())
+ *       .url("http://publicobject.com/helloworld.txt")
+ *       .build();
+ * }</pre>
+ *
+ * If it is only necessary to force a cached response to be validated by the server, use the more
+ * efficient {@code max-age=0} directive instead: <pre>   {@code
+ *
+ *   Request request = new Request.Builder()
+ *       .cacheControl(new CacheControl.Builder()
+ *           .maxAge(0, TimeUnit.SECONDS)
+ *           .build())
+ *       .url("http://publicobject.com/helloworld.txt")
+ *       .build();
+ * }</pre>
+ *
+ * <h3>Force a Cache Response</h3>
+ * Sometimes you'll want to show resources if they are available immediately, but not otherwise.
+ * This can be used so your application can show <i>something</i> while waiting for the latest data
+ * to be downloaded. To restrict a request to locally-cached resources, add the {@code
+ * only-if-cached} directive: <pre>   {@code
+ *
+ *     Request request = new Request.Builder()
+ *         .cacheControl(new CacheControl.Builder()
+ *             .onlyIfCached()
+ *             .build())
+ *         .url("http://publicobject.com/helloworld.txt")
+ *         .build();
+ *     Response forceCacheResponse = client.newCall(request).execute();
+ *     if (forceCacheResponse.code() != 504) {
+ *       // The resource was cached! Show it.
+ *     } else {
+ *       // The resource was not cached.
+ *     }
+ * }</pre>
+ * This technique works even better in situations where a stale response is better than no response.
+ * To permit stale cached responses, use the {@code max-stale} directive with the maximum staleness
+ * in seconds: <pre>   {@code
+ *
+ *   Request request = new Request.Builder()
+ *       .cacheControl(new CacheControl.Builder()
+ *           .maxStale(365, TimeUnit.DAYS)
+ *           .build())
+ *       .url("http://publicobject.com/helloworld.txt")
+ *       .build();
+ * }</pre>
+ *
+ * <p>The {@link CacheControl} class can configure request caching directives and parse response
+ * caching directives. It even offers convenient constants {@link CacheControl#FORCE_NETWORK} and
+ * {@link CacheControl#FORCE_CACHE} that address the use cases above.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Cache {
+  private static final int VERSION = 201105;
+  private static final int ENTRY_METADATA = 0;
+  private static final int ENTRY_BODY = 1;
+  private static final int ENTRY_COUNT = 2;
+
+  // Android-changed: internalCache made public so it can be used from Android internalapi package.
+  public final InternalCache internalCache = new InternalCache() {
+    @Override public Response get(Request request) throws IOException {
+      return Cache.this.get(request);
+    }
+    @Override public CacheRequest put(Response response) throws IOException {
+      return Cache.this.put(response);
+    }
+    @Override public void remove(Request request) throws IOException {
+      Cache.this.remove(request);
+    }
+    @Override public void update(Response cached, Response network) throws IOException {
+      Cache.this.update(cached, network);
+    }
+    @Override public void trackConditionalCacheHit() {
+      Cache.this.trackConditionalCacheHit();
+    }
+    @Override public void trackResponse(CacheStrategy cacheStrategy) {
+      Cache.this.trackResponse(cacheStrategy);
+    }
+  };
+
+  private final DiskLruCache cache;
+
+  /* read and write statistics, all guarded by 'this' */
+  private int writeSuccessCount;
+  private int writeAbortCount;
+  private int networkCount;
+  private int hitCount;
+  private int requestCount;
+
+  public Cache(File directory, long maxSize) {
+    this(directory, maxSize, FileSystem.SYSTEM);
+  }
+
+  Cache(File directory, long maxSize, FileSystem fileSystem) {
+    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
+  }
+
+  private static String urlToKey(Request request) {
+    return Util.md5Hex(request.urlString());
+  }
+
+  Response get(Request request) {
+    String key = urlToKey(request);
+    DiskLruCache.Snapshot snapshot;
+    Entry entry;
+    try {
+      snapshot = cache.get(key);
+      if (snapshot == null) {
+        return null;
+      }
+    } catch (IOException e) {
+      // Give up because the cache cannot be read.
+      return null;
+    }
+
+    try {
+      entry = new Entry(snapshot.getSource(ENTRY_METADATA));
+    } catch (IOException e) {
+      Util.closeQuietly(snapshot);
+      return null;
+    }
+
+    Response response = entry.response(request, snapshot);
+
+    if (!entry.matches(request, response)) {
+      Util.closeQuietly(response.body());
+      return null;
+    }
+
+    return response;
+  }
+
+  private CacheRequest put(Response response) throws IOException {
+    String requestMethod = response.request().method();
+
+    if (HttpMethod.invalidatesCache(response.request().method())) {
+      try {
+        remove(response.request());
+      } catch (IOException ignored) {
+        // The cache cannot be written.
+      }
+      return null;
+    }
+    if (!requestMethod.equals("GET")) {
+      // Don't cache non-GET responses. We're technically allowed to cache
+      // HEAD requests and some POST requests, but the complexity of doing
+      // so is high and the benefit is low.
+      return null;
+    }
+
+    if (OkHeaders.hasVaryAll(response)) {
+      return null;
+    }
+
+    Entry entry = new Entry(response);
+    DiskLruCache.Editor editor = null;
+    try {
+      editor = cache.edit(urlToKey(response.request()));
+      if (editor == null) {
+        return null;
+      }
+      entry.writeTo(editor);
+      return new CacheRequestImpl(editor);
+    } catch (IOException e) {
+      abortQuietly(editor);
+      return null;
+    }
+  }
+
+  private void remove(Request request) throws IOException {
+    cache.remove(urlToKey(request));
+  }
+
+  private void update(Response cached, Response network) {
+    Entry entry = new Entry(network);
+    DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot;
+    DiskLruCache.Editor editor = null;
+    try {
+      editor = snapshot.edit(); // Returns null if snapshot is not current.
+      if (editor != null) {
+        entry.writeTo(editor);
+        editor.commit();
+      }
+    } catch (IOException e) {
+      abortQuietly(editor);
+    }
+  }
+
+  private void abortQuietly(DiskLruCache.Editor editor) {
+    // Give up because the cache cannot be written.
+    try {
+      if (editor != null) {
+        editor.abort();
+      }
+    } catch (IOException ignored) {
+    }
+  }
+
+  /**
+   * Initialize the cache. This will include reading the journal files from
+   * the storage and building up the necessary in-memory cache information.
+   * <p>
+   * The initialization time may vary depending on the journal file size and
+   * the current actual cache size. The application needs to be aware of calling
+   * this function during the initialization phase and preferably in a background
+   * worker thread.
+   * <p>
+   * Note that if the application chooses to not call this method to initialize
+   * the cache. By default, the okhttp will perform lazy initialization upon the
+   * first usage of the cache.
+   */
+  public void initialize() throws IOException {
+    cache.initialize();
+  }
+
+  /**
+   * Closes the cache and deletes all of its stored values. This will delete
+   * all files in the cache directory including files that weren't created by
+   * the cache.
+   */
+  public void delete() throws IOException {
+    cache.delete();
+  }
+
+  /**
+   * Deletes all values stored in the cache. In-flight writes to the cache will
+   * complete normally, but the corresponding responses will not be stored.
+   */
+  public void evictAll() throws IOException {
+    cache.evictAll();
+  }
+
+  /**
+   * Returns an iterator over the URLs in this cache. This iterator doesn't throw {@code
+   * ConcurrentModificationException}, but if new responses are added while iterating, their URLs
+   * will not be returned. If existing responses are evicted during iteration, they will be absent
+   * (unless they were already returned).
+   *
+   * <p>The iterator supports {@linkplain Iterator#remove}. Removing a URL from the iterator evicts
+   * the corresponding response from the cache. Use this to evict selected responses.
+   */
+  public Iterator<String> urls() throws IOException {
+    return new Iterator<String>() {
+      final Iterator<DiskLruCache.Snapshot> delegate = cache.snapshots();
+
+      String nextUrl;
+      boolean canRemove;
+
+      @Override public boolean hasNext() {
+        if (nextUrl != null) return true;
+
+        canRemove = false; // Prevent delegate.remove() on the wrong item!
+        while (delegate.hasNext()) {
+          DiskLruCache.Snapshot snapshot = delegate.next();
+          try {
+            BufferedSource metadata = Okio.buffer(snapshot.getSource(ENTRY_METADATA));
+            nextUrl = metadata.readUtf8LineStrict();
+            return true;
+          } catch (IOException ignored) {
+            // We couldn't read the metadata for this snapshot; possibly because the host filesystem
+            // has disappeared! Skip it.
+          } finally {
+            snapshot.close();
+          }
+        }
+
+        return false;
+      }
+
+      @Override public String next() {
+        if (!hasNext()) throw new NoSuchElementException();
+        String result = nextUrl;
+        nextUrl = null;
+        canRemove = true;
+        return result;
+      }
+
+      @Override public void remove() {
+        if (!canRemove) throw new IllegalStateException("remove() before next()");
+        delegate.remove();
+      }
+    };
+  }
+
+  public synchronized int getWriteAbortCount() {
+    return writeAbortCount;
+  }
+
+  public synchronized int getWriteSuccessCount() {
+    return writeSuccessCount;
+  }
+
+  public long getSize() throws IOException {
+    return cache.size();
+  }
+
+  public long getMaxSize() {
+    return cache.getMaxSize();
+  }
+
+  public void flush() throws IOException {
+    cache.flush();
+  }
+
+  public void close() throws IOException {
+    cache.close();
+  }
+
+  public File getDirectory() {
+    return cache.getDirectory();
+  }
+
+  public boolean isClosed() {
+    return cache.isClosed();
+  }
+
+  private synchronized void trackResponse(CacheStrategy cacheStrategy) {
+    requestCount++;
+
+    if (cacheStrategy.networkRequest != null) {
+      // If this is a conditional request, we'll increment hitCount if/when it hits.
+      networkCount++;
+
+    } else if (cacheStrategy.cacheResponse != null) {
+      // This response uses the cache and not the network. That's a cache hit.
+      hitCount++;
+    }
+  }
+
+  private synchronized void trackConditionalCacheHit() {
+    hitCount++;
+  }
+
+  public synchronized int getNetworkCount() {
+    return networkCount;
+  }
+
+  public synchronized int getHitCount() {
+    return hitCount;
+  }
+
+  public synchronized int getRequestCount() {
+    return requestCount;
+  }
+
+  private final class CacheRequestImpl implements CacheRequest {
+    private final DiskLruCache.Editor editor;
+    private Sink cacheOut;
+    private boolean done;
+    private Sink body;
+
+    public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
+      this.editor = editor;
+      this.cacheOut = editor.newSink(ENTRY_BODY);
+      this.body = new ForwardingSink(cacheOut) {
+        @Override public void close() throws IOException {
+          synchronized (Cache.this) {
+            if (done) {
+              return;
+            }
+            done = true;
+            writeSuccessCount++;
+          }
+          super.close();
+          editor.commit();
+        }
+      };
+    }
+
+    @Override public void abort() {
+      synchronized (Cache.this) {
+        if (done) {
+          return;
+        }
+        done = true;
+        writeAbortCount++;
+      }
+      Util.closeQuietly(cacheOut);
+      try {
+        editor.abort();
+      } catch (IOException ignored) {
+      }
+    }
+
+    @Override public Sink body() {
+      return body;
+    }
+  }
+
+  private static final class Entry {
+    private final String url;
+    private final Headers varyHeaders;
+    private final String requestMethod;
+    private final Protocol protocol;
+    private final int code;
+    private final String message;
+    private final Headers responseHeaders;
+    private final Handshake handshake;
+
+    /**
+     * Reads an entry from an input stream. A typical entry looks like this:
+     * <pre>{@code
+     *   http://google.com/foo
+     *   GET
+     *   2
+     *   Accept-Language: fr-CA
+     *   Accept-Charset: UTF-8
+     *   HTTP/1.1 200 OK
+     *   3
+     *   Content-Type: image/png
+     *   Content-Length: 100
+     *   Cache-Control: max-age=600
+     * }</pre>
+     *
+     * <p>A typical HTTPS file looks like this:
+     * <pre>{@code
+     *   https://google.com/foo
+     *   GET
+     *   2
+     *   Accept-Language: fr-CA
+     *   Accept-Charset: UTF-8
+     *   HTTP/1.1 200 OK
+     *   3
+     *   Content-Type: image/png
+     *   Content-Length: 100
+     *   Cache-Control: max-age=600
+     *
+     *   AES_256_WITH_MD5
+     *   2
+     *   base64-encoded peerCertificate[0]
+     *   base64-encoded peerCertificate[1]
+     *   -1
+     * }</pre>
+     * The file is newline separated. The first two lines are the URL and
+     * the request method. Next is the number of HTTP Vary request header
+     * lines, followed by those lines.
+     *
+     * <p>Next is the response status line, followed by the number of HTTP
+     * response header lines, followed by those lines.
+     *
+     * <p>HTTPS responses also contain SSL session information. This begins
+     * with a blank line, and then a line containing the cipher suite. Next
+     * is the length of the peer certificate chain. These certificates are
+     * base64-encoded and appear each on their own line. The next line
+     * contains the length of the local certificate chain. These
+     * certificates are also base64-encoded and appear each on their own
+     * line. A length of -1 is used to encode a null array.
+     */
+    public Entry(Source in) throws IOException {
+      try {
+        BufferedSource source = Okio.buffer(in);
+        url = source.readUtf8LineStrict();
+        requestMethod = source.readUtf8LineStrict();
+        Headers.Builder varyHeadersBuilder = new Headers.Builder();
+        int varyRequestHeaderLineCount = readInt(source);
+        for (int i = 0; i < varyRequestHeaderLineCount; i++) {
+          varyHeadersBuilder.addLenient(source.readUtf8LineStrict());
+        }
+        varyHeaders = varyHeadersBuilder.build();
+
+        StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
+        protocol = statusLine.protocol;
+        code = statusLine.code;
+        message = statusLine.message;
+        Headers.Builder responseHeadersBuilder = new Headers.Builder();
+        int responseHeaderLineCount = readInt(source);
+        for (int i = 0; i < responseHeaderLineCount; i++) {
+          responseHeadersBuilder.addLenient(source.readUtf8LineStrict());
+        }
+        responseHeaders = responseHeadersBuilder.build();
+
+        if (isHttps()) {
+          String blank = source.readUtf8LineStrict();
+          if (blank.length() > 0) {
+            throw new IOException("expected \"\" but was \"" + blank + "\"");
+          }
+          String cipherSuite = source.readUtf8LineStrict();
+          List<Certificate> peerCertificates = readCertificateList(source);
+          List<Certificate> localCertificates = readCertificateList(source);
+          handshake = Handshake.get(cipherSuite, peerCertificates, localCertificates);
+        } else {
+          handshake = null;
+        }
+      } finally {
+        in.close();
+      }
+    }
+
+    public Entry(Response response) {
+      this.url = response.request().urlString();
+      this.varyHeaders = OkHeaders.varyHeaders(response);
+      this.requestMethod = response.request().method();
+      this.protocol = response.protocol();
+      this.code = response.code();
+      this.message = response.message();
+      this.responseHeaders = response.headers();
+      this.handshake = response.handshake();
+    }
+
+    public void writeTo(DiskLruCache.Editor editor) throws IOException {
+      BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
+
+      sink.writeUtf8(url);
+      sink.writeByte('\n');
+      sink.writeUtf8(requestMethod);
+      sink.writeByte('\n');
+      sink.writeDecimalLong(varyHeaders.size());
+      sink.writeByte('\n');
+      for (int i = 0, size = varyHeaders.size(); i < size; i++) {
+        sink.writeUtf8(varyHeaders.name(i));
+        sink.writeUtf8(": ");
+        sink.writeUtf8(varyHeaders.value(i));
+        sink.writeByte('\n');
+      }
+
+      sink.writeUtf8(new StatusLine(protocol, code, message).toString());
+      sink.writeByte('\n');
+      sink.writeDecimalLong(responseHeaders.size());
+      sink.writeByte('\n');
+      for (int i = 0, size = responseHeaders.size(); i < size; i++) {
+        sink.writeUtf8(responseHeaders.name(i));
+        sink.writeUtf8(": ");
+        sink.writeUtf8(responseHeaders.value(i));
+        sink.writeByte('\n');
+      }
+
+      if (isHttps()) {
+        sink.writeByte('\n');
+        sink.writeUtf8(handshake.cipherSuite());
+        sink.writeByte('\n');
+        writeCertList(sink, handshake.peerCertificates());
+        writeCertList(sink, handshake.localCertificates());
+      }
+      sink.close();
+    }
+
+    private boolean isHttps() {
+      return url.startsWith("https://");
+    }
+
+    private List<Certificate> readCertificateList(BufferedSource source) throws IOException {
+      int length = readInt(source);
+      if (length == -1) return Collections.emptyList(); // OkHttp v1.2 used -1 to indicate null.
+
+      try {
+        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+        List<Certificate> result = new ArrayList<>(length);
+        for (int i = 0; i < length; i++) {
+          String line = source.readUtf8LineStrict();
+          Buffer bytes = new Buffer();
+          bytes.write(ByteString.decodeBase64(line));
+          result.add(certificateFactory.generateCertificate(bytes.inputStream()));
+        }
+        return result;
+      } catch (CertificateException e) {
+        throw new IOException(e.getMessage());
+      }
+    }
+
+    private void writeCertList(BufferedSink sink, List<Certificate> certificates)
+        throws IOException {
+      try {
+        sink.writeDecimalLong(certificates.size());
+        sink.writeByte('\n');
+        for (int i = 0, size = certificates.size(); i < size; i++) {
+          byte[] bytes = certificates.get(i).getEncoded();
+          String line = ByteString.of(bytes).base64();
+          sink.writeUtf8(line);
+          sink.writeByte('\n');
+        }
+      } catch (CertificateEncodingException e) {
+        throw new IOException(e.getMessage());
+      }
+    }
+
+    public boolean matches(Request request, Response response) {
+      return url.equals(request.urlString())
+          && requestMethod.equals(request.method())
+          && OkHeaders.varyMatches(response, varyHeaders, request);
+    }
+
+    public Response response(Request request, DiskLruCache.Snapshot snapshot) {
+      String contentType = responseHeaders.get("Content-Type");
+      String contentLength = responseHeaders.get("Content-Length");
+      Request cacheRequest = new Request.Builder()
+          .url(url)
+          .method(requestMethod, null)
+          .headers(varyHeaders)
+          .build();
+      return new Response.Builder()
+          .request(cacheRequest)
+          .protocol(protocol)
+          .code(code)
+          .message(message)
+          .headers(responseHeaders)
+          .body(new CacheResponseBody(snapshot, contentType, contentLength))
+          .handshake(handshake)
+          .build();
+    }
+  }
+
+  private static int readInt(BufferedSource source) throws IOException {
+    try {
+      long result = source.readDecimalLong();
+      String line = source.readUtf8LineStrict();
+      if (result < 0 || result > Integer.MAX_VALUE || !line.isEmpty()) {
+        throw new IOException("expected an int but was \"" + result + line + "\"");
+      }
+      return (int) result;
+    } catch (NumberFormatException e) {
+      throw new IOException(e.getMessage());
+    }
+  }
+
+  private static class CacheResponseBody extends ResponseBody {
+    private final DiskLruCache.Snapshot snapshot;
+    private final BufferedSource bodySource;
+    private final String contentType;
+    private final String contentLength;
+
+    public CacheResponseBody(final DiskLruCache.Snapshot snapshot,
+        String contentType, String contentLength) {
+      this.snapshot = snapshot;
+      this.contentType = contentType;
+      this.contentLength = contentLength;
+
+      Source source = snapshot.getSource(ENTRY_BODY);
+      bodySource = Okio.buffer(new ForwardingSource(source) {
+        @Override public void close() throws IOException {
+          snapshot.close();
+          super.close();
+        }
+      });
+    }
+
+    @Override public MediaType contentType() {
+      return contentType != null ? MediaType.parse(contentType) : null;
+    }
+
+    @Override public long contentLength() {
+      try {
+        return contentLength != null ? Long.parseLong(contentLength) : -1;
+      } catch (NumberFormatException e) {
+        return -1;
+      }
+    }
+
+    @Override public BufferedSource source() {
+      return bodySource;
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/CacheControl.java b/repackaged/okhttp/src/main/java/com/android/okhttp/CacheControl.java
new file mode 100644
index 0000000..27e2e4a
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/CacheControl.java
@@ -0,0 +1,369 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp;
+
+import com.android.okhttp.internal.http.HeaderParser;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A Cache-Control header with cache directives from a server or client. These
+ * directives set policy on what responses can be stored, and which requests can
+ * be satisfied by those stored responses.
+ *
+ * <p>See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">RFC
+ * 2616, 14.9</a>.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class CacheControl {
+  /**
+   * Cache control request directives that require network validation of
+   * responses. Note that such requests may be assisted by the cache via
+   * conditional GET requests.
+   */
+  public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
+
+  /**
+   * Cache control request directives that uses the cache only, even if the
+   * cached response is stale. If the response isn't available in the cache or
+   * requires server validation, the call will fail with a {@code 504
+   * Unsatisfiable Request}.
+   */
+  public static final CacheControl FORCE_CACHE = new Builder()
+      .onlyIfCached()
+      .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
+      .build();
+
+  private final boolean noCache;
+  private final boolean noStore;
+  private final int maxAgeSeconds;
+  private final int sMaxAgeSeconds;
+  private final boolean isPrivate;
+  private final boolean isPublic;
+  private final boolean mustRevalidate;
+  private final int maxStaleSeconds;
+  private final int minFreshSeconds;
+  private final boolean onlyIfCached;
+  private final boolean noTransform;
+
+  String headerValue; // Lazily computed, if absent.
+
+  private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds,
+      boolean isPrivate, boolean isPublic, boolean mustRevalidate, int maxStaleSeconds,
+      int minFreshSeconds, boolean onlyIfCached, boolean noTransform, String headerValue) {
+    this.noCache = noCache;
+    this.noStore = noStore;
+    this.maxAgeSeconds = maxAgeSeconds;
+    this.sMaxAgeSeconds = sMaxAgeSeconds;
+    this.isPrivate = isPrivate;
+    this.isPublic = isPublic;
+    this.mustRevalidate = mustRevalidate;
+    this.maxStaleSeconds = maxStaleSeconds;
+    this.minFreshSeconds = minFreshSeconds;
+    this.onlyIfCached = onlyIfCached;
+    this.noTransform = noTransform;
+    this.headerValue = headerValue;
+  }
+
+  private CacheControl(Builder builder) {
+    this.noCache = builder.noCache;
+    this.noStore = builder.noStore;
+    this.maxAgeSeconds = builder.maxAgeSeconds;
+    this.sMaxAgeSeconds = -1;
+    this.isPrivate = false;
+    this.isPublic = false;
+    this.mustRevalidate = false;
+    this.maxStaleSeconds = builder.maxStaleSeconds;
+    this.minFreshSeconds = builder.minFreshSeconds;
+    this.onlyIfCached = builder.onlyIfCached;
+    this.noTransform = builder.noTransform;
+  }
+
+  /**
+   * In a response, this field's name "no-cache" is misleading. It doesn't
+   * prevent us from caching the response; it only means we have to validate the
+   * response with the origin server before returning it. We can do this with a
+   * conditional GET.
+   *
+   * <p>In a request, it means do not use a cache to satisfy the request.
+   */
+  public boolean noCache() {
+    return noCache;
+  }
+
+  /** If true, this response should not be cached. */
+  public boolean noStore() {
+    return noStore;
+  }
+
+  /**
+   * The duration past the response's served date that it can be served without
+   * validation.
+   */
+  public int maxAgeSeconds() {
+    return maxAgeSeconds;
+  }
+
+  /**
+   * The "s-maxage" directive is the max age for shared caches. Not to be
+   * confused with "max-age" for non-shared caches, As in Firefox and Chrome,
+   * this directive is not honored by this cache.
+   */
+  public int sMaxAgeSeconds() {
+    return sMaxAgeSeconds;
+  }
+
+  public boolean isPrivate() {
+    return isPrivate;
+  }
+
+  public boolean isPublic() {
+    return isPublic;
+  }
+
+  public boolean mustRevalidate() {
+    return mustRevalidate;
+  }
+
+  public int maxStaleSeconds() {
+    return maxStaleSeconds;
+  }
+
+  public int minFreshSeconds() {
+    return minFreshSeconds;
+  }
+
+  /**
+   * This field's name "only-if-cached" is misleading. It actually means "do
+   * not use the network". It is set by a client who only wants to make a
+   * request if it can be fully satisfied by the cache. Cached responses that
+   * would require validation (ie. conditional gets) are not permitted if this
+   * header is set.
+   */
+  public boolean onlyIfCached() {
+    return onlyIfCached;
+  }
+
+  public boolean noTransform() {
+    return noTransform;
+  }
+
+  /**
+   * Returns the cache directives of {@code headers}. This honors both
+   * Cache-Control and Pragma headers if they are present.
+   */
+  public static CacheControl parse(Headers headers) {
+    boolean noCache = false;
+    boolean noStore = false;
+    int maxAgeSeconds = -1;
+    int sMaxAgeSeconds = -1;
+    boolean isPrivate = false;
+    boolean isPublic = false;
+    boolean mustRevalidate = false;
+    int maxStaleSeconds = -1;
+    int minFreshSeconds = -1;
+    boolean onlyIfCached = false;
+    boolean noTransform = false;
+
+    boolean canUseHeaderValue = true;
+    String headerValue = null;
+
+    for (int i = 0, size = headers.size(); i < size; i++) {
+      String name = headers.name(i);
+      String value = headers.value(i);
+
+      if (name.equalsIgnoreCase("Cache-Control")) {
+        if (headerValue != null) {
+          // Multiple cache-control headers means we can't use the raw value.
+          canUseHeaderValue = false;
+        } else {
+          headerValue = value;
+        }
+      } else if (name.equalsIgnoreCase("Pragma")) {
+        // Might specify additional cache-control params. We invalidate just in case.
+        canUseHeaderValue = false;
+      } else {
+        continue;
+      }
+
+      int pos = 0;
+      while (pos < value.length()) {
+        int tokenStart = pos;
+        pos = HeaderParser.skipUntil(value, pos, "=,;");
+        String directive = value.substring(tokenStart, pos).trim();
+        String parameter;
+
+        if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
+          pos++; // consume ',' or ';' (if necessary)
+          parameter = null;
+        } else {
+          pos++; // consume '='
+          pos = HeaderParser.skipWhitespace(value, pos);
+
+          // quoted string
+          if (pos < value.length() && value.charAt(pos) == '\"') {
+            pos++; // consume '"' open quote
+            int parameterStart = pos;
+            pos = HeaderParser.skipUntil(value, pos, "\"");
+            parameter = value.substring(parameterStart, pos);
+            pos++; // consume '"' close quote (if necessary)
+
+            // unquoted string
+          } else {
+            int parameterStart = pos;
+            pos = HeaderParser.skipUntil(value, pos, ",;");
+            parameter = value.substring(parameterStart, pos).trim();
+          }
+        }
+
+        if ("no-cache".equalsIgnoreCase(directive)) {
+          noCache = true;
+        } else if ("no-store".equalsIgnoreCase(directive)) {
+          noStore = true;
+        } else if ("max-age".equalsIgnoreCase(directive)) {
+          maxAgeSeconds = HeaderParser.parseSeconds(parameter, -1);
+        } else if ("s-maxage".equalsIgnoreCase(directive)) {
+          sMaxAgeSeconds = HeaderParser.parseSeconds(parameter, -1);
+        } else if ("private".equalsIgnoreCase(directive)) {
+          isPrivate = true;
+        } else if ("public".equalsIgnoreCase(directive)) {
+          isPublic = true;
+        } else if ("must-revalidate".equalsIgnoreCase(directive)) {
+          mustRevalidate = true;
+        } else if ("max-stale".equalsIgnoreCase(directive)) {
+          maxStaleSeconds = HeaderParser.parseSeconds(parameter, Integer.MAX_VALUE);
+        } else if ("min-fresh".equalsIgnoreCase(directive)) {
+          minFreshSeconds = HeaderParser.parseSeconds(parameter, -1);
+        } else if ("only-if-cached".equalsIgnoreCase(directive)) {
+          onlyIfCached = true;
+        } else if ("no-transform".equalsIgnoreCase(directive)) {
+          noTransform = true;
+        }
+      }
+    }
+
+    if (!canUseHeaderValue) {
+      headerValue = null;
+    }
+    return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic,
+        mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, headerValue);
+  }
+
+  @Override public String toString() {
+    String result = headerValue;
+    return result != null ? result : (headerValue = headerValue());
+  }
+
+  private String headerValue() {
+    StringBuilder result = new StringBuilder();
+    if (noCache) result.append("no-cache, ");
+    if (noStore) result.append("no-store, ");
+    if (maxAgeSeconds != -1) result.append("max-age=").append(maxAgeSeconds).append(", ");
+    if (sMaxAgeSeconds != -1) result.append("s-maxage=").append(sMaxAgeSeconds).append(", ");
+    if (isPrivate) result.append("private, ");
+    if (isPublic) result.append("public, ");
+    if (mustRevalidate) result.append("must-revalidate, ");
+    if (maxStaleSeconds != -1) result.append("max-stale=").append(maxStaleSeconds).append(", ");
+    if (minFreshSeconds != -1) result.append("min-fresh=").append(minFreshSeconds).append(", ");
+    if (onlyIfCached) result.append("only-if-cached, ");
+    if (noTransform) result.append("no-transform, ");
+    if (result.length() == 0) return "";
+    result.delete(result.length() - 2, result.length());
+    return result.toString();
+  }
+
+  /** Builds a {@code Cache-Control} request header. 
+   * @hide This class is not part of the Android public SDK API*/
+  public static final class Builder {
+    boolean noCache;
+    boolean noStore;
+    int maxAgeSeconds = -1;
+    int maxStaleSeconds = -1;
+    int minFreshSeconds = -1;
+    boolean onlyIfCached;
+    boolean noTransform;
+
+    /** Don't accept an unvalidated cached response. */
+    public Builder noCache() {
+      this.noCache = true;
+      return this;
+    }
+
+    /** Don't store the server's response in any cache. */
+    public Builder noStore() {
+      this.noStore = true;
+      return this;
+    }
+
+    /**
+     * Sets the maximum age of a cached response. If the cache response's age
+     * exceeds {@code maxAge}, it will not be used and a network request will
+     * be made.
+     *
+     * @param maxAge a non-negative integer. This is stored and transmitted with
+     *     {@link TimeUnit#SECONDS} precision; finer precision will be lost.
+     */
+    public Builder maxAge(int maxAge, TimeUnit timeUnit) {
+      if (maxAge < 0) throw new IllegalArgumentException("maxAge < 0: " + maxAge);
+      long maxAgeSecondsLong = timeUnit.toSeconds(maxAge);
+      this.maxAgeSeconds = maxAgeSecondsLong > Integer.MAX_VALUE
+          ? Integer.MAX_VALUE
+          : (int) maxAgeSecondsLong;
+      return this;
+    }
+
+    /**
+     * Accept cached responses that have exceeded their freshness lifetime by
+     * up to {@code maxStale}. If unspecified, stale cache responses will not be
+     * used.
+     *
+     * @param maxStale a non-negative integer. This is stored and transmitted
+     *     with {@link TimeUnit#SECONDS} precision; finer precision will be
+     *     lost.
+     */
+    public Builder maxStale(int maxStale, TimeUnit timeUnit) {
+      if (maxStale < 0) throw new IllegalArgumentException("maxStale < 0: " + maxStale);
+      long maxStaleSecondsLong = timeUnit.toSeconds(maxStale);
+      this.maxStaleSeconds = maxStaleSecondsLong > Integer.MAX_VALUE
+          ? Integer.MAX_VALUE
+          : (int) maxStaleSecondsLong;
+      return this;
+    }
+
+    /**
+     * Sets the minimum number of seconds that a response will continue to be
+     * fresh for. If the response will be stale when {@code minFresh} have
+     * elapsed, the cached response will not be used and a network request will
+     * be made.
+     *
+     * @param minFresh a non-negative integer. This is stored and transmitted
+     *     with {@link TimeUnit#SECONDS} precision; finer precision will be
+     *     lost.
+     */
+    public Builder minFresh(int minFresh, TimeUnit timeUnit) {
+      if (minFresh < 0) throw new IllegalArgumentException("minFresh < 0: " + minFresh);
+      long minFreshSecondsLong = timeUnit.toSeconds(minFresh);
+      this.minFreshSeconds = minFreshSecondsLong > Integer.MAX_VALUE
+          ? Integer.MAX_VALUE
+          : (int) minFreshSecondsLong;
+      return this;
+    }
+
+    /**
+     * Only accept the response if it is in the cache. If the response isn't
+     * cached, a {@code 504 Unsatisfiable Request} response will be returned.
+     */
+    public Builder onlyIfCached() {
+      this.onlyIfCached = true;
+      return this;
+    }
+
+    /** Don't accept a transformed response. */
+    public Builder noTransform() {
+      this.noTransform = true;
+      return this;
+    }
+
+    public CacheControl build() {
+      return new CacheControl(this);
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Call.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Call.java
new file mode 100644
index 0000000..25cddd0
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Call.java
@@ -0,0 +1,350 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.NamedRunnable;
+import com.android.okhttp.internal.http.HttpEngine;
+import com.android.okhttp.internal.http.RequestException;
+import com.android.okhttp.internal.http.RouteException;
+import com.android.okhttp.internal.http.StreamAllocation;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.logging.Level;
+
+import static com.android.okhttp.internal.Internal.logger;
+import static com.android.okhttp.internal.http.HttpEngine.MAX_FOLLOW_UPS;
+
+/**
+ * A call is a request that has been prepared for execution. A call can be
+ * canceled. As this object represents a single request/response pair (stream),
+ * it cannot be executed twice.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class Call {
+  private final OkHttpClient client;
+
+  // Guarded by this.
+  private boolean executed;
+  volatile boolean canceled;
+
+  /** The application's original request unadulterated by redirects or auth headers. */
+  Request originalRequest;
+  HttpEngine engine;
+
+  protected Call(OkHttpClient client, Request originalRequest) {
+    // Copy the client. Otherwise changes (socket factory, redirect policy,
+    // etc.) may incorrectly be reflected in the request when it is executed.
+    this.client = client.copyWithDefaults();
+    this.originalRequest = originalRequest;
+  }
+
+  /**
+   * Invokes the request immediately, and blocks until the response can be
+   * processed or is in error.
+   *
+   * <p>The caller may read the response body with the response's
+   * {@link Response#body} method.  To facilitate connection recycling, callers
+   * should always {@link ResponseBody#close() close the response body}.
+   *
+   * <p>Note that transport-layer success (receiving a HTTP response code,
+   * headers and body) does not necessarily indicate application-layer success:
+   * {@code response} may still indicate an unhappy HTTP response code like 404
+   * or 500.
+   *
+   * @throws IOException if the request could not be executed due to
+   *     cancellation, a connectivity problem or timeout. Because networks can
+   *     fail during an exchange, it is possible that the remote server
+   *     accepted the request before the failure.
+   *
+   * @throws IllegalStateException when the call has already been executed.
+   */
+  public Response execute() throws IOException {
+    synchronized (this) {
+      if (executed) throw new IllegalStateException("Already Executed");
+      executed = true;
+    }
+    try {
+      client.getDispatcher().executed(this);
+      Response result = getResponseWithInterceptorChain(false);
+      if (result == null) throw new IOException("Canceled");
+      return result;
+    } finally {
+      client.getDispatcher().finished(this);
+    }
+  }
+
+  Object tag() {
+    return originalRequest.tag();
+  }
+
+  /**
+   * Schedules the request to be executed at some point in the future.
+   *
+   * <p>The {@link OkHttpClient#getDispatcher dispatcher} defines when the
+   * request will run: usually immediately unless there are several other
+   * requests currently being executed.
+   *
+   * <p>This client will later call back {@code responseCallback} with either
+   * an HTTP response or a failure exception. If you {@link #cancel} a request
+   * before it completes the callback will not be invoked.
+   *
+   * @throws IllegalStateException when the call has already been executed.
+   */
+  public void enqueue(Callback responseCallback) {
+    enqueue(responseCallback, false);
+  }
+
+  void enqueue(Callback responseCallback, boolean forWebSocket) {
+    synchronized (this) {
+      if (executed) throw new IllegalStateException("Already Executed");
+      executed = true;
+    }
+    client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
+  }
+
+  /**
+   * Cancels the request, if possible. Requests that are already complete
+   * cannot be canceled.
+   */
+  public void cancel() {
+    canceled = true;
+    if (engine != null) engine.cancel();
+  }
+
+  /**
+   * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
+   * #enqueue(Callback) enqueued}. It is an error to execute a call more than once.
+   */
+  public synchronized boolean isExecuted() {
+    return executed;
+  }
+
+  public boolean isCanceled() {
+    return canceled;
+  }
+
+  final class AsyncCall extends NamedRunnable {
+    private final Callback responseCallback;
+    private final boolean forWebSocket;
+
+    private AsyncCall(Callback responseCallback, boolean forWebSocket) {
+      super("OkHttp %s", originalRequest.urlString());
+      this.responseCallback = responseCallback;
+      this.forWebSocket = forWebSocket;
+    }
+
+    String host() {
+      return originalRequest.httpUrl().host();
+    }
+
+    Request request() {
+      return originalRequest;
+    }
+
+    Object tag() {
+      return originalRequest.tag();
+    }
+
+    void cancel() {
+      Call.this.cancel();
+    }
+
+    Call get() {
+      return Call.this;
+    }
+
+    @Override protected void execute() {
+      boolean signalledCallback = false;
+      try {
+        Response response = getResponseWithInterceptorChain(forWebSocket);
+        if (canceled) {
+          signalledCallback = true;
+          responseCallback.onFailure(originalRequest, new IOException("Canceled"));
+        } else {
+          signalledCallback = true;
+          responseCallback.onResponse(response);
+        }
+      } catch (IOException e) {
+        if (signalledCallback) {
+          // Do not signal the callback twice!
+          logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
+        } else {
+          Request request = engine == null ? originalRequest : engine.getRequest();
+          responseCallback.onFailure(request, e);
+        }
+      } finally {
+        client.getDispatcher().finished(this);
+      }
+    }
+  }
+
+  /**
+   * Returns a string that describes this call. Doesn't include a full URL as that might contain
+   * sensitive information.
+   */
+  private String toLoggableString() {
+    String string = canceled ? "canceled call" : "call";
+    HttpUrl redactedUrl = originalRequest.httpUrl().resolve("/...");
+    return string + " to " + redactedUrl;
+  }
+
+  private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
+    Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
+    return chain.proceed(originalRequest);
+  }
+
+  class ApplicationInterceptorChain implements Interceptor.Chain {
+    private final int index;
+    private final Request request;
+    private final boolean forWebSocket;
+
+    ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
+      this.index = index;
+      this.request = request;
+      this.forWebSocket = forWebSocket;
+    }
+
+    @Override public Connection connection() {
+      return null;
+    }
+
+    @Override public Request request() {
+      return request;
+    }
+
+    @Override public Response proceed(Request request) throws IOException {
+      // If there's another interceptor in the chain, call that.
+      if (index < client.interceptors().size()) {
+        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
+        Interceptor interceptor = client.interceptors().get(index);
+        Response interceptedResponse = interceptor.intercept(chain);
+
+        if (interceptedResponse == null) {
+          throw new NullPointerException("application interceptor " + interceptor
+              + " returned null");
+        }
+
+        return interceptedResponse;
+      }
+
+      // No more interceptors. Do HTTP.
+      return getResponse(request, forWebSocket);
+    }
+  }
+
+  /**
+   * Performs the request and returns the response. May return null if this
+   * call was canceled.
+   */
+  Response getResponse(Request request, boolean forWebSocket) throws IOException {
+    // Copy body metadata to the appropriate request headers.
+    RequestBody body = request.body();
+    if (body != null) {
+      Request.Builder requestBuilder = request.newBuilder();
+
+      MediaType contentType = body.contentType();
+      if (contentType != null) {
+        requestBuilder.header("Content-Type", contentType.toString());
+      }
+
+      long contentLength = body.contentLength();
+      if (contentLength != -1) {
+        requestBuilder.header("Content-Length", Long.toString(contentLength));
+        requestBuilder.removeHeader("Transfer-Encoding");
+      } else {
+        requestBuilder.header("Transfer-Encoding", "chunked");
+        requestBuilder.removeHeader("Content-Length");
+      }
+
+      request = requestBuilder.build();
+    }
+
+    // Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
+    engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
+
+    int followUpCount = 0;
+    while (true) {
+      if (canceled) {
+        engine.releaseStreamAllocation();
+        throw new IOException("Canceled");
+      }
+
+      boolean releaseConnection = true;
+      try {
+        engine.sendRequest();
+        engine.readResponse();
+        releaseConnection = false;
+      } catch (RequestException e) {
+        // The attempt to interpret the request failed. Give up.
+        throw e.getCause();
+      } catch (RouteException e) {
+        // The attempt to connect via a route failed. The request will not have been sent.
+        HttpEngine retryEngine = engine.recover(e);
+        if (retryEngine != null) {
+          releaseConnection = false;
+          engine = retryEngine;
+          continue;
+        }
+        // Give up; recovery is not possible.
+        throw e.getLastConnectException();
+      } catch (IOException e) {
+        // An attempt to communicate with a server failed. The request may have been sent.
+        HttpEngine retryEngine = engine.recover(e, null);
+        if (retryEngine != null) {
+          releaseConnection = false;
+          engine = retryEngine;
+          continue;
+        }
+
+        // Give up; recovery is not possible.
+        throw e;
+      } finally {
+        // We're throwing an unchecked exception. Release any resources.
+        if (releaseConnection) {
+          StreamAllocation streamAllocation = engine.close();
+          streamAllocation.release();
+        }
+      }
+
+      Response response = engine.getResponse();
+      Request followUp = engine.followUpRequest();
+
+      if (followUp == null) {
+        if (!forWebSocket) {
+          engine.releaseStreamAllocation();
+        }
+        return response;
+      }
+
+      StreamAllocation streamAllocation = engine.close();
+
+      if (++followUpCount > MAX_FOLLOW_UPS) {
+        streamAllocation.release();
+        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
+      }
+
+      if (!engine.sameConnection(followUp.httpUrl())) {
+        streamAllocation.release();
+        streamAllocation = null;
+      }
+
+      request = followUp;
+      engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
+          response);
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Callback.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Callback.java
new file mode 100644
index 0000000..10d78c6
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Callback.java
@@ -0,0 +1,46 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import java.io.IOException;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Callback {
+  /**
+   * Called when the request could not be executed due to cancellation, a
+   * connectivity problem or timeout. Because networks can fail during an
+   * exchange, it is possible that the remote server accepted the request
+   * before the failure.
+   */
+  void onFailure(Request request, IOException e);
+
+  /**
+   * Called when the HTTP response was successfully returned by the remote
+   * server. The callback may proceed to read the response body with {@link
+   * Response#body}. The response is still live until its response body is
+   * closed with {@code response.body().close()}. The recipient of the callback
+   * may even consume the response body on another thread.
+   *
+   * <p>Note that transport-layer success (receiving a HTTP response code,
+   * headers and body) does not necessarily indicate application-layer
+   * success: {@code response} may still indicate an unhappy HTTP response
+   * code like 404 or 500.
+   */
+  void onResponse(Response response) throws IOException;
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/CertificatePinner.java b/repackaged/okhttp/src/main/java/com/android/okhttp/CertificatePinner.java
new file mode 100644
index 0000000..638dea0
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/CertificatePinner.java
@@ -0,0 +1,275 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import com.android.okhttp.okio.ByteString;
+
+import static java.util.Collections.unmodifiableSet;
+
+/**
+ * Constrains which certificates are trusted. Pinning certificates defends
+ * against attacks on certificate authorities. It also prevents connections
+ * through man-in-the-middle certificate authorities either known or unknown to
+ * the application's user.
+ *
+ * <p>This class currently pins a certificate's Subject Public Key Info as
+ * described on <a href="http://goo.gl/AIx3e5">Adam Langley's Weblog</a>. Pins
+ * are base-64 SHA-1 hashes, consistent with the format Chromium uses for <a
+ * href="http://goo.gl/XDh6je">static certificates</a>. See Chromium's <a
+ * href="http://goo.gl/4CCnGs">pinsets</a> for hostnames that are pinned in that
+ * browser.
+ *
+ * <h3>Setting up Certificate Pinning</h3>
+ * The easiest way to pin a host is turn on pinning with a broken configuration
+ * and read the expected configuration when the connection fails. Be sure to
+ * do this on a trusted network, and without man-in-the-middle tools like <a
+ * href="http://charlesproxy.com">Charles</a> or <a
+ * href="http://fiddlertool.com">Fiddler</a>.
+ *
+ * <p>For example, to pin {@code https://publicobject.com}, start with a broken
+ * configuration: <pre>   {@code
+ *
+ *     String hostname = "publicobject.com";
+ *     CertificatePinner certificatePinner = new CertificatePinner.Builder()
+ *         .add(hostname, "sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=")
+ *         .build();
+ *     OkHttpClient client = new OkHttpClient();
+ *     client.setCertificatePinner(certificatePinner);
+ *
+ *     Request request = new Request.Builder()
+ *         .url("https://" + hostname)
+ *         .build();
+ *     client.newCall(request).execute();
+ * }</pre>
+ *
+ * As expected, this fails with a certificate pinning exception: <pre>   {@code
+ *
+ * javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
+ *   Peer certificate chain:
+ *     sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=: CN=publicobject.com, OU=PositiveSSL
+ *     sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=: CN=COMODO RSA Domain Validation Secure Server CA
+ *     sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=: CN=COMODO RSA Certification Authority
+ *     sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=: CN=AddTrust External CA Root
+ *   Pinned certificates for publicobject.com:
+ *     sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=
+ *   at com.squareup.okhttp.CertificatePinner.check(CertificatePinner.java)
+ *   at com.squareup.okhttp.Connection.upgradeToTls(Connection.java)
+ *   at com.squareup.okhttp.Connection.connect(Connection.java)
+ *   at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java)
+ * }</pre>
+ *
+ * Follow up by pasting the public key hashes from the exception into the
+ * certificate pinner's configuration: <pre>   {@code
+ *
+ *     CertificatePinner certificatePinner = new CertificatePinner.Builder()
+ *       .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
+ *       .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
+ *       .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
+ *       .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
+ *       .build();
+ * }</pre>
+ *
+ * Pinning is per-hostname and/or per-wildcard pattern. To pin both
+ * {@code publicobject.com} and {@code www.publicobject.com}, you must
+ * configure both hostnames.
+ *
+ * <p>Wildcard pattern rules:
+ * <ol>
+ *   <li>Asterisk {@code *} is only permitted in the left-most
+ *       domain name label and must be the only character in that label
+ *       (i.e., must match the whole left-most label). For example,
+ *       {@code *.example.com} is permitted, while {@code *a.example.com},
+ *       {@code a*.example.com}, {@code a*b.example.com}, {@code a.*.example.com}
+ *       are not permitted.
+ *   <li>Asterisk {@code *} cannot match across domain name labels.
+ *       For example, {@code *.example.com} matches {@code test.example.com}
+ *       but does not match {@code sub.test.example.com}.
+ *   <li>Wildcard patterns for single-label domain names are not permitted.
+ * </ol>
+ *
+ * If hostname pinned directly and via wildcard pattern, both
+ * direct and wildcard pins will be used. For example: {@code *.example.com} pinned
+ * with {@code pin1} and {@code a.example.com} pinned with {@code pin2},
+ * to check {@code a.example.com} both {@code pin1} and {@code pin2} will be used.
+ *
+ * <h3>Warning: Certificate Pinning is Dangerous!</h3>
+ * Pinning certificates limits your server team's abilities to update their TLS
+ * certificates. By pinning certificates, you take on additional operational
+ * complexity and limit your ability to migrate between certificate authorities.
+ * Do not use certificate pinning without the blessing of your server's TLS
+ * administrator!
+ *
+ * <h4>Note about self-signed certificates</h4>
+ * {@link CertificatePinner} can not be used to pin self-signed certificate
+ * if such certificate is not accepted by {@link javax.net.ssl.TrustManager}.
+ *
+ * @see <a href="https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning">
+ *     OWASP: Certificate and Public Key Pinning</a>
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class CertificatePinner {
+  public static final CertificatePinner DEFAULT = new Builder().build();
+
+  private final Map<String, Set<ByteString>> hostnameToPins;
+
+  private CertificatePinner(Builder builder) {
+    this.hostnameToPins = Util.immutableMap(builder.hostnameToPins);
+  }
+
+  /**
+   * Confirms that at least one of the certificates pinned for {@code hostname}
+   * is in {@code peerCertificates}. Does nothing if there are no certificates
+   * pinned for {@code hostname}. OkHttp calls this after a successful TLS
+   * handshake, but before the connection is used.
+   *
+   * @throws SSLPeerUnverifiedException if {@code peerCertificates} don't match
+   *     the certificates pinned for {@code hostname}.
+   */
+  public void check(String hostname, List<Certificate> peerCertificates)
+      throws SSLPeerUnverifiedException {
+
+    Set<ByteString> pins = findMatchingPins(hostname);
+
+    if (pins == null) return;
+
+    for (int i = 0, size = peerCertificates.size(); i < size; i++) {
+      X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(i);
+      if (pins.contains(sha1(x509Certificate))) return; // Success!
+    }
+
+    // If we couldn't find a matching pin, format a nice exception.
+    StringBuilder message = new StringBuilder()
+        .append("Certificate pinning failure!")
+        .append("\n  Peer certificate chain:");
+    for (int i = 0, size = peerCertificates.size(); i < size; i++) {
+      X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(i);
+      message.append("\n    ").append(pin(x509Certificate))
+          .append(": ").append(x509Certificate.getSubjectDN().getName());
+    }
+    message.append("\n  Pinned certificates for ").append(hostname).append(":");
+    for (ByteString pin : pins) {
+      message.append("\n    sha1/").append(pin.base64());
+    }
+    throw new SSLPeerUnverifiedException(message.toString());
+  }
+
+  /** @deprecated replaced with {@link #check(String, List)}. */
+  public void check(String hostname, Certificate... peerCertificates)
+      throws SSLPeerUnverifiedException {
+    check(hostname, Arrays.asList(peerCertificates));
+  }
+
+  /**
+   * Returns list of matching certificates' pins for the hostname
+   * or {@code null} if hostname does not have pinned certificates.
+   */
+  Set<ByteString> findMatchingPins(String hostname) {
+    Set<ByteString> directPins   = hostnameToPins.get(hostname);
+    Set<ByteString> wildcardPins = null;
+
+    int indexOfFirstDot = hostname.indexOf('.');
+    int indexOfLastDot  = hostname.lastIndexOf('.');
+
+    // Skip hostnames with one dot symbol for wildcard pattern search
+    //   example.com   will  be skipped
+    //   a.example.com won't be skipped
+    if (indexOfFirstDot != indexOfLastDot) {
+      // a.example.com -> search for wildcard pattern *.example.com
+      wildcardPins = hostnameToPins.get("*." + hostname.substring(indexOfFirstDot + 1));
+    }
+
+    if (directPins == null && wildcardPins == null) return null;
+
+    if (directPins != null && wildcardPins != null) {
+      Set<ByteString> pins = new LinkedHashSet<>();
+      pins.addAll(directPins);
+      pins.addAll(wildcardPins);
+      return pins;
+    }
+
+    if (directPins != null) return directPins;
+
+    return wildcardPins;
+  }
+
+  /**
+   * Returns the SHA-1 of {@code certificate}'s public key. This uses the
+   * mechanism Moxie Marlinspike describes in <a
+   * href="https://github.com/moxie0/AndroidPinning">Android Pinning</a>.
+   */
+  public static String pin(Certificate certificate) {
+    if (!(certificate instanceof X509Certificate)) {
+      throw new IllegalArgumentException("Certificate pinning requires X509 certificates");
+    }
+    return "sha1/" + sha1((X509Certificate) certificate).base64();
+  }
+
+  private static ByteString sha1(X509Certificate x509Certificate) {
+    return Util.sha1(ByteString.of(x509Certificate.getPublicKey().getEncoded()));
+  }
+
+  /** Builds a configured certificate pinner. 
+   * @hide This class is not part of the Android public SDK API*/
+  public static final class Builder {
+    private final Map<String, Set<ByteString>> hostnameToPins = new LinkedHashMap<>();
+
+    /**
+     * Pins certificates for {@code hostname}.
+     *
+     * @param hostname lower-case host name or wildcard pattern such as {@code *.example.com}.
+     * @param pins SHA-1 hashes. Each pin is a SHA-1 hash of a
+     *     certificate's Subject Public Key Info, base64-encoded and prefixed with
+     *     {@code sha1/}.
+     */
+    public Builder add(String hostname, String... pins) {
+      if (hostname == null) throw new IllegalArgumentException("hostname == null");
+
+      Set<ByteString> hostPins = new LinkedHashSet<>();
+      Set<ByteString> previousPins = hostnameToPins.put(hostname, unmodifiableSet(hostPins));
+      if (previousPins != null) {
+        hostPins.addAll(previousPins);
+      }
+
+      for (String pin : pins) {
+        if (!pin.startsWith("sha1/")) {
+          throw new IllegalArgumentException("pins must start with 'sha1/': " + pin);
+        }
+        ByteString decodedPin = ByteString.decodeBase64(pin.substring("sha1/".length()));
+        if (decodedPin == null) {
+          throw new IllegalArgumentException("pins must be base64: " + pin);
+        }
+        hostPins.add(decodedPin);
+      }
+
+      return this;
+    }
+
+    public CertificatePinner build() {
+      return new CertificatePinner(this);
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Challenge.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Challenge.java
new file mode 100644
index 0000000..20f11d2
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Challenge.java
@@ -0,0 +1,58 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import static com.android.okhttp.internal.Util.equal;
+
+/** An RFC 2617 challenge. 
+ * @hide This class is not part of the Android public SDK API*/
+public final class Challenge {
+  private final String scheme;
+  private final String realm;
+
+  public Challenge(String scheme, String realm) {
+    this.scheme = scheme;
+    this.realm = realm;
+  }
+
+  /** Returns the authentication scheme, like {@code Basic}. */
+  public String getScheme() {
+    return scheme;
+  }
+
+  /** Returns the protection space. */
+  public String getRealm() {
+    return realm;
+  }
+
+  @Override public boolean equals(Object o) {
+    return o instanceof Challenge
+        && equal(scheme, ((Challenge) o).scheme)
+        && equal(realm, ((Challenge) o).realm);
+  }
+
+  @Override public int hashCode() {
+    int result = 29;
+    result = 31 * result + (realm != null ? realm.hashCode() : 0);
+    result = 31 * result + (scheme != null ? scheme.hashCode() : 0);
+    return result;
+  }
+
+  @Override public String toString() {
+    return scheme + " realm=\"" + realm + "\"";
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/CipherSuite.java b/repackaged/okhttp/src/main/java/com/android/okhttp/CipherSuite.java
new file mode 100644
index 0000000..bc15fb7
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/CipherSuite.java
@@ -0,0 +1,377 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import static java.lang.Integer.MAX_VALUE;
+
+/**
+ * <a href="https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml">TLS cipher
+ * suites</a>.
+ *
+ * <p><strong>Not all cipher suites are supported on all platforms.</strong> As newer cipher suites
+ * are created (for stronger privacy, better performance, etc.) they will be adopted by the platform
+ * and then exposed here. Cipher suites that are not available on either Android (through API level
+ * 20) or Java (through JDK 8) are omitted for brevity.
+ *
+ * <p>See also <a href="https://android.googlesource.com/platform/external/conscrypt/+/master/src/main/java/org/conscrypt/NativeCrypto.java">NativeCrypto.java</a>
+ * from conscrypt, which lists the cipher suites supported by Android.
+ * @hide This class is not part of the Android public SDK API
+ */
+public enum CipherSuite {
+  // Last updated 2014-11-11 using cipher suites from Android 21 and Java 8.
+
+  // TLS_NULL_WITH_NULL_NULL("TLS_NULL_WITH_NULL_NULL", 0x0000, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_RSA_WITH_NULL_MD5("SSL_RSA_WITH_NULL_MD5", 0x0001, 5246, 6, 10),
+  TLS_RSA_WITH_NULL_SHA("SSL_RSA_WITH_NULL_SHA", 0x0002, 5246, 6, 10),
+  TLS_RSA_EXPORT_WITH_RC4_40_MD5("SSL_RSA_EXPORT_WITH_RC4_40_MD5", 0x0003, 4346, 6, 10),
+  TLS_RSA_WITH_RC4_128_MD5("SSL_RSA_WITH_RC4_128_MD5", 0x0004, 5246, 6, 10),
+  TLS_RSA_WITH_RC4_128_SHA("SSL_RSA_WITH_RC4_128_SHA", 0x0005, 5246, 6, 10),
+  // TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5("SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5", 0x0006, 4346, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_IDEA_CBC_SHA("TLS_RSA_WITH_IDEA_CBC_SHA", 0x0007, 5469, MAX_VALUE, MAX_VALUE),
+  TLS_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0008, 4346, 6, 10),
+  TLS_RSA_WITH_DES_CBC_SHA("SSL_RSA_WITH_DES_CBC_SHA", 0x0009, 5469, 6, 10),
+  TLS_RSA_WITH_3DES_EDE_CBC_SHA("SSL_RSA_WITH_3DES_EDE_CBC_SHA", 0x000a, 5246, 6, 10),
+  // TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x000b, 4346, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_DES_CBC_SHA("TLS_DH_DSS_WITH_DES_CBC_SHA", 0x000c, 5469, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", 0x000d, 5246, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x000e, 4346, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_DES_CBC_SHA("TLS_DH_RSA_WITH_DES_CBC_SHA", 0x000f, 5469, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", 0x0010, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x0011, 4346, 6, 10),
+  TLS_DHE_DSS_WITH_DES_CBC_SHA("SSL_DHE_DSS_WITH_DES_CBC_SHA", 0x0012, 5469, 6, 10),
+  TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA("SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", 0x0013, 5246, 6, 10),
+  TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0014, 4346, 6, 10),
+  TLS_DHE_RSA_WITH_DES_CBC_SHA("SSL_DHE_RSA_WITH_DES_CBC_SHA", 0x0015, 5469, 6, 10),
+  TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", 0x0016, 5246, 6, 10),
+  TLS_DH_anon_EXPORT_WITH_RC4_40_MD5("SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", 0x0017, 4346, 6, 10),
+  TLS_DH_anon_WITH_RC4_128_MD5("SSL_DH_anon_WITH_RC4_128_MD5", 0x0018, 5246, 6, 10),
+  TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", 0x0019, 4346, 6, 10),
+  TLS_DH_anon_WITH_DES_CBC_SHA("SSL_DH_anon_WITH_DES_CBC_SHA", 0x001a, 5469, 6, 10),
+  TLS_DH_anon_WITH_3DES_EDE_CBC_SHA("SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", 0x001b, 5246, 6, 10),
+  TLS_KRB5_WITH_DES_CBC_SHA("TLS_KRB5_WITH_DES_CBC_SHA", 0x001e, 2712, 6, MAX_VALUE),
+  TLS_KRB5_WITH_3DES_EDE_CBC_SHA("TLS_KRB5_WITH_3DES_EDE_CBC_SHA", 0x001f, 2712, 6, MAX_VALUE),
+  TLS_KRB5_WITH_RC4_128_SHA("TLS_KRB5_WITH_RC4_128_SHA", 0x0020, 2712, 6, MAX_VALUE),
+  // TLS_KRB5_WITH_IDEA_CBC_SHA("TLS_KRB5_WITH_IDEA_CBC_SHA", 0x0021, 2712, MAX_VALUE, MAX_VALUE),
+  TLS_KRB5_WITH_DES_CBC_MD5("TLS_KRB5_WITH_DES_CBC_MD5", 0x0022, 2712, 6, MAX_VALUE),
+  TLS_KRB5_WITH_3DES_EDE_CBC_MD5("TLS_KRB5_WITH_3DES_EDE_CBC_MD5", 0x0023, 2712, 6, MAX_VALUE),
+  TLS_KRB5_WITH_RC4_128_MD5("TLS_KRB5_WITH_RC4_128_MD5", 0x0024, 2712, 6, MAX_VALUE),
+  // TLS_KRB5_WITH_IDEA_CBC_MD5("TLS_KRB5_WITH_IDEA_CBC_MD5", 0x0025, 2712, MAX_VALUE, MAX_VALUE),
+  TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", 0x0026, 2712, 6, MAX_VALUE),
+  // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", 0x0027, 2712, MAX_VALUE, MAX_VALUE),
+  TLS_KRB5_EXPORT_WITH_RC4_40_SHA("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", 0x0028, 2712, 6, MAX_VALUE),
+  TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", 0x0029, 2712, 6, MAX_VALUE),
+  // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", 0x002a, 2712, MAX_VALUE, MAX_VALUE),
+  TLS_KRB5_EXPORT_WITH_RC4_40_MD5("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", 0x002b, 2712, 6, MAX_VALUE),
+  // TLS_PSK_WITH_NULL_SHA("TLS_PSK_WITH_NULL_SHA", 0x002c, 4785, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_NULL_SHA("TLS_DHE_PSK_WITH_NULL_SHA", 0x002d, 4785, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_NULL_SHA("TLS_RSA_PSK_WITH_NULL_SHA", 0x002e, 4785, MAX_VALUE, MAX_VALUE),
+  TLS_RSA_WITH_AES_128_CBC_SHA("TLS_RSA_WITH_AES_128_CBC_SHA", 0x002f, 5246, 6, 10),
+  // TLS_DH_DSS_WITH_AES_128_CBC_SHA("TLS_DH_DSS_WITH_AES_128_CBC_SHA", 0x0030, 5246, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_AES_128_CBC_SHA("TLS_DH_RSA_WITH_AES_128_CBC_SHA", 0x0031, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_WITH_AES_128_CBC_SHA("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", 0x0032, 5246, 6, 10),
+  TLS_DHE_RSA_WITH_AES_128_CBC_SHA("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", 0x0033, 5246, 6, 10),
+  TLS_DH_anon_WITH_AES_128_CBC_SHA("TLS_DH_anon_WITH_AES_128_CBC_SHA", 0x0034, 5246, 6, 10),
+  TLS_RSA_WITH_AES_256_CBC_SHA("TLS_RSA_WITH_AES_256_CBC_SHA", 0x0035, 5246, 6, 10),
+  // TLS_DH_DSS_WITH_AES_256_CBC_SHA("TLS_DH_DSS_WITH_AES_256_CBC_SHA", 0x0036, 5246, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_AES_256_CBC_SHA("TLS_DH_RSA_WITH_AES_256_CBC_SHA", 0x0037, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_WITH_AES_256_CBC_SHA("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", 0x0038, 5246, 6, 10),
+  TLS_DHE_RSA_WITH_AES_256_CBC_SHA("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", 0x0039, 5246, 6, 10),
+  TLS_DH_anon_WITH_AES_256_CBC_SHA("TLS_DH_anon_WITH_AES_256_CBC_SHA", 0x003a, 5246, 6, 10),
+  TLS_RSA_WITH_NULL_SHA256("TLS_RSA_WITH_NULL_SHA256", 0x003b, 5246, 7, 21),
+  TLS_RSA_WITH_AES_128_CBC_SHA256("TLS_RSA_WITH_AES_128_CBC_SHA256", 0x003c, 5246, 7, 21),
+  TLS_RSA_WITH_AES_256_CBC_SHA256("TLS_RSA_WITH_AES_256_CBC_SHA256", 0x003d, 5246, 7, 21),
+  // TLS_DH_DSS_WITH_AES_128_CBC_SHA256("TLS_DH_DSS_WITH_AES_128_CBC_SHA256", 0x003e, 5246, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_AES_128_CBC_SHA256("TLS_DH_RSA_WITH_AES_128_CBC_SHA256", 0x003f, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_WITH_AES_128_CBC_SHA256("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", 0x0040, 5246, 7, 21),
+  // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0041, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0042, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0043, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0044, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0045, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", 0x0046, 5932, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_RSA_WITH_AES_128_CBC_SHA256("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", 0x0067, 5246, 7, 21),
+  // TLS_DH_DSS_WITH_AES_256_CBC_SHA256("TLS_DH_DSS_WITH_AES_256_CBC_SHA256", 0x0068, 5246, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_AES_256_CBC_SHA256("TLS_DH_RSA_WITH_AES_256_CBC_SHA256", 0x0069, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_WITH_AES_256_CBC_SHA256("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", 0x006a, 5246, 7, 21),
+  TLS_DHE_RSA_WITH_AES_256_CBC_SHA256("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", 0x006b, 5246, 7, 21),
+  TLS_DH_anon_WITH_AES_128_CBC_SHA256("TLS_DH_anon_WITH_AES_128_CBC_SHA256", 0x006c, 5246, 7, 21),
+  TLS_DH_anon_WITH_AES_256_CBC_SHA256("TLS_DH_anon_WITH_AES_256_CBC_SHA256", 0x006d, 5246, 7, 21),
+  // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0084, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0085, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0086, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0087, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0088, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", 0x0089, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_RC4_128_SHA("TLS_PSK_WITH_RC4_128_SHA", 0x008a, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_3DES_EDE_CBC_SHA("TLS_PSK_WITH_3DES_EDE_CBC_SHA", 0x008b, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_128_CBC_SHA("TLS_PSK_WITH_AES_128_CBC_SHA", 0x008c, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_256_CBC_SHA("TLS_PSK_WITH_AES_256_CBC_SHA", 0x008d, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_RC4_128_SHA("TLS_DHE_PSK_WITH_RC4_128_SHA", 0x008e, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", 0x008f, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_128_CBC_SHA("TLS_DHE_PSK_WITH_AES_128_CBC_SHA", 0x0090, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_256_CBC_SHA("TLS_DHE_PSK_WITH_AES_256_CBC_SHA", 0x0091, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_RC4_128_SHA("TLS_RSA_PSK_WITH_RC4_128_SHA", 0x0092, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", 0x0093, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_128_CBC_SHA("TLS_RSA_PSK_WITH_AES_128_CBC_SHA", 0x0094, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_256_CBC_SHA("TLS_RSA_PSK_WITH_AES_256_CBC_SHA", 0x0095, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_SEED_CBC_SHA("TLS_RSA_WITH_SEED_CBC_SHA", 0x0096, 4162, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_SEED_CBC_SHA("TLS_DH_DSS_WITH_SEED_CBC_SHA", 0x0097, 4162, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_SEED_CBC_SHA("TLS_DH_RSA_WITH_SEED_CBC_SHA", 0x0098, 4162, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_SEED_CBC_SHA("TLS_DHE_DSS_WITH_SEED_CBC_SHA", 0x0099, 4162, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_SEED_CBC_SHA("TLS_DHE_RSA_WITH_SEED_CBC_SHA", 0x009a, 4162, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_SEED_CBC_SHA("TLS_DH_anon_WITH_SEED_CBC_SHA", 0x009b, 4162, MAX_VALUE, MAX_VALUE),
+  TLS_RSA_WITH_AES_128_GCM_SHA256("TLS_RSA_WITH_AES_128_GCM_SHA256", 0x009c, 5288, 8, 21),
+  TLS_RSA_WITH_AES_256_GCM_SHA384("TLS_RSA_WITH_AES_256_GCM_SHA384", 0x009d, 5288, 8, 21),
+  TLS_DHE_RSA_WITH_AES_128_GCM_SHA256("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", 0x009e, 5288, 8, 21),
+  TLS_DHE_RSA_WITH_AES_256_GCM_SHA384("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", 0x009f, 5288, 8, 21),
+  // TLS_DH_RSA_WITH_AES_128_GCM_SHA256("TLS_DH_RSA_WITH_AES_128_GCM_SHA256", 0x00a0, 5288, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_AES_256_GCM_SHA384("TLS_DH_RSA_WITH_AES_256_GCM_SHA384", 0x00a1, 5288, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_WITH_AES_128_GCM_SHA256("TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", 0x00a2, 5288, 8, 21),
+  TLS_DHE_DSS_WITH_AES_256_GCM_SHA384("TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", 0x00a3, 5288, 8, 21),
+  // TLS_DH_DSS_WITH_AES_128_GCM_SHA256("TLS_DH_DSS_WITH_AES_128_GCM_SHA256", 0x00a4, 5288, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_AES_256_GCM_SHA384("TLS_DH_DSS_WITH_AES_256_GCM_SHA384", 0x00a5, 5288, MAX_VALUE, MAX_VALUE),
+  TLS_DH_anon_WITH_AES_128_GCM_SHA256("TLS_DH_anon_WITH_AES_128_GCM_SHA256", 0x00a6, 5288, 8, 21),
+  TLS_DH_anon_WITH_AES_256_GCM_SHA384("TLS_DH_anon_WITH_AES_256_GCM_SHA384", 0x00a7, 5288, 8, 21),
+  // TLS_PSK_WITH_AES_128_GCM_SHA256("TLS_PSK_WITH_AES_128_GCM_SHA256", 0x00a8, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_256_GCM_SHA384("TLS_PSK_WITH_AES_256_GCM_SHA384", 0x00a9, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_128_GCM_SHA256("TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", 0x00aa, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_256_GCM_SHA384("TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", 0x00ab, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_128_GCM_SHA256("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", 0x00ac, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_256_GCM_SHA384("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", 0x00ad, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_128_CBC_SHA256("TLS_PSK_WITH_AES_128_CBC_SHA256", 0x00ae, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_256_CBC_SHA384("TLS_PSK_WITH_AES_256_CBC_SHA384", 0x00af, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_NULL_SHA256("TLS_PSK_WITH_NULL_SHA256", 0x00b0, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_NULL_SHA384("TLS_PSK_WITH_NULL_SHA384", 0x00b1, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_128_CBC_SHA256("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", 0x00b2, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_256_CBC_SHA384("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", 0x00b3, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_NULL_SHA256("TLS_DHE_PSK_WITH_NULL_SHA256", 0x00b4, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_NULL_SHA384("TLS_DHE_PSK_WITH_NULL_SHA384", 0x00b5, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_128_CBC_SHA256("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", 0x00b6, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_256_CBC_SHA384("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", 0x00b7, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_NULL_SHA256("TLS_RSA_PSK_WITH_NULL_SHA256", 0x00b8, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_NULL_SHA384("TLS_RSA_PSK_WITH_NULL_SHA384", 0x00b9, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00ba, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bb, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00bc, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bd, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00be, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", 0x00bf, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c0, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c1, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c2, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c3, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c4, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", 0x00c5, 5932, MAX_VALUE, MAX_VALUE),
+  TLS_EMPTY_RENEGOTIATION_INFO_SCSV("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", 0x00ff, 5746, 6, 14),
+  TLS_ECDH_ECDSA_WITH_NULL_SHA("TLS_ECDH_ECDSA_WITH_NULL_SHA", 0xc001, 4492, 7, 14),
+  TLS_ECDH_ECDSA_WITH_RC4_128_SHA("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", 0xc002, 4492, 7, 14),
+  TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc003, 4492, 7, 14),
+  TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", 0xc004, 4492, 7, 14),
+  TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", 0xc005, 4492, 7, 14),
+  TLS_ECDHE_ECDSA_WITH_NULL_SHA("TLS_ECDHE_ECDSA_WITH_NULL_SHA", 0xc006, 4492, 7, 14),
+  TLS_ECDHE_ECDSA_WITH_RC4_128_SHA("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", 0xc007, 4492, 7, 14),
+  TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc008, 4492, 7, 14),
+  TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 0xc009, 4492, 7, 14),
+  TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 0xc00a, 4492, 7, 14),
+  TLS_ECDH_RSA_WITH_NULL_SHA("TLS_ECDH_RSA_WITH_NULL_SHA", 0xc00b, 4492, 7, 14),
+  TLS_ECDH_RSA_WITH_RC4_128_SHA("TLS_ECDH_RSA_WITH_RC4_128_SHA", 0xc00c, 4492, 7, 14),
+  TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", 0xc00d, 4492, 7, 14),
+  TLS_ECDH_RSA_WITH_AES_128_CBC_SHA("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", 0xc00e, 4492, 7, 14),
+  TLS_ECDH_RSA_WITH_AES_256_CBC_SHA("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", 0xc00f, 4492, 7, 14),
+  TLS_ECDHE_RSA_WITH_NULL_SHA("TLS_ECDHE_RSA_WITH_NULL_SHA", 0xc010, 4492, 7, 14),
+  TLS_ECDHE_RSA_WITH_RC4_128_SHA("TLS_ECDHE_RSA_WITH_RC4_128_SHA", 0xc011, 4492, 7, 14),
+  TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", 0xc012, 4492, 7, 14),
+  TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 0xc013, 4492, 7, 14),
+  TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 0xc014, 4492, 7, 14),
+  TLS_ECDH_anon_WITH_NULL_SHA("TLS_ECDH_anon_WITH_NULL_SHA", 0xc015, 4492, 7, 14),
+  TLS_ECDH_anon_WITH_RC4_128_SHA("TLS_ECDH_anon_WITH_RC4_128_SHA", 0xc016, 4492, 7, 14),
+  TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", 0xc017, 4492, 7, 14),
+  TLS_ECDH_anon_WITH_AES_128_CBC_SHA("TLS_ECDH_anon_WITH_AES_128_CBC_SHA", 0xc018, 4492, 7, 14),
+  TLS_ECDH_anon_WITH_AES_256_CBC_SHA("TLS_ECDH_anon_WITH_AES_256_CBC_SHA", 0xc019, 4492, 7, 14),
+  // TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", 0xc01a, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", 0xc01b, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", 0xc01c, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_WITH_AES_128_CBC_SHA", 0xc01d, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", 0xc01e, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", 0xc01f, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_WITH_AES_256_CBC_SHA", 0xc020, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", 0xc021, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", 0xc022, 5054, MAX_VALUE, MAX_VALUE),
+  TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 0xc023, 5289, 7, 21),
+  TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 0xc024, 5289, 7, 21),
+  TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", 0xc025, 5289, 7, 21),
+  TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", 0xc026, 5289, 7, 21),
+  TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 0xc027, 5289, 7, 21),
+  TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", 0xc028, 5289, 7, 21),
+  TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", 0xc029, 5289, 7, 21),
+  TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", 0xc02a, 5289, 7, 21),
+  TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02b, 5289, 8, 21),
+  TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02c, 5289, 8, 21),
+  TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02d, 5289, 8, 21),
+  TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02e, 5289, 8, 21),
+  TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 0xc02f, 5289, 8, 21),
+  TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 0xc030, 5289, 8, 21),
+  TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", 0xc031, 5289, 8, 21),
+  TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", 0xc032, 5289, 8, 21),
+  // TLS_ECDHE_PSK_WITH_RC4_128_SHA("TLS_ECDHE_PSK_WITH_RC4_128_SHA", 0xc033, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", 0xc034, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", 0xc035, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", 0xc036, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", 0xc037, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", 0xc038, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_NULL_SHA("TLS_ECDHE_PSK_WITH_NULL_SHA", 0xc039, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_NULL_SHA256("TLS_ECDHE_PSK_WITH_NULL_SHA256", 0xc03a, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_NULL_SHA384("TLS_ECDHE_PSK_WITH_NULL_SHA384", 0xc03b, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_ARIA_128_CBC_SHA256("TLS_RSA_WITH_ARIA_128_CBC_SHA256", 0xc03c, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_ARIA_256_CBC_SHA384("TLS_RSA_WITH_ARIA_256_CBC_SHA384", 0xc03d, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", 0xc03e, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", 0xc03f, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc040, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc041, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", 0xc042, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", 0xc043, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc044, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc045, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_ARIA_128_CBC_SHA256("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", 0xc046, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_ARIA_256_CBC_SHA384("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", 0xc047, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc048, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc049, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc04a, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc04b, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04c, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04d, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04e, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04f, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_ARIA_128_GCM_SHA256("TLS_RSA_WITH_ARIA_128_GCM_SHA256", 0xc050, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_ARIA_256_GCM_SHA384("TLS_RSA_WITH_ARIA_256_GCM_SHA384", 0xc051, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256("TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc052, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384("TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc053, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc054, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc055, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256("TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", 0xc056, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384("TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", 0xc057, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", 0xc058, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", 0xc059, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_ARIA_128_GCM_SHA256("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", 0xc05a, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_ARIA_256_GCM_SHA384("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", 0xc05b, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05c, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05d, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05e, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05f, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc060, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc061, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc062, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc063, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_ARIA_128_CBC_SHA256("TLS_PSK_WITH_ARIA_128_CBC_SHA256", 0xc064, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_ARIA_256_CBC_SHA384("TLS_PSK_WITH_ARIA_256_CBC_SHA384", 0xc065, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc066, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc067, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", 0xc068, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", 0xc069, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_ARIA_128_GCM_SHA256("TLS_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06a, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_ARIA_256_GCM_SHA384("TLS_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06b, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256("TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06c, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384("TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06d, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06e, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06f, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc070, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc071, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc072, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc073, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc074, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc075, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc076, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc077, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc078, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc079, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07a, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07b, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07c, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07d, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07e, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07f, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc080, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc081, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc082, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc083, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", 0xc084, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", 0xc085, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc086, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc087, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc088, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc089, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08a, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08b, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08c, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08d, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc08e, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc08f, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc090, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc091, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc092, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc093, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc094, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc095, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc096, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc097, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc098, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc099, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc09a, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc09b, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_AES_128_CCM("TLS_RSA_WITH_AES_128_CCM", 0xc09c, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_AES_256_CCM("TLS_RSA_WITH_AES_256_CCM", 0xc09d, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_AES_128_CCM("TLS_DHE_RSA_WITH_AES_128_CCM", 0xc09e, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_AES_256_CCM("TLS_DHE_RSA_WITH_AES_256_CCM", 0xc09f, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_AES_128_CCM_8("TLS_RSA_WITH_AES_128_CCM_8", 0xc0a0, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_AES_256_CCM_8("TLS_RSA_WITH_AES_256_CCM_8", 0xc0a1, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_AES_128_CCM_8("TLS_DHE_RSA_WITH_AES_128_CCM_8", 0xc0a2, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_AES_256_CCM_8("TLS_DHE_RSA_WITH_AES_256_CCM_8", 0xc0a3, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_128_CCM("TLS_PSK_WITH_AES_128_CCM", 0xc0a4, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_256_CCM("TLS_PSK_WITH_AES_256_CCM", 0xc0a5, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_128_CCM("TLS_DHE_PSK_WITH_AES_128_CCM", 0xc0a6, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_256_CCM("TLS_DHE_PSK_WITH_AES_256_CCM", 0xc0a7, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_128_CCM_8("TLS_PSK_WITH_AES_128_CCM_8", 0xc0a8, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_256_CCM_8("TLS_PSK_WITH_AES_256_CCM_8", 0xc0a9, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_DHE_WITH_AES_128_CCM_8("TLS_PSK_DHE_WITH_AES_128_CCM_8", 0xc0aa, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_DHE_WITH_AES_256_CCM_8("TLS_PSK_DHE_WITH_AES_256_CCM_8", 0xc0ab, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_AES_128_CCM("TLS_ECDHE_ECDSA_WITH_AES_128_CCM", 0xc0ac, 7251, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_AES_256_CCM("TLS_ECDHE_ECDSA_WITH_AES_256_CCM", 0xc0ad, 7251, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", 0xc0ae, 7251, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", 0xc0af, 7251, MAX_VALUE, MAX_VALUE),
+  ;
+
+  final String javaName;
+
+  /**
+   * @param javaName the name used by Java APIs for this cipher suite. Different than the IANA name
+   *     for older cipher suites because the prefix is {@code SSL_} instead of {@code TLS_}.
+   * @param value the integer identifier for this cipher suite. (Documentation only.)
+   * @param rfc the RFC describing this cipher suite. (Documentation only.)
+   * @param sinceJavaVersion the first major Java release supporting this cipher suite.
+   * @param sinceAndroidVersion the first Android SDK version supporting this cipher suite.
+   */
+  private CipherSuite(
+      String javaName, int value, int rfc, int sinceJavaVersion, int sinceAndroidVersion) {
+    this.javaName = javaName;
+  }
+
+  public static CipherSuite forJavaName(String javaName) {
+    return javaName.startsWith("SSL_")
+        ? valueOf("TLS_" + javaName.substring(4))
+        : valueOf(javaName);
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Connection.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Connection.java
new file mode 100644
index 0000000..89f2546
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Connection.java
@@ -0,0 +1,86 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp;
+
+import java.net.Socket;
+
+/**
+ * The sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection. May be used for multiple
+ * HTTP request/response exchanges. Connections may be direct to the origin server or via a proxy.
+ *
+ * <p>Typically instances of this class are created, connected and exercised automatically by the
+ * HTTP client. Applications may use this class to monitor HTTP connections as members of a
+ * {@linkplain ConnectionPool connection pool}.
+ *
+ * <p>Do not confuse this class with the misnamed {@code HttpURLConnection}, which isn't so much a
+ * connection as a single request/response exchange.
+ *
+ * <h3>Modern TLS</h3>
+ * There are tradeoffs when selecting which options to include when negotiating a secure connection
+ * to a remote host. Newer TLS options are quite useful:
+ * <ul>
+ *   <li>Server Name Indication (SNI) enables one IP address to negotiate secure connections for
+ *       multiple domain names.
+ *   <li>Application Layer Protocol Negotiation (ALPN) enables the HTTPS port (443) to be used for
+ *       different HTTP and SPDY protocols.
+ * </ul>
+ * Unfortunately, older HTTPS servers refuse to connect when such options are presented. Rather than
+ * avoiding these options entirely, this class allows a connection to be attempted with modern
+ * options and then retried without them should the attempt fail.
+ *
+ * <h3>Connection Reuse</h3>
+ * <p>Each connection can carry a varying number streams, depending on the underlying protocol being
+ * used. HTTP/1.x connections can carry either zero or one streams. HTTP/2 connections can carry any
+ * number of streams, dynamically configured with {@code SETTINGS_MAX_CONCURRENT_STREAMS}. A
+ * connection currently carrying zero streams is an idle stream. We keep it alive because reusing an
+ * existing connection is typically faster than establishing a new one.
+ *
+ * <p>When a single logical call requires multiple streams due to redirects or authorization
+ * challenges, we prefer to use the same physical connection for all streams in the sequence. There
+ * are potential performance and behavior consequences to this preference. To support this feature,
+ * this class separates <i>allocations</i> from <i>streams</i>. An allocation is created by a call,
+ * used for one or more streams, and then released. An allocated connection won't be stolen by
+ * other calls while a redirect or authorization challenge is being handled.
+ *
+ * <p>When the maximum concurrent streams limit is reduced, some allocations will be rescinded.
+ * Attempting to create new streams on these allocations will fail.
+ *
+ * <p>Note that an allocation may be released before its stream is completed. This is intended to
+ * make bookkeeping easier for the caller: releasing the allocation as soon as the terminal stream
+ * has been found. But only complete the stream once its data stream has been exhausted.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Connection {
+  /** Returns the route used by this connection. */
+  Route getRoute();
+
+  /**
+   * Returns the socket that this connection uses, or null if the connection
+   * is not currently connected.
+   */
+  Socket getSocket();
+
+  Handshake getHandshake();
+
+  /**
+   * Returns the protocol negotiated by this connection, or {@link Protocol#HTTP_1_1} if no protocol
+   * has been negotiated. This method returns {@link Protocol#HTTP_1_1} even if the remote peer is
+   * using {@link Protocol#HTTP_1_0}.
+   */
+  Protocol getProtocol();
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionPool.java b/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionPool.java
new file mode 100644
index 0000000..657751b
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionPool.java
@@ -0,0 +1,329 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp;
+
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.RouteDatabase;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.http.StreamAllocation;
+import com.android.okhttp.internal.io.RealConnection;
+import java.lang.ref.Reference;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP
+ * requests that share the same {@link com.android.okhttp.Address} may share a
+ * {@link Connection}. This class implements the policy of which connections to
+ * keep open for future use.
+ *
+ * <p>The {@link #getDefault() system-wide default} uses system properties for
+ * tuning parameters:
+ * <ul>
+ *     <li>{@code http.keepAlive} true if HTTP and SPDY connections should be
+ *         pooled at all. Default is true.
+ *     <li>{@code http.maxConnections} maximum number of idle connections to
+ *         each to keep in the pool. Default is 5.
+ *     <li>{@code http.keepAliveDuration} Time in milliseconds to keep the
+ *         connection alive in the pool before closing it. Default is 5 minutes.
+ *         This property isn't used by {@code HttpURLConnection}.
+ * </ul>
+ *
+ * <p>The default instance <i>doesn't</i> adjust its configuration as system
+ * properties are changed. This assumes that the applications that set these
+ * parameters do so before making HTTP connections, and that this class is
+ * initialized lazily.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class ConnectionPool {
+  private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min
+
+  private static final ConnectionPool systemDefault;
+
+  static {
+    String keepAlive = System.getProperty("http.keepAlive");
+    String keepAliveDuration = System.getProperty("http.keepAliveDuration");
+    String maxIdleConnections = System.getProperty("http.maxConnections");
+    long keepAliveDurationMs = keepAliveDuration != null
+        ? Long.parseLong(keepAliveDuration)
+        : DEFAULT_KEEP_ALIVE_DURATION_MS;
+    if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {
+      systemDefault = new ConnectionPool(0, keepAliveDurationMs);
+    } else if (maxIdleConnections != null) {
+      systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs);
+    } else {
+      systemDefault = new ConnectionPool(5, keepAliveDurationMs);
+    }
+  }
+
+  /**
+   * A background thread is used to cleanup expired connections. There will be, at most, a single
+   * thread running per connection pool. We use a thread pool executor because it can shrink to
+   * zero threads, permitting this pool to be garbage collected.
+   */
+  private final Executor executor = new ThreadPoolExecutor(
+      0 /* corePoolSize */, 1 /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
+      new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
+
+  /** The maximum number of idle connections for each address. */
+  private final int maxIdleConnections;
+  private final long keepAliveDurationNs;
+  private Runnable cleanupRunnable = new Runnable() {
+    @Override public void run() {
+      while (true) {
+        long waitNanos = cleanup(System.nanoTime());
+        if (waitNanos == -1) return;
+        if (waitNanos > 0) {
+          long waitMillis = waitNanos / 1000000L;
+          waitNanos -= (waitMillis * 1000000L);
+          synchronized (ConnectionPool.this) {
+            try {
+              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
+            } catch (InterruptedException ignored) {
+            }
+          }
+        }
+      }
+    }
+  };
+
+  private final Deque<RealConnection> connections = new ArrayDeque<>();
+  final RouteDatabase routeDatabase = new RouteDatabase();
+
+  public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) {
+    this(maxIdleConnections, keepAliveDurationMs, TimeUnit.MILLISECONDS);
+  }
+
+  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
+    this.maxIdleConnections = maxIdleConnections;
+    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
+
+    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
+    if (keepAliveDuration <= 0) {
+      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
+    }
+  }
+
+  public static ConnectionPool getDefault() {
+    return systemDefault;
+  }
+
+  /** Returns the number of idle connections in the pool. */
+  public synchronized int getIdleConnectionCount() {
+    int total = 0;
+    for (RealConnection connection : connections) {
+      if (connection.allocations.isEmpty()) total++;
+    }
+    return total;
+  }
+
+  /**
+   * Returns total number of connections in the pool. Note that prior to OkHttp 2.7 this included
+   * only idle connections and SPDY connections. In OkHttp 2.7 this includes all connections, both
+   * active and inactive. Use {@link #getIdleConnectionCount()} to count connections not currently
+   * in use.
+   */
+  public synchronized int getConnectionCount() {
+    return connections.size();
+  }
+
+  /** @deprecated Use {@link #getMultiplexedConnectionCount()}. */
+  @Deprecated
+  public synchronized int getSpdyConnectionCount() {
+    return getMultiplexedConnectionCount();
+  }
+
+  /** Returns total number of multiplexed connections in the pool. */
+  public synchronized int getMultiplexedConnectionCount() {
+    int total = 0;
+    for (RealConnection connection : connections) {
+      if (connection.isMultiplexed()) total++;
+    }
+    return total;
+  }
+
+  /** Returns total number of http connections in the pool. */
+  public synchronized int getHttpConnectionCount() {
+    return connections.size() - getMultiplexedConnectionCount();
+  }
+
+  /** Returns a recycled connection to {@code address}, or null if no such connection exists. */
+  RealConnection get(Address address, StreamAllocation streamAllocation) {
+    assert (Thread.holdsLock(this));
+    for (RealConnection connection : connections) {
+      // TODO(jwilson): this is awkward. We're already holding a lock on 'this', and
+      //     connection.allocationLimit() may also lock the FramedConnection.
+      if (connection.allocations.size() < connection.allocationLimit()
+          && address.equals(connection.getRoute().address)
+          && !connection.noNewStreams) {
+        streamAllocation.acquire(connection);
+        return connection;
+      }
+    }
+    return null;
+  }
+
+  void put(RealConnection connection) {
+    assert (Thread.holdsLock(this));
+    if (connections.isEmpty()) {
+      executor.execute(cleanupRunnable);
+    }
+    connections.add(connection);
+  }
+
+  /**
+   * Notify this pool that {@code connection} has become idle. Returns true if the connection
+   * has been removed from the pool and should be closed.
+   */
+  boolean connectionBecameIdle(RealConnection connection) {
+    assert (Thread.holdsLock(this));
+    if (connection.noNewStreams || maxIdleConnections == 0) {
+      connections.remove(connection);
+      return true;
+    } else {
+      notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
+      return false;
+    }
+  }
+
+  /** Close and remove all idle connections in the pool. */
+  public void evictAll() {
+    List<RealConnection> evictedConnections = new ArrayList<>();
+    synchronized (this) {
+      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
+        RealConnection connection = i.next();
+        if (connection.allocations.isEmpty()) {
+          connection.noNewStreams = true;
+          evictedConnections.add(connection);
+          i.remove();
+        }
+      }
+    }
+
+    for (RealConnection connection : evictedConnections) {
+      Util.closeQuietly(connection.getSocket());
+    }
+  }
+
+  /**
+   * Performs maintenance on this pool, evicting the connection that has been idle the longest if
+   * either it has exceeded the keep alive limit or the idle connections limit.
+   *
+   * <p>Returns the duration in nanos to sleep until the next scheduled call to this method.
+   * Returns -1 if no further cleanups are required.
+   */
+  long cleanup(long now) {
+    int inUseConnectionCount = 0;
+    int idleConnectionCount = 0;
+    RealConnection longestIdleConnection = null;
+    long longestIdleDurationNs = Long.MIN_VALUE;
+
+    // Find either a connection to evict, or the time that the next eviction is due.
+    synchronized (this) {
+      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
+        RealConnection connection = i.next();
+
+        // If the connection is in use, keep searching.
+        if (pruneAndGetAllocationCount(connection, now) > 0) {
+          inUseConnectionCount++;
+          continue;
+        }
+
+        idleConnectionCount++;
+
+        // If the connection is ready to be evicted, we're done.
+        long idleDurationNs = now - connection.idleAtNanos;
+        if (idleDurationNs > longestIdleDurationNs) {
+          longestIdleDurationNs = idleDurationNs;
+          longestIdleConnection = connection;
+        }
+      }
+
+      if (longestIdleDurationNs >= this.keepAliveDurationNs
+          || idleConnectionCount > this.maxIdleConnections) {
+        // We've found a connection to evict. Remove it from the list, then close it below (outside
+        // of the synchronized block).
+        connections.remove(longestIdleConnection);
+
+      } else if (idleConnectionCount > 0) {
+        // A connection will be ready to evict soon.
+        return keepAliveDurationNs - longestIdleDurationNs;
+
+      } else if (inUseConnectionCount > 0) {
+        // All connections are in use. It'll be at least the keep alive duration 'til we run again.
+        return keepAliveDurationNs;
+
+      } else {
+        // No connections, idle or in use.
+        return -1;
+      }
+    }
+
+    Util.closeQuietly(longestIdleConnection.getSocket());
+
+    // Cleanup again immediately.
+    return 0;
+  }
+
+  /**
+   * Prunes any leaked allocations and then returns the number of remaining live allocations on
+   * {@code connection}. Allocations are leaked if the connection is tracking them but the
+   * application code has abandoned them. Leak detection is imprecise and relies on garbage
+   * collection.
+   */
+  private int pruneAndGetAllocationCount(RealConnection connection, long now) {
+    List<Reference<StreamAllocation>> references = connection.allocations;
+    for (int i = 0; i < references.size(); ) {
+      Reference<StreamAllocation> reference = references.get(i);
+
+      if (reference.get() != null) {
+        i++;
+        continue;
+      }
+
+      // Android-removed: Drop warning about a leak that may not be the app's fault.
+      // We can't tell here whether the app accessed the response body (InputStream) or
+      // only the header fields; at least in the latter case, the app has done nothing
+      // wrong so we shouldn't warn. http://b/64789755
+      // // We've discovered a leaked allocation. This is an application bug.
+      // Internal.logger.warning("A connection to " + connection.getRoute().getAddress().url()
+      //      + " was leaked. Did you forget to close a response body?");
+      references.remove(i);
+      connection.noNewStreams = true;
+
+      // If this was the last allocation, the connection is eligible for immediate eviction.
+      if (references.isEmpty()) {
+        connection.idleAtNanos = now - keepAliveDurationNs;
+        return 0;
+      }
+    }
+
+    return references.size();
+  }
+
+  void setCleanupRunnableForTest(Runnable cleanupRunnable) {
+    this.cleanupRunnable = cleanupRunnable;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionSpec.java b/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionSpec.java
new file mode 100644
index 0000000..516d596
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/ConnectionSpec.java
@@ -0,0 +1,336 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.util.Arrays;
+import java.util.List;
+import javax.net.ssl.SSLSocket;
+
+import static com.android.okhttp.internal.Util.concat;
+import static com.android.okhttp.internal.Util.contains;
+
+/**
+ * Specifies configuration for the socket connection that HTTP traffic travels through. For {@code
+ * https:} URLs, this includes the TLS version and cipher suites to use when negotiating a secure
+ * connection.
+ *
+ * <p>The TLS versions configured in a connection spec are only be used if they are also enabled in
+ * the SSL socket. For example, if an SSL socket does not have TLS 1.2 enabled, it will not be used
+ * even if it is present on the connection spec. The same policy also applies to cipher suites.
+ *
+ * <p>Use {@link Builder#allEnabledTlsVersions()} and {@link Builder#allEnabledCipherSuites} to
+ * defer all feature selection to the underlying SSL socket.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class ConnectionSpec {
+
+  // This is a subset of the cipher suites supported in Chrome 46, current as of 2015-11-05.
+  // All of these suites are available on Android 5.0; earlier releases support a subset of
+  // these suites. https://github.com/square/okhttp/issues/330
+  private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] {
+      CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+      CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+      CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+
+      // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll
+      // continue to include them until better suites are commonly available. For example, none
+      // of the better cipher suites listed above shipped with Android 4.4 or Java 7.
+      CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+      CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+      CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+      CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+      CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+      CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+      CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
+      CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+      CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
+      CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+  };
+
+  /** A modern TLS connection with extensions like SNI and ALPN available. */
+  public static final ConnectionSpec MODERN_TLS = new Builder(true)
+      .cipherSuites(APPROVED_CIPHER_SUITES)
+      .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
+      .supportsTlsExtensions(true)
+      .build();
+
+  /** A backwards-compatible fallback connection for interop with obsolete servers. */
+  public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS)
+      .tlsVersions(TlsVersion.TLS_1_0)
+      .supportsTlsExtensions(true)
+      .build();
+
+  /** Unencrypted, unauthenticated connections for {@code http:} URLs. */
+  public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
+
+  private final boolean tls;
+  private final boolean supportsTlsExtensions;
+  private final String[] cipherSuites;
+  private final String[] tlsVersions;
+
+  private ConnectionSpec(Builder builder) {
+    this.tls = builder.tls;
+    this.cipherSuites = builder.cipherSuites;
+    this.tlsVersions = builder.tlsVersions;
+    this.supportsTlsExtensions = builder.supportsTlsExtensions;
+  }
+
+  public boolean isTls() {
+    return tls;
+  }
+
+  /**
+   * Returns the cipher suites to use for a connection. Returns {@code null} if all of the SSL
+   * socket's enabled cipher suites should be used.
+   */
+  public List<CipherSuite> cipherSuites() {
+    if (cipherSuites == null) return null;
+
+    CipherSuite[] result = new CipherSuite[cipherSuites.length];
+    for (int i = 0; i < cipherSuites.length; i++) {
+      result[i] = CipherSuite.forJavaName(cipherSuites[i]);
+    }
+    return Util.immutableList(result);
+  }
+
+  /**
+   * Returns the TLS versions to use when negotiating a connection. Returns {@code null} if all of
+   * the SSL socket's enabled TLS versions should be used.
+   */
+  public List<TlsVersion> tlsVersions() {
+    if (tlsVersions == null) return null;
+
+    TlsVersion[] result = new TlsVersion[tlsVersions.length];
+    for (int i = 0; i < tlsVersions.length; i++) {
+      result[i] = TlsVersion.forJavaName(tlsVersions[i]);
+    }
+    return Util.immutableList(result);
+  }
+
+  public boolean supportsTlsExtensions() {
+    return supportsTlsExtensions;
+  }
+
+  /** Applies this spec to {@code sslSocket}. */
+  void apply(SSLSocket sslSocket, boolean isFallback) {
+    ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
+
+    if (specToApply.tlsVersions != null) {
+      sslSocket.setEnabledProtocols(specToApply.tlsVersions);
+    }
+    if (specToApply.cipherSuites != null) {
+      sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
+    }
+  }
+
+  /**
+   * Returns a copy of this that omits cipher suites and TLS versions not enabled by {@code
+   * sslSocket}.
+   */
+  private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) {
+    String[] cipherSuitesIntersection = cipherSuites != null
+        ? Util.intersect(String.class, cipherSuites, sslSocket.getEnabledCipherSuites())
+        : sslSocket.getEnabledCipherSuites();
+    String[] tlsVersionsIntersection = tlsVersions != null
+        ? Util.intersect(String.class, tlsVersions, sslSocket.getEnabledProtocols())
+        : sslSocket.getEnabledProtocols();
+
+    // In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
+    // the SCSV cipher is added to signal that a protocol fallback has taken place.
+    if (isFallback && contains(sslSocket.getSupportedCipherSuites(), "TLS_FALLBACK_SCSV")) {
+      cipherSuitesIntersection = concat(cipherSuitesIntersection, "TLS_FALLBACK_SCSV");
+    }
+
+    return new Builder(this)
+        .cipherSuites(cipherSuitesIntersection)
+        .tlsVersions(tlsVersionsIntersection)
+        .build();
+  }
+
+  /**
+   * Returns {@code true} if the socket, as currently configured, supports this connection spec.
+   * In order for a socket to be compatible the enabled cipher suites and protocols must intersect.
+   *
+   * <p>For cipher suites, at least one of the {@link #cipherSuites() required cipher suites} must
+   * match the socket's enabled cipher suites. If there are no required cipher suites the socket
+   * must have at least one cipher suite enabled.
+   *
+   * <p>For protocols, at least one of the {@link #tlsVersions() required protocols} must match the
+   * socket's enabled protocols.
+   */
+  public boolean isCompatible(SSLSocket socket) {
+    if (!tls) {
+      return false;
+    }
+
+    if (tlsVersions != null
+        && !nonEmptyIntersection(tlsVersions, socket.getEnabledProtocols())) {
+      return false;
+    }
+
+    if (cipherSuites != null
+        && !nonEmptyIntersection(cipherSuites, socket.getEnabledCipherSuites())) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * An N*M intersection that terminates if any intersection is found. The sizes of both
+   * arguments are assumed to be so small, and the likelihood of an intersection so great, that it
+   * is not worth the CPU cost of sorting or the memory cost of hashing.
+   */
+  private static boolean nonEmptyIntersection(String[] a, String[] b) {
+    if (a == null || b == null || a.length == 0 || b.length == 0) {
+      return false;
+    }
+    for (String toFind : a) {
+      if (contains(b, toFind)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override public boolean equals(Object other) {
+    if (!(other instanceof ConnectionSpec)) return false;
+    if (other == this) return true;
+
+    ConnectionSpec that = (ConnectionSpec) other;
+    if (this.tls != that.tls) return false;
+
+    if (tls) {
+      if (!Arrays.equals(this.cipherSuites, that.cipherSuites)) return false;
+      if (!Arrays.equals(this.tlsVersions, that.tlsVersions)) return false;
+      if (this.supportsTlsExtensions != that.supportsTlsExtensions) return false;
+    }
+
+    return true;
+  }
+
+  @Override public int hashCode() {
+    int result = 17;
+    if (tls) {
+      result = 31 * result + Arrays.hashCode(cipherSuites);
+      result = 31 * result + Arrays.hashCode(tlsVersions);
+      result = 31 * result + (supportsTlsExtensions ? 0 : 1);
+    }
+    return result;
+  }
+
+  @Override public String toString() {
+    if (!tls) {
+      return "ConnectionSpec()";
+    }
+
+    String cipherSuitesString = cipherSuites != null ? cipherSuites().toString() : "[all enabled]";
+    String tlsVersionsString = tlsVersions != null ? tlsVersions().toString() : "[all enabled]";
+    return "ConnectionSpec("
+        + "cipherSuites=" + cipherSuitesString
+        + ", tlsVersions=" + tlsVersionsString
+        + ", supportsTlsExtensions=" + supportsTlsExtensions
+        + ")";
+  }
+
+  /**
+   * @hide This class is not part of the Android public SDK API
+   */
+  public static final class Builder {
+    private boolean tls;
+    private String[] cipherSuites;
+    private String[] tlsVersions;
+    private boolean supportsTlsExtensions;
+
+    Builder(boolean tls) {
+      this.tls = tls;
+    }
+
+    public Builder(ConnectionSpec connectionSpec) {
+      this.tls = connectionSpec.tls;
+      this.cipherSuites = connectionSpec.cipherSuites;
+      this.tlsVersions = connectionSpec.tlsVersions;
+      this.supportsTlsExtensions = connectionSpec.supportsTlsExtensions;
+    }
+
+    public Builder allEnabledCipherSuites() {
+      if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
+      this.cipherSuites = null;
+      return this;
+    }
+
+    public Builder cipherSuites(CipherSuite... cipherSuites) {
+      if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
+
+      String[] strings = new String[cipherSuites.length];
+      for (int i = 0; i < cipherSuites.length; i++) {
+        strings[i] = cipherSuites[i].javaName;
+      }
+      return cipherSuites(strings);
+    }
+
+    public Builder cipherSuites(String... cipherSuites) {
+      if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
+
+      if (cipherSuites.length == 0) {
+        throw new IllegalArgumentException("At least one cipher suite is required");
+      }
+
+      this.cipherSuites = cipherSuites.clone(); // Defensive copy.
+      return this;
+    }
+
+    public Builder allEnabledTlsVersions() {
+      if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
+      this.tlsVersions = null;
+      return this;
+    }
+
+    public Builder tlsVersions(TlsVersion... tlsVersions) {
+      if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
+
+      String[] strings = new String[tlsVersions.length];
+      for (int i = 0; i < tlsVersions.length; i++) {
+        strings[i] = tlsVersions[i].javaName;
+      }
+
+      return tlsVersions(strings);
+    }
+
+    public Builder tlsVersions(String... tlsVersions) {
+      if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
+
+      if (tlsVersions.length == 0) {
+        throw new IllegalArgumentException("At least one TLS version is required");
+      }
+
+      this.tlsVersions = tlsVersions.clone(); // Defensive copy.
+      return this;
+    }
+
+    public Builder supportsTlsExtensions(boolean supportsTlsExtensions) {
+      if (!tls) throw new IllegalStateException("no TLS extensions for cleartext connections");
+      this.supportsTlsExtensions = supportsTlsExtensions;
+      return this;
+    }
+
+    public ConnectionSpec build() {
+      return new ConnectionSpec(this);
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Credentials.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Credentials.java
new file mode 100644
index 0000000..dd758be
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Credentials.java
@@ -0,0 +1,39 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import java.io.UnsupportedEncodingException;
+import com.android.okhttp.okio.ByteString;
+
+/** Factory for HTTP authorization credentials. 
+ * @hide This class is not part of the Android public SDK API*/
+public final class Credentials {
+  private Credentials() {
+  }
+
+  /** Returns an auth credential for the Basic scheme. */
+  public static String basic(String userName, String password) {
+    try {
+      String usernameAndPassword = userName + ":" + password;
+      byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
+      String encoded = ByteString.of(bytes).base64();
+      return "Basic " + encoded;
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError();
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Dispatcher.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Dispatcher.java
new file mode 100644
index 0000000..ef518c2
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Dispatcher.java
@@ -0,0 +1,190 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.Call.AsyncCall;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.http.HttpEngine;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Policy on when async requests are executed.
+ *
+ * <p>Each dispatcher uses an {@link ExecutorService} to run calls internally. If you
+ * supply your own executor, it should be able to run {@linkplain #getMaxRequests the
+ * configured maximum} number of calls concurrently.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Dispatcher {
+  private int maxRequests = 64;
+  private int maxRequestsPerHost = 5;
+
+  /** Executes calls. Created lazily. */
+  private ExecutorService executorService;
+
+  /** Ready calls in the order they'll be run. */
+  private final Deque<AsyncCall> readyCalls = new ArrayDeque<>();
+
+  /** Running calls. Includes canceled calls that haven't finished yet. */
+  private final Deque<AsyncCall> runningCalls = new ArrayDeque<>();
+
+  /** In-flight synchronous calls. Includes canceled calls that haven't finished yet. */
+  private final Deque<Call> executedCalls = new ArrayDeque<>();
+
+  public Dispatcher(ExecutorService executorService) {
+    this.executorService = executorService;
+  }
+
+  public Dispatcher() {
+  }
+
+  public synchronized ExecutorService getExecutorService() {
+    if (executorService == null) {
+      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
+          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
+    }
+    return executorService;
+  }
+
+  /**
+   * Set the maximum number of requests to execute concurrently. Above this
+   * requests queue in memory, waiting for the running calls to complete.
+   *
+   * <p>If more than {@code maxRequests} requests are in flight when this is
+   * invoked, those requests will remain in flight.
+   */
+  public synchronized void setMaxRequests(int maxRequests) {
+    if (maxRequests < 1) {
+      throw new IllegalArgumentException("max < 1: " + maxRequests);
+    }
+    this.maxRequests = maxRequests;
+    promoteCalls();
+  }
+
+  public synchronized int getMaxRequests() {
+    return maxRequests;
+  }
+
+  /**
+   * Set the maximum number of requests for each host to execute concurrently.
+   * This limits requests by the URL's host name. Note that concurrent requests
+   * to a single IP address may still exceed this limit: multiple hostnames may
+   * share an IP address or be routed through the same HTTP proxy.
+   *
+   * <p>If more than {@code maxRequestsPerHost} requests are in flight when this
+   * is invoked, those requests will remain in flight.
+   */
+  public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {
+    if (maxRequestsPerHost < 1) {
+      throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
+    }
+    this.maxRequestsPerHost = maxRequestsPerHost;
+    promoteCalls();
+  }
+
+  public synchronized int getMaxRequestsPerHost() {
+    return maxRequestsPerHost;
+  }
+
+  synchronized void enqueue(AsyncCall call) {
+    if (runningCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
+      runningCalls.add(call);
+      getExecutorService().execute(call);
+    } else {
+      readyCalls.add(call);
+    }
+  }
+
+  /** Cancel all calls with the tag {@code tag}. */
+  public synchronized void cancel(Object tag) {
+    for (AsyncCall call : readyCalls) {
+      if (Util.equal(tag, call.tag())) {
+        call.cancel();
+      }
+    }
+
+    for (AsyncCall call : runningCalls) {
+      if (Util.equal(tag, call.tag())) {
+        call.get().canceled = true;
+        HttpEngine engine = call.get().engine;
+        if (engine != null) engine.cancel();
+      }
+    }
+
+    for (Call call : executedCalls) {
+      if (Util.equal(tag, call.tag())) {
+        call.cancel();
+      }
+    }
+  }
+
+  /** Used by {@code AsyncCall#run} to signal completion. */
+  synchronized void finished(AsyncCall call) {
+    if (!runningCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
+    promoteCalls();
+  }
+
+  private void promoteCalls() {
+    if (runningCalls.size() >= maxRequests) return; // Already running max capacity.
+    if (readyCalls.isEmpty()) return; // No ready calls to promote.
+
+    for (Iterator<AsyncCall> i = readyCalls.iterator(); i.hasNext(); ) {
+      AsyncCall call = i.next();
+
+      if (runningCallsForHost(call) < maxRequestsPerHost) {
+        i.remove();
+        runningCalls.add(call);
+        getExecutorService().execute(call);
+      }
+
+      if (runningCalls.size() >= maxRequests) return; // Reached max capacity.
+    }
+  }
+
+  /** Returns the number of running calls that share a host with {@code call}. */
+  private int runningCallsForHost(AsyncCall call) {
+    int result = 0;
+    for (AsyncCall c : runningCalls) {
+      if (c.host().equals(call.host())) result++;
+    }
+    return result;
+  }
+
+  /** Used by {@code Call#execute} to signal it is in-flight. */
+  synchronized void executed(Call call) {
+    executedCalls.add(call);
+  }
+
+  /** Used by {@code Call#execute} to signal completion. */
+  synchronized void finished(Call call) {
+    if (!executedCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
+  }
+
+  public synchronized int getRunningCallCount() {
+    return runningCalls.size();
+  }
+
+  public synchronized int getQueuedCallCount() {
+    return readyCalls.size();
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Dns.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Dns.java
new file mode 100644
index 0000000..20610bc
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Dns.java
@@ -0,0 +1,52 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A domain name service that resolves IP addresses for host names. Most applications will use the
+ * {@linkplain #SYSTEM system DNS service}, which is the default. Some applications may provide
+ * their own implementation to use a different DNS server, to prefer IPv6 addresses, to prefer IPv4
+ * addresses, or to force a specific known IP address.
+ *
+ * <p>Implementations of this interface must be safe for concurrent use.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Dns {
+  /**
+   * A DNS that uses {@link InetAddress#getAllByName} to ask the underlying operating system to
+   * lookup IP addresses. Most custom {@link Dns} implementations should delegate to this instance.
+   */
+  Dns SYSTEM = new Dns() {
+    @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
+      if (hostname == null) throw new UnknownHostException("hostname == null");
+      return Arrays.asList(InetAddress.getAllByName(hostname));
+    }
+  };
+
+  /**
+   * Returns the IP addresses of {@code hostname}, in the order they will be attempted by OkHttp.
+   * If a connection to an address fails, OkHttp will retry the connection with the next address
+   * until either a connection is made, the set of IP addresses is exhausted, or a limit is
+   * exceeded.
+   */
+  List<InetAddress> lookup(String hostname) throws UnknownHostException;
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/FormEncodingBuilder.java b/repackaged/okhttp/src/main/java/com/android/okhttp/FormEncodingBuilder.java
new file mode 100644
index 0000000..b95deed
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/FormEncodingBuilder.java
@@ -0,0 +1,61 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.okio.Buffer;
+
+/**
+ * Fluent API to build <a href="http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2.1">HTML
+ * 2.0</a>-compliant form data.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class FormEncodingBuilder {
+  private static final MediaType CONTENT_TYPE =
+      MediaType.parse("application/x-www-form-urlencoded");
+
+  private final Buffer content = new Buffer();
+
+  /** Add new key-value pair. */
+  public FormEncodingBuilder add(String name, String value) {
+    if (content.size() > 0) {
+      content.writeByte('&');
+    }
+    HttpUrl.canonicalize(content, name, 0, name.length(),
+        HttpUrl.FORM_ENCODE_SET, false, false, true, true);
+    content.writeByte('=');
+    HttpUrl.canonicalize(content, value, 0, value.length(),
+        HttpUrl.FORM_ENCODE_SET, false, false, true, true);
+    return this;
+  }
+
+  /** Add new key-value pair. */
+  public FormEncodingBuilder addEncoded(String name, String value) {
+    if (content.size() > 0) {
+      content.writeByte('&');
+    }
+    HttpUrl.canonicalize(content, name, 0, name.length(),
+        HttpUrl.FORM_ENCODE_SET, true, false, true, true);
+    content.writeByte('=');
+    HttpUrl.canonicalize(content, value, 0, value.length(),
+        HttpUrl.FORM_ENCODE_SET, true, false, true, true);
+    return this;
+  }
+
+  public RequestBody build() {
+    return RequestBody.create(CONTENT_TYPE, content.snapshot());
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Handshake.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Handshake.java
new file mode 100644
index 0000000..4f39576
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Handshake.java
@@ -0,0 +1,122 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+
+/**
+ * A record of a TLS handshake. For HTTPS clients, the client is <i>local</i>
+ * and the remote server is its <i>peer</i>.
+ *
+ * <p>This value object describes a completed handshake. Use {@link
+ * javax.net.ssl.SSLSocketFactory} to set policy for new handshakes.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Handshake {
+  private final String cipherSuite;
+  private final List<Certificate> peerCertificates;
+  private final List<Certificate> localCertificates;
+
+  private Handshake(
+      String cipherSuite, List<Certificate> peerCertificates, List<Certificate> localCertificates) {
+    this.cipherSuite = cipherSuite;
+    this.peerCertificates = peerCertificates;
+    this.localCertificates = localCertificates;
+  }
+
+  public static Handshake get(SSLSession session) {
+    String cipherSuite = session.getCipherSuite();
+    if (cipherSuite == null) throw new IllegalStateException("cipherSuite == null");
+
+    Certificate[] peerCertificates;
+    try {
+      peerCertificates = session.getPeerCertificates();
+    } catch (SSLPeerUnverifiedException ignored) {
+      peerCertificates = null;
+    }
+    List<Certificate> peerCertificatesList = peerCertificates != null
+        ? Util.immutableList(peerCertificates)
+        : Collections.<Certificate>emptyList();
+
+    Certificate[] localCertificates = session.getLocalCertificates();
+    List<Certificate> localCertificatesList = localCertificates != null
+        ? Util.immutableList(localCertificates)
+        : Collections.<Certificate>emptyList();
+
+    return new Handshake(cipherSuite, peerCertificatesList, localCertificatesList);
+  }
+
+  public static Handshake get(
+      String cipherSuite, List<Certificate> peerCertificates, List<Certificate> localCertificates) {
+    if (cipherSuite == null) throw new IllegalArgumentException("cipherSuite == null");
+    return new Handshake(cipherSuite, Util.immutableList(peerCertificates),
+        Util.immutableList(localCertificates));
+  }
+
+  /** Returns a cipher suite name like "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA". */
+  public String cipherSuite() {
+    return cipherSuite;
+  }
+
+  /** Returns a possibly-empty list of certificates that identify the remote peer. */
+  public List<Certificate> peerCertificates() {
+    return peerCertificates;
+  }
+
+  /** Returns the remote peer's principle, or null if that peer is anonymous. */
+  public Principal peerPrincipal() {
+    return !peerCertificates.isEmpty()
+        ? ((X509Certificate) peerCertificates.get(0)).getSubjectX500Principal()
+        : null;
+  }
+
+  /** Returns a possibly-empty list of certificates that identify this peer. */
+  public List<Certificate> localCertificates() {
+    return localCertificates;
+  }
+
+  /** Returns the local principle, or null if this peer is anonymous. */
+  public Principal localPrincipal() {
+    return !localCertificates.isEmpty()
+        ? ((X509Certificate) localCertificates.get(0)).getSubjectX500Principal()
+        : null;
+  }
+
+  @Override public boolean equals(Object other) {
+    if (!(other instanceof Handshake)) return false;
+    Handshake that = (Handshake) other;
+    return cipherSuite.equals(that.cipherSuite)
+        && peerCertificates.equals(that.peerCertificates)
+        && localCertificates.equals(that.localCertificates);
+  }
+
+  @Override public int hashCode() {
+    int result = 17;
+    result = 31 * result + cipherSuite.hashCode();
+    result = 31 * result + peerCertificates.hashCode();
+    result = 31 * result + localCertificates.hashCode();
+    return result;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Headers.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Headers.java
new file mode 100644
index 0000000..ec740a1
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Headers.java
@@ -0,0 +1,339 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp;
+
+import com.android.okhttp.internal.http.HttpDate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * The header fields of a single HTTP message. Values are uninterpreted strings;
+ * use {@code Request} and {@code Response} for interpreted headers. This class
+ * maintains the order of the header fields within the HTTP message.
+ *
+ * <p>This class tracks header values line-by-line. A field with multiple comma-
+ * separated values on the same line will be treated as a field with a single
+ * value by this class. It is the caller's responsibility to detect and split
+ * on commas if their field permits multiple values. This simplifies use of
+ * single-valued fields whose values routinely contain commas, such as cookies
+ * or dates.
+ *
+ * <p>This class trims whitespace from values. It never returns values with
+ * leading or trailing whitespace.
+ *
+ * <p>Instances of this class are immutable. Use {@link Builder} to create
+ * instances.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Headers {
+  private final String[] namesAndValues;
+
+  private Headers(Builder builder) {
+    this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
+  }
+
+  private Headers(String[] namesAndValues) {
+    this.namesAndValues = namesAndValues;
+  }
+
+  /** Returns the last value corresponding to the specified field, or null. */
+  public String get(String name) {
+    return get(namesAndValues, name);
+  }
+
+  /**
+   * Returns the last value corresponding to the specified field parsed as an
+   * HTTP date, or null if either the field is absent or cannot be parsed as a
+   * date.
+   */
+  public Date getDate(String name) {
+    String value = get(name);
+    return value != null ? HttpDate.parse(value) : null;
+  }
+
+  /** Returns the number of field values. */
+  public int size() {
+    return namesAndValues.length / 2;
+  }
+
+  /** Returns the field at {@code position} or null if that is out of range. */
+  public String name(int index) {
+    int nameIndex = index * 2;
+    if (nameIndex < 0 || nameIndex >= namesAndValues.length) {
+      return null;
+    }
+    return namesAndValues[nameIndex];
+  }
+
+  /** Returns the value at {@code index} or null if that is out of range. */
+  public String value(int index) {
+    int valueIndex = index * 2 + 1;
+    if (valueIndex < 0 || valueIndex >= namesAndValues.length) {
+      return null;
+    }
+    return namesAndValues[valueIndex];
+  }
+
+  /** Returns an immutable case-insensitive set of header names. */
+  public Set<String> names() {
+    TreeSet<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+    for (int i = 0, size = size(); i < size; i++) {
+      result.add(name(i));
+    }
+    return Collections.unmodifiableSet(result);
+  }
+
+  /** Returns an immutable list of the header values for {@code name}. */
+  public List<String> values(String name) {
+    List<String> result = null;
+    for (int i = 0, size = size(); i < size; i++) {
+      if (name.equalsIgnoreCase(name(i))) {
+        if (result == null) result = new ArrayList<>(2);
+        result.add(value(i));
+      }
+    }
+    return result != null
+        ? Collections.unmodifiableList(result)
+        : Collections.<String>emptyList();
+  }
+
+  public Builder newBuilder() {
+    Builder result = new Builder();
+    Collections.addAll(result.namesAndValues, namesAndValues);
+    return result;
+  }
+
+  @Override public String toString() {
+    StringBuilder result = new StringBuilder();
+    for (int i = 0, size = size(); i < size; i++) {
+      result.append(name(i)).append(": ").append(value(i)).append("\n");
+    }
+    return result.toString();
+  }
+
+  public Map<String, List<String>> toMultimap() {
+    Map<String, List<String>> result = new LinkedHashMap<String, List<String>>();
+    for (int i = 0, size = size(); i < size; i++) {
+      String name = name(i);
+      List<String> values = result.get(name);
+      if (values == null) {
+        values = new ArrayList<>(2);
+        result.put(name, values);
+      }
+      values.add(value(i));
+    }
+    return result;
+  }
+
+  private static String get(String[] namesAndValues, String name) {
+    for (int i = namesAndValues.length - 2; i >= 0; i -= 2) {
+      if (name.equalsIgnoreCase(namesAndValues[i])) {
+        return namesAndValues[i + 1];
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns headers for the alternating header names and values. There must be
+   * an even number of arguments, and they must alternate between header names
+   * and values.
+   */
+  public static Headers of(String... namesAndValues) {
+    if (namesAndValues == null || namesAndValues.length % 2 != 0) {
+      throw new IllegalArgumentException("Expected alternating header names and values");
+    }
+
+    // Make a defensive copy and clean it up.
+    namesAndValues = namesAndValues.clone();
+    for (int i = 0; i < namesAndValues.length; i++) {
+      if (namesAndValues[i] == null) throw new IllegalArgumentException("Headers cannot be null");
+      namesAndValues[i] = namesAndValues[i].trim();
+    }
+
+    // Check for malformed headers.
+    for (int i = 0; i < namesAndValues.length; i += 2) {
+      String name = namesAndValues[i];
+      String value = namesAndValues[i + 1];
+      if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) {
+        throw new IllegalArgumentException("Unexpected header: " + name + ": " + value);
+      }
+    }
+
+    return new Headers(namesAndValues);
+  }
+
+  /**
+   * Returns headers for the header names and values in the {@link Map}.
+   */
+  public static Headers of(Map<String, String> headers) {
+    if (headers == null) {
+      throw new IllegalArgumentException("Expected map with header names and values");
+    }
+
+    // Make a defensive copy and clean it up.
+    String[] namesAndValues = new String[headers.size() * 2];
+    int i = 0;
+    for (Map.Entry<String, String> header : headers.entrySet()) {
+      if (header.getKey() == null || header.getValue() == null) {
+        throw new IllegalArgumentException("Headers cannot be null");
+      }
+      String name = header.getKey().trim();
+      String value = header.getValue().trim();
+      if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) {
+        throw new IllegalArgumentException("Unexpected header: " + name + ": " + value);
+      }
+      namesAndValues[i] = name;
+      namesAndValues[i + 1] = value;
+      i += 2;
+    }
+
+    return new Headers(namesAndValues);
+  }
+
+  /**
+   * @hide This class is not part of the Android public SDK API
+   */
+  public static final class Builder {
+    private final List<String> namesAndValues = new ArrayList<>(20);
+
+    /**
+     * Add a header line without any validation. Only appropriate for headers from the remote peer
+     * or cache.
+     */
+    Builder addLenient(String line) {
+      int index = line.indexOf(":", 1);
+      if (index != -1) {
+        return addLenient(line.substring(0, index), line.substring(index + 1));
+      } else if (line.startsWith(":")) {
+        // Work around empty header names and header names that start with a
+        // colon (created by old broken SPDY versions of the response cache).
+        return addLenient("", line.substring(1)); // Empty header name.
+      } else {
+        return addLenient("", line); // No header name.
+      }
+    }
+
+    /** Add an header line containing a field name, a literal colon, and a value. */
+    public Builder add(String line) {
+      int index = line.indexOf(":");
+      if (index == -1) {
+        throw new IllegalArgumentException("Unexpected header: " + line);
+      }
+      return add(line.substring(0, index).trim(), line.substring(index + 1));
+    }
+
+    /** Add a field with the specified value. */
+    public Builder add(String name, String value) {
+      checkNameAndValue(name, value);
+      return addLenient(name, value);
+    }
+
+    /**
+     * Add a field with the specified value without any validation. Only
+     * appropriate for headers from the remote peer or cache.
+     */
+    Builder addLenient(String name, String value) {
+      namesAndValues.add(name);
+      namesAndValues.add(value.trim());
+      return this;
+    }
+
+    public Builder removeAll(String name) {
+      for (int i = 0; i < namesAndValues.size(); i += 2) {
+        if (name.equalsIgnoreCase(namesAndValues.get(i))) {
+          namesAndValues.remove(i); // name
+          namesAndValues.remove(i); // value
+          i -= 2;
+        }
+      }
+      return this;
+    }
+
+    /**
+     * Set a field with the specified value. If the field is not found, it is
+     * added. If the field is found, the existing values are replaced.
+     */
+    public Builder set(String name, String value) {
+      checkNameAndValue(name, value);
+      removeAll(name);
+      addLenient(name, value);
+      return this;
+    }
+
+    private void checkNameAndValue(String name, String value) {
+      if (name == null) throw new IllegalArgumentException("name == null");
+      if (name.isEmpty()) throw new IllegalArgumentException("name is empty");
+      for (int i = 0, length = name.length(); i < length; i++) {
+        char c = name.charAt(i);
+        if (c <= '\u001f' || c >= '\u007f') {
+          throw new IllegalArgumentException(String.format(
+              "Unexpected char %#04x at %d in header name: %s", (int) c, i, name));
+        }
+      }
+      if (value == null) throw new IllegalArgumentException("value == null");
+
+      // Workaround for applications that set trailing "\r", "\n" or "\r\n" on header values.
+      // http://b/26422335, http://b/26889631 Android used to allow anything except '\0'.
+      int valueLen = value.length();
+      if (valueLen >= 2 && value.charAt(valueLen - 2) == '\r'
+          && value.charAt(valueLen - 1) == '\n') {
+        value = value.substring(0, value.length() - 2);
+      } else if (valueLen > 0
+              && (value.charAt(valueLen - 1) == '\n'
+                      || value.charAt(valueLen - 1) == '\r')) {
+        value = value.substring(0, valueLen - 1);
+      }
+      // End of workaround.
+
+      for (int i = 0, length = value.length(); i < length; i++) {
+        char c = value.charAt(i);
+        // ANDROID-BEGIN
+        // http://b/28867041 - keep things working for apps that rely on Android's (out of spec)
+        // UTF-8 header encoding behavior.
+        // if ((c <= '\u001f' && c != '\u0009' /* htab */) || c >= '\u007f') {
+        if ((c <= '\u001f' && c != '\u0009' /* htab */) || c == '\u007f') {
+        // ANDROID-END
+          throw new IllegalArgumentException(String.format(
+              "Unexpected char %#04x at %d in header value: %s", (int) c, i, value));
+        }
+      }
+    }
+
+    /** Equivalent to {@code build().get(name)}, but potentially faster. */
+    public String get(String name) {
+      for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {
+        if (name.equalsIgnoreCase(namesAndValues.get(i))) {
+          return namesAndValues.get(i + 1);
+        }
+      }
+      return null;
+    }
+
+    public Headers build() {
+      return new Headers(this);
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/HttpUrl.java b/repackaged/okhttp/src/main/java/com/android/okhttp/HttpUrl.java
new file mode 100644
index 0000000..24cb0d4
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/HttpUrl.java
@@ -0,0 +1,1646 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import java.net.IDN;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import com.android.okhttp.okio.Buffer;
+
+/**
+ * A uniform resource locator (URL) with a scheme of either {@code http} or {@code https}. Use this
+ * class to compose and decompose Internet addresses. For example, this code will compose and print
+ * a URL for Google search: <pre>   {@code
+ *
+ *   HttpUrl url = new HttpUrl.Builder()
+ *       .scheme("https")
+ *       .host("www.google.com")
+ *       .addPathSegment("search")
+ *       .addQueryParameter("q", "polar bears")
+ *       .build();
+ *   System.out.println(url);
+ * }</pre>
+ *
+ * which prints: <pre>   {@code
+ *
+ *     https://www.google.com/search?q=polar%20bears
+ * }</pre>
+ *
+ * As another example, this code prints the human-readable query parameters of a Twitter search:
+ * <pre>   {@code
+ *
+ *   HttpUrl url = HttpUrl.parse("https://twitter.com/search?q=cute%20%23puppies&f=images");
+ *   for (int i = 0, size = url.querySize(); i < size; i++) {
+ *     System.out.println(url.queryParameterName(i) + ": " + url.queryParameterValue(i));
+ *   }
+ * }</pre>
+ *
+ * which prints: <pre>   {@code
+ *
+ *   q: cute #puppies
+ *   f: images
+ * }</pre>
+ *
+ * In addition to composing URLs from their component parts and decomposing URLs into their
+ * component parts, this class implements relative URL resolution: what address you'd reach by
+ * clicking a relative link on a specified page. For example: <pre>   {@code
+ *
+ *   HttpUrl base = HttpUrl.parse("https://www.youtube.com/user/WatchTheDaily/videos");
+ *   HttpUrl link = base.resolve("../../watch?v=cbP2N1BQdYc");
+ *   System.out.println(link);
+ * }</pre>
+ *
+ * which prints: <pre>   {@code
+ *
+ *   https://www.youtube.com/watch?v=cbP2N1BQdYc
+ * }</pre>
+ *
+ * <h3>What's in a URL?</h3>
+ *
+ * A URL has several components.
+ *
+ * <h4>Scheme</h4>
+ * Sometimes referred to as <i>protocol</i>, A URL's scheme describes what mechanism should be used
+ * to retrieve the resource. Although URLs have many schemes ({@code mailto}, {@code file}, {@code
+ * ftp}), this class only supports {@code http} and {@code https}. Use {@link URI java.net.URI} for
+ * URLs with arbitrary schemes.
+ *
+ * <h4>Username and Password</h4>
+ * Username and password are either present, or the empty string {@code ""} if absent. This class
+ * offers no mechanism to differentiate empty from absent. Neither of these components are popular
+ * in practice. Typically HTTP applications use other mechanisms for user identification and
+ * authentication.
+ *
+ * <h4>Host</h4>
+ * The host identifies the webserver that serves the URL's resource. It is either a hostname like
+ * {@code square.com} or {@code localhost}, an IPv4 address like {@code 192.168.0.1}, or an IPv6
+ * address like {@code ::1}.
+ *
+ * <p>Usually a webserver is reachable with multiple identifiers: its IP addresses, registered
+ * domain names, and even {@code localhost} when connecting from the server itself. Each of a
+ * webserver's names is a distinct URL and they are not interchangeable. For example, even if
+ * {@code http://square.github.io/dagger} and {@code http://google.github.io/dagger} are served by
+ * the same IP address, the two URLs identify different resources.
+ *
+ * <h4>Port</h4>
+ * The port used to connect to the webserver. By default this is 80 for HTTP and 443 for HTTPS. This
+ * class never returns -1 for the port: if no port is explicitly specified in the URL then the
+ * scheme's default is used.
+ *
+ * <h4>Path</h4>
+ * The path identifies a specific resource on the host. Paths have a hierarchical structure like
+ * "/square/okhttp/issues/1486". Each path segment is prefixed with "/". This class offers methods
+ * to compose and decompose paths by segment. If a path's last segment is the empty string, then the
+ * path ends with "/". This class always builds non-empty paths: if the path is omitted it defaults
+ * to "/", which is a path whose only segment is the empty string.
+ *
+ * <h4>Query</h4>
+ * The query is optional: it can be null, empty, or non-empty. For many HTTP URLs the query string
+ * is subdivided into a collection of name-value parameters. This class offers methods to set the
+ * query as the single string, or as individual name-value parameters. With name-value parameters
+ * the values are optional and names may be repeated.
+ *
+ * <h4>Fragment</h4>
+ * The fragment is optional: it can be null, empty, or non-empty. Unlike host, port, path, and query
+ * the fragment is not sent to the webserver: it's private to the client.
+ *
+ * <h3>Encoding</h3>
+ * Each component must be encoded before it is embedded in the complete URL. As we saw above, the
+ * string {@code cute #puppies} is encoded as {@code cute%20%23puppies} when used as a query
+ * parameter value.
+ *
+ * <h4>Percent encoding</h4>
+ * Percent encoding replaces a character (like {@code \ud83c\udf69}) with its UTF-8 hex bytes (like
+ * {@code %F0%9F%8D%A9}). This approach works for whitespace characters, control characters,
+ * non-ASCII characters, and characters that already have another meaning in a particular context.
+ *
+ * <p>Percent encoding is used in every URL component except for the hostname. But the set of
+ * characters that need to be encoded is different for each component. For example, the path
+ * component must escape all of its {@code ?} characters, otherwise it could be interpreted as the
+ * start of the URL's query. But within the query and fragment components, the {@code ?} character
+ * doesn't delimit anything and doesn't need to be escaped. <pre>   {@code
+ *
+ *   HttpUrl url = HttpUrl.parse("http://who-let-the-dogs.out").newBuilder()
+ *       .addPathSegment("_Who?_")
+ *       .query("_Who?_")
+ *       .fragment("_Who?_")
+ *       .build();
+ *   System.out.println(url);
+ * }</pre>
+ *
+ * This prints: <pre>   {@code
+ *
+ *   http://who-let-the-dogs.out/_Who%3F_?_Who?_#_Who?_
+ * }</pre>
+ *
+ * When parsing URLs that lack percent encoding where it is required, this class will percent encode
+ * the offending characters.
+ *
+ * <h4>IDNA Mapping and Punycode encoding</h4>
+ * Hostnames have different requirements and use a different encoding scheme. It consists of IDNA
+ * mapping and Punycode encoding.
+ *
+ * <p>In order to avoid confusion and discourage phishing attacks,
+ * <a href="http://www.unicode.org/reports/tr46/#ToASCII">IDNA Mapping</a> transforms names to avoid
+ * confusing characters. This includes basic case folding: transforming shouting {@code SQUARE.COM}
+ * into cool and casual {@code square.com}. It also handles more exotic characters. For example, the
+ * Unicode trademark sign (™) could be confused for the letters "TM" in {@code http://ho™mail.com}.
+ * To mitigate this, the single character (™) maps to the string (tm). There is similar policy for
+ * all of the 1.1 million Unicode code points. Note that some code points such as "\ud83c\udf69" are
+ * not mapped and cannot be used in a hostname.
+ *
+ * <p><a href="http://ietf.org/rfc/rfc3492.txt">Punycode</a> converts a Unicode string to an ASCII
+ * string to make international domain names work everywhere. For example, "σ" encodes as
+ * "xn--4xa". The encoded string is not human readable, but can be used with classes like {@link
+ * InetAddress} to establish connections.
+ *
+ * <h3>Why another URL model?</h3>
+ * Java includes both {@link URL java.net.URL} and {@link URI java.net.URI}. We offer a new URL
+ * model to address problems that the others don't.
+ *
+ * <h4>Different URLs should be different</h4>
+ * Although they have different content, {@code java.net.URL} considers the following two URLs
+ * equal, and the {@link Object#equals equals()} method between them returns true:
+ * <ul>
+ *   <li>http://square.github.io/
+ *   <li>http://google.github.io/
+ * </ul>
+ * This is because those two hosts share the same IP address. This is an old, bad design decision
+ * that makes {@code java.net.URL} unusable for many things. It shouldn't be used as a {@link
+ * java.util.Map Map} key or in a {@link Set}. Doing so is both inefficient because equality may
+ * require a DNS lookup, and incorrect because unequal URLs may be equal because of how they are
+ * hosted.
+ *
+ * <h4>Equal URLs should be equal</h4>
+ * These two URLs are semantically identical, but {@code java.net.URI} disagrees:
+ * <ul>
+ *   <li>http://host:80/
+ *   <li>http://host
+ * </ul>
+ * Both the unnecessary port specification ({@code :80}) and the absent trailing slash ({@code /})
+ * cause URI to bucket the two URLs separately. This harms URI's usefulness in collections. Any
+ * application that stores information-per-URL will need to either canonicalize manually, or suffer
+ * unnecessary redundancy for such URLs.
+ *
+ * <p>Because they don't attempt canonical form, these classes are surprisingly difficult to use
+ * securely. Suppose you're building a webservice that checks that incoming paths are prefixed
+ * "/static/images/" before serving the corresponding assets from the filesystem. <pre>   {@code
+ *
+ *   String attack = "http://example.com/static/images/../../../../../etc/passwd";
+ *   System.out.println(new URL(attack).getPath());
+ *   System.out.println(new URI(attack).getPath());
+ *   System.out.println(HttpUrl.parse(attack).path());
+ * }</pre>
+ *
+ * By canonicalizing the input paths, they are complicit in directory traversal attacks. Code that
+ * checks only the path prefix may suffer!
+ * <pre>   {@code
+ *
+ *    /static/images/../../../../../etc/passwd
+ *    /static/images/../../../../../etc/passwd
+ *    /etc/passwd
+ * }</pre>
+ *
+ * <h4>If it works on the web, it should work in your application</h4>
+ * The {@code java.net.URI} class is strict around what URLs it accepts. It rejects URLs like
+ * "http://example.com/abc|def" because the '|' character is unsupported. This class is more
+ * forgiving: it will automatically percent-encode the '|', yielding "http://example.com/abc%7Cdef".
+ * This kind behavior is consistent with web browsers. {@code HttpUrl} prefers consistency with
+ * major web browsers over consistency with obsolete specifications.
+ *
+ * <h4>Paths and Queries should decompose</h4>
+ * Neither of the built-in URL models offer direct access to path segments or query parameters.
+ * Manually using {@code StringBuilder} to assemble these components is cumbersome: do '+'
+ * characters get silently replaced with spaces? If a query parameter contains a '&amp;', does that
+ * get escaped? By offering methods to read and write individual query parameters directly,
+ * application developers are saved from the hassles of encoding and decoding.
+ *
+ * <h4>Plus a modern API</h4>
+ * The URL (JDK1.0) and URI (Java 1.4) classes predate builders and instead use telescoping
+ * constructors. For example, there's no API to compose a URI with a custom port without also
+ * providing a query and fragment.
+ *
+ * <p>Instances of {@link HttpUrl} are well-formed and always have a scheme, host, and path. With
+ * {@code java.net.URL} it's possible to create an awkward URL like {@code http:/} with scheme and
+ * path but no hostname. Building APIs that consume such malformed values is difficult!
+ *
+ * <p>This class has a modern API. It avoids punitive checked exceptions: {@link #parse parse()}
+ * returns null if the input is an invalid URL. You can even be explicit about whether each
+ * component has been encoded already.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpUrl {
+  private static final char[] HEX_DIGITS =
+      { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+  static final String USERNAME_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#";
+  static final String PASSWORD_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#";
+  static final String PATH_SEGMENT_ENCODE_SET = " \"<>^`{}|/\\?#";
+  static final String PATH_SEGMENT_ENCODE_SET_URI = "[]";
+  // ANDROID-CHANGED: http://b/30405333 - we do not encode single quote as %27 in query strings.
+  // static final String QUERY_ENCODE_SET = " \"'<>#";
+  // static final String QUERY_COMPONENT_ENCODE_SET = " \"'<>#&=";
+  static final String QUERY_ENCODE_SET = " \"<>#";
+  static final String QUERY_COMPONENT_ENCODE_SET = " \"<>#&=";
+  // ANDROID-CHANGED end.
+  static final String QUERY_COMPONENT_ENCODE_SET_URI = "\\^`{|}";
+  static final String FORM_ENCODE_SET = " \"':;<=>@[]^`{}|/\\?#&!$(),~";
+  static final String FRAGMENT_ENCODE_SET = "";
+  static final String FRAGMENT_ENCODE_SET_URI = " \"#<>\\^`{|}";
+
+  /** Either "http" or "https". */
+  private final String scheme;
+
+  /** Decoded username. */
+  private final String username;
+
+  /** Decoded password. */
+  private final String password;
+
+  /** Canonical hostname. */
+  private final String host;
+
+  /** Either 80, 443 or a user-specified port. In range [1..65535]. */
+  private final int port;
+
+  /**
+   * A list of canonical path segments. This list always contains at least one element, which may
+   * be the empty string. Each segment is formatted with a leading '/', so if path segments were
+   * ["a", "b", ""], then the encoded path would be "/a/b/".
+   */
+  private final List<String> pathSegments;
+
+  /**
+   * Alternating, decoded query names and values, or null for no query. Names may be empty or
+   * non-empty, but never null. Values are null if the name has no corresponding '=' separator, or
+   * empty, or non-empty.
+   */
+  private final List<String> queryNamesAndValues;
+
+  /** Decoded fragment. */
+  private final String fragment;
+
+  /** Canonical URL. */
+  private final String url;
+
+  private HttpUrl(Builder builder) {
+    this.scheme = builder.scheme;
+    this.username = percentDecode(builder.encodedUsername, false);
+    this.password = percentDecode(builder.encodedPassword, false);
+    this.host = builder.host;
+    this.port = builder.effectivePort();
+    this.pathSegments = percentDecode(builder.encodedPathSegments, false);
+    this.queryNamesAndValues = builder.encodedQueryNamesAndValues != null
+        ? percentDecode(builder.encodedQueryNamesAndValues, true)
+        : null;
+    this.fragment = builder.encodedFragment != null
+        ? percentDecode(builder.encodedFragment, false)
+        : null;
+    this.url = builder.toString();
+  }
+
+  /** Returns this URL as a {@link URL java.net.URL}. */
+  public URL url() {
+    try {
+      return new URL(url);
+    } catch (MalformedURLException e) {
+      throw new RuntimeException(e); // Unexpected!
+    }
+  }
+
+  /**
+   * Returns this URL as a {@link URI java.net.URI}. Because {@code URI} is more strict than this
+   * class, the returned URI may be semantically different from this URL:
+   * <ul>
+   *   <li>Characters forbidden by URI like {@code [} and {@code |} will be escaped.
+   *   <li>Invalid percent-encoded sequences like {@code %xx} will be encoded like {@code %25xx}.
+   *   <li>Whitespace and control characters in the fragment will be stripped.
+   * </ul>
+   *
+   * <p>These differences may have a significant consequence when the URI is interpretted by a
+   * webserver. For this reason the {@linkplain URI URI class} and this method should be avoided.
+   */
+  public URI uri() {
+    String uri = newBuilder().reencodeForUri().toString();
+    try {
+      return new URI(uri);
+    } catch (URISyntaxException e) {
+      // Unlikely edge case: the URI has a forbidden character in the fragment. Strip it & retry.
+      try {
+        String stripped = uri.replaceAll("[\\u0000-\\u001F\\u007F-\\u009F\\p{javaWhitespace}]", "");
+        return URI.create(stripped);
+      } catch (Exception e1) {
+        throw new RuntimeException(e); // Unexpected!
+      }
+    }
+  }
+
+  /** Returns either "http" or "https". */
+  public String scheme() {
+    return scheme;
+  }
+
+  public boolean isHttps() {
+    return scheme.equals("https");
+  }
+
+  /** Returns the username, or an empty string if none is set. */
+  public String encodedUsername() {
+    if (username.isEmpty()) return "";
+    int usernameStart = scheme.length() + 3; // "://".length() == 3.
+    int usernameEnd = delimiterOffset(url, usernameStart, url.length(), ":@");
+    return url.substring(usernameStart, usernameEnd);
+  }
+
+  public String username() {
+    return username;
+  }
+
+  /** Returns the password, or an empty string if none is set. */
+  public String encodedPassword() {
+    if (password.isEmpty()) return "";
+    int passwordStart = url.indexOf(':', scheme.length() + 3) + 1;
+    int passwordEnd = url.indexOf('@');
+    return url.substring(passwordStart, passwordEnd);
+  }
+
+  /** Returns the decoded password, or an empty string if none is present. */
+  public String password() {
+    return password;
+  }
+
+  /**
+   * Returns the host address suitable for use with {@link InetAddress#getAllByName(String)}. May
+   * be:
+   * <ul>
+   *   <li>A regular host name, like {@code android.com}.
+   *   <li>An IPv4 address, like {@code 127.0.0.1}.
+   *   <li>An IPv6 address, like {@code ::1}. Note that there are no square braces.
+   *   <li>An encoded IDN, like {@code xn--n3h.net}.
+   * </ul>
+   */
+  public String host() {
+    return host;
+  }
+
+  /**
+   * Returns the explicitly-specified port if one was provided, or the default port for this URL's
+   * scheme. For example, this returns 8443 for {@code https://square.com:8443/} and 443 for {@code
+   * https://square.com/}. The result is in {@code [1..65535]}.
+   */
+  public int port() {
+    return port;
+  }
+
+  /**
+   * Returns 80 if {@code scheme.equals("http")}, 443 if {@code scheme.equals("https")} and -1
+   * otherwise.
+   */
+  public static int defaultPort(String scheme) {
+    if (scheme.equals("http")) {
+      return 80;
+    } else if (scheme.equals("https")) {
+      return 443;
+    } else {
+      return -1;
+    }
+  }
+
+  public int pathSize() {
+    return pathSegments.size();
+  }
+
+  /**
+   * Returns the entire path of this URL, encoded for use in HTTP resource resolution. The
+   * returned path is always nonempty and is prefixed with {@code /}.
+   */
+  public String encodedPath() {
+    int pathStart = url.indexOf('/', scheme.length() + 3); // "://".length() == 3.
+    int pathEnd = delimiterOffset(url, pathStart, url.length(), "?#");
+    return url.substring(pathStart, pathEnd);
+  }
+
+  static void pathSegmentsToString(StringBuilder out, List<String> pathSegments) {
+    for (int i = 0, size = pathSegments.size(); i < size; i++) {
+      out.append('/');
+      out.append(pathSegments.get(i));
+    }
+  }
+
+  public List<String> encodedPathSegments() {
+    int pathStart = url.indexOf('/', scheme.length() + 3);
+    int pathEnd = delimiterOffset(url, pathStart, url.length(), "?#");
+    List<String> result = new ArrayList<>();
+    for (int i = pathStart; i < pathEnd; ) {
+      i++; // Skip the '/'.
+      int segmentEnd = delimiterOffset(url, i, pathEnd, "/");
+      result.add(url.substring(i, segmentEnd));
+      i = segmentEnd;
+    }
+    return result;
+  }
+
+  public List<String> pathSegments() {
+    return pathSegments;
+  }
+
+  /**
+   * Returns the query of this URL, encoded for use in HTTP resource resolution. The returned string
+   * may be null (for URLs with no query), empty (for URLs with an empty query) or non-empty (all
+   * other URLs).
+   */
+  public String encodedQuery() {
+    if (queryNamesAndValues == null) return null; // No query.
+    int queryStart = url.indexOf('?') + 1;
+    int queryEnd = delimiterOffset(url, queryStart + 1, url.length(), "#");
+    return url.substring(queryStart, queryEnd);
+  }
+
+  static void namesAndValuesToQueryString(StringBuilder out, List<String> namesAndValues) {
+    for (int i = 0, size = namesAndValues.size(); i < size; i += 2) {
+      String name = namesAndValues.get(i);
+      String value = namesAndValues.get(i + 1);
+      if (i > 0) out.append('&');
+      out.append(name);
+      if (value != null) {
+        out.append('=');
+        out.append(value);
+      }
+    }
+  }
+
+  /**
+   * Cuts {@code encodedQuery} up into alternating parameter names and values. This divides a
+   * query string like {@code subject=math&easy&problem=5-2=3} into the list {@code ["subject",
+   * "math", "easy", null, "problem", "5-2=3"]}. Note that values may be null and may contain
+   * '=' characters.
+   */
+  static List<String> queryStringToNamesAndValues(String encodedQuery) {
+    List<String> result = new ArrayList<>();
+    for (int pos = 0; pos <= encodedQuery.length(); ) {
+      int ampersandOffset = encodedQuery.indexOf('&', pos);
+      if (ampersandOffset == -1) ampersandOffset = encodedQuery.length();
+
+      int equalsOffset = encodedQuery.indexOf('=', pos);
+      if (equalsOffset == -1 || equalsOffset > ampersandOffset) {
+        result.add(encodedQuery.substring(pos, ampersandOffset));
+        result.add(null); // No value for this name.
+      } else {
+        result.add(encodedQuery.substring(pos, equalsOffset));
+        result.add(encodedQuery.substring(equalsOffset + 1, ampersandOffset));
+      }
+      pos = ampersandOffset + 1;
+    }
+    return result;
+  }
+
+  public String query() {
+    if (queryNamesAndValues == null) return null; // No query.
+    StringBuilder result = new StringBuilder();
+    namesAndValuesToQueryString(result, queryNamesAndValues);
+    return result.toString();
+  }
+
+  public int querySize() {
+    return queryNamesAndValues != null ? queryNamesAndValues.size() / 2 : 0;
+  }
+
+  /**
+   * Returns the first query parameter named {@code name} decoded using UTF-8, or null if there is
+   * no such query parameter.
+   */
+  public String queryParameter(String name) {
+    if (queryNamesAndValues == null) return null;
+    for (int i = 0, size = queryNamesAndValues.size(); i < size; i += 2) {
+      if (name.equals(queryNamesAndValues.get(i))) {
+        return queryNamesAndValues.get(i + 1);
+      }
+    }
+    return null;
+  }
+
+  public Set<String> queryParameterNames() {
+    if (queryNamesAndValues == null) return Collections.emptySet();
+    Set<String> result = new LinkedHashSet<>();
+    for (int i = 0, size = queryNamesAndValues.size(); i < size; i += 2) {
+      result.add(queryNamesAndValues.get(i));
+    }
+    return Collections.unmodifiableSet(result);
+  }
+
+  public List<String> queryParameterValues(String name) {
+    if (queryNamesAndValues == null) return Collections.emptyList();
+    List<String> result = new ArrayList<>();
+    for (int i = 0, size = queryNamesAndValues.size(); i < size; i += 2) {
+      if (name.equals(queryNamesAndValues.get(i))) {
+        result.add(queryNamesAndValues.get(i + 1));
+      }
+    }
+    return Collections.unmodifiableList(result);
+  }
+
+  public String queryParameterName(int index) {
+    return queryNamesAndValues.get(index * 2);
+  }
+
+  public String queryParameterValue(int index) {
+    return queryNamesAndValues.get(index * 2 + 1);
+  }
+
+  public String encodedFragment() {
+    if (fragment == null) return null;
+    int fragmentStart = url.indexOf('#') + 1;
+    return url.substring(fragmentStart);
+  }
+
+  public String fragment() {
+    return fragment;
+  }
+
+  /** Returns the URL that would be retrieved by following {@code link} from this URL. */
+  public HttpUrl resolve(String link) {
+    Builder builder = new Builder();
+    Builder.ParseResult result = builder.parse(this, link);
+    return result == Builder.ParseResult.SUCCESS ? builder.build() : null;
+  }
+
+  public Builder newBuilder() {
+    Builder result = new Builder();
+    result.scheme = scheme;
+    result.encodedUsername = encodedUsername();
+    result.encodedPassword = encodedPassword();
+    result.host = host;
+    // If we're set to a default port, unset it in case of a scheme change.
+    result.port = port != defaultPort(scheme) ? port : -1;
+    result.encodedPathSegments.clear();
+    result.encodedPathSegments.addAll(encodedPathSegments());
+    result.encodedQuery(encodedQuery());
+    result.encodedFragment = encodedFragment();
+    return result;
+  }
+
+  /**
+   * Returns a new {@code HttpUrl} representing {@code url} if it is a well-formed HTTP or HTTPS
+   * URL, or null if it isn't.
+   */
+  public static HttpUrl parse(String url) {
+    Builder builder = new Builder();
+    Builder.ParseResult result = builder.parse(null, url);
+    return result == Builder.ParseResult.SUCCESS ? builder.build() : null;
+  }
+
+  /**
+   * Returns an {@link HttpUrl} for {@code url} if its protocol is {@code http} or {@code https}, or
+   * null if it has any other protocol.
+   */
+  public static HttpUrl get(URL url) {
+    return parse(url.toString());
+  }
+
+  /**
+   * Returns a new {@code HttpUrl} representing {@code url} if it is a well-formed HTTP or HTTPS
+   * URL, or throws an exception if it isn't.
+   *
+   * @throws MalformedURLException if there was a non-host related URL issue
+   * @throws UnknownHostException if the host was invalid
+   */
+  static HttpUrl getChecked(String url) throws MalformedURLException, UnknownHostException {
+    Builder builder = new Builder();
+    Builder.ParseResult result = builder.parse(null, url);
+    switch (result) {
+      case SUCCESS:
+        return builder.build();
+      case INVALID_HOST:
+        throw new UnknownHostException("Invalid host: " + url);
+      case UNSUPPORTED_SCHEME:
+      case MISSING_SCHEME:
+      case INVALID_PORT:
+      default:
+        throw new MalformedURLException("Invalid URL: " + result + " for " + url);
+    }
+  }
+
+  public static HttpUrl get(URI uri) {
+    return parse(uri.toString());
+  }
+
+  @Override public boolean equals(Object o) {
+    return o instanceof HttpUrl && ((HttpUrl) o).url.equals(url);
+  }
+
+  @Override public int hashCode() {
+    return url.hashCode();
+  }
+
+  @Override public String toString() {
+    return url;
+  }
+
+  /**
+   * @hide This class is not part of the Android public SDK API
+   */
+  public static final class Builder {
+    String scheme;
+    String encodedUsername = "";
+    String encodedPassword = "";
+    String host;
+    int port = -1;
+    final List<String> encodedPathSegments = new ArrayList<>();
+    List<String> encodedQueryNamesAndValues;
+    String encodedFragment;
+
+    public Builder() {
+      encodedPathSegments.add(""); // The default path is '/' which needs a trailing space.
+    }
+
+    public Builder scheme(String scheme) {
+      if (scheme == null) {
+        throw new IllegalArgumentException("scheme == null");
+      } else if (scheme.equalsIgnoreCase("http")) {
+        this.scheme = "http";
+      } else if (scheme.equalsIgnoreCase("https")) {
+        this.scheme = "https";
+      } else {
+        throw new IllegalArgumentException("unexpected scheme: " + scheme);
+      }
+      return this;
+    }
+
+    public Builder username(String username) {
+      if (username == null) throw new IllegalArgumentException("username == null");
+      this.encodedUsername = canonicalize(username, USERNAME_ENCODE_SET, false, false, false, true);
+      return this;
+    }
+
+    public Builder encodedUsername(String encodedUsername) {
+      if (encodedUsername == null) throw new IllegalArgumentException("encodedUsername == null");
+      this.encodedUsername = canonicalize(
+          encodedUsername, USERNAME_ENCODE_SET, true, false, false, true);
+      return this;
+    }
+
+    public Builder password(String password) {
+      if (password == null) throw new IllegalArgumentException("password == null");
+      this.encodedPassword = canonicalize(password, PASSWORD_ENCODE_SET, false, false, false, true);
+      return this;
+    }
+
+    public Builder encodedPassword(String encodedPassword) {
+      if (encodedPassword == null) throw new IllegalArgumentException("encodedPassword == null");
+      this.encodedPassword = canonicalize(
+          encodedPassword, PASSWORD_ENCODE_SET, true, false, false, true);
+      return this;
+    }
+
+    /**
+     * @param host either a regular hostname, International Domain Name, IPv4 address, or IPv6
+     *     address.
+     */
+    public Builder host(String host) {
+      if (host == null) throw new IllegalArgumentException("host == null");
+      String encoded = canonicalizeHost(host, 0, host.length());
+      if (encoded == null) throw new IllegalArgumentException("unexpected host: " + host);
+      this.host = encoded;
+      return this;
+    }
+
+    public Builder port(int port) {
+      if (port <= 0 || port > 65535) throw new IllegalArgumentException("unexpected port: " + port);
+      this.port = port;
+      return this;
+    }
+
+    int effectivePort() {
+      return port != -1 ? port : defaultPort(scheme);
+    }
+
+    public Builder addPathSegment(String pathSegment) {
+      if (pathSegment == null) throw new IllegalArgumentException("pathSegment == null");
+      push(pathSegment, 0, pathSegment.length(), false, false);
+      return this;
+    }
+
+    public Builder addEncodedPathSegment(String encodedPathSegment) {
+      if (encodedPathSegment == null) {
+        throw new IllegalArgumentException("encodedPathSegment == null");
+      }
+      push(encodedPathSegment, 0, encodedPathSegment.length(), false, true);
+      return this;
+    }
+
+    public Builder setPathSegment(int index, String pathSegment) {
+      if (pathSegment == null) throw new IllegalArgumentException("pathSegment == null");
+      String canonicalPathSegment = canonicalize(
+          pathSegment, 0, pathSegment.length(), PATH_SEGMENT_ENCODE_SET, false, false, false, true);
+      if (isDot(canonicalPathSegment) || isDotDot(canonicalPathSegment)) {
+        throw new IllegalArgumentException("unexpected path segment: " + pathSegment);
+      }
+      encodedPathSegments.set(index, canonicalPathSegment);
+      return this;
+    }
+
+    public Builder setEncodedPathSegment(int index, String encodedPathSegment) {
+      if (encodedPathSegment == null) {
+        throw new IllegalArgumentException("encodedPathSegment == null");
+      }
+      String canonicalPathSegment = canonicalize(encodedPathSegment,
+          0, encodedPathSegment.length(), PATH_SEGMENT_ENCODE_SET, true, false, false, true);
+      encodedPathSegments.set(index, canonicalPathSegment);
+      if (isDot(canonicalPathSegment) || isDotDot(canonicalPathSegment)) {
+        throw new IllegalArgumentException("unexpected path segment: " + encodedPathSegment);
+      }
+      return this;
+    }
+
+    public Builder removePathSegment(int index) {
+      encodedPathSegments.remove(index);
+      if (encodedPathSegments.isEmpty()) {
+        encodedPathSegments.add(""); // Always leave at least one '/'.
+      }
+      return this;
+    }
+
+    public Builder encodedPath(String encodedPath) {
+      if (encodedPath == null) throw new IllegalArgumentException("encodedPath == null");
+      if (!encodedPath.startsWith("/")) {
+        throw new IllegalArgumentException("unexpected encodedPath: " + encodedPath);
+      }
+      resolvePath(encodedPath, 0, encodedPath.length());
+      return this;
+    }
+
+    public Builder query(String query) {
+      this.encodedQueryNamesAndValues = query != null
+          ? queryStringToNamesAndValues(canonicalize(
+              query, QUERY_ENCODE_SET, false, false, true, true))
+          : null;
+      return this;
+    }
+
+    public Builder encodedQuery(String encodedQuery) {
+      this.encodedQueryNamesAndValues = encodedQuery != null
+          ? queryStringToNamesAndValues(
+              canonicalize(encodedQuery, QUERY_ENCODE_SET, true, false, true, true))
+          : null;
+      return this;
+    }
+
+    /** Encodes the query parameter using UTF-8 and adds it to this URL's query string. */
+    public Builder addQueryParameter(String name, String value) {
+      if (name == null) throw new IllegalArgumentException("name == null");
+      if (encodedQueryNamesAndValues == null) encodedQueryNamesAndValues = new ArrayList<>();
+      encodedQueryNamesAndValues.add(
+          canonicalize(name, QUERY_COMPONENT_ENCODE_SET, false, false, true, true));
+      encodedQueryNamesAndValues.add(value != null
+          ? canonicalize(value, QUERY_COMPONENT_ENCODE_SET, false, false, true, true)
+          : null);
+      return this;
+    }
+
+    /** Adds the pre-encoded query parameter to this URL's query string. */
+    public Builder addEncodedQueryParameter(String encodedName, String encodedValue) {
+      if (encodedName == null) throw new IllegalArgumentException("encodedName == null");
+      if (encodedQueryNamesAndValues == null) encodedQueryNamesAndValues = new ArrayList<>();
+      encodedQueryNamesAndValues.add(
+          canonicalize(encodedName, QUERY_COMPONENT_ENCODE_SET, true, false, true, true));
+      encodedQueryNamesAndValues.add(encodedValue != null
+          ? canonicalize(encodedValue, QUERY_COMPONENT_ENCODE_SET, true, false, true, true)
+          : null);
+      return this;
+    }
+
+    public Builder setQueryParameter(String name, String value) {
+      removeAllQueryParameters(name);
+      addQueryParameter(name, value);
+      return this;
+    }
+
+    public Builder setEncodedQueryParameter(String encodedName, String encodedValue) {
+      removeAllEncodedQueryParameters(encodedName);
+      addEncodedQueryParameter(encodedName, encodedValue);
+      return this;
+    }
+
+    public Builder removeAllQueryParameters(String name) {
+      if (name == null) throw new IllegalArgumentException("name == null");
+      if (encodedQueryNamesAndValues == null) return this;
+      String nameToRemove = canonicalize(
+          name, QUERY_COMPONENT_ENCODE_SET, false, false, true, true);
+      removeAllCanonicalQueryParameters(nameToRemove);
+      return this;
+    }
+
+    public Builder removeAllEncodedQueryParameters(String encodedName) {
+      if (encodedName == null) throw new IllegalArgumentException("encodedName == null");
+      if (encodedQueryNamesAndValues == null) return this;
+      removeAllCanonicalQueryParameters(
+          canonicalize(encodedName, QUERY_COMPONENT_ENCODE_SET, true, false, true, true));
+      return this;
+    }
+
+    private void removeAllCanonicalQueryParameters(String canonicalName) {
+      for (int i = encodedQueryNamesAndValues.size() - 2; i >= 0; i -= 2) {
+        if (canonicalName.equals(encodedQueryNamesAndValues.get(i))) {
+          encodedQueryNamesAndValues.remove(i + 1);
+          encodedQueryNamesAndValues.remove(i);
+          if (encodedQueryNamesAndValues.isEmpty()) {
+            encodedQueryNamesAndValues = null;
+            return;
+          }
+        }
+      }
+    }
+
+    public Builder fragment(String fragment) {
+      this.encodedFragment = fragment != null
+          ? canonicalize(fragment, FRAGMENT_ENCODE_SET, false, false, false, false)
+          : null;
+      return this;
+    }
+
+    public Builder encodedFragment(String encodedFragment) {
+      this.encodedFragment = encodedFragment != null
+          ? canonicalize(encodedFragment, FRAGMENT_ENCODE_SET, true, false, false, false)
+          : null;
+      return this;
+    }
+
+    /**
+     * Re-encodes the components of this URL so that it satisfies (obsolete) RFC 2396, which is
+     * particularly strict for certain components.
+     */
+    Builder reencodeForUri() {
+      for (int i = 0, size = encodedPathSegments.size(); i < size; i++) {
+        String pathSegment = encodedPathSegments.get(i);
+        encodedPathSegments.set(i,
+            canonicalize(pathSegment, PATH_SEGMENT_ENCODE_SET_URI, true, true, false, true));
+      }
+      if (encodedQueryNamesAndValues != null) {
+        for (int i = 0, size = encodedQueryNamesAndValues.size(); i < size; i++) {
+          String component = encodedQueryNamesAndValues.get(i);
+          if (component != null) {
+            encodedQueryNamesAndValues.set(i,
+                canonicalize(component, QUERY_COMPONENT_ENCODE_SET_URI, true, true, true, true));
+          }
+        }
+      }
+      if (encodedFragment != null) {
+        encodedFragment = canonicalize(
+            encodedFragment, FRAGMENT_ENCODE_SET_URI, true, true, false, false);
+      }
+      return this;
+    }
+
+    public HttpUrl build() {
+      if (scheme == null) throw new IllegalStateException("scheme == null");
+      if (host == null) throw new IllegalStateException("host == null");
+      return new HttpUrl(this);
+    }
+
+    @Override public String toString() {
+      StringBuilder result = new StringBuilder();
+      result.append(scheme);
+      result.append("://");
+
+      if (!encodedUsername.isEmpty() || !encodedPassword.isEmpty()) {
+        result.append(encodedUsername);
+        if (!encodedPassword.isEmpty()) {
+          result.append(':');
+          result.append(encodedPassword);
+        }
+        result.append('@');
+      }
+
+      if (host.indexOf(':') != -1) {
+        // Host is an IPv6 address.
+        result.append('[');
+        result.append(host);
+        result.append(']');
+      } else {
+        result.append(host);
+      }
+
+      int effectivePort = effectivePort();
+      if (effectivePort != defaultPort(scheme)) {
+        result.append(':');
+        result.append(effectivePort);
+      }
+
+      pathSegmentsToString(result, encodedPathSegments);
+
+      if (encodedQueryNamesAndValues != null) {
+        result.append('?');
+        namesAndValuesToQueryString(result, encodedQueryNamesAndValues);
+      }
+
+      if (encodedFragment != null) {
+        result.append('#');
+        result.append(encodedFragment);
+      }
+
+      return result.toString();
+    }
+
+    enum ParseResult {
+      SUCCESS,
+      MISSING_SCHEME,
+      UNSUPPORTED_SCHEME,
+      INVALID_PORT,
+      INVALID_HOST,
+    }
+
+    ParseResult parse(HttpUrl base, String input) {
+      int pos = skipLeadingAsciiWhitespace(input, 0, input.length());
+      int limit = skipTrailingAsciiWhitespace(input, pos, input.length());
+
+      // Scheme.
+      int schemeDelimiterOffset = schemeDelimiterOffset(input, pos, limit);
+      if (schemeDelimiterOffset != -1) {
+        if (input.regionMatches(true, pos, "https:", 0, 6)) {
+          this.scheme = "https";
+          pos += "https:".length();
+        } else if (input.regionMatches(true, pos, "http:", 0, 5)) {
+          this.scheme = "http";
+          pos += "http:".length();
+        } else {
+          return ParseResult.UNSUPPORTED_SCHEME; // Not an HTTP scheme.
+        }
+      } else if (base != null) {
+        this.scheme = base.scheme;
+      } else {
+        return ParseResult.MISSING_SCHEME; // No scheme.
+      }
+
+      // Authority.
+      boolean hasUsername = false;
+      boolean hasPassword = false;
+      int slashCount = slashCount(input, pos, limit);
+      if (slashCount >= 2 || base == null || !base.scheme.equals(this.scheme)) {
+        // Read an authority if either:
+        //  * The input starts with 2 or more slashes. These follow the scheme if it exists.
+        //  * The input scheme exists and is different from the base URL's scheme.
+        //
+        // The structure of an authority is:
+        //   username:password@host:port
+        //
+        // Username, password and port are optional.
+        //   [username[:password]@]host[:port]
+        pos += slashCount;
+        authority:
+        while (true) {
+          int componentDelimiterOffset = delimiterOffset(input, pos, limit, "@/\\?#");
+          int c = componentDelimiterOffset != limit
+              ? input.charAt(componentDelimiterOffset)
+              : -1;
+          switch (c) {
+            case '@':
+              // User info precedes.
+              if (!hasPassword) {
+                int passwordColonOffset = delimiterOffset(
+                    input, pos, componentDelimiterOffset, ":");
+                String canonicalUsername = canonicalize(
+                    input, pos, passwordColonOffset, USERNAME_ENCODE_SET, true, false, false, true);
+                this.encodedUsername = hasUsername
+                    ? this.encodedUsername + "%40" + canonicalUsername
+                    : canonicalUsername;
+                if (passwordColonOffset != componentDelimiterOffset) {
+                  hasPassword = true;
+                  this.encodedPassword = canonicalize(input, passwordColonOffset + 1,
+                      componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true);
+                }
+                hasUsername = true;
+              } else {
+                this.encodedPassword = this.encodedPassword + "%40" + canonicalize(input, pos,
+                    componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true);
+              }
+              pos = componentDelimiterOffset + 1;
+              break;
+
+            case -1:
+            case '/':
+            case '\\':
+            case '?':
+            case '#':
+              // Host info precedes.
+              int portColonOffset = portColonOffset(input, pos, componentDelimiterOffset);
+              if (portColonOffset + 1 < componentDelimiterOffset) {
+                this.host = canonicalizeHost(input, pos, portColonOffset);
+                this.port = parsePort(input, portColonOffset + 1, componentDelimiterOffset);
+                if (this.port == -1) return ParseResult.INVALID_PORT; // Invalid port.
+              } else {
+                this.host = canonicalizeHost(input, pos, portColonOffset);
+                this.port = defaultPort(this.scheme);
+              }
+              if (this.host == null) return ParseResult.INVALID_HOST; // Invalid host.
+              pos = componentDelimiterOffset;
+              break authority;
+          }
+        }
+      } else {
+        // This is a relative link. Copy over all authority components. Also maybe the path & query.
+        this.encodedUsername = base.encodedUsername();
+        this.encodedPassword = base.encodedPassword();
+        this.host = base.host;
+        this.port = base.port;
+        this.encodedPathSegments.clear();
+        this.encodedPathSegments.addAll(base.encodedPathSegments());
+        if (pos == limit || input.charAt(pos) == '#') {
+          encodedQuery(base.encodedQuery());
+        }
+      }
+
+      // Resolve the relative path.
+      int pathDelimiterOffset = delimiterOffset(input, pos, limit, "?#");
+      resolvePath(input, pos, pathDelimiterOffset);
+      pos = pathDelimiterOffset;
+
+      // Query.
+      if (pos < limit && input.charAt(pos) == '?') {
+        int queryDelimiterOffset = delimiterOffset(input, pos, limit, "#");
+        this.encodedQueryNamesAndValues = queryStringToNamesAndValues(canonicalize(
+            input, pos + 1, queryDelimiterOffset, QUERY_ENCODE_SET, true, false, true, true));
+        pos = queryDelimiterOffset;
+      }
+
+      // Fragment.
+      if (pos < limit && input.charAt(pos) == '#') {
+        this.encodedFragment = canonicalize(
+            input, pos + 1, limit, FRAGMENT_ENCODE_SET, true, false, false, false);
+      }
+
+      return ParseResult.SUCCESS;
+    }
+
+    private void resolvePath(String input, int pos, int limit) {
+      // Read a delimiter.
+      if (pos == limit) {
+        // Empty path: keep the base path as-is.
+        return;
+      }
+      char c = input.charAt(pos);
+      if (c == '/' || c == '\\') {
+        // Absolute path: reset to the default "/".
+        encodedPathSegments.clear();
+        encodedPathSegments.add("");
+        pos++;
+      } else {
+        // Relative path: clear everything after the last '/'.
+        encodedPathSegments.set(encodedPathSegments.size() - 1, "");
+      }
+
+      // Read path segments.
+      for (int i = pos; i < limit; ) {
+        int pathSegmentDelimiterOffset = delimiterOffset(input, i, limit, "/\\");
+        boolean segmentHasTrailingSlash = pathSegmentDelimiterOffset < limit;
+        push(input, i, pathSegmentDelimiterOffset, segmentHasTrailingSlash, true);
+        i = pathSegmentDelimiterOffset;
+        if (segmentHasTrailingSlash) i++;
+      }
+    }
+
+    /** Adds a path segment. If the input is ".." or equivalent, this pops a path segment. */
+    private void push(String input, int pos, int limit, boolean addTrailingSlash,
+        boolean alreadyEncoded) {
+      String segment = canonicalize(
+          input, pos, limit, PATH_SEGMENT_ENCODE_SET, alreadyEncoded, false, false, true);
+      if (isDot(segment)) {
+        return; // Skip '.' path segments.
+      }
+      if (isDotDot(segment)) {
+        pop();
+        return;
+      }
+      if (encodedPathSegments.get(encodedPathSegments.size() - 1).isEmpty()) {
+        encodedPathSegments.set(encodedPathSegments.size() - 1, segment);
+      } else {
+        encodedPathSegments.add(segment);
+      }
+      if (addTrailingSlash) {
+        encodedPathSegments.add("");
+      }
+    }
+
+    private boolean isDot(String input) {
+      return input.equals(".") || input.equalsIgnoreCase("%2e");
+    }
+
+    private boolean isDotDot(String input) {
+      return input.equals("..")
+          || input.equalsIgnoreCase("%2e.")
+          || input.equalsIgnoreCase(".%2e")
+          || input.equalsIgnoreCase("%2e%2e");
+    }
+
+    /**
+     * Removes a path segment. When this method returns the last segment is always "", which means
+     * the encoded path will have a trailing '/'.
+     *
+     * <p>Popping "/a/b/c/" yields "/a/b/". In this case the list of path segments goes from
+     * ["a", "b", "c", ""] to ["a", "b", ""].
+     *
+     * <p>Popping "/a/b/c" also yields "/a/b/". The list of path segments goes from ["a", "b", "c"]
+     * to ["a", "b", ""].
+     */
+    private void pop() {
+      String removed = encodedPathSegments.remove(encodedPathSegments.size() - 1);
+
+      // Make sure the path ends with a '/' by either adding an empty string or clearing a segment.
+      if (removed.isEmpty() && !encodedPathSegments.isEmpty()) {
+        encodedPathSegments.set(encodedPathSegments.size() - 1, "");
+      } else {
+        encodedPathSegments.add("");
+      }
+    }
+
+    /**
+     * Increments {@code pos} until {@code input[pos]} is not ASCII whitespace. Stops at {@code
+     * limit}.
+     */
+    private int skipLeadingAsciiWhitespace(String input, int pos, int limit) {
+      for (int i = pos; i < limit; i++) {
+        switch (input.charAt(i)) {
+          case '\t':
+          case '\n':
+          case '\f':
+          case '\r':
+          case ' ':
+            continue;
+          default:
+            return i;
+        }
+      }
+      return limit;
+    }
+
+    /**
+     * Decrements {@code limit} until {@code input[limit - 1]} is not ASCII whitespace. Stops at
+     * {@code pos}.
+     */
+    private int skipTrailingAsciiWhitespace(String input, int pos, int limit) {
+      for (int i = limit - 1; i >= pos; i--) {
+        switch (input.charAt(i)) {
+          case '\t':
+          case '\n':
+          case '\f':
+          case '\r':
+          case ' ':
+            continue;
+          default:
+            return i + 1;
+        }
+      }
+      return pos;
+    }
+
+    /**
+     * Returns the index of the ':' in {@code input} that is after scheme characters. Returns -1 if
+     * {@code input} does not have a scheme that starts at {@code pos}.
+     */
+    private static int schemeDelimiterOffset(String input, int pos, int limit) {
+      if (limit - pos < 2) return -1;
+
+      char c0 = input.charAt(pos);
+      if ((c0 < 'a' || c0 > 'z') && (c0 < 'A' || c0 > 'Z')) return -1; // Not a scheme start char.
+
+      for (int i = pos + 1; i < limit; i++) {
+        char c = input.charAt(i);
+
+        if ((c >= 'a' && c <= 'z')
+            || (c >= 'A' && c <= 'Z')
+            || (c >= '0' && c <= '9')
+            || c == '+'
+            || c == '-'
+            || c == '.') {
+          continue; // Scheme character. Keep going.
+        } else if (c == ':') {
+          return i; // Scheme prefix!
+        } else {
+          return -1; // Non-scheme character before the first ':'.
+        }
+      }
+
+      return -1; // No ':'; doesn't start with a scheme.
+    }
+
+    /** Returns the number of '/' and '\' slashes in {@code input}, starting at {@code pos}. */
+    private static int slashCount(String input, int pos, int limit) {
+      int slashCount = 0;
+      while (pos < limit) {
+        char c = input.charAt(pos);
+        if (c == '\\' || c == '/') {
+          slashCount++;
+          pos++;
+        } else {
+          break;
+        }
+      }
+      return slashCount;
+    }
+
+    /** Finds the first ':' in {@code input}, skipping characters between square braces "[...]". */
+    private static int portColonOffset(String input, int pos, int limit) {
+      for (int i = pos; i < limit; i++) {
+        switch (input.charAt(i)) {
+          case '[':
+            while (++i < limit) {
+              if (input.charAt(i) == ']') break;
+            }
+            break;
+          case ':':
+            return i;
+        }
+      }
+      return limit; // No colon.
+    }
+
+    private static String canonicalizeHost(String input, int pos, int limit) {
+      // Start by percent decoding the host. The WHATWG spec suggests doing this only after we've
+      // checked for IPv6 square braces. But Chrome does it first, and that's more lenient.
+      String percentDecoded = percentDecode(input, pos, limit, false);
+
+      // If the input contains a :, it’s an IPv6 address.
+      if (percentDecoded.contains(":")) {
+        // If the input is encased in square braces "[...]", drop 'em.
+        InetAddress inetAddress = percentDecoded.startsWith("[") && percentDecoded.endsWith("]")
+            ? decodeIpv6(percentDecoded, 1, percentDecoded.length() - 1)
+            : decodeIpv6(percentDecoded, 0, percentDecoded.length());
+        if (inetAddress == null) return null;
+        byte[] address = inetAddress.getAddress();
+        if (address.length == 16) return inet6AddressToAscii(address);
+        throw new AssertionError();
+      }
+
+      return domainToAscii(percentDecoded);
+    }
+
+    /** Decodes an IPv6 address like 1111:2222:3333:4444:5555:6666:7777:8888 or ::1. */
+    private static InetAddress decodeIpv6(String input, int pos, int limit) {
+      byte[] address = new byte[16];
+      int b = 0;
+      int compress = -1;
+      int groupOffset = -1;
+
+      for (int i = pos; i < limit; ) {
+        if (b == address.length) return null; // Too many groups.
+
+        // Read a delimiter.
+        if (i + 2 <= limit && input.regionMatches(i, "::", 0, 2)) {
+          // Compression "::" delimiter, which is anywhere in the input, including its prefix.
+          if (compress != -1) return null; // Multiple "::" delimiters.
+          i += 2;
+          b += 2;
+          compress = b;
+          if (i == limit) break;
+        } else if (b != 0) {
+          // Group separator ":" delimiter.
+          if (input.regionMatches(i, ":", 0, 1)) {
+            i++;
+          } else if (input.regionMatches(i, ".", 0, 1)) {
+            // If we see a '.', rewind to the beginning of the previous group and parse as IPv4.
+            if (!decodeIpv4Suffix(input, groupOffset, limit, address, b - 2)) return null;
+            b += 2; // We rewound two bytes and then added four.
+            break;
+          } else {
+            return null; // Wrong delimiter.
+          }
+        }
+
+        // Read a group, one to four hex digits.
+        int value = 0;
+        groupOffset = i;
+        for (; i < limit; i++) {
+          char c = input.charAt(i);
+          int hexDigit = decodeHexDigit(c);
+          if (hexDigit == -1) break;
+          value = (value << 4) + hexDigit;
+        }
+        int groupLength = i - groupOffset;
+        if (groupLength == 0 || groupLength > 4) return null; // Group is the wrong size.
+
+        // We've successfully read a group. Assign its value to our byte array.
+        address[b++] = (byte) ((value >>> 8) & 0xff);
+        address[b++] = (byte) (value & 0xff);
+      }
+
+      // All done. If compression happened, we need to move bytes to the right place in the
+      // address. Here's a sample:
+      //
+      //      input: "1111:2222:3333::7777:8888"
+      //     before: { 11, 11, 22, 22, 33, 33, 00, 00, 77, 77, 88, 88, 00, 00, 00, 00  }
+      //   compress: 6
+      //          b: 10
+      //      after: { 11, 11, 22, 22, 33, 33, 00, 00, 00, 00, 00, 00, 77, 77, 88, 88 }
+      //
+      if (b != address.length) {
+        if (compress == -1) return null; // Address didn't have compression or enough groups.
+        System.arraycopy(address, compress, address, address.length - (b - compress), b - compress);
+        Arrays.fill(address, compress, compress + (address.length - b), (byte) 0);
+      }
+
+      try {
+        return InetAddress.getByAddress(address);
+      } catch (UnknownHostException e) {
+        throw new AssertionError();
+      }
+    }
+
+    /** Decodes an IPv4 address suffix of an IPv6 address, like 1111::5555:6666:192.168.0.1. */
+    private static boolean decodeIpv4Suffix(
+        String input, int pos, int limit, byte[] address, int addressOffset) {
+      int b = addressOffset;
+
+      for (int i = pos; i < limit; ) {
+        if (b == address.length) return false; // Too many groups.
+
+        // Read a delimiter.
+        if (b != addressOffset) {
+          if (input.charAt(i) != '.') return false; // Wrong delimiter.
+          i++;
+        }
+
+        // Read 1 or more decimal digits for a value in 0..255.
+        int value = 0;
+        int groupOffset = i;
+        for (; i < limit; i++) {
+          char c = input.charAt(i);
+          if (c < '0' || c > '9') break;
+          if (value == 0 && groupOffset != i) return false; // Reject unnecessary leading '0's.
+          value = (value * 10) + c - '0';
+          if (value > 255) return false; // Value out of range.
+        }
+        int groupLength = i - groupOffset;
+        if (groupLength == 0) return false; // No digits.
+
+        // We've successfully read a byte.
+        address[b++] = (byte) value;
+      }
+
+      if (b != addressOffset + 4) return false; // Too few groups. We wanted exactly four.
+      return true; // Success.
+    }
+
+    /**
+     * Performs IDN ToASCII encoding and canonicalize the result to lowercase. e.g. This converts
+     * {@code ☃.net} to {@code xn--n3h.net}, and {@code WwW.GoOgLe.cOm} to {@code www.google.com}.
+     * {@code null} will be returned if the input cannot be ToASCII encoded or if the result
+     * contains unsupported ASCII characters.
+     */
+    private static String domainToAscii(String input) {
+      try {
+        String result = IDN.toASCII(input).toLowerCase(Locale.US);
+        if (result.isEmpty()) return null;
+
+        // Confirm that the IDN ToASCII result doesn't contain any illegal characters.
+        if (containsInvalidHostnameAsciiCodes(result)) {
+          return null;
+        }
+        // TODO: implement all label limits.
+        return result;
+      } catch (IllegalArgumentException e) {
+        return null;
+      }
+    }
+
+    private static boolean containsInvalidHostnameAsciiCodes(String hostnameAscii) {
+      for (int i = 0; i < hostnameAscii.length(); i++) {
+        char c = hostnameAscii.charAt(i);
+        // The WHATWG Host parsing rules accepts some character codes which are invalid by
+        // definition for OkHttp's host header checks (and the WHATWG Host syntax definition). Here
+        // we rule out characters that would cause problems in host headers.
+        if (c <= '\u001f' || c >= '\u007f') {
+          return true;
+        }
+        // Check for the characters mentioned in the WHATWG Host parsing spec:
+        // U+0000, U+0009, U+000A, U+000D, U+0020, "#", "%", "/", ":", "?", "@", "[", "\", and "]"
+        // (excluding the characters covered above).
+        if (" #%/:?@[\\]".indexOf(c) != -1) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    private static String inet6AddressToAscii(byte[] address) {
+      // Go through the address looking for the longest run of 0s. Each group is 2-bytes.
+      int longestRunOffset = -1;
+      int longestRunLength = 0;
+      for (int i = 0; i < address.length; i += 2) {
+        int currentRunOffset = i;
+        while (i < 16 && address[i] == 0 && address[i + 1] == 0) {
+          i += 2;
+        }
+        int currentRunLength = i - currentRunOffset;
+        if (currentRunLength > longestRunLength) {
+          longestRunOffset = currentRunOffset;
+          longestRunLength = currentRunLength;
+        }
+      }
+
+      // Emit each 2-byte group in hex, separated by ':'. The longest run of zeroes is "::".
+      Buffer result = new Buffer();
+      for (int i = 0; i < address.length; ) {
+        if (i == longestRunOffset) {
+          result.writeByte(':');
+          i += longestRunLength;
+          if (i == 16) result.writeByte(':');
+        } else {
+          if (i > 0) result.writeByte(':');
+          int group = (address[i] & 0xff) << 8 | address[i + 1] & 0xff;
+          result.writeHexadecimalUnsignedLong(group);
+          i += 2;
+        }
+      }
+      return result.readUtf8();
+    }
+
+    private static int parsePort(String input, int pos, int limit) {
+      try {
+        // Canonicalize the port string to skip '\n' etc.
+        String portString = canonicalize(input, pos, limit, "", false, false, false, true);
+        int i = Integer.parseInt(portString);
+        if (i > 0 && i <= 65535) return i;
+        return -1;
+      } catch (NumberFormatException e) {
+        return -1; // Invalid port.
+      }
+    }
+  }
+
+  /**
+   * Returns the index of the first character in {@code input} that contains a character in {@code
+   * delimiters}. Returns limit if there is no such character.
+   */
+  private static int delimiterOffset(String input, int pos, int limit, String delimiters) {
+    for (int i = pos; i < limit; i++) {
+      if (delimiters.indexOf(input.charAt(i)) != -1) return i;
+    }
+    return limit;
+  }
+
+  static String percentDecode(String encoded, boolean plusIsSpace) {
+    return percentDecode(encoded, 0, encoded.length(), plusIsSpace);
+  }
+
+  private List<String> percentDecode(List<String> list, boolean plusIsSpace) {
+    List<String> result = new ArrayList<>(list.size());
+    for (String s : list) {
+      result.add(s != null ? percentDecode(s, plusIsSpace) : null);
+    }
+    return Collections.unmodifiableList(result);
+  }
+
+  static String percentDecode(String encoded, int pos, int limit, boolean plusIsSpace) {
+    for (int i = pos; i < limit; i++) {
+      char c = encoded.charAt(i);
+      if (c == '%' || (c == '+' && plusIsSpace)) {
+        // Slow path: the character at i requires decoding!
+        Buffer out = new Buffer();
+        out.writeUtf8(encoded, pos, i);
+        percentDecode(out, encoded, i, limit, plusIsSpace);
+        return out.readUtf8();
+      }
+    }
+
+    // Fast path: no characters in [pos..limit) required decoding.
+    return encoded.substring(pos, limit);
+  }
+
+  static void percentDecode(Buffer out, String encoded, int pos, int limit, boolean plusIsSpace) {
+    int codePoint;
+    for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
+      codePoint = encoded.codePointAt(i);
+      if (codePoint == '%' && i + 2 < limit) {
+        int d1 = decodeHexDigit(encoded.charAt(i + 1));
+        int d2 = decodeHexDigit(encoded.charAt(i + 2));
+        if (d1 != -1 && d2 != -1) {
+          out.writeByte((d1 << 4) + d2);
+          i += 2;
+          continue;
+        }
+      } else if (codePoint == '+' && plusIsSpace) {
+        out.writeByte(' ');
+        continue;
+      }
+      out.writeUtf8CodePoint(codePoint);
+    }
+  }
+
+  static boolean percentEncoded(String encoded, int pos, int limit) {
+    return pos + 2 < limit
+        && encoded.charAt(pos) == '%'
+        && decodeHexDigit(encoded.charAt(pos + 1)) != -1
+        && decodeHexDigit(encoded.charAt(pos + 2)) != -1;
+  }
+
+  static int decodeHexDigit(char c) {
+    if (c >= '0' && c <= '9') return c - '0';
+    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+    return -1;
+  }
+
+  /**
+   * Returns a substring of {@code input} on the range {@code [pos..limit)} with the following
+   * transformations:
+   * <ul>
+   *   <li>Tabs, newlines, form feeds and carriage returns are skipped.
+   *   <li>In queries, ' ' is encoded to '+' and '+' is encoded to "%2B".
+   *   <li>Characters in {@code encodeSet} are percent-encoded.
+   *   <li>Control characters and non-ASCII characters are percent-encoded.
+   *   <li>All other characters are copied without transformation.
+   * </ul>
+   *
+   * @param alreadyEncoded true to leave '%' as-is; false to convert it to '%25'.
+   * @param strict true to encode '%' if it is not the prefix of a valid percent encoding.
+   * @param plusIsSpace true to encode '+' as "%2B" if it is not already encoded.
+   * @param asciiOnly true to encode all non-ASCII codepoints.
+   */
+  static String canonicalize(String input, int pos, int limit, String encodeSet,
+      boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly) {
+    int codePoint;
+    for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
+      codePoint = input.codePointAt(i);
+      if (codePoint < 0x20
+          || codePoint == 0x7f
+          || codePoint >= 0x80 && asciiOnly
+          || encodeSet.indexOf(codePoint) != -1
+          || codePoint == '%' && (!alreadyEncoded || strict && !percentEncoded(input, i, limit))
+          || codePoint == '+' && plusIsSpace) {
+        // Slow path: the character at i requires encoding!
+        Buffer out = new Buffer();
+        out.writeUtf8(input, pos, i);
+        canonicalize(out, input, i, limit, encodeSet, alreadyEncoded, strict, plusIsSpace,
+            asciiOnly);
+        return out.readUtf8();
+      }
+    }
+
+    // Fast path: no characters in [pos..limit) required encoding.
+    return input.substring(pos, limit);
+  }
+
+  static void canonicalize(Buffer out, String input, int pos, int limit, String encodeSet,
+      boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly) {
+    Buffer utf8Buffer = null; // Lazily allocated.
+    int codePoint;
+    for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
+      codePoint = input.codePointAt(i);
+      if (alreadyEncoded
+          && (codePoint == '\t' || codePoint == '\n' || codePoint == '\f' || codePoint == '\r')) {
+        // Skip this character.
+      } else if (codePoint == '+' && plusIsSpace) {
+        // Encode '+' as '%2B' since we permit ' ' to be encoded as either '+' or '%20'.
+        out.writeUtf8(alreadyEncoded ? "+" : "%2B");
+      } else if (codePoint < 0x20
+          || codePoint == 0x7f
+          || codePoint >= 0x80 && asciiOnly
+          || encodeSet.indexOf(codePoint) != -1
+          || codePoint == '%' && (!alreadyEncoded || strict && !percentEncoded(input, i, limit))) {
+        // Percent encode this character.
+        if (utf8Buffer == null) {
+          utf8Buffer = new Buffer();
+        }
+        utf8Buffer.writeUtf8CodePoint(codePoint);
+        while (!utf8Buffer.exhausted()) {
+          int b = utf8Buffer.readByte() & 0xff;
+          out.writeByte('%');
+          out.writeByte(HEX_DIGITS[(b >> 4) & 0xf]);
+          out.writeByte(HEX_DIGITS[b & 0xf]);
+        }
+      } else {
+        // This character doesn't need encoding. Just copy it over.
+        out.writeUtf8CodePoint(codePoint);
+      }
+    }
+  }
+
+  static String canonicalize(String input, String encodeSet, boolean alreadyEncoded,
+      boolean strict, boolean plusIsSpace, boolean asciiOnly) {
+    return canonicalize(input, 0, input.length(),
+            encodeSet, alreadyEncoded, strict, plusIsSpace, asciiOnly);
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Interceptor.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Interceptor.java
new file mode 100644
index 0000000..5dee765
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Interceptor.java
@@ -0,0 +1,35 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import java.io.IOException;
+
+/**
+ * Observes, modifies, and potentially short-circuits requests going out and the corresponding
+ * requests coming back in. Typically interceptors will be used to add, remove, or transform headers
+ * on the request or response.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Interceptor {
+  Response intercept(Chain chain) throws IOException;
+
+  interface Chain {
+    Request request();
+    Response proceed(Request request) throws IOException;
+    Connection connection();
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/MediaType.java b/repackaged/okhttp/src/main/java/com/android/okhttp/MediaType.java
new file mode 100644
index 0000000..15048e4
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/MediaType.java
@@ -0,0 +1,125 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import java.nio.charset.Charset;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a> Media Type,
+ * appropriate to describe the content type of an HTTP request or response body.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class MediaType {
+  private static final String TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)";
+  private static final String QUOTED = "\"([^\"]*)\"";
+  private static final Pattern TYPE_SUBTYPE = Pattern.compile(TOKEN + "/" + TOKEN);
+  private static final Pattern PARAMETER = Pattern.compile(
+      ";\\s*(?:" + TOKEN + "=(?:" + TOKEN + "|" + QUOTED + "))?");
+
+  private final String mediaType;
+  private final String type;
+  private final String subtype;
+  private final String charset;
+
+  private MediaType(String mediaType, String type, String subtype, String charset) {
+    this.mediaType = mediaType;
+    this.type = type;
+    this.subtype = subtype;
+    this.charset = charset;
+  }
+
+  /**
+   * Returns a media type for {@code string}, or null if {@code string} is not a
+   * well-formed media type.
+   */
+  public static MediaType parse(String string) {
+    Matcher typeSubtype = TYPE_SUBTYPE.matcher(string);
+    if (!typeSubtype.lookingAt()) return null;
+    String type = typeSubtype.group(1).toLowerCase(Locale.US);
+    String subtype = typeSubtype.group(2).toLowerCase(Locale.US);
+
+    String charset = null;
+    Matcher parameter = PARAMETER.matcher(string);
+    for (int s = typeSubtype.end(); s < string.length(); s = parameter.end()) {
+      parameter.region(s, string.length());
+      if (!parameter.lookingAt()) return null; // This is not a well-formed media type.
+
+      String name = parameter.group(1);
+      if (name == null || !name.equalsIgnoreCase("charset")) continue;
+      String charsetParameter = parameter.group(2) != null
+          ? parameter.group(2)  // Value is a token.
+          : parameter.group(3); // Value is a quoted string.
+      if (charset != null && !charsetParameter.equalsIgnoreCase(charset)) {
+        throw new IllegalArgumentException("Multiple different charsets: " + string);
+      }
+      charset = charsetParameter;
+    }
+
+    return new MediaType(string, type, subtype, charset);
+  }
+
+  /**
+   * Returns the high-level media type, such as "text", "image", "audio",
+   * "video", or "application".
+   */
+  public String type() {
+    return type;
+  }
+
+  /**
+   * Returns a specific media subtype, such as "plain" or "png", "mpeg",
+   * "mp4" or "xml".
+   */
+  public String subtype() {
+    return subtype;
+  }
+
+  /**
+   * Returns the charset of this media type, or null if this media type doesn't
+   * specify a charset.
+   */
+  public Charset charset() {
+    return charset != null ? Charset.forName(charset) : null;
+  }
+
+  /**
+   * Returns the charset of this media type, or {@code defaultValue} if this
+   * media type doesn't specify a charset.
+   */
+  public Charset charset(Charset defaultValue) {
+    return charset != null ? Charset.forName(charset) : defaultValue;
+  }
+
+  /**
+   * Returns the encoded media type, like "text/plain; charset=utf-8",
+   * appropriate for use in a Content-Type header.
+   */
+  @Override public String toString() {
+    return mediaType;
+  }
+
+  @Override public boolean equals(Object o) {
+    return o instanceof MediaType && ((MediaType) o).mediaType.equals(mediaType);
+  }
+
+  @Override public int hashCode() {
+    return mediaType.hashCode();
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/MultipartBuilder.java b/repackaged/okhttp/src/main/java/com/android/okhttp/MultipartBuilder.java
new file mode 100644
index 0000000..2bce7c5
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/MultipartBuilder.java
@@ -0,0 +1,302 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.ByteString;
+
+/**
+ * Fluent API to build <a href="http://www.ietf.org/rfc/rfc2387.txt">RFC
+ * 2387</a>-compliant request bodies.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class MultipartBuilder {
+  /**
+   * The "mixed" subtype of "multipart" is intended for use when the body
+   * parts are independent and need to be bundled in a particular order. Any
+   * "multipart" subtypes that an implementation does not recognize must be
+   * treated as being of subtype "mixed".
+   */
+  public static final MediaType MIXED = MediaType.parse("multipart/mixed");
+
+  /**
+   * The "multipart/alternative" type is syntactically identical to
+   * "multipart/mixed", but the semantics are different. In particular, each
+   * of the body parts is an "alternative" version of the same information.
+   */
+  public static final MediaType ALTERNATIVE = MediaType.parse("multipart/alternative");
+
+  /**
+   * This type is syntactically identical to "multipart/mixed", but the
+   * semantics are different. In particular, in a digest, the default {@code
+   * Content-Type} value for a body part is changed from "text/plain" to
+   * "message/rfc822".
+   */
+  public static final MediaType DIGEST = MediaType.parse("multipart/digest");
+
+  /**
+   * This type is syntactically identical to "multipart/mixed", but the
+   * semantics are different. In particular, in a parallel entity, the order
+   * of body parts is not significant.
+   */
+  public static final MediaType PARALLEL = MediaType.parse("multipart/parallel");
+
+  /**
+   * The media-type multipart/form-data follows the rules of all multipart
+   * MIME data streams as outlined in RFC 2046. In forms, there are a series
+   * of fields to be supplied by the user who fills out the form. Each field
+   * has a name. Within a given form, the names are unique.
+   */
+  public static final MediaType FORM = MediaType.parse("multipart/form-data");
+
+  private static final byte[] COLONSPACE = { ':', ' ' };
+  private static final byte[] CRLF = { '\r', '\n' };
+  private static final byte[] DASHDASH = { '-', '-' };
+
+  private final ByteString boundary;
+  private MediaType type = MIXED;
+
+  // Parallel lists of nullable headers and non-null bodies.
+  private final List<Headers> partHeaders = new ArrayList<>();
+  private final List<RequestBody> partBodies = new ArrayList<>();
+
+  /** Creates a new multipart builder that uses a random boundary token. */
+  public MultipartBuilder() {
+    this(UUID.randomUUID().toString());
+  }
+
+  /**
+   * Creates a new multipart builder that uses {@code boundary} to separate
+   * parts. Prefer the no-argument constructor to defend against injection
+   * attacks.
+   */
+  public MultipartBuilder(String boundary) {
+    this.boundary = ByteString.encodeUtf8(boundary);
+  }
+
+  /**
+   * Set the MIME type. Expected values for {@code type} are {@link #MIXED} (the
+   * default), {@link #ALTERNATIVE}, {@link #DIGEST}, {@link #PARALLEL} and
+   * {@link #FORM}.
+   */
+  public MultipartBuilder type(MediaType type) {
+    if (type == null) {
+      throw new NullPointerException("type == null");
+    }
+    if (!type.type().equals("multipart")) {
+      throw new IllegalArgumentException("multipart != " + type);
+    }
+    this.type = type;
+    return this;
+  }
+
+  /** Add a part to the body. */
+  public MultipartBuilder addPart(RequestBody body) {
+    return addPart(null, body);
+  }
+
+  /** Add a part to the body. */
+  public MultipartBuilder addPart(Headers headers, RequestBody body) {
+    if (body == null) {
+      throw new NullPointerException("body == null");
+    }
+    if (headers != null && headers.get("Content-Type") != null) {
+      throw new IllegalArgumentException("Unexpected header: Content-Type");
+    }
+    if (headers != null && headers.get("Content-Length") != null) {
+      throw new IllegalArgumentException("Unexpected header: Content-Length");
+    }
+
+    partHeaders.add(headers);
+    partBodies.add(body);
+    return this;
+  }
+
+  /**
+   * Appends a quoted-string to a StringBuilder.
+   *
+   * <p>RFC 2388 is rather vague about how one should escape special characters
+   * in form-data parameters, and as it turns out Firefox and Chrome actually
+   * do rather different things, and both say in their comments that they're
+   * not really sure what the right approach is. We go with Chrome's behavior
+   * (which also experimentally seems to match what IE does), but if you
+   * actually want to have a good chance of things working, please avoid
+   * double-quotes, newlines, percent signs, and the like in your field names.
+   */
+  private static StringBuilder appendQuotedString(StringBuilder target, String key) {
+    target.append('"');
+    for (int i = 0, len = key.length(); i < len; i++) {
+      char ch = key.charAt(i);
+      switch (ch) {
+        case '\n':
+          target.append("%0A");
+          break;
+        case '\r':
+          target.append("%0D");
+          break;
+        case '"':
+          target.append("%22");
+          break;
+        default:
+          target.append(ch);
+          break;
+      }
+    }
+    target.append('"');
+    return target;
+  }
+
+  /** Add a form data part to the body. */
+  public MultipartBuilder addFormDataPart(String name, String value) {
+    return addFormDataPart(name, null, RequestBody.create(null, value));
+  }
+
+  /** Add a form data part to the body. */
+  public MultipartBuilder addFormDataPart(String name, String filename, RequestBody value) {
+    if (name == null) {
+      throw new NullPointerException("name == null");
+    }
+    StringBuilder disposition = new StringBuilder("form-data; name=");
+    appendQuotedString(disposition, name);
+
+    if (filename != null) {
+      disposition.append("; filename=");
+      appendQuotedString(disposition, filename);
+    }
+
+    return addPart(Headers.of("Content-Disposition", disposition.toString()), value);
+  }
+
+  /** Assemble the specified parts into a request body. */
+  public RequestBody build() {
+    if (partHeaders.isEmpty()) {
+      throw new IllegalStateException("Multipart body must have at least one part.");
+    }
+    return new MultipartRequestBody(type, boundary, partHeaders, partBodies);
+  }
+
+  private static final class MultipartRequestBody extends RequestBody {
+    private final ByteString boundary;
+    private final MediaType contentType;
+    private final List<Headers> partHeaders;
+    private final List<RequestBody> partBodies;
+    private long contentLength = -1L;
+
+    public MultipartRequestBody(MediaType type, ByteString boundary, List<Headers> partHeaders,
+        List<RequestBody> partBodies) {
+      if (type == null) throw new NullPointerException("type == null");
+
+      this.boundary = boundary;
+      this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());
+      this.partHeaders = Util.immutableList(partHeaders);
+      this.partBodies = Util.immutableList(partBodies);
+    }
+
+    @Override public MediaType contentType() {
+      return contentType;
+    }
+
+    @Override public long contentLength() throws IOException {
+      long result = contentLength;
+      if (result != -1L) return result;
+      return contentLength = writeOrCountBytes(null, true);
+    }
+
+    /**
+     * Either writes this request to {@code sink} or measures its content length. We have one method
+     * do double-duty to make sure the counting and content are consistent, particularly when it
+     * comes to awkward operations like measuring the encoded length of header strings, or the
+     * length-in-digits of an encoded integer.
+     */
+    private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {
+      long byteCount = 0L;
+
+      Buffer byteCountBuffer = null;
+      if (countBytes) {
+        sink = byteCountBuffer = new Buffer();
+      }
+
+      for (int p = 0, partCount = partHeaders.size(); p < partCount; p++) {
+        Headers headers = partHeaders.get(p);
+        RequestBody body = partBodies.get(p);
+
+        sink.write(DASHDASH);
+        sink.write(boundary);
+        sink.write(CRLF);
+
+        if (headers != null) {
+          for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
+            sink.writeUtf8(headers.name(h))
+                .write(COLONSPACE)
+                .writeUtf8(headers.value(h))
+                .write(CRLF);
+          }
+        }
+
+        MediaType contentType = body.contentType();
+        if (contentType != null) {
+          sink.writeUtf8("Content-Type: ")
+              .writeUtf8(contentType.toString())
+              .write(CRLF);
+        }
+
+        long contentLength = body.contentLength();
+        if (contentLength != -1) {
+          sink.writeUtf8("Content-Length: ")
+              .writeDecimalLong(contentLength)
+              .write(CRLF);
+        } else if (countBytes) {
+          // We can't measure the body's size without the sizes of its components.
+          byteCountBuffer.clear();
+          return -1L;
+        }
+
+        sink.write(CRLF);
+
+        if (countBytes) {
+          byteCount += contentLength;
+        } else {
+          partBodies.get(p).writeTo(sink);
+        }
+
+        sink.write(CRLF);
+      }
+
+      sink.write(DASHDASH);
+      sink.write(boundary);
+      sink.write(DASHDASH);
+      sink.write(CRLF);
+
+      if (countBytes) {
+        byteCount += byteCountBuffer.size();
+        byteCountBuffer.clear();
+      }
+
+      return byteCount;
+    }
+
+    @Override public void writeTo(BufferedSink sink) throws IOException {
+      writeOrCountBytes(sink, false);
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/OkHttpClient.java b/repackaged/okhttp/src/main/java/com/android/okhttp/OkHttpClient.java
new file mode 100644
index 0000000..b6698d3
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/OkHttpClient.java
@@ -0,0 +1,647 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.InternalCache;
+import com.android.okhttp.internal.RouteDatabase;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.http.AuthenticatorAdapter;
+import com.android.okhttp.internal.http.StreamAllocation;
+import com.android.okhttp.internal.io.RealConnection;
+import com.android.okhttp.internal.tls.OkHostnameVerifier;
+import java.net.CookieHandler;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.net.SocketFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Configures and creates HTTP connections. Most applications can use a single
+ * OkHttpClient for all of their HTTP requests - benefiting from a shared
+ * response cache, thread pool, connection re-use, etc.
+ *
+ * <p>Instances of OkHttpClient are intended to be fully configured before they're
+ * shared - once shared they should be treated as immutable and can safely be used
+ * to concurrently open new connections. If required, threads can call
+ * {@link #clone()} to make a shallow copy of the OkHttpClient that can be
+ * safely modified with further configuration changes.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class OkHttpClient implements Cloneable {
+  private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
+      Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
+
+  private static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
+      ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT);
+
+  static {
+    Internal.instance = new Internal() {
+      @Override public void addLenient(Headers.Builder builder, String line) {
+        builder.addLenient(line);
+      }
+
+      @Override public void addLenient(Headers.Builder builder, String name, String value) {
+        builder.addLenient(name, value);
+      }
+
+      @Override public void setCache(OkHttpClient client, InternalCache internalCache) {
+        client.setInternalCache(internalCache);
+      }
+
+      @Override public InternalCache internalCache(OkHttpClient client) {
+        return client.internalCache();
+      }
+
+      @Override public boolean connectionBecameIdle(
+          ConnectionPool pool, RealConnection connection) {
+        return pool.connectionBecameIdle(connection);
+      }
+
+      @Override public RealConnection get(
+          ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
+        return pool.get(address, streamAllocation);
+      }
+
+      @Override public void put(ConnectionPool pool, RealConnection connection) {
+        pool.put(connection);
+      }
+
+      @Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) {
+        return connectionPool.routeDatabase;
+      }
+
+      @Override
+      public void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket) {
+        call.enqueue(responseCallback, forWebSocket);
+      }
+
+      @Override public StreamAllocation callEngineGetStreamAllocation(Call call) {
+        return call.engine.streamAllocation;
+      }
+
+      @Override
+      public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) {
+        tlsConfiguration.apply(sslSocket, isFallback);
+      }
+
+      @Override public HttpUrl getHttpUrlChecked(String url)
+          throws MalformedURLException, UnknownHostException {
+        return HttpUrl.getChecked(url);
+      }
+    };
+  }
+
+  /** Lazily-initialized. */
+  private static SSLSocketFactory defaultSslSocketFactory;
+
+  private final RouteDatabase routeDatabase;
+  private Dispatcher dispatcher;
+  private Proxy proxy;
+  private List<Protocol> protocols;
+  private List<ConnectionSpec> connectionSpecs;
+  private final List<Interceptor> interceptors = new ArrayList<>();
+  private final List<Interceptor> networkInterceptors = new ArrayList<>();
+  private ProxySelector proxySelector;
+  private CookieHandler cookieHandler;
+
+  /** Non-null if this client is caching; possibly by {@code cache}. */
+  private InternalCache internalCache;
+  private Cache cache;
+
+  private SocketFactory socketFactory;
+  private SSLSocketFactory sslSocketFactory;
+  private HostnameVerifier hostnameVerifier;
+  private CertificatePinner certificatePinner;
+  private Authenticator authenticator;
+  private ConnectionPool connectionPool;
+  private Dns dns;
+  private boolean followSslRedirects = true;
+  private boolean followRedirects = true;
+  private boolean retryOnConnectionFailure = true;
+  private int connectTimeout = 10_000;
+  private int readTimeout = 10_000;
+  private int writeTimeout = 10_000;
+
+  public OkHttpClient() {
+    routeDatabase = new RouteDatabase();
+    dispatcher = new Dispatcher();
+  }
+
+  private OkHttpClient(OkHttpClient okHttpClient) {
+    this.routeDatabase = okHttpClient.routeDatabase;
+    this.dispatcher = okHttpClient.dispatcher;
+    this.proxy = okHttpClient.proxy;
+    this.protocols = okHttpClient.protocols;
+    this.connectionSpecs = okHttpClient.connectionSpecs;
+    this.interceptors.addAll(okHttpClient.interceptors);
+    this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
+    this.proxySelector = okHttpClient.proxySelector;
+    this.cookieHandler = okHttpClient.cookieHandler;
+    this.cache = okHttpClient.cache;
+    this.internalCache = cache != null ? cache.internalCache : okHttpClient.internalCache;
+    this.socketFactory = okHttpClient.socketFactory;
+    this.sslSocketFactory = okHttpClient.sslSocketFactory;
+    this.hostnameVerifier = okHttpClient.hostnameVerifier;
+    this.certificatePinner = okHttpClient.certificatePinner;
+    this.authenticator = okHttpClient.authenticator;
+    this.connectionPool = okHttpClient.connectionPool;
+    this.dns = okHttpClient.dns;
+    this.followSslRedirects = okHttpClient.followSslRedirects;
+    this.followRedirects = okHttpClient.followRedirects;
+    this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure;
+    this.connectTimeout = okHttpClient.connectTimeout;
+    this.readTimeout = okHttpClient.readTimeout;
+    this.writeTimeout = okHttpClient.writeTimeout;
+  }
+
+  /**
+   * Sets the default connect timeout for new connections. A value of 0 means no timeout, otherwise
+   * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
+   *
+   * @see URLConnection#setConnectTimeout(int)
+   */
+  public void setConnectTimeout(long timeout, TimeUnit unit) {
+    if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
+    if (unit == null) throw new IllegalArgumentException("unit == null");
+    long millis = unit.toMillis(timeout);
+    if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
+    if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
+    connectTimeout = (int) millis;
+  }
+
+  /** Default connect timeout (in milliseconds). */
+  public int getConnectTimeout() {
+    return connectTimeout;
+  }
+
+  /**
+   * Sets the default read timeout for new connections. A value of 0 means no timeout, otherwise
+   * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
+   *
+   * @see URLConnection#setReadTimeout(int)
+   */
+  public void setReadTimeout(long timeout, TimeUnit unit) {
+    if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
+    if (unit == null) throw new IllegalArgumentException("unit == null");
+    long millis = unit.toMillis(timeout);
+    if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
+    if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
+    readTimeout = (int) millis;
+  }
+
+  /** Default read timeout (in milliseconds). */
+  public int getReadTimeout() {
+    return readTimeout;
+  }
+
+  /**
+   * Sets the default write timeout for new connections. A value of 0 means no timeout, otherwise
+   * values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
+   */
+  public void setWriteTimeout(long timeout, TimeUnit unit) {
+    if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
+    if (unit == null) throw new IllegalArgumentException("unit == null");
+    long millis = unit.toMillis(timeout);
+    if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
+    if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
+    writeTimeout = (int) millis;
+  }
+
+  /** Default write timeout (in milliseconds). */
+  public int getWriteTimeout() {
+    return writeTimeout;
+  }
+
+  /**
+   * Sets the HTTP proxy that will be used by connections created by this
+   * client. This takes precedence over {@link #setProxySelector}, which is
+   * only honored when this proxy is null (which it is by default). To disable
+   * proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.
+   */
+  public OkHttpClient setProxy(Proxy proxy) {
+    this.proxy = proxy;
+    return this;
+  }
+
+  public Proxy getProxy() {
+    return proxy;
+  }
+
+  /**
+   * Sets the proxy selection policy to be used if no {@link #setProxy proxy}
+   * is specified explicitly. The proxy selector may return multiple proxies;
+   * in that case they will be tried in sequence until a successful connection
+   * is established.
+   *
+   * <p>If unset, the {@link ProxySelector#getDefault() system-wide default}
+   * proxy selector will be used.
+   */
+  public OkHttpClient setProxySelector(ProxySelector proxySelector) {
+    this.proxySelector = proxySelector;
+    return this;
+  }
+
+  public ProxySelector getProxySelector() {
+    return proxySelector;
+  }
+
+  /**
+   * Sets the cookie handler to be used to read outgoing cookies and write
+   * incoming cookies.
+   *
+   * <p>If unset, the {@link CookieHandler#getDefault() system-wide default}
+   * cookie handler will be used.
+   */
+  public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {
+    this.cookieHandler = cookieHandler;
+    return this;
+  }
+
+  public CookieHandler getCookieHandler() {
+    return cookieHandler;
+  }
+
+  /** Sets the response cache to be used to read and write cached responses. */
+  void setInternalCache(InternalCache internalCache) {
+    this.internalCache = internalCache;
+    this.cache = null;
+  }
+
+  InternalCache internalCache() {
+    return internalCache;
+  }
+
+  public OkHttpClient setCache(Cache cache) {
+    this.cache = cache;
+    this.internalCache = null;
+    return this;
+  }
+
+  public Cache getCache() {
+    return cache;
+  }
+
+  /**
+   * Sets the DNS service used to lookup IP addresses for hostnames.
+   *
+   * <p>If unset, the {@link Dns#SYSTEM system-wide default} DNS will be used.
+   */
+  public OkHttpClient setDns(Dns dns) {
+    this.dns = dns;
+    return this;
+  }
+
+  public Dns getDns() {
+    return dns;
+  }
+
+  /**
+   * Sets the socket factory used to create connections. OkHttp only uses
+   * the parameterless {@link SocketFactory#createSocket() createSocket()}
+   * method to create unconnected sockets. Overriding this method,
+   * e. g., allows the socket to be bound to a specific local address.
+   *
+   * <p>If unset, the {@link SocketFactory#getDefault() system-wide default}
+   * socket factory will be used.
+   */
+  public OkHttpClient setSocketFactory(SocketFactory socketFactory) {
+    this.socketFactory = socketFactory;
+    return this;
+  }
+
+  public SocketFactory getSocketFactory() {
+    return socketFactory;
+  }
+
+  /**
+   * Sets the socket factory used to secure HTTPS connections.
+   *
+   * <p>If unset, a lazily created SSL socket factory will be used.
+   */
+  public OkHttpClient setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
+    this.sslSocketFactory = sslSocketFactory;
+    return this;
+  }
+
+  public SSLSocketFactory getSslSocketFactory() {
+    return sslSocketFactory;
+  }
+
+  /**
+   * Sets the verifier used to confirm that response certificates apply to
+   * requested hostnames for HTTPS connections.
+   *
+   * <p>If unset, a default hostname verifier will be used.
+   */
+  public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+    this.hostnameVerifier = hostnameVerifier;
+    return this;
+  }
+
+  public HostnameVerifier getHostnameVerifier() {
+    return hostnameVerifier;
+  }
+
+  /**
+   * Sets the certificate pinner that constrains which certificates are trusted.
+   * By default HTTPS connections rely on only the {@link #setSslSocketFactory
+   * SSL socket factory} to establish trust. Pinning certificates avoids the
+   * need to trust certificate authorities.
+   */
+  public OkHttpClient setCertificatePinner(CertificatePinner certificatePinner) {
+    this.certificatePinner = certificatePinner;
+    return this;
+  }
+
+  public CertificatePinner getCertificatePinner() {
+    return certificatePinner;
+  }
+
+  /**
+   * Sets the authenticator used to respond to challenges from the remote web
+   * server or proxy server.
+   *
+   * <p>If unset, the {@link java.net.Authenticator#setDefault system-wide default}
+   * authenticator will be used.
+   */
+  public OkHttpClient setAuthenticator(Authenticator authenticator) {
+    this.authenticator = authenticator;
+    return this;
+  }
+
+  public Authenticator getAuthenticator() {
+    return authenticator;
+  }
+
+  /**
+   * Sets the connection pool used to recycle HTTP and HTTPS connections.
+   *
+   * <p>If unset, the {@link ConnectionPool#getDefault() system-wide
+   * default} connection pool will be used.
+   */
+  public OkHttpClient setConnectionPool(ConnectionPool connectionPool) {
+    this.connectionPool = connectionPool;
+    return this;
+  }
+
+  public ConnectionPool getConnectionPool() {
+    return connectionPool;
+  }
+
+  /**
+   * Configure this client to follow redirects from HTTPS to HTTP and from HTTP
+   * to HTTPS.
+   *
+   * <p>If unset, protocol redirects will be followed. This is different than
+   * the built-in {@code HttpURLConnection}'s default.
+   */
+  public OkHttpClient setFollowSslRedirects(boolean followProtocolRedirects) {
+    this.followSslRedirects = followProtocolRedirects;
+    return this;
+  }
+
+  public boolean getFollowSslRedirects() {
+    return followSslRedirects;
+  }
+
+  /** Configure this client to follow redirects. If unset, redirects be followed. */
+  public void setFollowRedirects(boolean followRedirects) {
+    this.followRedirects = followRedirects;
+  }
+
+  public boolean getFollowRedirects() {
+    return followRedirects;
+  }
+
+  /**
+   * Configure this client to retry or not when a connectivity problem is encountered. By default,
+   * this client silently recovers from the following problems:
+   *
+   * <ul>
+   *   <li><strong>Unreachable IP addresses.</strong> If the URL's host has multiple IP addresses,
+   *       failure to reach any individual IP address doesn't fail the overall request. This can
+   *       increase availability of multi-homed services.
+   *   <li><strong>Stale pooled connections.</strong> The {@link ConnectionPool} reuses sockets
+   *       to decrease request latency, but these connections will occasionally time out.
+   *   <li><strong>Unreachable proxy servers.</strong> A {@link ProxySelector} can be used to
+   *       attempt multiple proxy servers in sequence, eventually falling back to a direct
+   *       connection.
+   * </ul>
+   *
+   * Set this to false to avoid retrying requests when doing so is destructive. In this case the
+   * calling application should do its own recovery of connectivity failures.
+   */
+  public void setRetryOnConnectionFailure(boolean retryOnConnectionFailure) {
+    this.retryOnConnectionFailure = retryOnConnectionFailure;
+  }
+
+  public boolean getRetryOnConnectionFailure() {
+    return retryOnConnectionFailure;
+  }
+
+  RouteDatabase routeDatabase() {
+    return routeDatabase;
+  }
+
+  /**
+   * Sets the dispatcher used to set policy and execute asynchronous requests.
+   * Must not be null.
+   */
+  public OkHttpClient setDispatcher(Dispatcher dispatcher) {
+    if (dispatcher == null) throw new IllegalArgumentException("dispatcher == null");
+    this.dispatcher = dispatcher;
+    return this;
+  }
+
+  public Dispatcher getDispatcher() {
+    return dispatcher;
+  }
+
+  /**
+   * Configure the protocols used by this client to communicate with remote
+   * servers. By default this client will prefer the most efficient transport
+   * available, falling back to more ubiquitous protocols. Applications should
+   * only call this method to avoid specific compatibility problems, such as web
+   * servers that behave incorrectly when SPDY is enabled.
+   *
+   * <p>The following protocols are currently supported:
+   * <ul>
+   *   <li><a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">http/1.1</a>
+   *   <li><a href="http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1">spdy/3.1</a>
+   *   <li><a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-17">h2</a>
+   * </ul>
+   *
+   * <p><strong>This is an evolving set.</strong> Future releases include
+   * support for transitional protocols. The http/1.1 transport will never be
+   * dropped.
+   *
+   * <p>If multiple protocols are specified, <a
+   * href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg">ALPN</a>
+   * will be used to negotiate a transport.
+   *
+   * <p>{@link Protocol#HTTP_1_0} is not supported in this set. Requests are
+   * initiated with {@code HTTP/1.1} only. If the server responds with {@code
+   * HTTP/1.0}, that will be exposed by {@link Response#protocol()}.
+   *
+   * @param protocols the protocols to use, in order of preference. The list
+   *     must contain {@link Protocol#HTTP_1_1}. It must not contain null or
+   *     {@link Protocol#HTTP_1_0}.
+   */
+  public OkHttpClient setProtocols(List<Protocol> protocols) {
+    protocols = Util.immutableList(protocols);
+    if (!protocols.contains(Protocol.HTTP_1_1)) {
+      throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
+    }
+    if (protocols.contains(Protocol.HTTP_1_0)) {
+      throw new IllegalArgumentException("protocols must not contain http/1.0: " + protocols);
+    }
+    if (protocols.contains(null)) {
+      throw new IllegalArgumentException("protocols must not contain null");
+    }
+    this.protocols = Util.immutableList(protocols);
+    return this;
+  }
+
+  public List<Protocol> getProtocols() {
+    return protocols;
+  }
+
+  public OkHttpClient setConnectionSpecs(List<ConnectionSpec> connectionSpecs) {
+    this.connectionSpecs = Util.immutableList(connectionSpecs);
+    return this;
+  }
+
+  public List<ConnectionSpec> getConnectionSpecs() {
+    return connectionSpecs;
+  }
+
+  /**
+   * Returns a modifiable list of interceptors that observe the full span of each call: from before
+   * the connection is established (if any) until after the response source is selected (either the
+   * origin server, cache, or both).
+   */
+  public List<Interceptor> interceptors() {
+    return interceptors;
+  }
+
+  /**
+   * Returns a modifiable list of interceptors that observe a single network request and response.
+   * These interceptors must call {@link Interceptor.Chain#proceed} exactly once: it is an error for
+   * a network interceptor to short-circuit or repeat a network request.
+   */
+  public List<Interceptor> networkInterceptors() {
+    return networkInterceptors;
+  }
+
+  /**
+   * Prepares the {@code request} to be executed at some point in the future.
+   */
+  public Call newCall(Request request) {
+    return new Call(this, request);
+  }
+
+  /**
+   * Cancels all scheduled or in-flight calls tagged with {@code tag}. Requests
+   * that are already complete cannot be canceled.
+   */
+  public OkHttpClient cancel(Object tag) {
+    getDispatcher().cancel(tag);
+    return this;
+  }
+
+  /**
+   * Returns a shallow copy of this OkHttpClient that uses the system-wide
+   * default for each field that hasn't been explicitly configured.
+   */
+  OkHttpClient copyWithDefaults() {
+    OkHttpClient result = new OkHttpClient(this);
+    if (result.proxySelector == null) {
+      result.proxySelector = ProxySelector.getDefault();
+    }
+    if (result.cookieHandler == null) {
+      result.cookieHandler = CookieHandler.getDefault();
+    }
+    if (result.socketFactory == null) {
+      result.socketFactory = SocketFactory.getDefault();
+    }
+    if (result.sslSocketFactory == null) {
+      result.sslSocketFactory = getDefaultSSLSocketFactory();
+    }
+    if (result.hostnameVerifier == null) {
+      result.hostnameVerifier = OkHostnameVerifier.INSTANCE;
+    }
+    if (result.certificatePinner == null) {
+      result.certificatePinner = CertificatePinner.DEFAULT;
+    }
+    if (result.authenticator == null) {
+      result.authenticator = AuthenticatorAdapter.INSTANCE;
+    }
+    if (result.connectionPool == null) {
+      result.connectionPool = ConnectionPool.getDefault();
+    }
+    if (result.protocols == null) {
+      result.protocols = DEFAULT_PROTOCOLS;
+    }
+    if (result.connectionSpecs == null) {
+      result.connectionSpecs = DEFAULT_CONNECTION_SPECS;
+    }
+    if (result.dns == null) {
+      result.dns = Dns.SYSTEM;
+    }
+    return result;
+  }
+
+  /**
+   * Java and Android programs default to using a single global SSL context,
+   * accessible to HTTP clients as {@link SSLSocketFactory#getDefault()}. If we
+   * used the shared SSL context, when OkHttp enables ALPN for its SPDY-related
+   * stuff, it would also enable ALPN for other usages, which might crash them
+   * because ALPN is enabled when it isn't expected to be.
+   *
+   * <p>This code avoids that by defaulting to an OkHttp-created SSL context.
+   * The drawback of this approach is that apps that customize the global SSL
+   * context will lose these customizations.
+   */
+  private synchronized SSLSocketFactory getDefaultSSLSocketFactory() {
+    if (defaultSslSocketFactory == null) {
+      try {
+        SSLContext sslContext = SSLContext.getInstance("TLS");
+        sslContext.init(null, null, null);
+        defaultSslSocketFactory = sslContext.getSocketFactory();
+      } catch (GeneralSecurityException e) {
+        throw new AssertionError(); // The system has no TLS. Just give up.
+      }
+    }
+    return defaultSslSocketFactory;
+  }
+
+  /** Returns a shallow copy of this OkHttpClient. */
+  @Override public OkHttpClient clone() {
+    return new OkHttpClient(this);
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Protocol.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Protocol.java
new file mode 100644
index 0000000..3ddb8bf
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Protocol.java
@@ -0,0 +1,99 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import java.io.IOException;
+
+/**
+ * Protocols that OkHttp implements for <a
+ * href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg">ALPN</a>
+ * selection.
+ *
+ * <h3>Protocol vs Scheme</h3>
+ * Despite its name, {@link java.net.URL#getProtocol()} returns the
+ * {@linkplain java.net.URI#getScheme() scheme} (http, https, etc.) of the URL, not
+ * the protocol (http/1.1, spdy/3.1, etc.). OkHttp uses the word <i>protocol</i>
+ * to identify how HTTP messages are framed.
+ * @hide This class is not part of the Android public SDK API
+ */
+public enum Protocol {
+  /**
+   * An obsolete plaintext framing that does not use persistent sockets by
+   * default.
+   */
+  HTTP_1_0("http/1.0"),
+
+  /**
+   * A plaintext framing that includes persistent connections.
+   *
+   * <p>This version of OkHttp implements <a
+   * href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</a>, and tracks
+   * revisions to that spec.
+   */
+  HTTP_1_1("http/1.1"),
+
+  /**
+   * Chromium's binary-framed protocol that includes header compression,
+   * multiplexing multiple requests on the same socket, and server-push.
+   * HTTP/1.1 semantics are layered on SPDY/3.
+   *
+   * <p>This version of OkHttp implements SPDY 3 <a
+   * href="http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1">draft
+   * 3.1</a>. Future releases of OkHttp may use this identifier for a newer draft
+   * of the SPDY spec.
+   */
+  SPDY_3("spdy/3.1"),
+
+  /**
+   * The IETF's binary-framed protocol that includes header compression,
+   * multiplexing multiple requests on the same socket, and server-push.
+   * HTTP/1.1 semantics are layered on HTTP/2.
+   *
+   * <p>HTTP/2 requires deployments of HTTP/2 that use TLS 1.2 support
+   * {@linkplain com.android.okhttp.CipherSuite#TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}
+   * , present in Java 8+ and Android 5+. Servers that enforce this may send an
+   * exception message including the string {@code INADEQUATE_SECURITY}.
+   */
+  HTTP_2("h2");
+
+  private final String protocol;
+
+  Protocol(String protocol) {
+    this.protocol = protocol;
+  }
+
+  /**
+   * Returns the protocol identified by {@code protocol}.
+   * @throws IOException if {@code protocol} is unknown.
+   */
+  public static Protocol get(String protocol) throws IOException {
+    // Unroll the loop over values() to save an allocation.
+    if (protocol.equals(HTTP_1_0.protocol)) return HTTP_1_0;
+    if (protocol.equals(HTTP_1_1.protocol)) return HTTP_1_1;
+    if (protocol.equals(HTTP_2.protocol)) return HTTP_2;
+    if (protocol.equals(SPDY_3.protocol)) return SPDY_3;
+    throw new IOException("Unexpected protocol: " + protocol);
+  }
+
+  /**
+   * Returns the string used to identify this protocol for ALPN, like
+   * "http/1.1", "spdy/3.1" or "h2".
+   */
+  @Override public String toString() {
+    return protocol;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Request.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Request.java
new file mode 100644
index 0000000..e16496e
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Request.java
@@ -0,0 +1,286 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.http.HttpMethod;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+
+/**
+ * An HTTP request. Instances of this class are immutable if their {@link #body}
+ * is null or itself immutable.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Request {
+  private final HttpUrl url;
+  private final String method;
+  private final Headers headers;
+  private final RequestBody body;
+  private final Object tag;
+
+  private volatile URL javaNetUrl; // Lazily initialized.
+  private volatile URI javaNetUri; // Lazily initialized.
+  private volatile CacheControl cacheControl; // Lazily initialized.
+
+  private Request(Builder builder) {
+    this.url = builder.url;
+    this.method = builder.method;
+    this.headers = builder.headers.build();
+    this.body = builder.body;
+    this.tag = builder.tag != null ? builder.tag : this;
+  }
+
+  public HttpUrl httpUrl() {
+    return url;
+  }
+
+  public URL url() {
+    URL result = javaNetUrl;
+    return result != null ? result : (javaNetUrl = url.url());
+  }
+
+  public URI uri() throws IOException {
+    try {
+      URI result = javaNetUri;
+      return result != null ? result : (javaNetUri = url.uri());
+    } catch (IllegalStateException e) {
+      throw new IOException(e.getMessage());
+    }
+  }
+
+  public String urlString() {
+    return url.toString();
+  }
+
+  public String method() {
+    return method;
+  }
+
+  public Headers headers() {
+    return headers;
+  }
+
+  public String header(String name) {
+    return headers.get(name);
+  }
+
+  public List<String> headers(String name) {
+    return headers.values(name);
+  }
+
+  public RequestBody body() {
+    return body;
+  }
+
+  public Object tag() {
+    return tag;
+  }
+
+  public Builder newBuilder() {
+    return new Builder(this);
+  }
+
+  /**
+   * Returns the cache control directives for this response. This is never null,
+   * even if this response contains no {@code Cache-Control} header.
+   */
+  public CacheControl cacheControl() {
+    CacheControl result = cacheControl;
+    return result != null ? result : (cacheControl = CacheControl.parse(headers));
+  }
+
+  public boolean isHttps() {
+    return url.isHttps();
+  }
+
+  @Override public String toString() {
+    return "Request{method="
+        + method
+        + ", url="
+        + url
+        + ", tag="
+        + (tag != this ? tag : null)
+        + '}';
+  }
+
+  /**
+   * @hide This class is not part of the Android public SDK API
+   */
+  public static class Builder {
+    private HttpUrl url;
+    private String method;
+    private Headers.Builder headers;
+    private RequestBody body;
+    private Object tag;
+
+    public Builder() {
+      this.method = "GET";
+      this.headers = new Headers.Builder();
+    }
+
+    private Builder(Request request) {
+      this.url = request.url;
+      this.method = request.method;
+      this.body = request.body;
+      this.tag = request.tag;
+      this.headers = request.headers.newBuilder();
+    }
+
+    public Builder url(HttpUrl url) {
+      if (url == null) throw new IllegalArgumentException("url == null");
+      this.url = url;
+      return this;
+    }
+
+    /**
+     * Sets the URL target of this request.
+     *
+     * @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this
+     *     exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs.
+     */
+    public Builder url(String url) {
+      if (url == null) throw new IllegalArgumentException("url == null");
+
+      // Silently replace websocket URLs with HTTP URLs.
+      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
+        url = "http:" + url.substring(3);
+      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
+        url = "https:" + url.substring(4);
+      }
+
+      HttpUrl parsed = HttpUrl.parse(url);
+      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
+      return url(parsed);
+    }
+
+    /**
+     * Sets the URL target of this request.
+     *
+     * @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code
+     *     https}.
+     */
+    public Builder url(URL url) {
+      if (url == null) throw new IllegalArgumentException("url == null");
+      HttpUrl parsed = HttpUrl.get(url);
+      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
+      return url(parsed);
+    }
+
+    /**
+     * Sets the header named {@code name} to {@code value}. If this request
+     * already has any headers with that name, they are all replaced.
+     */
+    public Builder header(String name, String value) {
+      headers.set(name, value);
+      return this;
+    }
+
+    /**
+     * Adds a header with {@code name} and {@code value}. Prefer this method for
+     * multiply-valued headers like "Cookie".
+     *
+     * <p>Note that for some headers including {@code Content-Length} and {@code Content-Encoding},
+     * OkHttp may replace {@code value} with a header derived from the request body.
+     */
+    public Builder addHeader(String name, String value) {
+      headers.add(name, value);
+      return this;
+    }
+
+    public Builder removeHeader(String name) {
+      headers.removeAll(name);
+      return this;
+    }
+
+    /** Removes all headers on this builder and adds {@code headers}. */
+    public Builder headers(Headers headers) {
+      this.headers = headers.newBuilder();
+      return this;
+    }
+
+    /**
+     * Sets this request's {@code Cache-Control} header, replacing any cache
+     * control headers already present. If {@code cacheControl} doesn't define
+     * any directives, this clears this request's cache-control headers.
+     */
+    public Builder cacheControl(CacheControl cacheControl) {
+      String value = cacheControl.toString();
+      if (value.isEmpty()) return removeHeader("Cache-Control");
+      return header("Cache-Control", value);
+    }
+
+    public Builder get() {
+      return method("GET", null);
+    }
+
+    public Builder head() {
+      return method("HEAD", null);
+    }
+
+    public Builder post(RequestBody body) {
+      return method("POST", body);
+    }
+
+    public Builder delete(RequestBody body) {
+      return method("DELETE", body);
+    }
+
+    public Builder delete() {
+      return delete(RequestBody.create(null, new byte[0]));
+    }
+
+    public Builder put(RequestBody body) {
+      return method("PUT", body);
+    }
+
+    public Builder patch(RequestBody body) {
+      return method("PATCH", body);
+    }
+
+    public Builder method(String method, RequestBody body) {
+      if (method == null || method.length() == 0) {
+        throw new IllegalArgumentException("method == null || method.length() == 0");
+      }
+      if (body != null && !HttpMethod.permitsRequestBody(method)) {
+        throw new IllegalArgumentException("method " + method + " must not have a request body.");
+      }
+      if (body == null && HttpMethod.requiresRequestBody(method)) {
+        throw new IllegalArgumentException("method " + method + " must have a request body.");
+      }
+      this.method = method;
+      this.body = body;
+      return this;
+    }
+
+    /**
+     * Attaches {@code tag} to the request. It can be used later to cancel the
+     * request. If the tag is unspecified or null, the request is canceled by
+     * using the request itself as the tag.
+     */
+    public Builder tag(Object tag) {
+      this.tag = tag;
+      return this;
+    }
+
+    public Request build() {
+      if (url == null) throw new IllegalStateException("url == null");
+      return new Request(this);
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/RequestBody.java b/repackaged/okhttp/src/main/java/com/android/okhttp/RequestBody.java
new file mode 100644
index 0000000..a762d8a
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/RequestBody.java
@@ -0,0 +1,129 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Source;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public abstract class RequestBody {
+  /** Returns the Content-Type header for this body. */
+  public abstract MediaType contentType();
+
+  /**
+   * Returns the number of bytes that will be written to {@code out} in a call
+   * to {@link #writeTo}, or -1 if that count is unknown.
+   */
+  public long contentLength() throws IOException {
+    return -1;
+  }
+
+  /** Writes the content of this request to {@code out}. */
+  public abstract void writeTo(BufferedSink sink) throws IOException;
+
+  /**
+   * Returns a new request body that transmits {@code content}. If {@code
+   * contentType} is non-null and lacks a charset, this will use UTF-8.
+   */
+  public static RequestBody create(MediaType contentType, String content) {
+    Charset charset = Util.UTF_8;
+    if (contentType != null) {
+      charset = contentType.charset();
+      if (charset == null) {
+        charset = Util.UTF_8;
+        contentType = MediaType.parse(contentType + "; charset=utf-8");
+      }
+    }
+    byte[] bytes = content.getBytes(charset);
+    return create(contentType, bytes);
+  }
+
+  /** Returns a new request body that transmits {@code content}. */
+  public static RequestBody create(final MediaType contentType, final ByteString content) {
+    return new RequestBody() {
+      @Override public MediaType contentType() {
+        return contentType;
+      }
+
+      @Override public long contentLength() throws IOException {
+        return content.size();
+      }
+
+      @Override public void writeTo(BufferedSink sink) throws IOException {
+        sink.write(content);
+      }
+    };
+  }
+
+  /** Returns a new request body that transmits {@code content}. */
+  public static RequestBody create(final MediaType contentType, final byte[] content) {
+    return create(contentType, content, 0, content.length);
+  }
+
+  /** Returns a new request body that transmits {@code content}. */
+  public static RequestBody create(final MediaType contentType, final byte[] content,
+      final int offset, final int byteCount) {
+    if (content == null) throw new NullPointerException("content == null");
+    Util.checkOffsetAndCount(content.length, offset, byteCount);
+    return new RequestBody() {
+      @Override public MediaType contentType() {
+        return contentType;
+      }
+
+      @Override public long contentLength() {
+        return byteCount;
+      }
+
+      @Override public void writeTo(BufferedSink sink) throws IOException {
+        sink.write(content, offset, byteCount);
+      }
+    };
+  }
+
+  /** Returns a new request body that transmits the content of {@code file}. */
+  public static RequestBody create(final MediaType contentType, final File file) {
+    if (file == null) throw new NullPointerException("content == null");
+
+    return new RequestBody() {
+      @Override public MediaType contentType() {
+        return contentType;
+      }
+
+      @Override public long contentLength() {
+        return file.length();
+      }
+
+      @Override public void writeTo(BufferedSink sink) throws IOException {
+        Source source = null;
+        try {
+          source = Okio.source(file);
+          sink.writeAll(source);
+        } finally {
+          Util.closeQuietly(source);
+        }
+      }
+    };
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Response.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Response.java
new file mode 100644
index 0000000..f4bba1b
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Response.java
@@ -0,0 +1,357 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.http.OkHeaders;
+import java.util.Collections;
+import java.util.List;
+
+import static com.android.okhttp.internal.http.StatusLine.HTTP_PERM_REDIRECT;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
+import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
+import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+
+/**
+ * An HTTP response. Instances of this class are not immutable: the response
+ * body is a one-shot value that may be consumed only once. All other properties
+ * are immutable.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Response {
+  private final Request request;
+  private final Protocol protocol;
+  private final int code;
+  private final String message;
+  private final Handshake handshake;
+  private final Headers headers;
+  private final ResponseBody body;
+  private Response networkResponse;
+  private Response cacheResponse;
+  private final Response priorResponse;
+
+  private volatile CacheControl cacheControl; // Lazily initialized.
+
+  private Response(Builder builder) {
+    this.request = builder.request;
+    this.protocol = builder.protocol;
+    this.code = builder.code;
+    this.message = builder.message;
+    this.handshake = builder.handshake;
+    this.headers = builder.headers.build();
+    this.body = builder.body;
+    this.networkResponse = builder.networkResponse;
+    this.cacheResponse = builder.cacheResponse;
+    this.priorResponse = builder.priorResponse;
+  }
+
+  /**
+   * The wire-level request that initiated this HTTP response. This is not
+   * necessarily the same request issued by the application:
+   * <ul>
+   *     <li>It may be transformed by the HTTP client. For example, the client
+   *         may copy headers like {@code Content-Length} from the request body.
+   *     <li>It may be the request generated in response to an HTTP redirect or
+   *         authentication challenge. In this case the request URL may be
+   *         different than the initial request URL.
+   * </ul>
+   */
+  public Request request() {
+    return request;
+  }
+
+  /**
+   * Returns the HTTP protocol, such as {@link Protocol#HTTP_1_1} or {@link
+   * Protocol#HTTP_1_0}.
+   */
+  public Protocol protocol() {
+    return protocol;
+  }
+
+  /** Returns the HTTP status code. */
+  public int code() {
+    return code;
+  }
+
+  /**
+   * Returns true if the code is in [200..300), which means the request was
+   * successfully received, understood, and accepted.
+   */
+  public boolean isSuccessful() {
+    return code >= 200 && code < 300;
+  }
+
+  /** Returns the HTTP status message or null if it is unknown. */
+  public String message() {
+    return message;
+  }
+
+  /**
+   * Returns the TLS handshake of the connection that carried this response, or
+   * null if the response was received without TLS.
+   */
+  public Handshake handshake() {
+    return handshake;
+  }
+
+  public List<String> headers(String name) {
+    return headers.values(name);
+  }
+
+  public String header(String name) {
+    return header(name, null);
+  }
+
+  public String header(String name, String defaultValue) {
+    String result = headers.get(name);
+    return result != null ? result : defaultValue;
+  }
+
+  public Headers headers() {
+    return headers;
+  }
+
+  public ResponseBody body() {
+    return body;
+  }
+
+  public Builder newBuilder() {
+    return new Builder(this);
+  }
+
+  /** Returns true if this response redirects to another resource. */
+  public boolean isRedirect() {
+    switch (code) {
+      case HTTP_PERM_REDIRECT:
+      case HTTP_TEMP_REDIRECT:
+      case HTTP_MULT_CHOICE:
+      case HTTP_MOVED_PERM:
+      case HTTP_MOVED_TEMP:
+      case HTTP_SEE_OTHER:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Returns the raw response received from the network. Will be null if this
+   * response didn't use the network, such as when the response is fully cached.
+   * The body of the returned response should not be read.
+   */
+  public Response networkResponse() {
+    return networkResponse;
+  }
+
+  /**
+   * Returns the raw response received from the cache. Will be null if this
+   * response didn't use the cache. For conditional get requests the cache
+   * response and network response may both be non-null. The body of the
+   * returned response should not be read.
+   */
+  public Response cacheResponse() {
+    return cacheResponse;
+  }
+
+  /**
+   * Returns the response for the HTTP redirect or authorization challenge that
+   * triggered this response, or null if this response wasn't triggered by an
+   * automatic retry. The body of the returned response should not be read
+   * because it has already been consumed by the redirecting client.
+   */
+  public Response priorResponse() {
+    return priorResponse;
+  }
+
+  /**
+   * Returns the authorization challenges appropriate for this response's code.
+   * If the response code is 401 unauthorized, this returns the
+   * "WWW-Authenticate" challenges. If the response code is 407 proxy
+   * unauthorized, this returns the "Proxy-Authenticate" challenges. Otherwise
+   * this returns an empty list of challenges.
+   */
+  public List<Challenge> challenges() {
+    String responseField;
+    if (code == HTTP_UNAUTHORIZED) {
+      responseField = "WWW-Authenticate";
+    } else if (code == HTTP_PROXY_AUTH) {
+      responseField = "Proxy-Authenticate";
+    } else {
+      return Collections.emptyList();
+    }
+    return OkHeaders.parseChallenges(headers(), responseField);
+  }
+
+  /**
+   * Returns the cache control directives for this response. This is never null,
+   * even if this response contains no {@code Cache-Control} header.
+   */
+  public CacheControl cacheControl() {
+    CacheControl result = cacheControl;
+    return result != null ? result : (cacheControl = CacheControl.parse(headers));
+  }
+
+  @Override public String toString() {
+    return "Response{protocol="
+        + protocol
+        + ", code="
+        + code
+        + ", message="
+        + message
+        + ", url="
+        + request.urlString()
+        + '}';
+  }
+
+  /**
+   * @hide This class is not part of the Android public SDK API
+   */
+  public static class Builder {
+    private Request request;
+    private Protocol protocol;
+    private int code = -1;
+    private String message;
+    private Handshake handshake;
+    private Headers.Builder headers;
+    private ResponseBody body;
+    private Response networkResponse;
+    private Response cacheResponse;
+    private Response priorResponse;
+
+    public Builder() {
+      headers = new Headers.Builder();
+    }
+
+    private Builder(Response response) {
+      this.request = response.request;
+      this.protocol = response.protocol;
+      this.code = response.code;
+      this.message = response.message;
+      this.handshake = response.handshake;
+      this.headers = response.headers.newBuilder();
+      this.body = response.body;
+      this.networkResponse = response.networkResponse;
+      this.cacheResponse = response.cacheResponse;
+      this.priorResponse = response.priorResponse;
+    }
+
+    public Builder request(Request request) {
+      this.request = request;
+      return this;
+    }
+
+    public Builder protocol(Protocol protocol) {
+      this.protocol = protocol;
+      return this;
+    }
+
+    public Builder code(int code) {
+      this.code = code;
+      return this;
+    }
+
+    public Builder message(String message) {
+      this.message = message;
+      return this;
+    }
+
+    public Builder handshake(Handshake handshake) {
+      this.handshake = handshake;
+      return this;
+    }
+
+    /**
+     * Sets the header named {@code name} to {@code value}. If this request
+     * already has any headers with that name, they are all replaced.
+     */
+    public Builder header(String name, String value) {
+      headers.set(name, value);
+      return this;
+    }
+
+    /**
+     * Adds a header with {@code name} and {@code value}. Prefer this method for
+     * multiply-valued headers like "Set-Cookie".
+     */
+    public Builder addHeader(String name, String value) {
+      headers.add(name, value);
+      return this;
+    }
+
+    public Builder removeHeader(String name) {
+      headers.removeAll(name);
+      return this;
+    }
+
+    /** Removes all headers on this builder and adds {@code headers}. */
+    public Builder headers(Headers headers) {
+      this.headers = headers.newBuilder();
+      return this;
+    }
+
+    public Builder body(ResponseBody body) {
+      this.body = body;
+      return this;
+    }
+
+    public Builder networkResponse(Response networkResponse) {
+      if (networkResponse != null) checkSupportResponse("networkResponse", networkResponse);
+      this.networkResponse = networkResponse;
+      return this;
+    }
+
+    public Builder cacheResponse(Response cacheResponse) {
+      if (cacheResponse != null) checkSupportResponse("cacheResponse", cacheResponse);
+      this.cacheResponse = cacheResponse;
+      return this;
+    }
+
+    private void checkSupportResponse(String name, Response response) {
+      if (response.body != null) {
+        throw new IllegalArgumentException(name + ".body != null");
+      } else if (response.networkResponse != null) {
+        throw new IllegalArgumentException(name + ".networkResponse != null");
+      } else if (response.cacheResponse != null) {
+        throw new IllegalArgumentException(name + ".cacheResponse != null");
+      } else if (response.priorResponse != null) {
+        throw new IllegalArgumentException(name + ".priorResponse != null");
+      }
+    }
+
+    public Builder priorResponse(Response priorResponse) {
+      if (priorResponse != null) checkPriorResponse(priorResponse);
+      this.priorResponse = priorResponse;
+      return this;
+    }
+
+    private void checkPriorResponse(Response response) {
+      if (response.body != null) {
+        throw new IllegalArgumentException("priorResponse.body != null");
+      }
+    }
+
+    public Response build() {
+      if (request == null) throw new IllegalStateException("request == null");
+      if (protocol == null) throw new IllegalStateException("protocol == null");
+      if (code < 0) throw new IllegalStateException("code < 0: " + code);
+      return new Response(this);
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/ResponseBody.java b/repackaged/okhttp/src/main/java/com/android/okhttp/ResponseBody.java
new file mode 100644
index 0000000..2596a3a
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/ResponseBody.java
@@ -0,0 +1,140 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import com.android.okhttp.internal.Util;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSource;
+
+import static com.android.okhttp.internal.Util.UTF_8;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public abstract class ResponseBody implements Closeable {
+  /** Multiple calls to {@link #charStream()} must return the same instance. */
+  private Reader reader;
+
+  public abstract MediaType contentType();
+
+  /**
+   * Returns the number of bytes in that will returned by {@link #bytes}, or
+   * {@link #byteStream}, or -1 if unknown.
+   */
+  public abstract long contentLength() throws IOException;
+
+  public final InputStream byteStream() throws IOException {
+    return source().inputStream();
+  }
+
+  public abstract BufferedSource source() throws IOException;
+
+  public final byte[] bytes() throws IOException {
+    long contentLength = contentLength();
+    if (contentLength > Integer.MAX_VALUE) {
+      throw new IOException("Cannot buffer entire body for content length: " + contentLength);
+    }
+
+    BufferedSource source = source();
+    byte[] bytes;
+    try {
+      bytes = source.readByteArray();
+    } finally {
+      Util.closeQuietly(source);
+    }
+    if (contentLength != -1 && contentLength != bytes.length) {
+      throw new IOException("Content-Length and stream length disagree");
+    }
+    return bytes;
+  }
+
+  /**
+   * Returns the response as a character stream decoded with the charset
+   * of the Content-Type header. If that header is either absent or lacks a
+   * charset, this will attempt to decode the response body as UTF-8.
+   */
+  public final Reader charStream() throws IOException {
+    Reader r = reader;
+    return r != null ? r : (reader = new InputStreamReader(byteStream(), charset()));
+  }
+
+  /**
+   * Returns the response as a string decoded with the charset of the
+   * Content-Type header. If that header is either absent or lacks a charset,
+   * this will attempt to decode the response body as UTF-8.
+   */
+  public final String string() throws IOException {
+    return new String(bytes(), charset().name());
+  }
+
+  private Charset charset() {
+    MediaType contentType = contentType();
+    return contentType != null ? contentType.charset(UTF_8) : UTF_8;
+  }
+
+  @Override public void close() throws IOException {
+    source().close();
+  }
+
+  /**
+   * Returns a new response body that transmits {@code content}. If {@code
+   * contentType} is non-null and lacks a charset, this will use UTF-8.
+   */
+  public static ResponseBody create(MediaType contentType, String content) {
+    Charset charset = Util.UTF_8;
+    if (contentType != null) {
+      charset = contentType.charset();
+      if (charset == null) {
+        charset = Util.UTF_8;
+        contentType = MediaType.parse(contentType + "; charset=utf-8");
+      }
+    }
+    Buffer buffer = new Buffer().writeString(content, charset);
+    return create(contentType, buffer.size(), buffer);
+  }
+
+  /** Returns a new response body that transmits {@code content}. */
+  public static ResponseBody create(final MediaType contentType, byte[] content) {
+    Buffer buffer = new Buffer().write(content);
+    return create(contentType, content.length, buffer);
+  }
+
+  /** Returns a new response body that transmits {@code content}. */
+  public static ResponseBody create(
+      final MediaType contentType, final long contentLength, final BufferedSource content) {
+    if (content == null) throw new NullPointerException("source == null");
+    return new ResponseBody() {
+      @Override public MediaType contentType() {
+        return contentType;
+      }
+
+      @Override public long contentLength() {
+        return contentLength;
+      }
+
+      @Override public BufferedSource source() {
+        return content;
+      }
+    };
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/Route.java b/repackaged/okhttp/src/main/java/com/android/okhttp/Route.java
new file mode 100644
index 0000000..ba33f86
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/Route.java
@@ -0,0 +1,100 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
+/**
+ * The concrete route used by a connection to reach an abstract origin server.
+ * When creating a connection the client has many options:
+ * <ul>
+ *   <li><strong>HTTP proxy:</strong> a proxy server may be explicitly
+ *       configured for the client. Otherwise the {@linkplain java.net.ProxySelector
+ *       proxy selector} is used. It may return multiple proxies to attempt.
+ *   <li><strong>IP address:</strong> whether connecting directly to an origin
+ *       server or a proxy, opening a socket requires an IP address. The DNS
+ *       server may return multiple IP addresses to attempt.
+ * </ul>
+ * Each route is a specific selection of these options.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Route {
+  final Address address;
+  final Proxy proxy;
+  final InetSocketAddress inetSocketAddress;
+
+  public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress) {
+    if (address == null) {
+      throw new NullPointerException("address == null");
+    }
+    if (proxy == null) {
+      throw new NullPointerException("proxy == null");
+    }
+    if (inetSocketAddress == null) {
+      throw new NullPointerException("inetSocketAddress == null");
+    }
+    this.address = address;
+    this.proxy = proxy;
+    this.inetSocketAddress = inetSocketAddress;
+  }
+
+  public Address getAddress() {
+    return address;
+  }
+
+  /**
+   * Returns the {@link Proxy} of this route.
+   *
+   * <strong>Warning:</strong> This may disagree with {@link Address#getProxy}
+   * when it is null. When the address's proxy is null, the proxy selector is
+   * used.
+   */
+  public Proxy getProxy() {
+    return proxy;
+  }
+
+  public InetSocketAddress getSocketAddress() {
+    return inetSocketAddress;
+  }
+
+  /**
+   * Returns true if this route tunnels HTTPS through an HTTP proxy. See <a
+   * href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section 5.2</a>.
+   */
+  public boolean requiresTunnel() {
+    return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj instanceof Route) {
+      Route other = (Route) obj;
+      return address.equals(other.address)
+          && proxy.equals(other.proxy)
+          && inetSocketAddress.equals(other.inetSocketAddress);
+    }
+    return false;
+  }
+
+  @Override public int hashCode() {
+    int result = 17;
+    result = 31 * result + address.hashCode();
+    result = 31 * result + proxy.hashCode();
+    result = 31 * result + inetSocketAddress.hashCode();
+    return result;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/TlsVersion.java b/repackaged/okhttp/src/main/java/com/android/okhttp/TlsVersion.java
new file mode 100644
index 0000000..c8cd1e3
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/TlsVersion.java
@@ -0,0 +1,52 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp;
+
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Versions of TLS that can be offered when negotiating a secure socket. See
+ * {@link SSLSocket#setEnabledProtocols}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public enum TlsVersion {
+  TLS_1_2("TLSv1.2"), // 2008.
+  TLS_1_1("TLSv1.1"), // 2006.
+  TLS_1_0("TLSv1"),   // 1999.
+  SSL_3_0("SSLv3"),   // 1996.
+  ;
+
+  final String javaName;
+
+  TlsVersion(String javaName) {
+    this.javaName = javaName;
+  }
+
+  public static TlsVersion forJavaName(String javaName) {
+    switch (javaName) {
+      case "TLSv1.2": return TLS_1_2;
+      case "TLSv1.1": return TLS_1_1;
+      case "TLSv1": return TLS_1_0;
+      case "SSLv3": return SSL_3_0;
+    }
+    throw new IllegalArgumentException("Unexpected TLS version: " + javaName);
+  }
+
+  public String javaName() {
+    return javaName;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/ConnectionSpecSelector.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/ConnectionSpecSelector.java
new file mode 100644
index 0000000..c91a00a
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/ConnectionSpecSelector.java
@@ -0,0 +1,145 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal;
+
+import com.android.okhttp.ConnectionSpec;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.ProtocolException;
+import java.net.UnknownServiceException;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+import java.util.List;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLProtocolException;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Handles the connection spec fallback strategy: When a secure socket connection fails
+ * due to a handshake / protocol problem the connection may be retried with different protocols.
+ * Instances are stateful and should be created and used for a single connection attempt.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class ConnectionSpecSelector {
+
+  private final List<ConnectionSpec> connectionSpecs;
+  private int nextModeIndex;
+  private boolean isFallbackPossible;
+  private boolean isFallback;
+
+  public ConnectionSpecSelector(List<ConnectionSpec> connectionSpecs) {
+    this.nextModeIndex = 0;
+    this.connectionSpecs = connectionSpecs;
+  }
+
+  /**
+   * Configures the supplied {@link SSLSocket} to connect to the specified host using an appropriate
+   * {@link ConnectionSpec}. Returns the chosen {@link ConnectionSpec}, never {@code null}.
+   *
+   * @throws IOException if the socket does not support any of the TLS modes available
+   */
+  public ConnectionSpec configureSecureSocket(SSLSocket sslSocket) throws IOException {
+    ConnectionSpec tlsConfiguration = null;
+    for (int i = nextModeIndex, size = connectionSpecs.size(); i < size; i++) {
+      ConnectionSpec connectionSpec = connectionSpecs.get(i);
+      if (connectionSpec.isCompatible(sslSocket)) {
+        tlsConfiguration = connectionSpec;
+        nextModeIndex = i + 1;
+        break;
+      }
+    }
+
+    if (tlsConfiguration == null) {
+      // This may be the first time a connection has been attempted and the socket does not support
+      // any the required protocols, or it may be a retry (but this socket supports fewer
+      // protocols than was suggested by a prior socket).
+      throw new UnknownServiceException(
+          "Unable to find acceptable protocols. isFallback=" + isFallback
+              + ", modes=" + connectionSpecs
+              + ", supported protocols=" + Arrays.toString(sslSocket.getEnabledProtocols()));
+    }
+
+    isFallbackPossible = isFallbackPossible(sslSocket);
+
+    Internal.instance.apply(tlsConfiguration, sslSocket, isFallback);
+
+    return tlsConfiguration;
+  }
+
+  /**
+   * Reports a failure to complete a connection. Determines the next {@link ConnectionSpec} to
+   * try, if any.
+   *
+   * @return {@code true} if the connection should be retried using
+   *     {@link #configureSecureSocket(SSLSocket)} or {@code false} if not
+   */
+  public boolean connectionFailed(IOException e) {
+    // Any future attempt to connect using this strategy will be a fallback attempt.
+    isFallback = true;
+
+    if (!isFallbackPossible) {
+      return false;
+    }
+
+    // If there was a protocol problem, don't recover.
+    if (e instanceof ProtocolException) {
+      return false;
+    }
+
+    // If there was an interruption or timeout (SocketTimeoutException), don't recover.
+    // For the socket connect timeout case we do not try the same host with a different
+    // ConnectionSpec: we assume it is unreachable.
+    if (e instanceof InterruptedIOException) {
+      return false;
+    }
+
+    // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
+    // again with a different connection spec.
+    if (e instanceof SSLHandshakeException) {
+      // If the problem was a CertificateException from the X509TrustManager,
+      // do not retry.
+      if (e.getCause() instanceof CertificateException) {
+        return false;
+      }
+    }
+    if (e instanceof SSLPeerUnverifiedException) {
+      // e.g. a certificate pinning error.
+      return false;
+    }
+
+
+    // On Android, SSLProtocolExceptions can be caused by TLS_FALLBACK_SCSV failures, which means we
+    // retry those when we probably should not.
+    return (e instanceof SSLHandshakeException || e instanceof SSLProtocolException);
+  }
+
+  /**
+   * Returns {@code true} if any later {@link ConnectionSpec} in the fallback strategy looks
+   * possible based on the supplied {@link SSLSocket}. It assumes that a future socket will have the
+   * same capabilities as the supplied socket.
+   */
+  private boolean isFallbackPossible(SSLSocket socket) {
+    for (int i = nextModeIndex; i < connectionSpecs.size(); i++) {
+      if (connectionSpecs.get(i).isCompatible(socket)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/DiskLruCache.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/DiskLruCache.java
new file mode 100644
index 0000000..2169de9
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/DiskLruCache.java
@@ -0,0 +1,1048 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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 com.android.okhttp.internal;
+
+import com.android.okhttp.internal.io.FileSystem;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.NoSuchElementException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+import com.android.okhttp.okio.Timeout;
+
+/**
+ * A cache that uses a bounded amount of space on a filesystem. Each cache
+ * entry has a string key and a fixed number of values. Each key must match
+ * the regex <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences,
+ * accessible as streams or files. Each value must be between {@code 0} and
+ * {@code Integer.MAX_VALUE} bytes in length.
+ *
+ * <p>The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ * <p>This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ * <p>Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ * <ul>
+ *     <li>When an entry is being <strong>created</strong> it is necessary to
+ *         supply a full set of values; the empty value should be used as a
+ *         placeholder if necessary.
+ *     <li>When an entry is being <strong>edited</strong>, it is not necessary
+ *         to supply data for every value; values default to their previous
+ *         value.
+ * </ul>
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ * <p>This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class DiskLruCache implements Closeable {
+  static final String JOURNAL_FILE = "journal";
+  static final String JOURNAL_FILE_TEMP = "journal.tmp";
+  static final String JOURNAL_FILE_BACKUP = "journal.bkp";
+  static final String MAGIC = "libcore.io.DiskLruCache";
+  static final String VERSION_1 = "1";
+  static final long ANY_SEQUENCE_NUMBER = -1;
+  static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,120}");
+  private static final String CLEAN = "CLEAN";
+  private static final String DIRTY = "DIRTY";
+  private static final String REMOVE = "REMOVE";
+  private static final String READ = "READ";
+
+    /*
+     * This cache uses a journal file named "journal". A typical journal file
+     * looks like this:
+     *     libcore.io.DiskLruCache
+     *     1
+     *     100
+     *     2
+     *
+     *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
+     *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
+     *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
+     *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
+     *     DIRTY 1ab96a171faeeee38496d8b330771a7a
+     *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
+     *     READ 335c4c6028171cfddfbaae1a9c313c52
+     *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
+     *
+     * The first five lines of the journal form its header. They are the
+     * constant string "libcore.io.DiskLruCache", the disk cache's version,
+     * the application's version, the value count, and a blank line.
+     *
+     * Each of the subsequent lines in the file is a record of the state of a
+     * cache entry. Each line contains space-separated values: a state, a key,
+     * and optional state-specific values.
+     *   o DIRTY lines track that an entry is actively being created or updated.
+     *     Every successful DIRTY action should be followed by a CLEAN or REMOVE
+     *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+     *     temporary files may need to be deleted.
+     *   o CLEAN lines track a cache entry that has been successfully published
+     *     and may be read. A publish line is followed by the lengths of each of
+     *     its values.
+     *   o READ lines track accesses for LRU.
+     *   o REMOVE lines track entries that have been deleted.
+     *
+     * The journal file is appended to as cache operations occur. The journal may
+     * occasionally be compacted by dropping redundant lines. A temporary file named
+     * "journal.tmp" will be used during compaction; that file should be deleted if
+     * it exists when the cache is opened.
+     */
+
+  private final FileSystem fileSystem;
+  private final File directory;
+  private final File journalFile;
+  private final File journalFileTmp;
+  private final File journalFileBackup;
+  private final int appVersion;
+  private long maxSize;
+  private final int valueCount;
+  private long size = 0;
+  private BufferedSink journalWriter;
+  private final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);
+  private int redundantOpCount;
+  private boolean hasJournalErrors;
+
+  // Must be read and written when synchronized on 'this'.
+  private boolean initialized;
+  private boolean closed;
+
+  /**
+   * To differentiate between old and current snapshots, each entry is given
+   * a sequence number each time an edit is committed. A snapshot is stale if
+   * its sequence number is not equal to its entry's sequence number.
+   */
+  private long nextSequenceNumber = 0;
+
+  /** Used to run 'cleanupRunnable' for journal rebuilds. */
+  private final Executor executor;
+  private final Runnable cleanupRunnable = new Runnable() {
+    public void run() {
+      synchronized (DiskLruCache.this) {
+        if (!initialized | closed) {
+          return; // Nothing to do
+        }
+        try {
+          trimToSize();
+          if (journalRebuildRequired()) {
+            rebuildJournal();
+            redundantOpCount = 0;
+          }
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    }
+  };
+
+  DiskLruCache(FileSystem fileSystem, File directory, int appVersion, int valueCount, long maxSize,
+      Executor executor) {
+    this.fileSystem = fileSystem;
+    this.directory = directory;
+    this.appVersion = appVersion;
+    this.journalFile = new File(directory, JOURNAL_FILE);
+    this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
+    this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
+    this.valueCount = valueCount;
+    this.maxSize = maxSize;
+    this.executor = executor;
+  }
+
+  public synchronized void initialize() throws IOException {
+    assert Thread.holdsLock(this);
+
+    if (initialized) {
+      return; // Already initialized.
+    }
+
+    // If a bkp file exists, use it instead.
+    if (fileSystem.exists(journalFileBackup)) {
+      // If journal file also exists just delete backup file.
+      if (fileSystem.exists(journalFile)) {
+        fileSystem.delete(journalFileBackup);
+      } else {
+        fileSystem.rename(journalFileBackup, journalFile);
+      }
+    }
+
+    // Prefer to pick up where we left off.
+    if (fileSystem.exists(journalFile)) {
+      try {
+        readJournal();
+        processJournal();
+        initialized = true;
+        return;
+      } catch (IOException journalIsCorrupt) {
+        Platform.get().logW("DiskLruCache " + directory + " is corrupt: "
+            + journalIsCorrupt.getMessage() + ", removing");
+        delete();
+        closed = false;
+      }
+    }
+
+    rebuildJournal();
+
+    initialized = true;
+  }
+
+  /**
+   * Create a cache which will reside in {@code directory}. This cache is lazily initialized on
+   * first access and will be created if it does not exist.
+   *
+   * @param directory a writable directory
+   * @param valueCount the number of values per cache entry. Must be positive.
+   * @param maxSize the maximum number of bytes this cache should use to store
+   */
+  public static DiskLruCache create(FileSystem fileSystem, File directory, int appVersion,
+      int valueCount, long maxSize) {
+    if (maxSize <= 0) {
+      throw new IllegalArgumentException("maxSize <= 0");
+    }
+    if (valueCount <= 0) {
+      throw new IllegalArgumentException("valueCount <= 0");
+    }
+
+    // Use a single background thread to evict entries.
+    Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
+        new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true));
+
+    return new DiskLruCache(fileSystem, directory, appVersion, valueCount, maxSize, executor);
+  }
+
+  private void readJournal() throws IOException {
+    BufferedSource source = Okio.buffer(fileSystem.source(journalFile));
+    try {
+      String magic = source.readUtf8LineStrict();
+      String version = source.readUtf8LineStrict();
+      String appVersionString = source.readUtf8LineStrict();
+      String valueCountString = source.readUtf8LineStrict();
+      String blank = source.readUtf8LineStrict();
+      if (!MAGIC.equals(magic)
+          || !VERSION_1.equals(version)
+          || !Integer.toString(appVersion).equals(appVersionString)
+          || !Integer.toString(valueCount).equals(valueCountString)
+          || !"".equals(blank)) {
+        throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+            + valueCountString + ", " + blank + "]");
+      }
+
+      int lineCount = 0;
+      while (true) {
+        try {
+          readJournalLine(source.readUtf8LineStrict());
+          lineCount++;
+        } catch (EOFException endOfJournal) {
+          break;
+        }
+      }
+      redundantOpCount = lineCount - lruEntries.size();
+
+      // If we ended on a truncated line, rebuild the journal before appending to it.
+      if (!source.exhausted()) {
+        rebuildJournal();
+      } else {
+        journalWriter = newJournalWriter();
+      }
+    } finally {
+      Util.closeQuietly(source);
+    }
+  }
+
+  private BufferedSink newJournalWriter() throws FileNotFoundException {
+    Sink fileSink = fileSystem.appendingSink(journalFile);
+    Sink faultHidingSink = new FaultHidingSink(fileSink) {
+      @Override protected void onException(IOException e) {
+        assert (Thread.holdsLock(DiskLruCache.this));
+        hasJournalErrors = true;
+      }
+    };
+    return Okio.buffer(faultHidingSink);
+  }
+
+  private void readJournalLine(String line) throws IOException {
+    int firstSpace = line.indexOf(' ');
+    if (firstSpace == -1) {
+      throw new IOException("unexpected journal line: " + line);
+    }
+
+    int keyBegin = firstSpace + 1;
+    int secondSpace = line.indexOf(' ', keyBegin);
+    final String key;
+    if (secondSpace == -1) {
+      key = line.substring(keyBegin);
+      if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
+        lruEntries.remove(key);
+        return;
+      }
+    } else {
+      key = line.substring(keyBegin, secondSpace);
+    }
+
+    Entry entry = lruEntries.get(key);
+    if (entry == null) {
+      entry = new Entry(key);
+      lruEntries.put(key, entry);
+    }
+
+    if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
+      String[] parts = line.substring(secondSpace + 1).split(" ");
+      entry.readable = true;
+      entry.currentEditor = null;
+      entry.setLengths(parts);
+    } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
+      entry.currentEditor = new Editor(entry);
+    } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
+      // This work was already done by calling lruEntries.get().
+    } else {
+      throw new IOException("unexpected journal line: " + line);
+    }
+  }
+
+  /**
+   * Computes the initial size and collects garbage as a part of opening the
+   * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+   */
+  private void processJournal() throws IOException {
+    fileSystem.delete(journalFileTmp);
+    for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
+      Entry entry = i.next();
+      if (entry.currentEditor == null) {
+        for (int t = 0; t < valueCount; t++) {
+          size += entry.lengths[t];
+        }
+      } else {
+        entry.currentEditor = null;
+        for (int t = 0; t < valueCount; t++) {
+          fileSystem.delete(entry.cleanFiles[t]);
+          fileSystem.delete(entry.dirtyFiles[t]);
+        }
+        i.remove();
+      }
+    }
+  }
+
+  /**
+   * Creates a new journal that omits redundant information. This replaces the
+   * current journal if it exists.
+   */
+  private synchronized void rebuildJournal() throws IOException {
+    if (journalWriter != null) {
+      journalWriter.close();
+    }
+
+    BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp));
+    try {
+      writer.writeUtf8(MAGIC).writeByte('\n');
+      writer.writeUtf8(VERSION_1).writeByte('\n');
+      writer.writeDecimalLong(appVersion).writeByte('\n');
+      writer.writeDecimalLong(valueCount).writeByte('\n');
+      writer.writeByte('\n');
+
+      for (Entry entry : lruEntries.values()) {
+        if (entry.currentEditor != null) {
+          writer.writeUtf8(DIRTY).writeByte(' ');
+          writer.writeUtf8(entry.key);
+          writer.writeByte('\n');
+        } else {
+          writer.writeUtf8(CLEAN).writeByte(' ');
+          writer.writeUtf8(entry.key);
+          entry.writeLengths(writer);
+          writer.writeByte('\n');
+        }
+      }
+    } finally {
+      writer.close();
+    }
+
+    if (fileSystem.exists(journalFile)) {
+      fileSystem.rename(journalFile, journalFileBackup);
+    }
+    fileSystem.rename(journalFileTmp, journalFile);
+    fileSystem.delete(journalFileBackup);
+
+    journalWriter = newJournalWriter();
+    hasJournalErrors = false;
+  }
+
+  /**
+   * Returns a snapshot of the entry named {@code key}, or null if it doesn't
+   * exist is not currently readable. If a value is returned, it is moved to
+   * the head of the LRU queue.
+   */
+  public synchronized Snapshot get(String key) throws IOException {
+    initialize();
+
+    checkNotClosed();
+    validateKey(key);
+    Entry entry = lruEntries.get(key);
+    if (entry == null || !entry.readable) return null;
+
+    Snapshot snapshot = entry.snapshot();
+    if (snapshot == null) return null;
+
+    redundantOpCount++;
+    journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n');
+    if (journalRebuildRequired()) {
+      executor.execute(cleanupRunnable);
+    }
+
+    return snapshot;
+  }
+
+  /**
+   * Returns an editor for the entry named {@code key}, or null if another
+   * edit is in progress.
+   */
+  public Editor edit(String key) throws IOException {
+    return edit(key, ANY_SEQUENCE_NUMBER);
+  }
+
+  private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
+    initialize();
+
+    checkNotClosed();
+    validateKey(key);
+    Entry entry = lruEntries.get(key);
+    if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
+        || entry.sequenceNumber != expectedSequenceNumber)) {
+      return null; // Snapshot is stale.
+    }
+    if (entry != null && entry.currentEditor != null) {
+      return null; // Another edit is in progress.
+    }
+
+    // Flush the journal before creating files to prevent file leaks.
+    journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n');
+    journalWriter.flush();
+
+    if (hasJournalErrors) {
+      return null; // Don't edit; the journal can't be written.
+    }
+
+    if (entry == null) {
+      entry = new Entry(key);
+      lruEntries.put(key, entry);
+    }
+    Editor editor = new Editor(entry);
+    entry.currentEditor = editor;
+    return editor;
+  }
+
+  /** Returns the directory where this cache stores its data. */
+  public File getDirectory() {
+    return directory;
+  }
+
+  /**
+   * Returns the maximum number of bytes that this cache should use to store
+   * its data.
+   */
+  public synchronized long getMaxSize() {
+    return maxSize;
+  }
+
+  /**
+   * Changes the maximum number of bytes the cache can store and queues a job
+   * to trim the existing store, if necessary.
+   */
+  public synchronized void setMaxSize(long maxSize) {
+    this.maxSize = maxSize;
+    if (initialized) {
+      executor.execute(cleanupRunnable);
+    }
+  }
+
+  /**
+   * Returns the number of bytes currently being used to store the values in
+   * this cache. This may be greater than the max size if a background
+   * deletion is pending.
+   */
+  public synchronized long size() throws IOException {
+    initialize();
+    return size;
+  }
+
+  private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
+    Entry entry = editor.entry;
+    if (entry.currentEditor != editor) {
+      throw new IllegalStateException();
+    }
+
+    // If this edit is creating the entry for the first time, every index must have a value.
+    if (success && !entry.readable) {
+      for (int i = 0; i < valueCount; i++) {
+        if (!editor.written[i]) {
+          editor.abort();
+          throw new IllegalStateException("Newly created entry didn't create value for index " + i);
+        }
+        if (!fileSystem.exists(entry.dirtyFiles[i])) {
+          editor.abort();
+          return;
+        }
+      }
+    }
+
+    for (int i = 0; i < valueCount; i++) {
+      File dirty = entry.dirtyFiles[i];
+      if (success) {
+        if (fileSystem.exists(dirty)) {
+          File clean = entry.cleanFiles[i];
+          fileSystem.rename(dirty, clean);
+          long oldLength = entry.lengths[i];
+          long newLength = fileSystem.size(clean);
+          entry.lengths[i] = newLength;
+          size = size - oldLength + newLength;
+        }
+      } else {
+        fileSystem.delete(dirty);
+      }
+    }
+
+    redundantOpCount++;
+    entry.currentEditor = null;
+    if (entry.readable | success) {
+      entry.readable = true;
+      journalWriter.writeUtf8(CLEAN).writeByte(' ');
+      journalWriter.writeUtf8(entry.key);
+      entry.writeLengths(journalWriter);
+      journalWriter.writeByte('\n');
+      if (success) {
+        entry.sequenceNumber = nextSequenceNumber++;
+      }
+    } else {
+      lruEntries.remove(entry.key);
+      journalWriter.writeUtf8(REMOVE).writeByte(' ');
+      journalWriter.writeUtf8(entry.key);
+      journalWriter.writeByte('\n');
+    }
+    journalWriter.flush();
+
+    if (size > maxSize || journalRebuildRequired()) {
+      executor.execute(cleanupRunnable);
+    }
+  }
+
+  /**
+   * We only rebuild the journal when it will halve the size of the journal
+   * and eliminate at least 2000 ops.
+   */
+  private boolean journalRebuildRequired() {
+    final int redundantOpCompactThreshold = 2000;
+    return redundantOpCount >= redundantOpCompactThreshold
+        && redundantOpCount >= lruEntries.size();
+  }
+
+  /**
+   * Drops the entry for {@code key} if it exists and can be removed. If the
+   * entry for {@code key} is currently being edited, that edit will complete
+   * normally but its value will not be stored.
+   *
+   * @return true if an entry was removed.
+   */
+  public synchronized boolean remove(String key) throws IOException {
+    initialize();
+
+    checkNotClosed();
+    validateKey(key);
+    Entry entry = lruEntries.get(key);
+    if (entry == null) return false;
+    return removeEntry(entry);
+  }
+
+  private boolean removeEntry(Entry entry) throws IOException {
+    if (entry.currentEditor != null) {
+      entry.currentEditor.detach(); // Prevent the edit from completing normally.
+    }
+
+    for (int i = 0; i < valueCount; i++) {
+      fileSystem.delete(entry.cleanFiles[i]);
+      size -= entry.lengths[i];
+      entry.lengths[i] = 0;
+    }
+
+    redundantOpCount++;
+    journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(entry.key).writeByte('\n');
+    lruEntries.remove(entry.key);
+
+    if (journalRebuildRequired()) {
+      executor.execute(cleanupRunnable);
+    }
+
+    return true;
+  }
+
+  /** Returns true if this cache has been closed. */
+  public synchronized boolean isClosed() {
+    return closed;
+  }
+
+  private synchronized void checkNotClosed() {
+    if (isClosed()) {
+      throw new IllegalStateException("cache is closed");
+    }
+  }
+
+  /** Force buffered operations to the filesystem. */
+  public synchronized void flush() throws IOException {
+    if (!initialized) return;
+
+    checkNotClosed();
+    trimToSize();
+    journalWriter.flush();
+  }
+
+  /** Closes this cache. Stored values will remain on the filesystem. */
+  public synchronized void close() throws IOException {
+    if (!initialized || closed) {
+      closed = true;
+      return;
+    }
+    // Copying for safe iteration.
+    for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) {
+      if (entry.currentEditor != null) {
+        entry.currentEditor.abort();
+      }
+    }
+    trimToSize();
+    journalWriter.close();
+    journalWriter = null;
+    closed = true;
+  }
+
+  private void trimToSize() throws IOException {
+    while (size > maxSize) {
+      Entry toEvict = lruEntries.values().iterator().next();
+      removeEntry(toEvict);
+    }
+  }
+
+  /**
+   * Closes the cache and deletes all of its stored values. This will delete
+   * all files in the cache directory including files that weren't created by
+   * the cache.
+   */
+  public void delete() throws IOException {
+    close();
+    fileSystem.deleteContents(directory);
+  }
+
+  /**
+   * Deletes all stored values from the cache. In-flight edits will complete
+   * normally but their values will not be stored.
+   */
+  public synchronized void evictAll() throws IOException {
+    initialize();
+    // Copying for safe iteration.
+    for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) {
+      removeEntry(entry);
+    }
+  }
+
+  private void validateKey(String key) {
+    Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
+    if (!matcher.matches()) {
+      throw new IllegalArgumentException(
+          "keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"");
+    }
+  }
+
+  /**
+   * Returns an iterator over the cache's current entries. This iterator doesn't throw {@code
+   * ConcurrentModificationException}, but if new entries are added while iterating, those new
+   * entries will not be returned by the iterator. If existing entries are removed during iteration,
+   * they will be absent (unless they were already returned).
+   *
+   * <p>If there are I/O problems during iteration, this iterator fails silently. For example, if
+   * the hosting filesystem becomes unreachable, the iterator will omit elements rather than
+   * throwing exceptions.
+   *
+   * <p><strong>The caller must {@link Snapshot#close close}</strong> each snapshot returned by
+   * {@link Iterator#next}. Failing to do so leaks open files!
+   *
+   * <p>The returned iterator supports {@link Iterator#remove}.
+   */
+  public synchronized Iterator<Snapshot> snapshots() throws IOException {
+    initialize();
+    return new Iterator<Snapshot>() {
+      /** Iterate a copy of the entries to defend against concurrent modification errors. */
+      final Iterator<Entry> delegate = new ArrayList<>(lruEntries.values()).iterator();
+
+      /** The snapshot to return from {@link #next}. Null if we haven't computed that yet. */
+      Snapshot nextSnapshot;
+
+      /** The snapshot to remove with {@link #remove}. Null if removal is illegal. */
+      Snapshot removeSnapshot;
+
+      @Override public boolean hasNext() {
+        if (nextSnapshot != null) return true;
+
+        synchronized (DiskLruCache.this) {
+          // If the cache is closed, truncate the iterator.
+          if (closed) return false;
+
+          while (delegate.hasNext()) {
+            Entry entry = delegate.next();
+            Snapshot snapshot = entry.snapshot();
+            if (snapshot == null) continue; // Evicted since we copied the entries.
+            nextSnapshot = snapshot;
+            return true;
+          }
+        }
+
+        return false;
+      }
+
+      @Override public Snapshot next() {
+        if (!hasNext()) throw new NoSuchElementException();
+        removeSnapshot = nextSnapshot;
+        nextSnapshot = null;
+        return removeSnapshot;
+      }
+
+      @Override public void remove() {
+        if (removeSnapshot == null) throw new IllegalStateException("remove() before next()");
+        try {
+          DiskLruCache.this.remove(removeSnapshot.key);
+        } catch (IOException ignored) {
+          // Nothing useful to do here. We failed to remove from the cache. Most likely that's
+          // because we couldn't update the journal, but the cached entry will still be gone.
+        } finally {
+          removeSnapshot = null;
+        }
+      }
+    };
+  }
+
+  /** A snapshot of the values for an entry. 
+   * @hide This class is not part of the Android public SDK API*/
+  public final class Snapshot implements Closeable {
+    private final String key;
+    private final long sequenceNumber;
+    private final Source[] sources;
+    private final long[] lengths;
+
+    private Snapshot(String key, long sequenceNumber, Source[] sources, long[] lengths) {
+      this.key = key;
+      this.sequenceNumber = sequenceNumber;
+      this.sources = sources;
+      this.lengths = lengths;
+    }
+
+    public String key() {
+      return key;
+    }
+
+    /**
+     * Returns an editor for this snapshot's entry, or null if either the
+     * entry has changed since this snapshot was created or if another edit
+     * is in progress.
+     */
+    public Editor edit() throws IOException {
+      return DiskLruCache.this.edit(key, sequenceNumber);
+    }
+
+    /** Returns the unbuffered stream with the value for {@code index}. */
+    public Source getSource(int index) {
+      return sources[index];
+    }
+
+    /** Returns the byte length of the value for {@code index}. */
+    public long getLength(int index) {
+      return lengths[index];
+    }
+
+    public void close() {
+      for (Source in : sources) {
+        Util.closeQuietly(in);
+      }
+    }
+  }
+
+  private static final Sink NULL_SINK = new Sink() {
+    @Override public void write(Buffer source, long byteCount) throws IOException {
+      source.skip(byteCount);
+    }
+
+    @Override public void flush() throws IOException {
+    }
+
+    @Override public Timeout timeout() {
+      return Timeout.NONE;
+    }
+
+    @Override public void close() throws IOException {
+    }
+  };
+
+  /** Edits the values for an entry. 
+   * @hide This class is not part of the Android public SDK API*/
+  public final class Editor {
+    private final Entry entry;
+    private final boolean[] written;
+    private boolean done;
+
+    private Editor(Entry entry) {
+      this.entry = entry;
+      this.written = (entry.readable) ? null : new boolean[valueCount];
+    }
+
+    /**
+     * Prevents this editor from completing normally. This is necessary either when the edit causes
+     * an I/O error, or if the target entry is evicted while this editor is active. In either case
+     * we delete the editor's created files and prevent new files from being created. Note that once
+     * an editor has been detached it is possible for another editor to edit the entry.
+     */
+    void detach() {
+      if (entry.currentEditor == this) {
+        for (int i = 0; i < valueCount; i++) {
+          try {
+            fileSystem.delete(entry.dirtyFiles[i]);
+          } catch (IOException e) {
+            // This file is potentially leaked. Not much we can do about that.
+          }
+        }
+        entry.currentEditor = null;
+      }
+    }
+
+    /**
+     * Returns an unbuffered input stream to read the last committed value,
+     * or null if no value has been committed.
+     */
+    public Source newSource(int index) throws IOException {
+      synchronized (DiskLruCache.this) {
+        if (done) {
+          throw new IllegalStateException();
+        }
+        if (!entry.readable || entry.currentEditor != this) {
+          return null;
+        }
+        try {
+          return fileSystem.source(entry.cleanFiles[index]);
+        } catch (FileNotFoundException e) {
+          return null;
+        }
+      }
+    }
+
+    /**
+     * Returns a new unbuffered output stream to write the value at
+     * {@code index}. If the underlying output stream encounters errors
+     * when writing to the filesystem, this edit will be aborted when
+     * {@link #commit} is called. The returned output stream does not throw
+     * IOExceptions.
+     */
+    public Sink newSink(int index) throws IOException {
+      synchronized (DiskLruCache.this) {
+        if (done) {
+          throw new IllegalStateException();
+        }
+        if (entry.currentEditor != this) {
+          return NULL_SINK;
+        }
+        if (!entry.readable) {
+          written[index] = true;
+        }
+        File dirtyFile = entry.dirtyFiles[index];
+        Sink sink;
+        try {
+          sink = fileSystem.sink(dirtyFile);
+        } catch (FileNotFoundException e) {
+          return NULL_SINK;
+        }
+        return new FaultHidingSink(sink) {
+          @Override protected void onException(IOException e) {
+            synchronized (DiskLruCache.this) {
+              detach();
+            }
+          }
+        };
+      }
+    }
+
+    /**
+     * Commits this edit so it is visible to readers.  This releases the
+     * edit lock so another edit may be started on the same key.
+     */
+    public void commit() throws IOException {
+      synchronized (DiskLruCache.this) {
+        if (done) {
+          throw new IllegalStateException();
+        }
+        if (entry.currentEditor == this) {
+          completeEdit(this, true);
+        }
+        done = true;
+      }
+    }
+
+    /**
+     * Aborts this edit. This releases the edit lock so another edit may be
+     * started on the same key.
+     */
+    public void abort() throws IOException {
+      synchronized (DiskLruCache.this) {
+        if (done) {
+          throw new IllegalStateException();
+        }
+        if (entry.currentEditor == this) {
+          completeEdit(this, false);
+        }
+        done = true;
+      }
+    }
+
+    public void abortUnlessCommitted() {
+      synchronized (DiskLruCache.this) {
+        if (!done && entry.currentEditor == this) {
+          try {
+            completeEdit(this, false);
+          } catch (IOException ignored) {
+          }
+        }
+      }
+    }
+  }
+
+  private final class Entry {
+    private final String key;
+
+    /** Lengths of this entry's files. */
+    private final long[] lengths;
+    private final File[] cleanFiles;
+    private final File[] dirtyFiles;
+
+    /** True if this entry has ever been published. */
+    private boolean readable;
+
+    /** The ongoing edit or null if this entry is not being edited. */
+    private Editor currentEditor;
+
+    /** The sequence number of the most recently committed edit to this entry. */
+    private long sequenceNumber;
+
+    private Entry(String key) {
+      this.key = key;
+
+      lengths = new long[valueCount];
+      cleanFiles = new File[valueCount];
+      dirtyFiles = new File[valueCount];
+
+      // The names are repetitive so re-use the same builder to avoid allocations.
+      StringBuilder fileBuilder = new StringBuilder(key).append('.');
+      int truncateTo = fileBuilder.length();
+      for (int i = 0; i < valueCount; i++) {
+        fileBuilder.append(i);
+        cleanFiles[i] = new File(directory, fileBuilder.toString());
+        fileBuilder.append(".tmp");
+        dirtyFiles[i] = new File(directory, fileBuilder.toString());
+        fileBuilder.setLength(truncateTo);
+      }
+    }
+
+    /** Set lengths using decimal numbers like "10123". */
+    private void setLengths(String[] strings) throws IOException {
+      if (strings.length != valueCount) {
+        throw invalidLengths(strings);
+      }
+
+      try {
+        for (int i = 0; i < strings.length; i++) {
+          lengths[i] = Long.parseLong(strings[i]);
+        }
+      } catch (NumberFormatException e) {
+        throw invalidLengths(strings);
+      }
+    }
+
+    /** Append space-prefixed lengths to {@code writer}. */
+    void writeLengths(BufferedSink writer) throws IOException {
+      for (long length : lengths) {
+        writer.writeByte(' ').writeDecimalLong(length);
+      }
+    }
+
+    private IOException invalidLengths(String[] strings) throws IOException {
+      throw new IOException("unexpected journal line: " + Arrays.toString(strings));
+    }
+
+    /**
+     * Returns a snapshot of this entry. This opens all streams eagerly to guarantee that we see a
+     * single published snapshot. If we opened streams lazily then the streams could come from
+     * different edits.
+     */
+    Snapshot snapshot() {
+      if (!Thread.holdsLock(DiskLruCache.this)) throw new AssertionError();
+
+      Source[] sources = new Source[valueCount];
+      long[] lengths = this.lengths.clone(); // Defensive copy since these can be zeroed out.
+      try {
+        for (int i = 0; i < valueCount; i++) {
+          sources[i] = fileSystem.source(cleanFiles[i]);
+        }
+        return new Snapshot(key, sequenceNumber, sources, lengths);
+      } catch (FileNotFoundException e) {
+        // A file must have been deleted manually!
+        for (int i = 0; i < valueCount; i++) {
+          if (sources[i] != null) {
+            Util.closeQuietly(sources[i]);
+          } else {
+            break;
+          }
+        }
+        return null;
+      }
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/FaultHidingSink.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/FaultHidingSink.java
new file mode 100644
index 0000000..d76152d
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/FaultHidingSink.java
@@ -0,0 +1,52 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal;
+
+import java.io.IOException;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.ForwardingSink;
+import com.android.okhttp.okio.Sink;
+
+/** A sink that never throws IOExceptions, even if the underlying sink does. */
+class FaultHidingSink extends ForwardingSink {
+  private boolean hasErrors;
+
+  public FaultHidingSink(Sink delegate) {
+    super(delegate);
+  }
+
+  @Override public void write(Buffer source, long byteCount) throws IOException {
+    if (hasErrors) {
+      source.skip(byteCount);
+      return;
+    }
+    try {
+      super.write(source, byteCount);
+    } catch (IOException e) {
+      hasErrors = true;
+      onException(e);
+    }
+  }
+
+  @Override public void flush() throws IOException {
+    if (hasErrors) return;
+    try {
+      super.flush();
+    } catch (IOException e) {
+      hasErrors = true;
+      onException(e);
+    }
+  }
+
+  @Override public void close() throws IOException {
+    if (hasErrors) return;
+    try {
+      super.close();
+    } catch (IOException e) {
+      hasErrors = true;
+      onException(e);
+    }
+  }
+
+  protected void onException(IOException e) {
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Internal.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Internal.java
new file mode 100644
index 0000000..20f937d
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Internal.java
@@ -0,0 +1,76 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal;
+
+import com.android.okhttp.Address;
+import com.android.okhttp.Call;
+import com.android.okhttp.Callback;
+import com.android.okhttp.ConnectionPool;
+import com.android.okhttp.ConnectionSpec;
+import com.android.okhttp.Headers;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.internal.http.StreamAllocation;
+import com.android.okhttp.internal.io.RealConnection;
+import java.net.MalformedURLException;
+import java.net.UnknownHostException;
+import java.util.logging.Logger;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Escalate internal APIs in {@code com.squareup.okhttp} so they can be used
+ * from OkHttp's implementation packages. The only implementation of this
+ * interface is in {@link com.android.okhttp.OkHttpClient}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public abstract class Internal {
+  public static final Logger logger = Logger.getLogger(OkHttpClient.class.getName());
+
+  public static void initializeInstanceForTests() {
+    // Needed in tests to ensure that the instance is actually pointing to something.
+    new OkHttpClient();
+  }
+
+  public static Internal instance;
+
+  public abstract void addLenient(Headers.Builder builder, String line);
+
+  public abstract void addLenient(Headers.Builder builder, String name, String value);
+
+  public abstract void setCache(OkHttpClient client, InternalCache internalCache);
+
+  public abstract InternalCache internalCache(OkHttpClient client);
+
+  public abstract RealConnection get(
+      ConnectionPool pool, Address address, StreamAllocation streamAllocation);
+
+  public abstract void put(ConnectionPool pool, RealConnection connection);
+
+  public abstract boolean connectionBecameIdle(ConnectionPool pool, RealConnection connection);
+
+  public abstract RouteDatabase routeDatabase(ConnectionPool connectionPool);
+
+  public abstract void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket,
+      boolean isFallback);
+
+  public abstract HttpUrl getHttpUrlChecked(String url)
+      throws MalformedURLException, UnknownHostException;
+
+  // TODO delete the following when web sockets move into the main package.
+  public abstract void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket);
+  public abstract StreamAllocation callEngineGetStreamAllocation(Call call);
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/InternalCache.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/InternalCache.java
new file mode 100644
index 0000000..772e13e
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/InternalCache.java
@@ -0,0 +1,54 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal;
+
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.internal.http.CacheRequest;
+import com.android.okhttp.internal.http.CacheStrategy;
+import java.io.IOException;
+
+/**
+ * OkHttp's internal cache interface. Applications shouldn't implement this:
+ * instead use {@link com.android.okhttp.Cache}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface InternalCache {
+  Response get(Request request) throws IOException;
+
+  CacheRequest put(Response response) throws IOException;
+
+  /**
+   * Remove any cache entries for the supplied {@code request}. This is invoked
+   * when the client invalidates the cache, such as when making POST requests.
+   */
+  void remove(Request request) throws IOException;
+
+  /**
+   * Handles a conditional request hit by updating the stored cache response
+   * with the headers from {@code network}. The cached response body is not
+   * updated. If the stored response has changed since {@code cached} was
+   * returned, this does nothing.
+   */
+  void update(Response cached, Response network) throws IOException;
+
+  /** Track an conditional GET that was satisfied by this cache. */
+  void trackConditionalCacheHit();
+
+  /** Track an HTTP response being satisfied with {@code cacheStrategy}. */
+  void trackResponse(CacheStrategy cacheStrategy);
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/NamedRunnable.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/NamedRunnable.java
new file mode 100644
index 0000000..dcaeb91
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/NamedRunnable.java
@@ -0,0 +1,42 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal;
+
+/**
+ * Runnable implementation which always sets its thread name.
+ * @hide This class is not part of the Android public SDK API
+ */
+public abstract class NamedRunnable implements Runnable {
+  protected final String name;
+
+  public NamedRunnable(String format, Object... args) {
+    this.name = String.format(format, args);
+  }
+
+  @Override public final void run() {
+    String oldName = Thread.currentThread().getName();
+    Thread.currentThread().setName(name);
+    try {
+      execute();
+    } finally {
+      Thread.currentThread().setName(oldName);
+    }
+  }
+
+  protected abstract void execute();
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/OptionalMethod.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/OptionalMethod.java
new file mode 100644
index 0000000..ee2425f
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/OptionalMethod.java
@@ -0,0 +1,177 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp.internal;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Duck-typing for methods: Represents a method that may or may not be present on an object.
+ *
+ * @param <T> the type of the object the method might be on, typically an interface or base class
+ */
+class OptionalMethod<T> {
+
+  /** The return type of the method. null means "don't care". */
+  private final Class<?> returnType;
+
+  private final String methodName;
+
+  private final Class[] methodParams;
+
+  /**
+   * Creates an optional method.
+   *
+   * @param returnType the return type to required, null if it does not matter
+   * @param methodName the name of the method
+   * @param methodParams the method parameter types
+   */
+  public OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) {
+    this.returnType = returnType;
+    this.methodName = methodName;
+    this.methodParams = methodParams;
+  }
+
+  /**
+   * Returns true if the method exists on the supplied {@code target}.
+   */
+  public boolean isSupported(T target) {
+    return getMethod(target.getClass()) != null;
+  }
+
+  /**
+   * Invokes the method on {@code target} with {@code args}. If the method does not exist or is not
+   * public then {@code null} is returned. See also
+   * {@link #invokeOptionalWithoutCheckedException(Object, Object...)}.
+   *
+   * @throws IllegalArgumentException if the arguments are invalid
+   * @throws InvocationTargetException if the invocation throws an exception
+   */
+  public Object invokeOptional(T target, Object... args) throws InvocationTargetException {
+    Method m = getMethod(target.getClass());
+    if (m == null) {
+      return null;
+    }
+    try {
+      return m.invoke(target, args);
+    } catch (IllegalAccessException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Invokes the method on {@code target}.  If the method does not exist or is not
+   * public then {@code null} is returned. Any RuntimeException thrown by the method is thrown,
+   * checked exceptions are wrapped in an {@link AssertionError}.
+   *
+   * @throws IllegalArgumentException if the arguments are invalid
+   */
+  public Object invokeOptionalWithoutCheckedException(T target, Object... args) {
+    try {
+      return invokeOptional(target, args);
+    } catch (InvocationTargetException e) {
+      Throwable targetException = e.getTargetException();
+      if (targetException instanceof RuntimeException) {
+        throw (RuntimeException) targetException;
+      }
+      AssertionError error = new AssertionError("Unexpected exception");
+      error.initCause(targetException);
+      throw error;
+    }
+  }
+
+  /**
+   * Invokes the method on {@code target} with {@code args}. Throws an error if the method is not
+   * supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}.
+   *
+   * @throws IllegalArgumentException if the arguments are invalid
+   * @throws InvocationTargetException if the invocation throws an exception
+   */
+  public Object invoke(T target, Object... args) throws InvocationTargetException {
+    Method m = getMethod(target.getClass());
+    if (m == null) {
+      throw new AssertionError("Method " + methodName + " not supported for object " + target);
+    }
+    try {
+      return m.invoke(target, args);
+    } catch (IllegalAccessException e) {
+      // Method should be public: we checked.
+      AssertionError error = new AssertionError("Unexpectedly could not call: " + m);
+      error.initCause(e);
+      throw error;
+    }
+  }
+
+  /**
+   * Invokes the method on {@code target}. Throws an error if the method is not supported. Any
+   * RuntimeException thrown by the method is thrown, checked exceptions are wrapped in
+   * an {@link AssertionError}.
+   *
+   * @throws IllegalArgumentException if the arguments are invalid
+   */
+  public Object invokeWithoutCheckedException(T target, Object... args) {
+    try {
+      return invoke(target, args);
+    } catch (InvocationTargetException e) {
+      Throwable targetException = e.getTargetException();
+      if (targetException instanceof RuntimeException) {
+        throw (RuntimeException) targetException;
+      }
+      AssertionError error = new AssertionError("Unexpected exception");
+      error.initCause(targetException);
+      throw error;
+    }
+  }
+
+  /**
+   * Perform a lookup for the method. No caching.
+   * In order to return a method the method name and arguments must match those specified when
+   * the {@link OptionalMethod} was created. If the return type is specified (i.e. non-null) it
+   * must also be compatible. The method must also be public.
+   */
+  private Method getMethod(Class<?> clazz) {
+    Method method = null;
+    if (methodName != null) {
+      method = getPublicMethod(clazz, methodName, methodParams);
+      if (method != null
+          && returnType != null
+          && !returnType.isAssignableFrom(method.getReturnType())) {
+
+        // If the return type is non-null it must be compatible.
+        method = null;
+      }
+    }
+    return method;
+  }
+
+  private static Method getPublicMethod(Class<?> clazz, String methodName, Class[] parameterTypes) {
+    Method method = null;
+    try {
+      method = clazz.getMethod(methodName, parameterTypes);
+      if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
+        method = null;
+      }
+    } catch (NoSuchMethodException e) {
+      // None.
+    }
+    return method;
+  }
+}
+
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/RouteDatabase.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/RouteDatabase.java
new file mode 100644
index 0000000..afe2317
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/RouteDatabase.java
@@ -0,0 +1,51 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal;
+
+import com.android.okhttp.Route;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * A blacklist of failed routes to avoid when creating a new connection to a
+ * target address. This is used so that OkHttp can learn from its mistakes: if
+ * there was a failure attempting to connect to a specific IP address or proxy
+ * server, that failure is remembered and alternate routes are preferred.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RouteDatabase {
+  private final Set<Route> failedRoutes = new LinkedHashSet<>();
+
+  /** Records a failure connecting to {@code failedRoute}. */
+  public synchronized void failed(Route failedRoute) {
+    failedRoutes.add(failedRoute);
+  }
+
+  /** Records success connecting to {@code failedRoute}. */
+  public synchronized void connected(Route route) {
+    failedRoutes.remove(route);
+  }
+
+  /** Returns true if {@code route} has failed recently and should be avoided. */
+  public synchronized boolean shouldPostpone(Route route) {
+    return failedRoutes.contains(route);
+  }
+
+  public synchronized int failedRoutesCount() {
+    return failedRoutes.size();
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Util.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Util.java
new file mode 100644
index 0000000..b611109
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/Util.java
@@ -0,0 +1,306 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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 com.android.okhttp.internal;
+
+import com.android.okhttp.HttpUrl;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.Source;
+
+/** Junk drawer of utility methods. 
+ * @hide This class is not part of the Android public SDK API*/
+public final class Util {
+  public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+  public static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+  /** A cheap and type-safe constant for the UTF-8 Charset. */
+  public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  private Util() {
+  }
+
+  public static void checkOffsetAndCount(long arrayLength, long offset, long count) {
+    if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
+      throw new ArrayIndexOutOfBoundsException();
+    }
+  }
+
+  /** Returns true if two possibly-null objects are equal. */
+  public static boolean equal(Object a, Object b) {
+    return a == b || (a != null && a.equals(b));
+  }
+
+  /**
+   * Closes {@code closeable}, ignoring any checked exceptions. Does nothing
+   * if {@code closeable} is null.
+   */
+  public static void closeQuietly(Closeable closeable) {
+    if (closeable != null) {
+      try {
+        closeable.close();
+      } catch (RuntimeException rethrown) {
+        throw rethrown;
+      } catch (Exception ignored) {
+      }
+    }
+  }
+
+  /**
+   * Closes {@code socket}, ignoring any checked exceptions. Does nothing if
+   * {@code socket} is null.
+   */
+  public static void closeQuietly(Socket socket) {
+    if (socket != null) {
+      try {
+        socket.close();
+      } catch (AssertionError e) {
+        if (!isAndroidGetsocknameError(e)) throw e;
+      } catch (RuntimeException rethrown) {
+        throw rethrown;
+      } catch (Exception ignored) {
+      }
+    }
+  }
+
+  /**
+   * Closes {@code serverSocket}, ignoring any checked exceptions. Does nothing if
+   * {@code serverSocket} is null.
+   */
+  public static void closeQuietly(ServerSocket serverSocket) {
+    if (serverSocket != null) {
+      try {
+        serverSocket.close();
+      } catch (RuntimeException rethrown) {
+        throw rethrown;
+      } catch (Exception ignored) {
+      }
+    }
+  }
+
+  /**
+   * Closes {@code a} and {@code b}. If either close fails, this completes
+   * the other close and rethrows the first encountered exception.
+   */
+  public static void closeAll(Closeable a, Closeable b) throws IOException {
+    Throwable thrown = null;
+    try {
+      a.close();
+    } catch (Throwable e) {
+      thrown = e;
+    }
+    try {
+      b.close();
+    } catch (Throwable e) {
+      if (thrown == null) thrown = e;
+    }
+    if (thrown == null) return;
+    if (thrown instanceof IOException) throw (IOException) thrown;
+    if (thrown instanceof RuntimeException) throw (RuntimeException) thrown;
+    if (thrown instanceof Error) throw (Error) thrown;
+    throw new AssertionError(thrown);
+  }
+
+  /**
+   * Attempts to exhaust {@code source}, returning true if successful. This is useful when reading
+   * a complete source is helpful, such as when doing so completes a cache body or frees a socket
+   * connection for reuse.
+   */
+  public static boolean discard(Source source, int timeout, TimeUnit timeUnit) {
+    try {
+      return skipAll(source, timeout, timeUnit);
+    } catch (IOException e) {
+      return false;
+    }
+  }
+
+  /**
+   * Reads until {@code in} is exhausted or the deadline has been reached. This is careful to not
+   * extend the deadline if one exists already.
+   */
+  public static boolean skipAll(Source source, int duration, TimeUnit timeUnit) throws IOException {
+    long now = System.nanoTime();
+    long originalDuration = source.timeout().hasDeadline()
+        ? source.timeout().deadlineNanoTime() - now
+        : Long.MAX_VALUE;
+    source.timeout().deadlineNanoTime(now + Math.min(originalDuration, timeUnit.toNanos(duration)));
+    try {
+      Buffer skipBuffer = new Buffer();
+      while (source.read(skipBuffer, 2048) != -1) {
+        skipBuffer.clear();
+      }
+      return true; // Success! The source has been exhausted.
+    } catch (InterruptedIOException e) {
+      return false; // We ran out of time before exhausting the source.
+    } finally {
+      if (originalDuration == Long.MAX_VALUE) {
+        source.timeout().clearDeadline();
+      } else {
+        source.timeout().deadlineNanoTime(now + originalDuration);
+      }
+    }
+  }
+
+  /** Returns a 32 character string containing an MD5 hash of {@code s}. */
+  public static String md5Hex(String s) {
+    try {
+      MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+      byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8"));
+      return ByteString.of(md5bytes).hex();
+    } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /** Returns a Base 64-encoded string containing a SHA-1 hash of {@code s}. */
+  public static String shaBase64(String s) {
+    try {
+      MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+      byte[] sha1Bytes = messageDigest.digest(s.getBytes("UTF-8"));
+      return ByteString.of(sha1Bytes).base64();
+    } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /** Returns a SHA-1 hash of {@code s}. */
+  public static ByteString sha1(ByteString s) {
+    try {
+      MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+      byte[] sha1Bytes = messageDigest.digest(s.toByteArray());
+      return ByteString.of(sha1Bytes);
+    } catch (NoSuchAlgorithmException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /** Returns an immutable copy of {@code list}. */
+  public static <T> List<T> immutableList(List<T> list) {
+    return Collections.unmodifiableList(new ArrayList<>(list));
+  }
+
+  /** Returns an immutable list containing {@code elements}. */
+  public static <T> List<T> immutableList(T... elements) {
+    return Collections.unmodifiableList(Arrays.asList(elements.clone()));
+  }
+
+  /** Returns an immutable copy of {@code map}. */
+  public static <K, V> Map<K, V> immutableMap(Map<K, V> map) {
+    return Collections.unmodifiableMap(new LinkedHashMap<>(map));
+  }
+
+  public static ThreadFactory threadFactory(final String name, final boolean daemon) {
+    return new ThreadFactory() {
+      @Override public Thread newThread(Runnable runnable) {
+        Thread result = new Thread(runnable, name);
+        result.setDaemon(daemon);
+        return result;
+      }
+    };
+  }
+
+  /**
+   * Returns an array containing containing only elements found in {@code first}  and also in
+   * {@code second}. The returned elements are in the same order as in {@code first}.
+   */
+  @SuppressWarnings("unchecked")
+  public static <T> T[] intersect(Class<T> arrayType, T[] first, T[] second) {
+    List<T> result = intersect(first, second);
+    return result.toArray((T[]) Array.newInstance(arrayType, result.size()));
+  }
+
+  /**
+   * Returns a list containing containing only elements found in {@code first}  and also in
+   * {@code second}. The returned elements are in the same order as in {@code first}.
+   */
+  private static <T> List<T> intersect(T[] first, T[] second) {
+    List<T> result = new ArrayList<>();
+    for (T a : first) {
+      for (T b : second) {
+        if (a.equals(b)) {
+          result.add(b);
+          break;
+        }
+      }
+    }
+    return result;
+  }
+
+  public static String hostHeader(HttpUrl url, boolean includeDefaultPort) {
+    String host = url.host().contains(":")
+        ? "[" + url.host() + "]"
+        : url.host();
+    return includeDefaultPort || url.port() != HttpUrl.defaultPort(url.scheme())
+        ? host + ":" + url.port()
+        : host;
+  }
+
+  /** Returns {@code s} with control characters and non-ASCII characters replaced with '?'. */
+  public static String toHumanReadableAscii(String s) {
+    for (int i = 0, length = s.length(), c; i < length; i += Character.charCount(c)) {
+      c = s.codePointAt(i);
+      if (c > '\u001f' && c < '\u007f') continue;
+
+      Buffer buffer = new Buffer();
+      buffer.writeUtf8(s, 0, i);
+      for (int j = i; j < length; j += Character.charCount(c)) {
+        c = s.codePointAt(j);
+        buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?');
+      }
+      return buffer.readUtf8();
+    }
+    return s;
+  }
+
+  /**
+   * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2.
+   * https://code.google.com/p/android/issues/detail?id=54072
+   */
+  public static boolean isAndroidGetsocknameError(AssertionError e) {
+    return e.getCause() != null && e.getMessage() != null
+        && e.getMessage().contains("getsockname failed");
+  }
+
+  public static boolean contains(String[] array, String value) {
+    return Arrays.asList(array).contains(value);
+  }
+
+  public static String[] concat(String[] array, String value) {
+    String[] result = new String[array.length + 1];
+    System.arraycopy(array, 0, result, 0, array.length);
+    result[result.length - 1] = value;
+    return result;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/ErrorCode.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/ErrorCode.java
new file mode 100644
index 0000000..d5d71ff
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/ErrorCode.java
@@ -0,0 +1,95 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.framed;
+
+// http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-7
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public enum ErrorCode {
+  /** Not an error! For SPDY stream resets, prefer null over NO_ERROR. */
+  NO_ERROR(0, -1, 0),
+
+  PROTOCOL_ERROR(1, 1, 1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  INVALID_STREAM(1, 2, -1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  UNSUPPORTED_VERSION(1, 4, -1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  STREAM_IN_USE(1, 8, -1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  STREAM_ALREADY_CLOSED(1, 9, -1),
+
+  INTERNAL_ERROR(2, 6, 2),
+
+  FLOW_CONTROL_ERROR(3, 7, -1),
+
+  STREAM_CLOSED(5, -1, -1),
+
+  FRAME_TOO_LARGE(6, 11, -1),
+
+  REFUSED_STREAM(7, 3, -1),
+
+  CANCEL(8, 5, -1),
+
+  COMPRESSION_ERROR(9, -1, -1),
+
+  CONNECT_ERROR(10, -1, -1),
+
+  ENHANCE_YOUR_CALM(11, -1, -1),
+
+  INADEQUATE_SECURITY(12, -1, -1),
+
+  HTTP_1_1_REQUIRED(13, -1, -1),
+
+  INVALID_CREDENTIALS(-1, 10, -1);
+
+  public final int httpCode;
+  public final int spdyRstCode;
+  public final int spdyGoAwayCode;
+
+  private ErrorCode(int httpCode, int spdyRstCode, int spdyGoAwayCode) {
+    this.httpCode = httpCode;
+    this.spdyRstCode = spdyRstCode;
+    this.spdyGoAwayCode = spdyGoAwayCode;
+  }
+
+  public static ErrorCode fromSpdy3Rst(int code) {
+    for (ErrorCode errorCode : ErrorCode.values()) {
+      if (errorCode.spdyRstCode == code) return errorCode;
+    }
+    return null;
+  }
+
+  public static ErrorCode fromHttp2(int code) {
+    for (ErrorCode errorCode : ErrorCode.values()) {
+      if (errorCode.httpCode == code) return errorCode;
+    }
+    return null;
+  }
+
+  public static ErrorCode fromSpdyGoAway(int code) {
+    for (ErrorCode errorCode : ErrorCode.values()) {
+      if (errorCode.spdyGoAwayCode == code) return errorCode;
+    }
+    return null;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameReader.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameReader.java
new file mode 100644
index 0000000..f17ed42
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameReader.java
@@ -0,0 +1,139 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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 com.android.okhttp.internal.framed;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+
+/** Reads transport frames for SPDY/3 or HTTP/2. 
+ * @hide This class is not part of the Android public SDK API*/
+public interface FrameReader extends Closeable {
+  void readConnectionPreface() throws IOException;
+  boolean nextFrame(Handler handler) throws IOException;
+
+  interface Handler {
+    void data(boolean inFinished, int streamId, BufferedSource source, int length)
+        throws IOException;
+
+    /**
+     * Create or update incoming headers, creating the corresponding streams
+     * if necessary. Frames that trigger this are SPDY SYN_STREAM, HEADERS, and
+     * SYN_REPLY, and HTTP/2 HEADERS and PUSH_PROMISE.
+     *
+     * @param outFinished true if the receiver should not send further frames.
+     * @param inFinished true if the sender will not send further frames.
+     * @param streamId the stream owning these headers.
+     * @param associatedStreamId the stream that triggered the sender to create
+     *     this stream.
+     */
+    void headers(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
+        List<Header> headerBlock, HeadersMode headersMode);
+    void rstStream(int streamId, ErrorCode errorCode);
+    void settings(boolean clearPrevious, Settings settings);
+
+    /** HTTP/2 only. */
+    void ackSettings();
+
+    /**
+     *  Read a connection-level ping from the peer.  {@code ack} indicates this
+     *  is a reply.  Payload parameters are different between SPDY/3 and HTTP/2.
+     *  <p>
+     *  In SPDY/3, only the first {@code payload1} parameter is set.  If the
+     *  reader is a client, it is an unsigned even number.  Likewise, a server
+     *  will receive an odd number.
+     *  <p>
+     *  In HTTP/2, both {@code payload1} and {@code payload2} parameters are
+     *  set. The data is opaque binary, and there are no rules on the content.
+     */
+    void ping(boolean ack, int payload1, int payload2);
+
+    /**
+     * The peer tells us to stop creating streams.  It is safe to replay
+     * streams with {@code ID > lastGoodStreamId} on a new connection.  In-
+     * flight streams with {@code ID <= lastGoodStreamId} can only be replayed
+     * on a new connection if they are idempotent.
+     *
+     * @param lastGoodStreamId the last stream ID the peer processed before
+     *     sending this message. If {@code lastGoodStreamId} is zero, the peer
+     *     processed no frames.
+     * @param errorCode reason for closing the connection.
+     * @param debugData only valid for HTTP/2; opaque debug data to send.
+     */
+    void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData);
+
+    /**
+     * Notifies that an additional {@code windowSizeIncrement} bytes can be
+     * sent on {@code streamId}, or the connection if {@code streamId} is zero.
+     */
+    void windowUpdate(int streamId, long windowSizeIncrement);
+
+    /**
+     * Called when reading a headers or priority frame. This may be used to
+     * change the stream's weight from the default (16) to a new value.
+     *
+     * @param streamId stream which has a priority change.
+     * @param streamDependency the stream ID this stream is dependent on.
+     * @param weight relative proportion of priority in [1..256].
+     * @param exclusive inserts this stream ID as the sole child of
+     *     {@code streamDependency}.
+     */
+    void priority(int streamId, int streamDependency, int weight, boolean exclusive);
+
+    /**
+     * HTTP/2 only. Receive a push promise header block.
+     * <p>
+     * A push promise contains all the headers that pertain to a server-initiated
+     * request, and a {@code promisedStreamId} to which response frames will be
+     * delivered. Push promise frames are sent as a part of the response to
+     * {@code streamId}.
+     *
+     * @param streamId client-initiated stream ID.  Must be an odd number.
+     * @param promisedStreamId server-initiated stream ID.  Must be an even
+     * number.
+     * @param requestHeaders minimally includes {@code :method}, {@code :scheme},
+     * {@code :authority}, and (@code :path}.
+     */
+    void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
+        throws IOException;
+
+    /**
+     * HTTP/2 only. Expresses that resources for the connection or a client-
+     * initiated stream are available from a different network location or
+     * protocol configuration.
+     *
+     * <p>See <a href="http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-01">alt-svc</a>
+     *
+     * @param streamId when a client-initiated stream ID (odd number), the
+     *     origin of this alternate service is the origin of the stream. When
+     *     zero, the origin is specified in the {@code origin} parameter.
+     * @param origin when present, the
+     *     <a href="http://tools.ietf.org/html/rfc6454">origin</a> is typically
+     *     represented as a combination of scheme, host and port. When empty,
+     *     the origin is that of the {@code streamId}.
+     * @param protocol an ALPN protocol, such as {@code h2}.
+     * @param host an IP address or hostname.
+     * @param port the IP port associated with the service.
+     * @param maxAge time in seconds that this alternative is considered fresh.
+     */
+    void alternateService(int streamId, String origin, ByteString protocol, String host, int port,
+        long maxAge);
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameWriter.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameWriter.java
new file mode 100644
index 0000000..75b049d
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FrameWriter.java
@@ -0,0 +1,105 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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 com.android.okhttp.internal.framed;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import com.android.okhttp.okio.Buffer;
+
+/** Writes transport frames for SPDY/3 or HTTP/2. 
+ * @hide This class is not part of the Android public SDK API*/
+public interface FrameWriter extends Closeable {
+  /** HTTP/2 only. */
+  void connectionPreface() throws IOException;
+  /** Informs the peer that we've applied its latest settings. */
+  void ackSettings(Settings peerSettings) throws IOException;
+
+  /**
+   * HTTP/2 only. Send a push promise header block.
+   * <p>
+   * A push promise contains all the headers that pertain to a server-initiated
+   * request, and a {@code promisedStreamId} to which response frames will be
+   * delivered. Push promise frames are sent as a part of the response to
+   * {@code streamId}.  The {@code promisedStreamId} has a priority of one
+   * greater than {@code streamId}.
+   *
+   * @param streamId client-initiated stream ID.  Must be an odd number.
+   * @param promisedStreamId server-initiated stream ID.  Must be an even
+   * number.
+   * @param requestHeaders minimally includes {@code :method}, {@code :scheme},
+   * {@code :authority}, and (@code :path}.
+   */
+  void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
+      throws IOException;
+
+  /** SPDY/3 only. */
+  void flush() throws IOException;
+  void synStream(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
+      List<Header> headerBlock) throws IOException;
+  void synReply(boolean outFinished, int streamId, List<Header> headerBlock)
+      throws IOException;
+  void headers(int streamId, List<Header> headerBlock) throws IOException;
+  void rstStream(int streamId, ErrorCode errorCode) throws IOException;
+
+  /** The maximum size of bytes that may be sent in a single call to {@link #data}. */
+  int maxDataLength();
+
+  /**
+   * {@code source.length} may be longer than the max length of the variant's data frame.
+   * Implementations must send multiple frames as necessary.
+   *
+   * @param source the buffer to draw bytes from. May be null if byteCount is 0.
+   * @param byteCount must be between 0 and the minimum of {code source.length}
+   * and {@link #maxDataLength}.
+   */
+  void data(boolean outFinished, int streamId, Buffer source, int byteCount) throws IOException;
+
+  /** Write okhttp's settings to the peer. */
+  void settings(Settings okHttpSettings) throws IOException;
+
+  /**
+   *  Send a connection-level ping to the peer.  {@code ack} indicates this is
+   *  a reply.  Payload parameters are different between SPDY/3 and HTTP/2.
+   *  <p>
+   *  In SPDY/3, only the first {@code payload1} parameter is sent.  If the
+   *  sender is a client, it is an unsigned odd number.  Likewise, a server
+   *  will send an even number.
+   *  <p>
+   *  In HTTP/2, both {@code payload1} and {@code payload2} parameters are
+   *  sent.  The data is opaque binary, and there are no rules on the content.
+   */
+  void ping(boolean ack, int payload1, int payload2) throws IOException;
+
+  /**
+   * Tell the peer to stop creating streams and that we last processed
+   * {@code lastGoodStreamId}, or zero if no streams were processed.
+   *
+   * @param lastGoodStreamId the last stream ID processed, or zero if no
+   * streams were processed.
+   * @param errorCode reason for closing the connection.
+   * @param debugData only valid for HTTP/2; opaque debug data to send.
+   */
+  void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) throws IOException;
+
+  /**
+   * Inform peer that an additional {@code windowSizeIncrement} bytes can be
+   * sent on {@code streamId}, or the connection if {@code streamId} is zero.
+   */
+  void windowUpdate(int streamId, long windowSizeIncrement) throws IOException;
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedConnection.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedConnection.java
new file mode 100644
index 0000000..2945e65
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedConnection.java
@@ -0,0 +1,949 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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 com.android.okhttp.internal.framed;
+
+import com.android.okhttp.Protocol;
+import com.android.okhttp.internal.NamedRunnable;
+import com.android.okhttp.internal.Util;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.Okio;
+
+import static com.android.okhttp.internal.Internal.logger;
+import static com.android.okhttp.internal.framed.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
+
+/**
+ * A socket connection to a remote peer. A connection hosts streams which can
+ * send and receive data.
+ *
+ * <p>Many methods in this API are <strong>synchronous:</strong> the call is
+ * completed before the method returns. This is typical for Java but atypical
+ * for SPDY. This is motivated by exception transparency: an IOException that
+ * was triggered by a certain caller can be caught and handled by that caller.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class FramedConnection implements Closeable {
+
+  // Internal state of this connection is guarded by 'this'. No blocking
+  // operations may be performed while holding this lock!
+  //
+  // Socket writes are guarded by frameWriter.
+  //
+  // Socket reads are unguarded but are only made by the reader thread.
+  //
+  // Certain operations (like SYN_STREAM) need to synchronize on both the
+  // frameWriter (to do blocking I/O) and this (to create streams). Such
+  // operations must synchronize on 'this' last. This ensures that we never
+  // wait for a blocking operation while holding 'this'.
+
+  private static final ExecutorService executor = new ThreadPoolExecutor(0,
+      Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
+      Util.threadFactory("OkHttp FramedConnection", true));
+
+  /** The protocol variant, like {@link com.android.okhttp.internal.framed.Spdy3}. */
+  final Protocol protocol;
+
+  /** True if this peer initiated the connection. */
+  final boolean client;
+
+  /**
+   * User code to run in response to incoming streams or settings. Calls to this are always invoked
+   * on {@link #executor}.
+   */
+  private final Listener listener;
+  private final Map<Integer, FramedStream> streams = new HashMap<>();
+  private final String hostName;
+  private int lastGoodStreamId;
+  private int nextStreamId;
+  private boolean shutdown;
+  private long idleStartTimeNs = System.nanoTime();
+
+  /** Ensures push promise callbacks events are sent in order per stream. */
+  private final ExecutorService pushExecutor;
+
+  /** Lazily-created map of in-flight pings awaiting a response. Guarded by this. */
+  private Map<Integer, Ping> pings;
+  /** User code to run in response to push promise events. */
+  private final PushObserver pushObserver;
+  private int nextPingId;
+
+  /**
+   * The total number of bytes consumed by the application, but not yet
+   * acknowledged by sending a {@code WINDOW_UPDATE} frame on this connection.
+   */
+  // Visible for testing
+  long unacknowledgedBytesRead = 0;
+
+  /**
+   * Count of bytes that can be written on the connection before receiving a
+   * window update.
+   */
+  // Visible for testing
+  long bytesLeftInWriteWindow;
+
+  /** Settings we communicate to the peer. */
+  Settings okHttpSettings = new Settings();
+
+  private static final int OKHTTP_CLIENT_WINDOW_SIZE = 16 * 1024 * 1024;
+
+  /** Settings we receive from the peer. */
+  // TODO: MWS will need to guard on this setting before attempting to push.
+  final Settings peerSettings = new Settings();
+
+  private boolean receivedInitialPeerSettings = false;
+  final Variant variant;
+  final Socket socket;
+  final FrameWriter frameWriter;
+
+  // Visible for testing
+  final Reader readerRunnable;
+
+  private FramedConnection(Builder builder) throws IOException {
+    protocol = builder.protocol;
+    pushObserver = builder.pushObserver;
+    client = builder.client;
+    listener = builder.listener;
+    // http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-5.1.1
+    nextStreamId = builder.client ? 1 : 2;
+    if (builder.client && protocol == Protocol.HTTP_2) {
+      nextStreamId += 2; // In HTTP/2, 1 on client is reserved for Upgrade.
+    }
+
+    nextPingId = builder.client ? 1 : 2;
+
+    // Flow control was designed more for servers, or proxies than edge clients.
+    // If we are a client, set the flow control window to 16MiB.  This avoids
+    // thrashing window updates every 64KiB, yet small enough to avoid blowing
+    // up the heap.
+    if (builder.client) {
+      okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, OKHTTP_CLIENT_WINDOW_SIZE);
+    }
+
+    hostName = builder.hostName;
+
+    if (protocol == Protocol.HTTP_2) {
+      variant = new Http2();
+      // Like newSingleThreadExecutor, except lazy creates the thread.
+      pushExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS,
+          new LinkedBlockingQueue<Runnable>(),
+          Util.threadFactory(String.format("OkHttp %s Push Observer", hostName), true));
+      // 1 less than SPDY http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-6.9.2
+      peerSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, 65535);
+      peerSettings.set(Settings.MAX_FRAME_SIZE, 0, Http2.INITIAL_MAX_FRAME_SIZE);
+    } else if (protocol == Protocol.SPDY_3) {
+      variant = new Spdy3();
+      pushExecutor = null;
+    } else {
+      throw new AssertionError(protocol);
+    }
+    bytesLeftInWriteWindow = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
+    socket = builder.socket;
+    frameWriter = variant.newWriter(builder.sink, client);
+
+    readerRunnable = new Reader(variant.newReader(builder.source, client));
+    new Thread(readerRunnable).start(); // Not a daemon thread.
+  }
+
+  /** The protocol as selected using ALPN. */
+  public Protocol getProtocol() {
+    return protocol;
+  }
+
+  /**
+   * Returns the number of {@link FramedStream#isOpen() open streams} on this
+   * connection.
+   */
+  public synchronized int openStreamCount() {
+    return streams.size();
+  }
+
+  synchronized FramedStream getStream(int id) {
+    return streams.get(id);
+  }
+
+  synchronized FramedStream removeStream(int streamId) {
+    FramedStream stream = streams.remove(streamId);
+    if (stream != null && streams.isEmpty()) {
+      setIdle(true);
+    }
+    notifyAll(); // The removed stream may be blocked on a connection-wide window update.
+    return stream;
+  }
+
+  private synchronized void setIdle(boolean value) {
+    idleStartTimeNs = value ? System.nanoTime() : Long.MAX_VALUE;
+  }
+
+  /** Returns true if this connection is idle. */
+  public synchronized boolean isIdle() {
+    return idleStartTimeNs != Long.MAX_VALUE;
+  }
+
+  public synchronized int maxConcurrentStreams() {
+    return peerSettings.getMaxConcurrentStreams(Integer.MAX_VALUE);
+  }
+
+  /**
+   * Returns the time in ns when this connection became idle or Long.MAX_VALUE
+   * if connection is not idle.
+   */
+  public synchronized long getIdleStartTimeNs() {
+    return idleStartTimeNs;
+  }
+
+  /**
+   * Returns a new server-initiated stream.
+   *
+   * @param associatedStreamId the stream that triggered the sender to create
+   *     this stream.
+   * @param out true to create an output stream that we can use to send data
+   *     to the remote peer. Corresponds to {@code FLAG_FIN}.
+   */
+  public FramedStream pushStream(int associatedStreamId, List<Header> requestHeaders, boolean out)
+      throws IOException {
+    if (client) throw new IllegalStateException("Client cannot push requests.");
+    if (protocol != Protocol.HTTP_2) throw new IllegalStateException("protocol != HTTP_2");
+    return newStream(associatedStreamId, requestHeaders, out, false);
+  }
+
+  /**
+   * Returns a new locally-initiated stream.
+   *
+   * @param out true to create an output stream that we can use to send data to the remote peer.
+   *     Corresponds to {@code FLAG_FIN}.
+   * @param in true to create an input stream that the remote peer can use to send data to us.
+   *     Corresponds to {@code FLAG_UNIDIRECTIONAL}.
+   */
+  public FramedStream newStream(List<Header> requestHeaders, boolean out, boolean in)
+      throws IOException {
+    return newStream(0, requestHeaders, out, in);
+  }
+
+  private FramedStream newStream(int associatedStreamId, List<Header> requestHeaders, boolean out,
+      boolean in) throws IOException {
+    boolean outFinished = !out;
+    boolean inFinished = !in;
+    FramedStream stream;
+    int streamId;
+
+    synchronized (frameWriter) {
+      synchronized (this) {
+        if (shutdown) {
+          throw new IOException("shutdown");
+        }
+        streamId = nextStreamId;
+        nextStreamId += 2;
+        stream = new FramedStream(streamId, this, outFinished, inFinished, requestHeaders);
+        if (stream.isOpen()) {
+          streams.put(streamId, stream);
+          setIdle(false);
+        }
+      }
+      if (associatedStreamId == 0) {
+        frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId,
+            requestHeaders);
+      } else if (client) {
+        throw new IllegalArgumentException("client streams shouldn't have associated stream IDs");
+      } else { // HTTP/2 has a PUSH_PROMISE frame.
+        frameWriter.pushPromise(associatedStreamId, streamId, requestHeaders);
+      }
+    }
+
+    if (!out) {
+      frameWriter.flush();
+    }
+
+    return stream;
+  }
+
+  void writeSynReply(int streamId, boolean outFinished, List<Header> alternating)
+      throws IOException {
+    frameWriter.synReply(outFinished, streamId, alternating);
+  }
+
+  /**
+   * Callers of this method are not thread safe, and sometimes on application threads. Most often,
+   * this method will be called to send a buffer worth of data to the peer.
+   *
+   * <p>Writes are subject to the write window of the stream and the connection. Until there is a
+   * window sufficient to send {@code byteCount}, the caller will block. For example, a user of
+   * {@code HttpURLConnection} who flushes more bytes to the output stream than the connection's
+   * write window will block.
+   *
+   * <p>Zero {@code byteCount} writes are not subject to flow control and will not block. The only
+   * use case for zero {@code byteCount} is closing a flushed output stream.
+   */
+  public void writeData(int streamId, boolean outFinished, Buffer buffer, long byteCount)
+      throws IOException {
+    if (byteCount == 0) { // Empty data frames are not flow-controlled.
+      frameWriter.data(outFinished, streamId, buffer, 0);
+      return;
+    }
+
+    while (byteCount > 0) {
+      int toWrite;
+      synchronized (FramedConnection.this) {
+        try {
+          while (bytesLeftInWriteWindow <= 0) {
+            // Before blocking, confirm that the stream we're writing is still open. It's possible
+            // that the stream has since been closed (such as if this write timed out.)
+            if (!streams.containsKey(streamId)) {
+              throw new IOException("stream closed");
+            }
+            FramedConnection.this.wait(); // Wait until we receive a WINDOW_UPDATE.
+          }
+        } catch (InterruptedException e) {
+          throw new InterruptedIOException();
+        }
+
+        toWrite = (int) Math.min(byteCount, bytesLeftInWriteWindow);
+        toWrite = Math.min(toWrite, frameWriter.maxDataLength());
+        bytesLeftInWriteWindow -= toWrite;
+      }
+
+      byteCount -= toWrite;
+      frameWriter.data(outFinished && byteCount == 0, streamId, buffer, toWrite);
+    }
+  }
+
+  /**
+   * {@code delta} will be negative if a settings frame initial window is
+   * smaller than the last.
+   */
+  void addBytesToWriteWindow(long delta) {
+    bytesLeftInWriteWindow += delta;
+    if (delta > 0) FramedConnection.this.notifyAll();
+  }
+
+  void writeSynResetLater(final int streamId, final ErrorCode errorCode) {
+    executor.submit(new NamedRunnable("OkHttp %s stream %d", hostName, streamId) {
+      @Override public void execute() {
+        try {
+          writeSynReset(streamId, errorCode);
+        } catch (IOException ignored) {
+        }
+      }
+    });
+  }
+
+  void writeSynReset(int streamId, ErrorCode statusCode) throws IOException {
+    frameWriter.rstStream(streamId, statusCode);
+  }
+
+  void writeWindowUpdateLater(final int streamId, final long unacknowledgedBytesRead) {
+    executor.execute(new NamedRunnable("OkHttp Window Update %s stream %d", hostName, streamId) {
+      @Override public void execute() {
+        try {
+          frameWriter.windowUpdate(streamId, unacknowledgedBytesRead);
+        } catch (IOException ignored) {
+        }
+      }
+    });
+  }
+
+  /**
+   * Sends a ping frame to the peer. Use the returned object to await the
+   * ping's response and observe its round trip time.
+   */
+  public Ping ping() throws IOException {
+    Ping ping = new Ping();
+    int pingId;
+    synchronized (this) {
+      if (shutdown) {
+        throw new IOException("shutdown");
+      }
+      pingId = nextPingId;
+      nextPingId += 2;
+      if (pings == null) pings = new HashMap<>();
+      pings.put(pingId, ping);
+    }
+    writePing(false, pingId, 0x4f4b6f6b /* ASCII "OKok" */, ping);
+    return ping;
+  }
+
+  private void writePingLater(
+      final boolean reply, final int payload1, final int payload2, final Ping ping) {
+    executor.execute(new NamedRunnable("OkHttp %s ping %08x%08x",
+        hostName, payload1, payload2) {
+      @Override public void execute() {
+        try {
+          writePing(reply, payload1, payload2, ping);
+        } catch (IOException ignored) {
+        }
+      }
+    });
+  }
+
+  private void writePing(boolean reply, int payload1, int payload2, Ping ping) throws IOException {
+    synchronized (frameWriter) {
+      // Observe the sent time immediately before performing I/O.
+      if (ping != null) ping.send();
+      frameWriter.ping(reply, payload1, payload2);
+    }
+  }
+
+  private synchronized Ping removePing(int id) {
+    return pings != null ? pings.remove(id) : null;
+  }
+
+  public void flush() throws IOException {
+    frameWriter.flush();
+  }
+
+  /**
+   * Degrades this connection such that new streams can neither be created
+   * locally, nor accepted from the remote peer. Existing streams are not
+   * impacted. This is intended to permit an endpoint to gracefully stop
+   * accepting new requests without harming previously established streams.
+   */
+  public void shutdown(ErrorCode statusCode) throws IOException {
+    synchronized (frameWriter) {
+      int lastGoodStreamId;
+      synchronized (this) {
+        if (shutdown) {
+          return;
+        }
+        shutdown = true;
+        lastGoodStreamId = this.lastGoodStreamId;
+      }
+      // TODO: propagate exception message into debugData
+      frameWriter.goAway(lastGoodStreamId, statusCode, Util.EMPTY_BYTE_ARRAY);
+    }
+  }
+
+  /**
+   * Closes this connection. This cancels all open streams and unanswered
+   * pings. It closes the underlying input and output streams and shuts down
+   * internal executor services.
+   */
+  @Override public void close() throws IOException {
+    close(ErrorCode.NO_ERROR, ErrorCode.CANCEL);
+  }
+
+  private void close(ErrorCode connectionCode, ErrorCode streamCode) throws IOException {
+    assert (!Thread.holdsLock(this));
+    IOException thrown = null;
+    try {
+      shutdown(connectionCode);
+    } catch (IOException e) {
+      thrown = e;
+    }
+
+    FramedStream[] streamsToClose = null;
+    Ping[] pingsToCancel = null;
+    synchronized (this) {
+      if (!streams.isEmpty()) {
+        streamsToClose = streams.values().toArray(new FramedStream[streams.size()]);
+        streams.clear();
+        setIdle(false);
+      }
+      if (pings != null) {
+        pingsToCancel = pings.values().toArray(new Ping[pings.size()]);
+        pings = null;
+      }
+    }
+
+    if (streamsToClose != null) {
+      for (FramedStream stream : streamsToClose) {
+        try {
+          stream.close(streamCode);
+        } catch (IOException e) {
+          if (thrown != null) thrown = e;
+        }
+      }
+    }
+
+    if (pingsToCancel != null) {
+      for (Ping ping : pingsToCancel) {
+        ping.cancel();
+      }
+    }
+
+    // Close the writer to release its resources (such as deflaters).
+    try {
+      frameWriter.close();
+    } catch (IOException e) {
+      if (thrown == null) thrown = e;
+    }
+
+    // Close the socket to break out the reader thread, which will clean up after itself.
+    try {
+      socket.close();
+    } catch (IOException e) {
+      thrown = e;
+    }
+
+    if (thrown != null) throw thrown;
+  }
+
+  /**
+   * Sends a connection header if the current variant requires it. This should
+   * be called after {@link Builder#build} for all new connections.
+   */
+  public void sendConnectionPreface() throws IOException {
+    frameWriter.connectionPreface();
+    frameWriter.settings(okHttpSettings);
+    int windowSize = okHttpSettings.getInitialWindowSize(Settings.DEFAULT_INITIAL_WINDOW_SIZE);
+    if (windowSize != Settings.DEFAULT_INITIAL_WINDOW_SIZE) {
+      frameWriter.windowUpdate(0, windowSize - Settings.DEFAULT_INITIAL_WINDOW_SIZE);
+    }
+  }
+
+  /** Merges {@code settings} into this peer's settings and sends them to the remote peer. */
+  public void setSettings(Settings settings) throws IOException {
+    synchronized (frameWriter) {
+      synchronized (this) {
+        if (shutdown) {
+          throw new IOException("shutdown");
+        }
+        okHttpSettings.merge(settings);
+        frameWriter.settings(settings);
+      }
+    }
+  }
+
+  /**
+   * @hide This class is not part of the Android public SDK API
+   */
+  public static class Builder {
+    private Socket socket;
+    private String hostName;
+    private BufferedSource source;
+    private BufferedSink sink;
+    private Listener listener = Listener.REFUSE_INCOMING_STREAMS;
+    private Protocol protocol = Protocol.SPDY_3;
+    private PushObserver pushObserver = PushObserver.CANCEL;
+    private boolean client;
+
+    /**
+     * @param client true if this peer initiated the connection; false if this
+     *     peer accepted the connection.
+     */
+    public Builder(boolean client) throws IOException {
+      this.client = client;
+    }
+
+    public Builder socket(Socket socket) throws IOException {
+      return socket(socket, ((InetSocketAddress) socket.getRemoteSocketAddress()).getHostName(),
+          Okio.buffer(Okio.source(socket)), Okio.buffer(Okio.sink(socket)));
+    }
+
+    public Builder socket(
+        Socket socket, String hostName, BufferedSource source, BufferedSink sink) {
+      this.socket = socket;
+      this.hostName = hostName;
+      this.source = source;
+      this.sink = sink;
+      return this;
+    }
+
+    public Builder listener(Listener listener) {
+      this.listener = listener;
+      return this;
+    }
+
+    public Builder protocol(Protocol protocol) {
+      this.protocol = protocol;
+      return this;
+    }
+
+    public Builder pushObserver(PushObserver pushObserver) {
+      this.pushObserver = pushObserver;
+      return this;
+    }
+
+    public FramedConnection build() throws IOException {
+      return new FramedConnection(this);
+    }
+  }
+
+  /**
+   * Methods in this class must not lock FrameWriter.  If a method needs to
+   * write a frame, create an async task to do so.
+   */
+  class Reader extends NamedRunnable implements FrameReader.Handler {
+    final FrameReader frameReader;
+
+    private Reader(FrameReader frameReader) {
+      super("OkHttp %s", hostName);
+      this.frameReader = frameReader;
+    }
+
+    @Override protected void execute() {
+      ErrorCode connectionErrorCode = ErrorCode.INTERNAL_ERROR;
+      ErrorCode streamErrorCode = ErrorCode.INTERNAL_ERROR;
+      try {
+        if (!client) {
+          frameReader.readConnectionPreface();
+        }
+        while (frameReader.nextFrame(this)) {
+        }
+        connectionErrorCode = ErrorCode.NO_ERROR;
+        streamErrorCode = ErrorCode.CANCEL;
+      } catch (IOException e) {
+        connectionErrorCode = ErrorCode.PROTOCOL_ERROR;
+        streamErrorCode = ErrorCode.PROTOCOL_ERROR;
+      } finally {
+        try {
+          close(connectionErrorCode, streamErrorCode);
+        } catch (IOException ignored) {
+        }
+        Util.closeQuietly(frameReader);
+      }
+    }
+
+    @Override public void data(boolean inFinished, int streamId, BufferedSource source, int length)
+        throws IOException {
+      if (pushedStream(streamId)) {
+        pushDataLater(streamId, source, length, inFinished);
+        return;
+      }
+      FramedStream dataStream = getStream(streamId);
+      if (dataStream == null) {
+        writeSynResetLater(streamId, ErrorCode.INVALID_STREAM);
+        source.skip(length);
+        return;
+      }
+      dataStream.receiveData(source, length);
+      if (inFinished) {
+        dataStream.receiveFin();
+      }
+    }
+
+    @Override public void headers(boolean outFinished, boolean inFinished, int streamId,
+        int associatedStreamId, List<Header> headerBlock, HeadersMode headersMode) {
+      if (pushedStream(streamId)) {
+        pushHeadersLater(streamId, headerBlock, inFinished);
+        return;
+      }
+      FramedStream stream;
+      synchronized (FramedConnection.this) {
+        // If we're shutdown, don't bother with this stream.
+        if (shutdown) return;
+
+        stream = getStream(streamId);
+
+        if (stream == null) {
+          // The headers claim to be for an existing stream, but we don't have one.
+          if (headersMode.failIfStreamAbsent()) {
+            writeSynResetLater(streamId, ErrorCode.INVALID_STREAM);
+            return;
+          }
+
+          // If the stream ID is less than the last created ID, assume it's already closed.
+          if (streamId <= lastGoodStreamId) return;
+
+          // If the stream ID is in the client's namespace, assume it's already closed.
+          if (streamId % 2 == nextStreamId % 2) return;
+
+          // Create a stream.
+          final FramedStream
+              newStream = new FramedStream(streamId, FramedConnection.this, outFinished,
+              inFinished, headerBlock);
+          lastGoodStreamId = streamId;
+          streams.put(streamId, newStream);
+          executor.execute(new NamedRunnable("OkHttp %s stream %d", hostName, streamId) {
+            @Override public void execute() {
+              try {
+                listener.onStream(newStream);
+              } catch (IOException e) {
+                logger.log(Level.INFO, "FramedConnection.Listener failure for " + hostName, e);
+                try {
+                  newStream.close(ErrorCode.PROTOCOL_ERROR);
+                } catch (IOException ignored) {
+                }
+              }
+            }
+          });
+          return;
+        }
+      }
+
+      // The headers claim to be for a new stream, but we already have one.
+      if (headersMode.failIfStreamPresent()) {
+        stream.closeLater(ErrorCode.PROTOCOL_ERROR);
+        removeStream(streamId);
+        return;
+      }
+
+      // Update an existing stream.
+      stream.receiveHeaders(headerBlock, headersMode);
+      if (inFinished) stream.receiveFin();
+    }
+
+    @Override public void rstStream(int streamId, ErrorCode errorCode) {
+      if (pushedStream(streamId)) {
+        pushResetLater(streamId, errorCode);
+        return;
+      }
+      FramedStream rstStream = removeStream(streamId);
+      if (rstStream != null) {
+        rstStream.receiveRstStream(errorCode);
+      }
+    }
+
+    @Override public void settings(boolean clearPrevious, Settings newSettings) {
+      long delta = 0;
+      FramedStream[] streamsToNotify = null;
+      synchronized (FramedConnection.this) {
+        int priorWriteWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
+        if (clearPrevious) peerSettings.clear();
+        peerSettings.merge(newSettings);
+        if (getProtocol() == Protocol.HTTP_2) {
+          ackSettingsLater(newSettings);
+        }
+        int peerInitialWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
+        if (peerInitialWindowSize != -1 && peerInitialWindowSize != priorWriteWindowSize) {
+          delta = peerInitialWindowSize - priorWriteWindowSize;
+          if (!receivedInitialPeerSettings) {
+            addBytesToWriteWindow(delta);
+            receivedInitialPeerSettings = true;
+          }
+          if (!streams.isEmpty()) {
+            streamsToNotify = streams.values().toArray(new FramedStream[streams.size()]);
+          }
+        }
+        executor.execute(new NamedRunnable("OkHttp %s settings", hostName) {
+          @Override public void execute() {
+            listener.onSettings(FramedConnection.this);
+          }
+        });
+      }
+      if (streamsToNotify != null && delta != 0) {
+        for (FramedStream stream : streamsToNotify) {
+          synchronized (stream) {
+            stream.addBytesToWriteWindow(delta);
+          }
+        }
+      }
+    }
+
+    private void ackSettingsLater(final Settings peerSettings) {
+      executor.execute(new NamedRunnable("OkHttp %s ACK Settings", hostName) {
+        @Override public void execute() {
+          try {
+            frameWriter.ackSettings(peerSettings);
+          } catch (IOException ignored) {
+          }
+        }
+      });
+    }
+
+    @Override public void ackSettings() {
+      // TODO: If we don't get this callback after sending settings to the peer, SETTINGS_TIMEOUT.
+    }
+
+    @Override public void ping(boolean reply, int payload1, int payload2) {
+      if (reply) {
+        Ping ping = removePing(payload1);
+        if (ping != null) {
+          ping.receive();
+        }
+      } else {
+        // Send a reply to a client ping if this is a server and vice versa.
+        writePingLater(true, payload1, payload2, null);
+      }
+    }
+
+    @Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
+      if (debugData.size() > 0) { // TODO: log the debugData
+      }
+
+      // Copy the streams first. We don't want to hold a lock when we call receiveRstStream().
+      FramedStream[] streamsCopy;
+      synchronized (FramedConnection.this) {
+        streamsCopy = streams.values().toArray(new FramedStream[streams.size()]);
+        shutdown = true;
+      }
+
+      // Fail all streams created after the last good stream ID.
+      for (FramedStream framedStream : streamsCopy) {
+        if (framedStream.getId() > lastGoodStreamId && framedStream.isLocallyInitiated()) {
+          framedStream.receiveRstStream(ErrorCode.REFUSED_STREAM);
+          removeStream(framedStream.getId());
+        }
+      }
+    }
+
+    @Override public void windowUpdate(int streamId, long windowSizeIncrement) {
+      if (streamId == 0) {
+        synchronized (FramedConnection.this) {
+          bytesLeftInWriteWindow += windowSizeIncrement;
+          FramedConnection.this.notifyAll();
+        }
+      } else {
+        FramedStream stream = getStream(streamId);
+        if (stream != null) {
+          synchronized (stream) {
+            stream.addBytesToWriteWindow(windowSizeIncrement);
+          }
+        }
+      }
+    }
+
+    @Override public void priority(int streamId, int streamDependency, int weight,
+        boolean exclusive) {
+      // TODO: honor priority.
+    }
+
+    @Override
+    public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders) {
+      pushRequestLater(promisedStreamId, requestHeaders);
+    }
+
+    @Override public void alternateService(int streamId, String origin, ByteString protocol,
+        String host, int port, long maxAge) {
+      // TODO: register alternate service.
+    }
+  }
+
+  /** Even, positive numbered streams are pushed streams in HTTP/2. */
+  private boolean pushedStream(int streamId) {
+    return protocol == Protocol.HTTP_2 && streamId != 0 && (streamId & 1) == 0;
+  }
+
+  // Guarded by this.
+  private final Set<Integer> currentPushRequests = new LinkedHashSet<>();
+
+  private void pushRequestLater(final int streamId, final List<Header> requestHeaders) {
+    synchronized (this) {
+      if (currentPushRequests.contains(streamId)) {
+        writeSynResetLater(streamId, ErrorCode.PROTOCOL_ERROR);
+        return;
+      }
+      currentPushRequests.add(streamId);
+    }
+    pushExecutor.execute(new NamedRunnable("OkHttp %s Push Request[%s]", hostName, streamId) {
+      @Override public void execute() {
+        boolean cancel = pushObserver.onRequest(streamId, requestHeaders);
+        try {
+          if (cancel) {
+            frameWriter.rstStream(streamId, ErrorCode.CANCEL);
+            synchronized (FramedConnection.this) {
+              currentPushRequests.remove(streamId);
+            }
+          }
+        } catch (IOException ignored) {
+        }
+      }
+    });
+  }
+
+  private void pushHeadersLater(final int streamId, final List<Header> requestHeaders,
+      final boolean inFinished) {
+    pushExecutor.execute(new NamedRunnable("OkHttp %s Push Headers[%s]", hostName, streamId) {
+      @Override public void execute() {
+        boolean cancel = pushObserver.onHeaders(streamId, requestHeaders, inFinished);
+        try {
+          if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL);
+          if (cancel || inFinished) {
+            synchronized (FramedConnection.this) {
+              currentPushRequests.remove(streamId);
+            }
+          }
+        } catch (IOException ignored) {
+        }
+      }
+    });
+  }
+
+  /**
+   * Eagerly reads {@code byteCount} bytes from the source before launching a background task to
+   * process the data.  This avoids corrupting the stream.
+   */
+  private void pushDataLater(final int streamId, final BufferedSource source, final int byteCount,
+      final boolean inFinished) throws IOException {
+    final Buffer buffer = new Buffer();
+    source.require(byteCount); // Eagerly read the frame before firing client thread.
+    source.read(buffer, byteCount);
+    if (buffer.size() != byteCount) throw new IOException(buffer.size() + " != " + byteCount);
+    pushExecutor.execute(new NamedRunnable("OkHttp %s Push Data[%s]", hostName, streamId) {
+      @Override public void execute() {
+        try {
+          boolean cancel = pushObserver.onData(streamId, buffer, byteCount, inFinished);
+          if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL);
+          if (cancel || inFinished) {
+            synchronized (FramedConnection.this) {
+              currentPushRequests.remove(streamId);
+            }
+          }
+        } catch (IOException ignored) {
+        }
+      }
+    });
+  }
+
+  private void pushResetLater(final int streamId, final ErrorCode errorCode) {
+    pushExecutor.execute(new NamedRunnable("OkHttp %s Push Reset[%s]", hostName, streamId) {
+      @Override public void execute() {
+        pushObserver.onReset(streamId, errorCode);
+        synchronized (FramedConnection.this) {
+          currentPushRequests.remove(streamId);
+        }
+      }
+    });
+  }
+
+  /** Listener of streams and settings initiated by the peer. 
+   * @hide This class is not part of the Android public SDK API*/
+  public abstract static class Listener {
+    public static final Listener REFUSE_INCOMING_STREAMS = new Listener() {
+      @Override public void onStream(FramedStream stream) throws IOException {
+        stream.close(ErrorCode.REFUSED_STREAM);
+      }
+    };
+
+    /**
+     * Handle a new stream from this connection's peer. Implementations should
+     * respond by either {@linkplain FramedStream#reply replying to the stream}
+     * or {@linkplain FramedStream#close closing it}. This response does not
+     * need to be synchronous.
+     */
+    public abstract void onStream(FramedStream stream) throws IOException;
+
+    /**
+     * Notification that the connection's peer's settings may have changed.
+     * Implementations should take appropriate action to handle the updated
+     * settings.
+     *
+     * <p>It is the implementation's responsibility to handle concurrent calls
+     * to this method. A remote peer that sends multiple settings frames will
+     * trigger multiple calls to this method, and those calls are not
+     * necessarily serialized.
+     */
+    public void onSettings(FramedConnection connection) {
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedStream.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedStream.java
new file mode 100644
index 0000000..9339acf
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/FramedStream.java
@@ -0,0 +1,618 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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 com.android.okhttp.internal.framed;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.List;
+import com.android.okhttp.okio.AsyncTimeout;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+import com.android.okhttp.okio.Timeout;
+
+import static com.android.okhttp.internal.framed.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
+
+/** A logical bidirectional stream. 
+ * @hide This class is not part of the Android public SDK API*/
+public final class FramedStream {
+  // Internal state is guarded by this. No long-running or potentially
+  // blocking operations are performed while the lock is held.
+
+  /**
+   * The total number of bytes consumed by the application (with {@link
+   * FramedDataSource#read}), but not yet acknowledged by sending a {@code
+   * WINDOW_UPDATE} frame on this stream.
+   */
+  // Visible for testing
+  long unacknowledgedBytesRead = 0;
+
+  /**
+   * Count of bytes that can be written on the stream before receiving a
+   * window update. Even if this is positive, writes will block until there
+   * available bytes in {@code connection.bytesLeftInWriteWindow}.
+   */
+  // guarded by this
+  long bytesLeftInWriteWindow;
+
+  private final int id;
+  private final FramedConnection connection;
+
+  /** Headers sent by the stream initiator. Immutable and non null. */
+  private final List<Header> requestHeaders;
+
+  /** Headers sent in the stream reply. Null if reply is either not sent or not sent yet. */
+  private List<Header> responseHeaders;
+
+  private final FramedDataSource source;
+  final FramedDataSink sink;
+  private final StreamTimeout readTimeout = new StreamTimeout();
+  private final StreamTimeout writeTimeout = new StreamTimeout();
+
+  /**
+   * The reason why this stream was abnormally closed. If there are multiple
+   * reasons to abnormally close this stream (such as both peers closing it
+   * near-simultaneously) then this is the first reason known to this peer.
+   */
+  private ErrorCode errorCode = null;
+
+  FramedStream(int id, FramedConnection connection, boolean outFinished, boolean inFinished,
+      List<Header> requestHeaders) {
+    if (connection == null) throw new NullPointerException("connection == null");
+    if (requestHeaders == null) throw new NullPointerException("requestHeaders == null");
+    this.id = id;
+    this.connection = connection;
+    this.bytesLeftInWriteWindow =
+        connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
+    this.source = new FramedDataSource(
+        connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE));
+    this.sink = new FramedDataSink();
+    this.source.finished = inFinished;
+    this.sink.finished = outFinished;
+    this.requestHeaders = requestHeaders;
+  }
+
+  public int getId() {
+    return id;
+  }
+
+  /**
+   * Returns true if this stream is open. A stream is open until either:
+   * <ul>
+   * <li>A {@code SYN_RESET} frame abnormally terminates the stream.
+   * <li>Both input and output streams have transmitted all data and
+   * headers.
+   * </ul>
+   * Note that the input stream may continue to yield data even after a stream
+   * reports itself as not open. This is because input data is buffered.
+   */
+  public synchronized boolean isOpen() {
+    if (errorCode != null) {
+      return false;
+    }
+    if ((source.finished || source.closed)
+        && (sink.finished || sink.closed)
+        && responseHeaders != null) {
+      return false;
+    }
+    return true;
+  }
+
+  /** Returns true if this stream was created by this peer. */
+  public boolean isLocallyInitiated() {
+    boolean streamIsClient = ((id & 1) == 1);
+    return connection.client == streamIsClient;
+  }
+
+  public FramedConnection getConnection() {
+    return connection;
+  }
+
+  public List<Header> getRequestHeaders() {
+    return requestHeaders;
+  }
+
+  /**
+   * Returns the stream's response headers, blocking if necessary if they
+   * have not been received yet.
+   */
+  public synchronized List<Header> getResponseHeaders() throws IOException {
+    readTimeout.enter();
+    try {
+      while (responseHeaders == null && errorCode == null) {
+        waitForIo();
+      }
+    } finally {
+      readTimeout.exitAndThrowIfTimedOut();
+    }
+    if (responseHeaders != null) return responseHeaders;
+    throw new IOException("stream was reset: " + errorCode);
+  }
+
+  /**
+   * Returns the reason why this stream was closed, or null if it closed
+   * normally or has not yet been closed.
+   */
+  public synchronized ErrorCode getErrorCode() {
+    return errorCode;
+  }
+
+  /**
+   * Sends a reply to an incoming stream.
+   *
+   * @param out true to create an output stream that we can use to send data
+   * to the remote peer. Corresponds to {@code FLAG_FIN}.
+   */
+  public void reply(List<Header> responseHeaders, boolean out) throws IOException {
+    assert (!Thread.holdsLock(FramedStream.this));
+    boolean outFinished = false;
+    synchronized (this) {
+      if (responseHeaders == null) {
+        throw new NullPointerException("responseHeaders == null");
+      }
+      if (this.responseHeaders != null) {
+        throw new IllegalStateException("reply already sent");
+      }
+      this.responseHeaders = responseHeaders;
+      if (!out) {
+        this.sink.finished = true;
+        outFinished = true;
+      }
+    }
+    connection.writeSynReply(id, outFinished, responseHeaders);
+
+    if (outFinished) {
+      connection.flush();
+    }
+  }
+
+  public Timeout readTimeout() {
+    return readTimeout;
+  }
+
+  public Timeout writeTimeout() {
+    return writeTimeout;
+  }
+
+  /** Returns a source that reads data from the peer. */
+  public Source getSource() {
+    return source;
+  }
+
+  /**
+   * Returns a sink that can be used to write data to the peer.
+   *
+   * @throws IllegalStateException if this stream was initiated by the peer
+   *     and a {@link #reply} has not yet been sent.
+   */
+  public Sink getSink() {
+    synchronized (this) {
+      if (responseHeaders == null && !isLocallyInitiated()) {
+        throw new IllegalStateException("reply before requesting the sink");
+      }
+    }
+    return sink;
+  }
+
+  /**
+   * Abnormally terminate this stream. This blocks until the {@code RST_STREAM}
+   * frame has been transmitted.
+   */
+  public void close(ErrorCode rstStatusCode) throws IOException {
+    if (!closeInternal(rstStatusCode)) {
+      return; // Already closed.
+    }
+    connection.writeSynReset(id, rstStatusCode);
+  }
+
+  /**
+   * Abnormally terminate this stream. This enqueues a {@code RST_STREAM}
+   * frame and returns immediately.
+   */
+  public void closeLater(ErrorCode errorCode) {
+    if (!closeInternal(errorCode)) {
+      return; // Already closed.
+    }
+    connection.writeSynResetLater(id, errorCode);
+  }
+
+  /** Returns true if this stream was closed. */
+  private boolean closeInternal(ErrorCode errorCode) {
+    assert (!Thread.holdsLock(this));
+    synchronized (this) {
+      if (this.errorCode != null) {
+        return false;
+      }
+      if (source.finished && sink.finished) {
+        return false;
+      }
+      this.errorCode = errorCode;
+      notifyAll();
+    }
+    connection.removeStream(id);
+    return true;
+  }
+
+  void receiveHeaders(List<Header> headers, HeadersMode headersMode) {
+    assert (!Thread.holdsLock(FramedStream.this));
+    ErrorCode errorCode = null;
+    boolean open = true;
+    synchronized (this) {
+      if (responseHeaders == null) {
+        if (headersMode.failIfHeadersAbsent()) {
+          errorCode = ErrorCode.PROTOCOL_ERROR;
+        } else {
+          responseHeaders = headers;
+          open = isOpen();
+          notifyAll();
+        }
+      } else {
+        if (headersMode.failIfHeadersPresent()) {
+          errorCode = ErrorCode.STREAM_IN_USE;
+        } else {
+          List<Header> newHeaders = new ArrayList<>();
+          newHeaders.addAll(responseHeaders);
+          newHeaders.addAll(headers);
+          this.responseHeaders = newHeaders;
+        }
+      }
+    }
+    if (errorCode != null) {
+      closeLater(errorCode);
+    } else if (!open) {
+      connection.removeStream(id);
+    }
+  }
+
+  void receiveData(BufferedSource in, int length) throws IOException {
+    assert (!Thread.holdsLock(FramedStream.this));
+    this.source.receive(in, length);
+  }
+
+  void receiveFin() {
+    assert (!Thread.holdsLock(FramedStream.this));
+    boolean open;
+    synchronized (this) {
+      this.source.finished = true;
+      open = isOpen();
+      notifyAll();
+    }
+    if (!open) {
+      connection.removeStream(id);
+    }
+  }
+
+  synchronized void receiveRstStream(ErrorCode errorCode) {
+    if (this.errorCode == null) {
+      this.errorCode = errorCode;
+      notifyAll();
+    }
+  }
+
+  /**
+   * A source that reads the incoming data frames of a stream. Although this
+   * class uses synchronization to safely receive incoming data frames, it is
+   * not intended for use by multiple readers.
+   */
+  private final class FramedDataSource implements Source {
+    /** Buffer to receive data from the network into. Only accessed by the reader thread. */
+    private final Buffer receiveBuffer = new Buffer();
+
+    /** Buffer with readable data. Guarded by FramedStream.this. */
+    private final Buffer readBuffer = new Buffer();
+
+    /** Maximum number of bytes to buffer before reporting a flow control error. */
+    private final long maxByteCount;
+
+    /** True if the caller has closed this stream. */
+    private boolean closed;
+
+    /**
+     * True if either side has cleanly shut down this stream. We will
+     * receive no more bytes beyond those already in the buffer.
+     */
+    private boolean finished;
+
+    private FramedDataSource(long maxByteCount) {
+      this.maxByteCount = maxByteCount;
+    }
+
+    @Override public long read(Buffer sink, long byteCount)
+        throws IOException {
+      if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+
+      long read;
+      synchronized (FramedStream.this) {
+        waitUntilReadable();
+        checkNotClosed();
+        if (readBuffer.size() == 0) return -1; // This source is exhausted.
+
+        // Move bytes from the read buffer into the caller's buffer.
+        read = readBuffer.read(sink, Math.min(byteCount, readBuffer.size()));
+
+        // Flow control: notify the peer that we're ready for more data!
+        unacknowledgedBytesRead += read;
+        if (unacknowledgedBytesRead
+            >= connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE) / 2) {
+          connection.writeWindowUpdateLater(id, unacknowledgedBytesRead);
+          unacknowledgedBytesRead = 0;
+        }
+      }
+
+      // Update connection.unacknowledgedBytesRead outside the stream lock.
+      synchronized (connection) { // Multiple application threads may hit this section.
+        connection.unacknowledgedBytesRead += read;
+        if (connection.unacknowledgedBytesRead
+            >= connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE) / 2) {
+          connection.writeWindowUpdateLater(0, connection.unacknowledgedBytesRead);
+          connection.unacknowledgedBytesRead = 0;
+        }
+      }
+
+      return read;
+    }
+
+    /** Returns once the source is either readable or finished. */
+    private void waitUntilReadable() throws IOException {
+      readTimeout.enter();
+      try {
+        while (readBuffer.size() == 0 && !finished && !closed && errorCode == null) {
+          waitForIo();
+        }
+      } finally {
+        readTimeout.exitAndThrowIfTimedOut();
+      }
+    }
+
+    void receive(BufferedSource in, long byteCount) throws IOException {
+      assert (!Thread.holdsLock(FramedStream.this));
+
+      while (byteCount > 0) {
+        boolean finished;
+        boolean flowControlError;
+        synchronized (FramedStream.this) {
+          finished = this.finished;
+          flowControlError = byteCount + readBuffer.size() > maxByteCount;
+        }
+
+        // If the peer sends more data than we can handle, discard it and close the connection.
+        if (flowControlError) {
+          in.skip(byteCount);
+          closeLater(ErrorCode.FLOW_CONTROL_ERROR);
+          return;
+        }
+
+        // Discard data received after the stream is finished. It's probably a benign race.
+        if (finished) {
+          in.skip(byteCount);
+          return;
+        }
+
+        // Fill the receive buffer without holding any locks.
+        long read = in.read(receiveBuffer, byteCount);
+        if (read == -1) throw new EOFException();
+        byteCount -= read;
+
+        // Move the received data to the read buffer to the reader can read it.
+        synchronized (FramedStream.this) {
+          boolean wasEmpty = readBuffer.size() == 0;
+          readBuffer.writeAll(receiveBuffer);
+          if (wasEmpty) {
+            FramedStream.this.notifyAll();
+          }
+        }
+      }
+    }
+
+    @Override public Timeout timeout() {
+      return readTimeout;
+    }
+
+    @Override public void close() throws IOException {
+      synchronized (FramedStream.this) {
+        closed = true;
+        readBuffer.clear();
+        FramedStream.this.notifyAll();
+      }
+      cancelStreamIfNecessary();
+    }
+
+    private void checkNotClosed() throws IOException {
+      if (closed) {
+        throw new IOException("stream closed");
+      }
+      if (errorCode != null) {
+        throw new IOException("stream was reset: " + errorCode);
+      }
+    }
+  }
+
+  private void cancelStreamIfNecessary() throws IOException {
+    assert (!Thread.holdsLock(FramedStream.this));
+    boolean open;
+    boolean cancel;
+    synchronized (this) {
+      cancel = !source.finished && source.closed && (sink.finished || sink.closed);
+      open = isOpen();
+    }
+    if (cancel) {
+      // RST this stream to prevent additional data from being sent. This
+      // is safe because the input stream is closed (we won't use any
+      // further bytes) and the output stream is either finished or closed
+      // (so RSTing both streams doesn't cause harm).
+      FramedStream.this.close(ErrorCode.CANCEL);
+    } else if (!open) {
+      connection.removeStream(id);
+    }
+  }
+
+  /**
+   * A sink that writes outgoing data frames of a stream. This class is not
+   * thread safe.
+   */
+  final class FramedDataSink implements Sink {
+    private static final long EMIT_BUFFER_SIZE = 16384;
+
+    /**
+     * Buffer of outgoing data. This batches writes of small writes into this sink as larges
+     * frames written to the outgoing connection. Batching saves the (small) framing overhead.
+     */
+    private final Buffer sendBuffer = new Buffer();
+
+    private boolean closed;
+
+    /**
+     * True if either side has cleanly shut down this stream. We shall send
+     * no more bytes.
+     */
+    private boolean finished;
+
+    @Override public void write(Buffer source, long byteCount) throws IOException {
+      assert (!Thread.holdsLock(FramedStream.this));
+      sendBuffer.write(source, byteCount);
+      while (sendBuffer.size() >= EMIT_BUFFER_SIZE) {
+        emitDataFrame(false);
+      }
+    }
+
+    /**
+     * Emit a single data frame to the connection. The frame's size be limited by this stream's
+     * write window. This method will block until the write window is nonempty.
+     */
+    private void emitDataFrame(boolean outFinished) throws IOException {
+      long toWrite;
+      synchronized (FramedStream.this) {
+        writeTimeout.enter();
+        try {
+          while (bytesLeftInWriteWindow <= 0 && !finished && !closed && errorCode == null) {
+            waitForIo(); // Wait until we receive a WINDOW_UPDATE for this stream.
+          }
+        } finally {
+          writeTimeout.exitAndThrowIfTimedOut();
+        }
+
+        checkOutNotClosed(); // Kick out if the stream was reset or closed while waiting.
+        toWrite = Math.min(bytesLeftInWriteWindow, sendBuffer.size());
+        bytesLeftInWriteWindow -= toWrite;
+      }
+
+      writeTimeout.enter();
+      try {
+        connection.writeData(id, outFinished && toWrite == sendBuffer.size(), sendBuffer, toWrite);
+      } finally {
+        writeTimeout.exitAndThrowIfTimedOut();
+      }
+    }
+
+    @Override public void flush() throws IOException {
+      assert (!Thread.holdsLock(FramedStream.this));
+      synchronized (FramedStream.this) {
+        checkOutNotClosed();
+      }
+      while (sendBuffer.size() > 0) {
+        emitDataFrame(false);
+        connection.flush();
+      }
+    }
+
+    @Override public Timeout timeout() {
+      return writeTimeout;
+    }
+
+    @Override public void close() throws IOException {
+      assert (!Thread.holdsLock(FramedStream.this));
+      synchronized (FramedStream.this) {
+        if (closed) return;
+      }
+      if (!sink.finished) {
+        // Emit the remaining data, setting the END_STREAM flag on the last frame.
+        if (sendBuffer.size() > 0) {
+          while (sendBuffer.size() > 0) {
+            emitDataFrame(true);
+          }
+        } else {
+          // Send an empty frame just so we can set the END_STREAM flag.
+          connection.writeData(id, true, null, 0);
+        }
+      }
+      synchronized (FramedStream.this) {
+        closed = true;
+      }
+      connection.flush();
+      cancelStreamIfNecessary();
+    }
+  }
+
+  /**
+   * {@code delta} will be negative if a settings frame initial window is
+   * smaller than the last.
+   */
+  void addBytesToWriteWindow(long delta) {
+    bytesLeftInWriteWindow += delta;
+    if (delta > 0) FramedStream.this.notifyAll();
+  }
+
+  private void checkOutNotClosed() throws IOException {
+    if (sink.closed) {
+      throw new IOException("stream closed");
+    } else if (sink.finished) {
+      throw new IOException("stream finished");
+    } else if (errorCode != null) {
+      throw new IOException("stream was reset: " + errorCode);
+    }
+  }
+
+  /**
+   * Like {@link #wait}, but throws an {@code InterruptedIOException} when
+   * interrupted instead of the more awkward {@link InterruptedException}.
+   */
+  private void waitForIo() throws InterruptedIOException {
+    try {
+      wait();
+    } catch (InterruptedException e) {
+      throw new InterruptedIOException();
+    }
+  }
+
+  /**
+   * The Okio timeout watchdog will call {@link #timedOut} if the timeout is
+   * reached. In that case we close the stream (asynchronously) which will
+   * notify the waiting thread.
+   */
+  class StreamTimeout extends AsyncTimeout {
+    @Override protected void timedOut() {
+      closeLater(ErrorCode.CANCEL);
+    }
+
+    @Override protected IOException newTimeoutException(IOException cause) {
+      SocketTimeoutException socketTimeoutException = new SocketTimeoutException("timeout");
+      if (cause != null) {
+        socketTimeoutException.initCause(cause);
+      }
+      return socketTimeoutException;
+    }
+
+    public void exitAndThrowIfTimedOut() throws IOException {
+      if (exit()) throw newTimeoutException(null /* cause */);
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Header.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Header.java
new file mode 100644
index 0000000..4e2c67f
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Header.java
@@ -0,0 +1,58 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal.framed;
+
+import com.android.okhttp.okio.ByteString;
+
+/** HTTP header: the name is an ASCII string, but the value can be UTF-8. 
+ * @hide This class is not part of the Android public SDK API*/
+public final class Header {
+  // Special header names defined in the SPDY and HTTP/2 specs.
+  public static final ByteString RESPONSE_STATUS = ByteString.encodeUtf8(":status");
+  public static final ByteString TARGET_METHOD = ByteString.encodeUtf8(":method");
+  public static final ByteString TARGET_PATH = ByteString.encodeUtf8(":path");
+  public static final ByteString TARGET_SCHEME = ByteString.encodeUtf8(":scheme");
+  public static final ByteString TARGET_AUTHORITY = ByteString.encodeUtf8(":authority"); // HTTP/2
+  public static final ByteString TARGET_HOST = ByteString.encodeUtf8(":host"); // spdy/3
+  public static final ByteString VERSION = ByteString.encodeUtf8(":version"); // spdy/3
+
+  /** Name in case-insensitive ASCII encoding. */
+  public final ByteString name;
+  /** Value in UTF-8 encoding. */
+  public final ByteString value;
+  final int hpackSize;
+
+  // TODO: search for toLowerCase and consider moving logic here.
+  public Header(String name, String value) {
+    this(ByteString.encodeUtf8(name), ByteString.encodeUtf8(value));
+  }
+
+  public Header(ByteString name, String value) {
+    this(name, ByteString.encodeUtf8(value));
+  }
+
+  public Header(ByteString name, ByteString value) {
+    this.name = name;
+    this.value = value;
+    this.hpackSize = 32 + name.size() + value.size();
+  }
+
+  @Override public boolean equals(Object other) {
+    if (other instanceof Header) {
+      Header that = (Header) other;
+      return this.name.equals(that.name)
+          && this.value.equals(that.value);
+    }
+    return false;
+  }
+
+  @Override public int hashCode() {
+    int result = 17;
+    result = 31 * result + name.hashCode();
+    result = 31 * result + value.hashCode();
+    return result;
+  }
+
+  @Override public String toString() {
+    return String.format("%s: %s", name.utf8(), value.utf8());
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/HeadersMode.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/HeadersMode.java
new file mode 100644
index 0000000..bcbb492
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/HeadersMode.java
@@ -0,0 +1,53 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.framed;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public enum HeadersMode {
+  SPDY_SYN_STREAM,
+  SPDY_REPLY,
+  SPDY_HEADERS,
+  HTTP_20_HEADERS;
+
+  /** Returns true if it is an error these headers to create a new stream. */
+  public boolean failIfStreamAbsent() {
+    return this == SPDY_REPLY || this == SPDY_HEADERS;
+  }
+
+  /** Returns true if it is an error these headers to update an existing stream. */
+  public boolean failIfStreamPresent() {
+    return this == SPDY_SYN_STREAM;
+  }
+
+  /**
+   * Returns true if it is an error these headers to be the initial headers of a
+   * response.
+   */
+  public boolean failIfHeadersAbsent() {
+    return this == SPDY_HEADERS;
+  }
+
+  /**
+   * Returns true if it is an error these headers to be update existing headers
+   * of a response.
+   */
+  public boolean failIfHeadersPresent() {
+    return this == SPDY_REPLY;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Hpack.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Hpack.java
new file mode 100644
index 0000000..538e498
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Hpack.java
@@ -0,0 +1,436 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.framed;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Source;
+
+/**
+ * Read and write HPACK v10.
+ *
+ * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12
+ *
+ * This implementation uses an array for the dynamic table and a list for
+ * indexed entries.  Dynamic entries are added to the array, starting in the
+ * last position moving forward.  When the array fills, it is doubled.
+ */
+final class Hpack {
+  private static final int PREFIX_4_BITS = 0x0f;
+  private static final int PREFIX_5_BITS = 0x1f;
+  private static final int PREFIX_6_BITS = 0x3f;
+  private static final int PREFIX_7_BITS = 0x7f;
+
+  private static final Header[] STATIC_HEADER_TABLE = new Header[] {
+      new Header(Header.TARGET_AUTHORITY, ""),
+      new Header(Header.TARGET_METHOD, "GET"),
+      new Header(Header.TARGET_METHOD, "POST"),
+      new Header(Header.TARGET_PATH, "/"),
+      new Header(Header.TARGET_PATH, "/index.html"),
+      new Header(Header.TARGET_SCHEME, "http"),
+      new Header(Header.TARGET_SCHEME, "https"),
+      new Header(Header.RESPONSE_STATUS, "200"),
+      new Header(Header.RESPONSE_STATUS, "204"),
+      new Header(Header.RESPONSE_STATUS, "206"),
+      new Header(Header.RESPONSE_STATUS, "304"),
+      new Header(Header.RESPONSE_STATUS, "400"),
+      new Header(Header.RESPONSE_STATUS, "404"),
+      new Header(Header.RESPONSE_STATUS, "500"),
+      new Header("accept-charset", ""),
+      new Header("accept-encoding", "gzip, deflate"),
+      new Header("accept-language", ""),
+      new Header("accept-ranges", ""),
+      new Header("accept", ""),
+      new Header("access-control-allow-origin", ""),
+      new Header("age", ""),
+      new Header("allow", ""),
+      new Header("authorization", ""),
+      new Header("cache-control", ""),
+      new Header("content-disposition", ""),
+      new Header("content-encoding", ""),
+      new Header("content-language", ""),
+      new Header("content-length", ""),
+      new Header("content-location", ""),
+      new Header("content-range", ""),
+      new Header("content-type", ""),
+      new Header("cookie", ""),
+      new Header("date", ""),
+      new Header("etag", ""),
+      new Header("expect", ""),
+      new Header("expires", ""),
+      new Header("from", ""),
+      new Header("host", ""),
+      new Header("if-match", ""),
+      new Header("if-modified-since", ""),
+      new Header("if-none-match", ""),
+      new Header("if-range", ""),
+      new Header("if-unmodified-since", ""),
+      new Header("last-modified", ""),
+      new Header("link", ""),
+      new Header("location", ""),
+      new Header("max-forwards", ""),
+      new Header("proxy-authenticate", ""),
+      new Header("proxy-authorization", ""),
+      new Header("range", ""),
+      new Header("referer", ""),
+      new Header("refresh", ""),
+      new Header("retry-after", ""),
+      new Header("server", ""),
+      new Header("set-cookie", ""),
+      new Header("strict-transport-security", ""),
+      new Header("transfer-encoding", ""),
+      new Header("user-agent", ""),
+      new Header("vary", ""),
+      new Header("via", ""),
+      new Header("www-authenticate", "")
+  };
+
+  private Hpack() {
+  }
+
+  // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-3.1
+  static final class Reader {
+
+    private final List<Header> headerList = new ArrayList<>();
+    private final BufferedSource source;
+
+    private int headerTableSizeSetting;
+    private int maxDynamicTableByteCount;
+    // Visible for testing.
+    Header[] dynamicTable = new Header[8];
+    // Array is populated back to front, so new entries always have lowest index.
+    int nextHeaderIndex = dynamicTable.length - 1;
+    int headerCount = 0;
+    int dynamicTableByteCount = 0;
+
+    Reader(int headerTableSizeSetting, Source source) {
+      this.headerTableSizeSetting = headerTableSizeSetting;
+      this.maxDynamicTableByteCount = headerTableSizeSetting;
+      this.source = Okio.buffer(source);
+    }
+
+    int maxDynamicTableByteCount() {
+      return maxDynamicTableByteCount;
+    }
+
+    /**
+     * Called by the reader when the peer sent {@link Settings#HEADER_TABLE_SIZE}.
+     * While this establishes the maximum dynamic table size, the
+     * {@link #maxDynamicTableByteCount} set during processing may limit the
+     * table size to a smaller amount.
+     * <p> Evicts entries or clears the table as needed.
+     */
+    void headerTableSizeSetting(int headerTableSizeSetting) {
+      this.headerTableSizeSetting = headerTableSizeSetting;
+      this.maxDynamicTableByteCount = headerTableSizeSetting;
+      adjustDynamicTableByteCount();
+    }
+
+    private void adjustDynamicTableByteCount() {
+      if (maxDynamicTableByteCount < dynamicTableByteCount) {
+        if (maxDynamicTableByteCount == 0) {
+          clearDynamicTable();
+        } else {
+          evictToRecoverBytes(dynamicTableByteCount - maxDynamicTableByteCount);
+        }
+      }
+    }
+
+    private void clearDynamicTable() {
+      headerList.clear();
+      Arrays.fill(dynamicTable, null);
+      nextHeaderIndex = dynamicTable.length - 1;
+      headerCount = 0;
+      dynamicTableByteCount = 0;
+    }
+
+    /** Returns the count of entries evicted. */
+    private int evictToRecoverBytes(int bytesToRecover) {
+      int entriesToEvict = 0;
+      if (bytesToRecover > 0) {
+        // determine how many headers need to be evicted.
+        for (int j = dynamicTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) {
+          bytesToRecover -= dynamicTable[j].hpackSize;
+          dynamicTableByteCount -= dynamicTable[j].hpackSize;
+          headerCount--;
+          entriesToEvict++;
+        }
+        System.arraycopy(dynamicTable, nextHeaderIndex + 1, dynamicTable,
+            nextHeaderIndex + 1 + entriesToEvict, headerCount);
+        nextHeaderIndex += entriesToEvict;
+      }
+      return entriesToEvict;
+    }
+
+    /**
+     * Read {@code byteCount} bytes of headers from the source stream. This
+     * implementation does not propagate the never indexed flag of a header.
+     */
+    void readHeaders() throws IOException {
+      while (!source.exhausted()) {
+        int b = source.readByte() & 0xff;
+        if (b == 0x80) { // 10000000
+          throw new IOException("index == 0");
+        } else if ((b & 0x80) == 0x80) { // 1NNNNNNN
+          int index = readInt(b, PREFIX_7_BITS);
+          readIndexedHeader(index - 1);
+        } else if (b == 0x40) { // 01000000
+          readLiteralHeaderWithIncrementalIndexingNewName();
+        } else if ((b & 0x40) == 0x40) {  // 01NNNNNN
+          int index = readInt(b, PREFIX_6_BITS);
+          readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
+        } else if ((b & 0x20) == 0x20) {  // 001NNNNN
+          maxDynamicTableByteCount = readInt(b, PREFIX_5_BITS);
+          if (maxDynamicTableByteCount < 0
+              || maxDynamicTableByteCount > headerTableSizeSetting) {
+            throw new IOException("Invalid dynamic table size update " + maxDynamicTableByteCount);
+          }
+          adjustDynamicTableByteCount();
+        } else if (b == 0x10 || b == 0) { // 000?0000 - Ignore never indexed bit.
+          readLiteralHeaderWithoutIndexingNewName();
+        } else { // 000?NNNN - Ignore never indexed bit.
+          int index = readInt(b, PREFIX_4_BITS);
+          readLiteralHeaderWithoutIndexingIndexedName(index - 1);
+        }
+      }
+    }
+
+    public List<Header> getAndResetHeaderList() {
+      List<Header> result = new ArrayList<>(headerList);
+      headerList.clear();
+      return result;
+    }
+
+    private void readIndexedHeader(int index) throws IOException {
+      if (isStaticHeader(index)) {
+        Header staticEntry = STATIC_HEADER_TABLE[index];
+        headerList.add(staticEntry);
+      } else {
+        int dynamicTableIndex = dynamicTableIndex(index - STATIC_HEADER_TABLE.length);
+        if (dynamicTableIndex < 0 || dynamicTableIndex > dynamicTable.length - 1) {
+          throw new IOException("Header index too large " + (index + 1));
+        }
+        headerList.add(dynamicTable[dynamicTableIndex]);
+      }
+    }
+
+    // referencedHeaders is relative to nextHeaderIndex + 1.
+    private int dynamicTableIndex(int index) {
+      return nextHeaderIndex + 1 + index;
+    }
+
+    private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException {
+      ByteString name = getName(index);
+      ByteString value = readByteString();
+      headerList.add(new Header(name, value));
+    }
+
+    private void readLiteralHeaderWithoutIndexingNewName() throws IOException {
+      ByteString name = checkLowercase(readByteString());
+      ByteString value = readByteString();
+      headerList.add(new Header(name, value));
+    }
+
+    private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)
+        throws IOException {
+      ByteString name = getName(nameIndex);
+      ByteString value = readByteString();
+      insertIntoDynamicTable(-1, new Header(name, value));
+    }
+
+    private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {
+      ByteString name = checkLowercase(readByteString());
+      ByteString value = readByteString();
+      insertIntoDynamicTable(-1, new Header(name, value));
+    }
+
+    private ByteString getName(int index) {
+      if (isStaticHeader(index)) {
+        return STATIC_HEADER_TABLE[index].name;
+      } else {
+        return dynamicTable[dynamicTableIndex(index - STATIC_HEADER_TABLE.length)].name;
+      }
+    }
+
+    private boolean isStaticHeader(int index) {
+      return index >= 0 && index <= STATIC_HEADER_TABLE.length - 1;
+    }
+
+    /** index == -1 when new. */
+    private void insertIntoDynamicTable(int index, Header entry) {
+      headerList.add(entry);
+
+      int delta = entry.hpackSize;
+      if (index != -1) { // Index -1 == new header.
+        delta -= dynamicTable[dynamicTableIndex(index)].hpackSize;
+      }
+
+      // if the new or replacement header is too big, drop all entries.
+      if (delta > maxDynamicTableByteCount) {
+        clearDynamicTable();
+        return;
+      }
+
+      // Evict headers to the required length.
+      int bytesToRecover = (dynamicTableByteCount + delta) - maxDynamicTableByteCount;
+      int entriesEvicted = evictToRecoverBytes(bytesToRecover);
+
+      if (index == -1) { // Adding a value to the dynamic table.
+        if (headerCount + 1 > dynamicTable.length) { // Need to grow the dynamic table.
+          Header[] doubled = new Header[dynamicTable.length * 2];
+          System.arraycopy(dynamicTable, 0, doubled, dynamicTable.length, dynamicTable.length);
+          nextHeaderIndex = dynamicTable.length - 1;
+          dynamicTable = doubled;
+        }
+        index = nextHeaderIndex--;
+        dynamicTable[index] = entry;
+        headerCount++;
+      } else { // Replace value at same position.
+        index += dynamicTableIndex(index) + entriesEvicted;
+        dynamicTable[index] = entry;
+      }
+      dynamicTableByteCount += delta;
+    }
+
+    private int readByte() throws IOException {
+      return source.readByte() & 0xff;
+    }
+
+    int readInt(int firstByte, int prefixMask) throws IOException {
+      int prefix = firstByte & prefixMask;
+      if (prefix < prefixMask) {
+        return prefix; // This was a single byte value.
+      }
+
+      // This is a multibyte value. Read 7 bits at a time.
+      int result = prefixMask;
+      int shift = 0;
+      while (true) {
+        int b = readByte();
+        if ((b & 0x80) != 0) { // Equivalent to (b >= 128) since b is in [0..255].
+          result += (b & 0x7f) << shift;
+          shift += 7;
+        } else {
+          result += b << shift; // Last byte.
+          break;
+        }
+      }
+      return result;
+    }
+
+    /** Reads a potentially Huffman encoded byte string. */
+    ByteString readByteString() throws IOException {
+      int firstByte = readByte();
+      boolean huffmanDecode = (firstByte & 0x80) == 0x80; // 1NNNNNNN
+      int length = readInt(firstByte, PREFIX_7_BITS);
+
+      if (huffmanDecode) {
+        return ByteString.of(Huffman.get().decode(source.readByteArray(length)));
+      } else {
+        return source.readByteString(length);
+      }
+    }
+  }
+
+  private static final Map<ByteString, Integer> NAME_TO_FIRST_INDEX = nameToFirstIndex();
+
+  private static Map<ByteString, Integer> nameToFirstIndex() {
+    Map<ByteString, Integer> result = new LinkedHashMap<>(STATIC_HEADER_TABLE.length);
+    for (int i = 0; i < STATIC_HEADER_TABLE.length; i++) {
+      if (!result.containsKey(STATIC_HEADER_TABLE[i].name)) {
+        result.put(STATIC_HEADER_TABLE[i].name, i);
+      }
+    }
+    return Collections.unmodifiableMap(result);
+  }
+
+  static final class Writer {
+    private final Buffer out;
+
+    Writer(Buffer out) {
+      this.out = out;
+    }
+
+    /** This does not use "never indexed" semantics for sensitive headers. */
+    // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-6.2.3
+    void writeHeaders(List<Header> headerBlock) throws IOException {
+      // TODO: implement index tracking
+      for (int i = 0, size = headerBlock.size(); i < size; i++) {
+        ByteString name = headerBlock.get(i).name.toAsciiLowercase();
+        Integer staticIndex = NAME_TO_FIRST_INDEX.get(name);
+        if (staticIndex != null) {
+          // Literal Header Field without Indexing - Indexed Name.
+          writeInt(staticIndex + 1, PREFIX_4_BITS, 0);
+          writeByteString(headerBlock.get(i).value);
+        } else {
+          out.writeByte(0x00); // Literal Header without Indexing - New Name.
+          writeByteString(name);
+          writeByteString(headerBlock.get(i).value);
+        }
+      }
+    }
+
+    // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-4.1.1
+    void writeInt(int value, int prefixMask, int bits) throws IOException {
+      // Write the raw value for a single byte value.
+      if (value < prefixMask) {
+        out.writeByte(bits | value);
+        return;
+      }
+
+      // Write the mask to start a multibyte value.
+      out.writeByte(bits | prefixMask);
+      value -= prefixMask;
+
+      // Write 7 bits at a time 'til we're done.
+      while (value >= 0x80) {
+        int b = value & 0x7f;
+        out.writeByte(b | 0x80);
+        value >>>= 7;
+      }
+      out.writeByte(value);
+    }
+
+    void writeByteString(ByteString data) throws IOException {
+      writeInt(data.size(), PREFIX_7_BITS, 0);
+      out.write(data);
+    }
+  }
+
+  /**
+   * An HTTP/2 response cannot contain uppercase header characters and must
+   * be treated as malformed.
+   */
+  private static ByteString checkLowercase(ByteString name) throws IOException {
+    for (int i = 0, length = name.size(); i < length; i++) {
+      byte c = name.getByte(i);
+      if (c >= 'A' && c <= 'Z') {
+        throw new IOException("PROTOCOL_ERROR response malformed: mixed case name: " + name.utf8());
+      }
+    }
+    return name;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Http2.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Http2.java
new file mode 100644
index 0000000..882d7e8
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Http2.java
@@ -0,0 +1,773 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.framed;
+
+import com.android.okhttp.Protocol;
+import java.io.IOException;
+import java.util.List;
+import java.util.logging.Logger;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.Source;
+import com.android.okhttp.okio.Timeout;
+
+import static com.android.okhttp.internal.framed.Http2.FrameLogger.formatHeader;
+import static java.lang.String.format;
+import static java.util.logging.Level.FINE;
+import static com.android.okhttp.okio.ByteString.EMPTY;
+
+/**
+ * Read and write HTTP/2 frames.
+ * <p>
+ * This implementation assumes we do not send an increased
+ * {@link Settings#getMaxFrameSize frame size setting} to the peer. Hence, we
+ * expect all frames to have a max length of {@link #INITIAL_MAX_FRAME_SIZE}.
+ * <p>http://tools.ietf.org/html/draft-ietf-httpbis-http2-17
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Http2 implements Variant {
+  private static final Logger logger = Logger.getLogger(FrameLogger.class.getName());
+
+  @Override public Protocol getProtocol() {
+    return Protocol.HTTP_2;
+  }
+
+  private static final ByteString CONNECTION_PREFACE
+      = ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
+
+  /** The initial max frame size, applied independently writing to, or reading from the peer. */
+  static final int INITIAL_MAX_FRAME_SIZE = 0x4000; // 16384
+
+  static final byte TYPE_DATA = 0x0;
+  static final byte TYPE_HEADERS = 0x1;
+  static final byte TYPE_PRIORITY = 0x2;
+  static final byte TYPE_RST_STREAM = 0x3;
+  static final byte TYPE_SETTINGS = 0x4;
+  static final byte TYPE_PUSH_PROMISE = 0x5;
+  static final byte TYPE_PING = 0x6;
+  static final byte TYPE_GOAWAY = 0x7;
+  static final byte TYPE_WINDOW_UPDATE = 0x8;
+  static final byte TYPE_CONTINUATION = 0x9;
+
+  static final byte FLAG_NONE = 0x0;
+  static final byte FLAG_ACK = 0x1; // Used for settings and ping.
+  static final byte FLAG_END_STREAM = 0x1; // Used for headers and data.
+  static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation.
+  static final byte FLAG_END_PUSH_PROMISE = 0x4;
+  static final byte FLAG_PADDED = 0x8; // Used for headers and data.
+  static final byte FLAG_PRIORITY = 0x20; // Used for headers.
+  static final byte FLAG_COMPRESSED = 0x20; // Used for data.
+
+  /**
+   * Creates a frame reader with max header table size of 4096 and data frame
+   * compression disabled.
+   */
+  @Override public FrameReader newReader(BufferedSource source, boolean client) {
+    return new Reader(source, 4096, client);
+  }
+
+  @Override public FrameWriter newWriter(BufferedSink sink, boolean client) {
+    return new Writer(sink, client);
+  }
+
+  static final class Reader implements FrameReader {
+    private final BufferedSource source;
+    private final ContinuationSource continuation;
+    private final boolean client;
+
+    // Visible for testing.
+    final Hpack.Reader hpackReader;
+
+    Reader(BufferedSource source, int headerTableSize, boolean client) {
+      this.source = source;
+      this.client = client;
+      this.continuation = new ContinuationSource(this.source);
+      this.hpackReader = new Hpack.Reader(headerTableSize, continuation);
+    }
+
+    @Override public void readConnectionPreface() throws IOException {
+      if (client) return; // Nothing to read; servers doesn't send a connection preface!
+      ByteString connectionPreface = source.readByteString(CONNECTION_PREFACE.size());
+      if (logger.isLoggable(FINE)) logger.fine(format("<< CONNECTION %s", connectionPreface.hex()));
+      if (!CONNECTION_PREFACE.equals(connectionPreface)) {
+        throw ioException("Expected a connection header but was %s", connectionPreface.utf8());
+      }
+    }
+
+    @Override public boolean nextFrame(Handler handler) throws IOException {
+      try {
+        source.require(9); // Frame header size
+      } catch (IOException e) {
+        return false; // This might be a normal socket close.
+      }
+
+      /*  0                   1                   2                   3
+       *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+       * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+       * |                 Length (24)                   |
+       * +---------------+---------------+---------------+
+       * |   Type (8)    |   Flags (8)   |
+       * +-+-+-----------+---------------+-------------------------------+
+       * |R|                 Stream Identifier (31)                      |
+       * +=+=============================================================+
+       * |                   Frame Payload (0...)                      ...
+       * +---------------------------------------------------------------+
+       */
+      int length = readMedium(source);
+      if (length < 0 || length > INITIAL_MAX_FRAME_SIZE) {
+        throw ioException("FRAME_SIZE_ERROR: %s", length);
+      }
+      byte type = (byte) (source.readByte() & 0xff);
+      byte flags = (byte) (source.readByte() & 0xff);
+      int streamId = (source.readInt() & 0x7fffffff); // Ignore reserved bit.
+      if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
+
+      switch (type) {
+        case TYPE_DATA:
+          readData(handler, length, flags, streamId);
+          break;
+
+        case TYPE_HEADERS:
+          readHeaders(handler, length, flags, streamId);
+          break;
+
+        case TYPE_PRIORITY:
+          readPriority(handler, length, flags, streamId);
+          break;
+
+        case TYPE_RST_STREAM:
+          readRstStream(handler, length, flags, streamId);
+          break;
+
+        case TYPE_SETTINGS:
+          readSettings(handler, length, flags, streamId);
+          break;
+
+        case TYPE_PUSH_PROMISE:
+          readPushPromise(handler, length, flags, streamId);
+          break;
+
+        case TYPE_PING:
+          readPing(handler, length, flags, streamId);
+          break;
+
+        case TYPE_GOAWAY:
+          readGoAway(handler, length, flags, streamId);
+          break;
+
+        case TYPE_WINDOW_UPDATE:
+          readWindowUpdate(handler, length, flags, streamId);
+          break;
+
+        default:
+          // Implementations MUST discard frames that have unknown or unsupported types.
+          source.skip(length);
+      }
+      return true;
+    }
+
+    private void readHeaders(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0");
+
+      boolean endStream = (flags & FLAG_END_STREAM) != 0;
+
+      short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+
+      if ((flags & FLAG_PRIORITY) != 0) {
+        readPriority(handler, streamId);
+        length -= 5; // account for above read.
+      }
+
+      length = lengthWithoutPadding(length, flags, padding);
+
+      List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
+
+      handler.headers(false, endStream, streamId, -1, headerBlock, HeadersMode.HTTP_20_HEADERS);
+    }
+
+    private List<Header> readHeaderBlock(int length, short padding, byte flags, int streamId)
+        throws IOException {
+      continuation.length = continuation.left = length;
+      continuation.padding = padding;
+      continuation.flags = flags;
+      continuation.streamId = streamId;
+
+      // TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20.
+      // http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-8.1.2.5
+      hpackReader.readHeaders();
+      return hpackReader.getAndResetHeaderList();
+    }
+
+    private void readData(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      // TODO: checkState open or half-closed (local) or raise STREAM_CLOSED
+      boolean inFinished = (flags & FLAG_END_STREAM) != 0;
+      boolean gzipped = (flags & FLAG_COMPRESSED) != 0;
+      if (gzipped) {
+        throw ioException("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA");
+      }
+
+      short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+      length = lengthWithoutPadding(length, flags, padding);
+
+      handler.data(inFinished, streamId, source, length);
+      source.skip(padding);
+    }
+
+    private void readPriority(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (length != 5) throw ioException("TYPE_PRIORITY length: %d != 5", length);
+      if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
+      readPriority(handler, streamId);
+    }
+
+    private void readPriority(Handler handler, int streamId) throws IOException {
+      int w1 = source.readInt();
+      boolean exclusive = (w1 & 0x80000000) != 0;
+      int streamDependency = (w1 & 0x7fffffff);
+      int weight = (source.readByte() & 0xff) + 1;
+      handler.priority(streamId, streamDependency, weight, exclusive);
+    }
+
+    private void readRstStream(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length);
+      if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0");
+      int errorCodeInt = source.readInt();
+      ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
+      if (errorCode == null) {
+        throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
+      }
+      handler.rstStream(streamId, errorCode);
+    }
+
+    private void readSettings(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
+      if ((flags & FLAG_ACK) != 0) {
+        if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!");
+        handler.ackSettings();
+        return;
+      }
+
+      if (length % 6 != 0) throw ioException("TYPE_SETTINGS length %% 6 != 0: %s", length);
+      Settings settings = new Settings();
+      for (int i = 0; i < length; i += 6) {
+        short id = source.readShort();
+        int value = source.readInt();
+
+        switch (id) {
+          case 1: // SETTINGS_HEADER_TABLE_SIZE
+            break;
+          case 2: // SETTINGS_ENABLE_PUSH
+            if (value != 0 && value != 1) {
+              throw ioException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1");
+            }
+            break;
+          case 3: // SETTINGS_MAX_CONCURRENT_STREAMS
+            id = 4; // Renumbered in draft 10.
+            break;
+          case 4: // SETTINGS_INITIAL_WINDOW_SIZE
+            id = 7; // Renumbered in draft 10.
+            if (value < 0) {
+              throw ioException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1");
+            }
+            break;
+          case 5: // SETTINGS_MAX_FRAME_SIZE
+            if (value < INITIAL_MAX_FRAME_SIZE || value > 16777215) {
+              throw ioException("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: %s", value);
+            }
+            break;
+          case 6: // SETTINGS_MAX_HEADER_LIST_SIZE
+            break; // Advisory only, so ignored.
+          default:
+            throw ioException("PROTOCOL_ERROR invalid settings id: %s", id);
+        }
+        settings.set(id, 0, value);
+      }
+      handler.settings(false, settings);
+      if (settings.getHeaderTableSize() >= 0) {
+        hpackReader.headerTableSizeSetting(settings.getHeaderTableSize());
+      }
+    }
+
+    private void readPushPromise(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (streamId == 0) {
+        throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0");
+      }
+      short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+      int promisedStreamId = source.readInt() & 0x7fffffff;
+      length -= 4; // account for above read.
+      length = lengthWithoutPadding(length, flags, padding);
+      List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
+      handler.pushPromise(streamId, promisedStreamId, headerBlock);
+    }
+
+    private void readPing(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (length != 8) throw ioException("TYPE_PING length != 8: %s", length);
+      if (streamId != 0) throw ioException("TYPE_PING streamId != 0");
+      int payload1 = source.readInt();
+      int payload2 = source.readInt();
+      boolean ack = (flags & FLAG_ACK) != 0;
+      handler.ping(ack, payload1, payload2);
+    }
+
+    private void readGoAway(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
+      if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0");
+      int lastStreamId = source.readInt();
+      int errorCodeInt = source.readInt();
+      int opaqueDataLength = length - 8;
+      ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
+      if (errorCode == null) {
+        throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
+      }
+      ByteString debugData = EMPTY;
+      if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection.
+        debugData = source.readByteString(opaqueDataLength);
+      }
+      handler.goAway(lastStreamId, errorCode, debugData);
+    }
+
+    private void readWindowUpdate(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length);
+      long increment = (source.readInt() & 0x7fffffffL);
+      if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
+      handler.windowUpdate(streamId, increment);
+    }
+
+    @Override public void close() throws IOException {
+      source.close();
+    }
+  }
+
+  static final class Writer implements FrameWriter {
+    private final BufferedSink sink;
+    private final boolean client;
+    private final Buffer hpackBuffer;
+    private final Hpack.Writer hpackWriter;
+    private int maxFrameSize;
+    private boolean closed;
+
+    Writer(BufferedSink sink, boolean client) {
+      this.sink = sink;
+      this.client = client;
+      this.hpackBuffer = new Buffer();
+      this.hpackWriter = new Hpack.Writer(hpackBuffer);
+      this.maxFrameSize = INITIAL_MAX_FRAME_SIZE;
+    }
+
+    @Override public synchronized void flush() throws IOException {
+      if (closed) throw new IOException("closed");
+      sink.flush();
+    }
+
+    @Override public synchronized void ackSettings(Settings peerSettings) throws IOException {
+      if (closed) throw new IOException("closed");
+      this.maxFrameSize = peerSettings.getMaxFrameSize(maxFrameSize);
+      int length = 0;
+      byte type = TYPE_SETTINGS;
+      byte flags = FLAG_ACK;
+      int streamId = 0;
+      frameHeader(streamId, length, type, flags);
+      sink.flush();
+    }
+
+    @Override public synchronized void connectionPreface() throws IOException {
+      if (closed) throw new IOException("closed");
+      if (!client) return; // Nothing to write; servers don't send connection headers!
+      if (logger.isLoggable(FINE)) {
+        logger.fine(format(">> CONNECTION %s", CONNECTION_PREFACE.hex()));
+      }
+      sink.write(CONNECTION_PREFACE.toByteArray());
+      sink.flush();
+    }
+
+    @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
+        int streamId, int associatedStreamId, List<Header> headerBlock)
+        throws IOException {
+      if (inFinished) throw new UnsupportedOperationException();
+      if (closed) throw new IOException("closed");
+      headers(outFinished, streamId, headerBlock);
+    }
+
+    @Override public synchronized void synReply(boolean outFinished, int streamId,
+        List<Header> headerBlock) throws IOException {
+      if (closed) throw new IOException("closed");
+      headers(outFinished, streamId, headerBlock);
+    }
+
+    @Override public synchronized void headers(int streamId, List<Header> headerBlock)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      headers(false, streamId, headerBlock);
+    }
+
+    @Override public synchronized void pushPromise(int streamId, int promisedStreamId,
+        List<Header> requestHeaders) throws IOException {
+      if (closed) throw new IOException("closed");
+      hpackWriter.writeHeaders(requestHeaders);
+
+      long byteCount = hpackBuffer.size();
+      int length = (int) Math.min(maxFrameSize - 4, byteCount);
+      byte type = TYPE_PUSH_PROMISE;
+      byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
+      frameHeader(streamId, length + 4, type, flags);
+      sink.writeInt(promisedStreamId & 0x7fffffff);
+      sink.write(hpackBuffer, length);
+
+      if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
+    }
+
+    void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {
+      if (closed) throw new IOException("closed");
+      hpackWriter.writeHeaders(headerBlock);
+
+      long byteCount = hpackBuffer.size();
+      int length = (int) Math.min(maxFrameSize, byteCount);
+      byte type = TYPE_HEADERS;
+      byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
+      if (outFinished) flags |= FLAG_END_STREAM;
+      frameHeader(streamId, length, type, flags);
+      sink.write(hpackBuffer, length);
+
+      if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
+    }
+
+    private void writeContinuationFrames(int streamId, long byteCount) throws IOException {
+      while (byteCount > 0) {
+        int length = (int) Math.min(maxFrameSize, byteCount);
+        byteCount -= length;
+        frameHeader(streamId, length, TYPE_CONTINUATION, byteCount == 0 ? FLAG_END_HEADERS : 0);
+        sink.write(hpackBuffer, length);
+      }
+    }
+
+    @Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      if (errorCode.httpCode == -1) throw new IllegalArgumentException();
+
+      int length = 4;
+      byte type = TYPE_RST_STREAM;
+      byte flags = FLAG_NONE;
+      frameHeader(streamId, length, type, flags);
+      sink.writeInt(errorCode.httpCode);
+      sink.flush();
+    }
+
+    @Override public int maxDataLength() {
+      return maxFrameSize;
+    }
+
+    @Override public synchronized void data(boolean outFinished, int streamId, Buffer source,
+        int byteCount) throws IOException {
+      if (closed) throw new IOException("closed");
+      byte flags = FLAG_NONE;
+      if (outFinished) flags |= FLAG_END_STREAM;
+      dataFrame(streamId, flags, source, byteCount);
+    }
+
+    void dataFrame(int streamId, byte flags, Buffer buffer, int byteCount) throws IOException {
+      byte type = TYPE_DATA;
+      frameHeader(streamId, byteCount, type, flags);
+      if (byteCount > 0) {
+        sink.write(buffer, byteCount);
+      }
+    }
+
+    @Override public synchronized void settings(Settings settings) throws IOException {
+      if (closed) throw new IOException("closed");
+      int length = settings.size() * 6;
+      byte type = TYPE_SETTINGS;
+      byte flags = FLAG_NONE;
+      int streamId = 0;
+      frameHeader(streamId, length, type, flags);
+      for (int i = 0; i < Settings.COUNT; i++) {
+        if (!settings.isSet(i)) continue;
+        int id = i;
+        if (id == 4) id = 3; // SETTINGS_MAX_CONCURRENT_STREAMS renumbered.
+        else if (id == 7) id = 4; // SETTINGS_INITIAL_WINDOW_SIZE renumbered.
+        sink.writeShort(id);
+        sink.writeInt(settings.get(i));
+      }
+      sink.flush();
+    }
+
+    @Override public synchronized void ping(boolean ack, int payload1, int payload2)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      int length = 8;
+      byte type = TYPE_PING;
+      byte flags = ack ? FLAG_ACK : FLAG_NONE;
+      int streamId = 0;
+      frameHeader(streamId, length, type, flags);
+      sink.writeInt(payload1);
+      sink.writeInt(payload2);
+      sink.flush();
+    }
+
+    @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode,
+        byte[] debugData) throws IOException {
+      if (closed) throw new IOException("closed");
+      if (errorCode.httpCode == -1) throw illegalArgument("errorCode.httpCode == -1");
+      int length = 8 + debugData.length;
+      byte type = TYPE_GOAWAY;
+      byte flags = FLAG_NONE;
+      int streamId = 0;
+      frameHeader(streamId, length, type, flags);
+      sink.writeInt(lastGoodStreamId);
+      sink.writeInt(errorCode.httpCode);
+      if (debugData.length > 0) {
+        sink.write(debugData);
+      }
+      sink.flush();
+    }
+
+    @Override public synchronized void windowUpdate(int streamId, long windowSizeIncrement)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      if (windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL) {
+        throw illegalArgument("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: %s",
+            windowSizeIncrement);
+      }
+      int length = 4;
+      byte type = TYPE_WINDOW_UPDATE;
+      byte flags = FLAG_NONE;
+      frameHeader(streamId, length, type, flags);
+      sink.writeInt((int) windowSizeIncrement);
+      sink.flush();
+    }
+
+    @Override public synchronized void close() throws IOException {
+      closed = true;
+      sink.close();
+    }
+
+    void frameHeader(int streamId, int length, byte type, byte flags) throws IOException {
+      if (logger.isLoggable(FINE)) logger.fine(formatHeader(false, streamId, length, type, flags));
+      if (length > maxFrameSize) {
+        throw illegalArgument("FRAME_SIZE_ERROR length > %d: %d", maxFrameSize, length);
+      }
+      if ((streamId & 0x80000000) != 0) throw illegalArgument("reserved bit set: %s", streamId);
+      writeMedium(sink, length);
+      sink.writeByte(type & 0xff);
+      sink.writeByte(flags & 0xff);
+      sink.writeInt(streamId & 0x7fffffff);
+    }
+  }
+
+  private static IllegalArgumentException illegalArgument(String message, Object... args) {
+    throw new IllegalArgumentException(format(message, args));
+  }
+
+  private static IOException ioException(String message, Object... args) throws IOException {
+    throw new IOException(format(message, args));
+  }
+
+  /**
+   * Decompression of the header block occurs above the framing layer. This
+   * class lazily reads continuation frames as they are needed by {@link
+   * Hpack.Reader#readHeaders()}.
+   */
+  static final class ContinuationSource implements Source {
+    private final BufferedSource source;
+
+    int length;
+    byte flags;
+    int streamId;
+
+    int left;
+    short padding;
+
+    public ContinuationSource(BufferedSource source) {
+      this.source = source;
+    }
+
+    @Override public long read(Buffer sink, long byteCount) throws IOException {
+      while (left == 0) {
+        source.skip(padding);
+        padding = 0;
+        if ((flags & FLAG_END_HEADERS) != 0) return -1;
+        readContinuationHeader();
+        // TODO: test case for empty continuation header?
+      }
+
+      long read = source.read(sink, Math.min(byteCount, left));
+      if (read == -1) return -1;
+      left -= read;
+      return read;
+    }
+
+    @Override public Timeout timeout() {
+      return source.timeout();
+    }
+
+    @Override public void close() throws IOException {
+    }
+
+    private void readContinuationHeader() throws IOException {
+      int previousStreamId = streamId;
+
+      length = left = readMedium(source);
+      byte type = (byte) (source.readByte() & 0xff);
+      flags = (byte) (source.readByte() & 0xff);
+      if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
+      streamId = (source.readInt() & 0x7fffffff);
+      if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type);
+      if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed");
+    }
+  }
+
+  private static int lengthWithoutPadding(int length, byte flags, short padding)
+      throws IOException {
+    if ((flags & FLAG_PADDED) != 0) length--; // Account for reading the padding length.
+    if (padding > length) {
+      throw ioException("PROTOCOL_ERROR padding %s > remaining length %s", padding, length);
+    }
+    return (short) (length - padding);
+  }
+
+  /**
+   * Logs a human-readable representation of HTTP/2 frame headers.
+   *
+   * <p>The format is:
+   *
+   * <pre>
+   *   direction streamID length type flags
+   * </pre>
+   * Where direction is {@code <<} for inbound and {@code >>} for outbound.
+   *
+   * <p> For example, the following would indicate a HEAD request sent from
+   * the client.
+   * <pre>
+   * {@code
+   *   << 0x0000000f    12 HEADERS       END_HEADERS|END_STREAM
+   * }
+   * </pre>
+   */
+  static final class FrameLogger {
+
+    static String formatHeader(boolean inbound, int streamId, int length, byte type, byte flags) {
+      String formattedType = type < TYPES.length ? TYPES[type] : format("0x%02x", type);
+      String formattedFlags = formatFlags(type, flags);
+      return format("%s 0x%08x %5d %-13s %s", inbound ? "<<" : ">>", streamId, length,
+          formattedType, formattedFlags);
+    }
+
+    /**
+     * Looks up valid string representing flags from the table. Invalid
+     * combinations are represented in binary.
+     */
+    // Visible for testing.
+    static String formatFlags(byte type, byte flags) {
+      if (flags == 0) return "";
+      switch (type) { // Special case types that have 0 or 1 flag.
+        case TYPE_SETTINGS:
+        case TYPE_PING:
+          return flags == FLAG_ACK ? "ACK" : BINARY[flags];
+        case TYPE_PRIORITY:
+        case TYPE_RST_STREAM:
+        case TYPE_GOAWAY:
+        case TYPE_WINDOW_UPDATE:
+          return BINARY[flags];
+      }
+      String result = flags < FLAGS.length ? FLAGS[flags] : BINARY[flags];
+      // Special case types that have overlap flag values.
+      if (type == TYPE_PUSH_PROMISE && (flags & FLAG_END_PUSH_PROMISE) != 0) {
+        return result.replace("HEADERS", "PUSH_PROMISE"); // TODO: Avoid allocation.
+      } else if (type == TYPE_DATA && (flags & FLAG_COMPRESSED) != 0) {
+        return result.replace("PRIORITY", "COMPRESSED"); // TODO: Avoid allocation.
+      }
+      return result;
+    }
+
+    /** Lookup table for valid frame types. */
+    private static final String[] TYPES = new String[] {
+        "DATA",
+        "HEADERS",
+        "PRIORITY",
+        "RST_STREAM",
+        "SETTINGS",
+        "PUSH_PROMISE",
+        "PING",
+        "GOAWAY",
+        "WINDOW_UPDATE",
+        "CONTINUATION"
+    };
+
+    /**
+     * Lookup table for valid flags for DATA, HEADERS, CONTINUATION. Invalid
+     * combinations are represented in binary.
+     */
+    private static final String[] FLAGS = new String[0x40]; // Highest bit flag is 0x20.
+    private static final String[] BINARY = new String[256];
+
+    static {
+      for (int i = 0; i < BINARY.length; i++) {
+        BINARY[i] = format("%8s", Integer.toBinaryString(i)).replace(' ', '0');
+      }
+
+      FLAGS[FLAG_NONE] = "";
+      FLAGS[FLAG_END_STREAM] = "END_STREAM";
+
+      int[] prefixFlags = new int[] {FLAG_END_STREAM};
+
+      FLAGS[FLAG_PADDED] = "PADDED";
+      for (int prefixFlag : prefixFlags) {
+         FLAGS[prefixFlag | FLAG_PADDED] = FLAGS[prefixFlag] + "|PADDED";
+      }
+
+      FLAGS[FLAG_END_HEADERS] = "END_HEADERS"; // Same as END_PUSH_PROMISE.
+      FLAGS[FLAG_PRIORITY] = "PRIORITY"; // Same as FLAG_COMPRESSED.
+      FLAGS[FLAG_END_HEADERS | FLAG_PRIORITY] = "END_HEADERS|PRIORITY"; // Only valid on HEADERS.
+      int[] frameFlags =
+          new int[] {FLAG_END_HEADERS, FLAG_PRIORITY, FLAG_END_HEADERS | FLAG_PRIORITY};
+
+      for (int frameFlag : frameFlags) {
+        for (int prefixFlag : prefixFlags) {
+          FLAGS[prefixFlag | frameFlag] = FLAGS[prefixFlag] + '|' + FLAGS[frameFlag];
+          FLAGS[prefixFlag | frameFlag | FLAG_PADDED] =
+              FLAGS[prefixFlag] + '|' + FLAGS[frameFlag] + "|PADDED";
+        }
+      }
+
+      for (int i = 0; i < FLAGS.length; i++) { // Fill in holes with binary representation.
+        if (FLAGS[i] == null) FLAGS[i] = BINARY[i];
+      }
+    }
+  }
+
+  private static int readMedium(BufferedSource source) throws IOException {
+    return (source.readByte() & 0xff) << 16
+        |  (source.readByte() & 0xff) <<  8
+        |  (source.readByte() & 0xff);
+  }
+
+  private static void writeMedium(BufferedSink sink, int i) throws IOException {
+    sink.writeByte((i >>> 16) & 0xff);
+    sink.writeByte((i >>>  8) & 0xff);
+    sink.writeByte(i          & 0xff);
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Huffman.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Huffman.java
new file mode 100644
index 0000000..311e5fc
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Huffman.java
@@ -0,0 +1,226 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright 2013 Twitter, Inc.
+ *
+ * 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 com.android.okhttp.internal.framed;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class was originally composed from the following classes in
+ * <a href="https://github.com/twitter/hpack">Twitter Hpack</a>.
+ * <ul>
+ * <li>{@code com.twitter.hpack.HuffmanEncoder}</li>
+ * <li>{@code com.twitter.hpack.HuffmanDecoder}</li>
+ * <li>{@code com.twitter.hpack.HpackUtil}</li>
+ * </ul>
+ */
+class Huffman {
+
+  // Appendix C: Huffman Codes
+  // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-B
+  private static final int[] CODES = {
+      0x1ff8, 0x7fffd8, 0xfffffe2, 0xfffffe3, 0xfffffe4, 0xfffffe5, 0xfffffe6, 0xfffffe7, 0xfffffe8,
+      0xffffea, 0x3ffffffc, 0xfffffe9, 0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed,
+      0xfffffee, 0xfffffef, 0xffffff0, 0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3, 0xffffff4,
+      0xffffff5, 0xffffff6, 0xffffff7, 0xffffff8, 0xffffff9, 0xffffffa, 0xffffffb, 0x14, 0x3f8,
+      0x3f9, 0xffa, 0x1ff9, 0x15, 0xf8, 0x7fa, 0x3fa, 0x3fb, 0xf9, 0x7fb, 0xfa, 0x16, 0x17, 0x18,
+      0x0, 0x1, 0x2, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x5c, 0xfb, 0x7ffc, 0x20, 0xffb,
+      0x3fc, 0x1ffa, 0x21, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+      0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xfc, 0x73, 0xfd, 0x1ffb, 0x7fff0,
+      0x1ffc, 0x3ffc, 0x22, 0x7ffd, 0x3, 0x23, 0x4, 0x24, 0x5, 0x25, 0x26, 0x27, 0x6, 0x74, 0x75,
+      0x28, 0x29, 0x2a, 0x7, 0x2b, 0x76, 0x2c, 0x8, 0x9, 0x2d, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7ffe,
+      0x7fc, 0x3ffd, 0x1ffd, 0xffffffc, 0xfffe6, 0x3fffd2, 0xfffe7, 0xfffe8, 0x3fffd3, 0x3fffd4,
+      0x3fffd5, 0x7fffd9, 0x3fffd6, 0x7fffda, 0x7fffdb, 0x7fffdc, 0x7fffdd, 0x7fffde, 0xffffeb,
+      0x7fffdf, 0xffffec, 0xffffed, 0x3fffd7, 0x7fffe0, 0xffffee, 0x7fffe1, 0x7fffe2, 0x7fffe3,
+      0x7fffe4, 0x1fffdc, 0x3fffd8, 0x7fffe5, 0x3fffd9, 0x7fffe6, 0x7fffe7, 0xffffef, 0x3fffda,
+      0x1fffdd, 0xfffe9, 0x3fffdb, 0x3fffdc, 0x7fffe8, 0x7fffe9, 0x1fffde, 0x7fffea, 0x3fffdd,
+      0x3fffde, 0xfffff0, 0x1fffdf, 0x3fffdf, 0x7fffeb, 0x7fffec, 0x1fffe0, 0x1fffe1, 0x3fffe0,
+      0x1fffe2, 0x7fffed, 0x3fffe1, 0x7fffee, 0x7fffef, 0xfffea, 0x3fffe2, 0x3fffe3, 0x3fffe4,
+      0x7ffff0, 0x3fffe5, 0x3fffe6, 0x7ffff1, 0x3ffffe0, 0x3ffffe1, 0xfffeb, 0x7fff1, 0x3fffe7,
+      0x7ffff2, 0x3fffe8, 0x1ffffec, 0x3ffffe2, 0x3ffffe3, 0x3ffffe4, 0x7ffffde, 0x7ffffdf,
+      0x3ffffe5, 0xfffff1, 0x1ffffed, 0x7fff2, 0x1fffe3, 0x3ffffe6, 0x7ffffe0, 0x7ffffe1, 0x3ffffe7,
+      0x7ffffe2, 0xfffff2, 0x1fffe4, 0x1fffe5, 0x3ffffe8, 0x3ffffe9, 0xffffffd, 0x7ffffe3,
+      0x7ffffe4, 0x7ffffe5, 0xfffec, 0xfffff3, 0xfffed, 0x1fffe6, 0x3fffe9, 0x1fffe7, 0x1fffe8,
+      0x7ffff3, 0x3fffea, 0x3fffeb, 0x1ffffee, 0x1ffffef, 0xfffff4, 0xfffff5, 0x3ffffea, 0x7ffff4,
+      0x3ffffeb, 0x7ffffe6, 0x3ffffec, 0x3ffffed, 0x7ffffe7, 0x7ffffe8, 0x7ffffe9, 0x7ffffea,
+      0x7ffffeb, 0xffffffe, 0x7ffffec, 0x7ffffed, 0x7ffffee, 0x7ffffef, 0x7fffff0, 0x3ffffee
+  };
+
+  private static final byte[] CODE_LENGTHS = {
+      13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 30,
+      28, 28, 28, 28, 28, 28, 28, 28, 28, 6, 10, 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6, 5,
+      5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10, 13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+      7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6, 15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6,
+      6, 6, 5, 6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28, 20, 22, 20, 20, 22, 22, 22, 23,
+      22, 23, 23, 23, 23, 23, 24, 23, 24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23,
+      24, 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, 23, 21, 21, 22, 21, 23, 22,
+      23, 23, 20, 22, 22, 22, 23, 22, 22, 23, 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27,
+      26, 24, 25, 19, 21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27, 20, 24, 20, 21,
+      22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23, 26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27,
+      27, 27, 27, 27, 26
+  };
+
+  private static final Huffman INSTANCE = new Huffman();
+
+  public static Huffman get() {
+    return INSTANCE;
+  }
+
+  private final Node root = new Node();
+
+  private Huffman() {
+    buildTree();
+  }
+
+  void encode(byte[] data, OutputStream out) throws IOException {
+    long current = 0;
+    int n = 0;
+
+    for (int i = 0; i < data.length; i++) {
+      int b = data[i] & 0xFF;
+      int code = CODES[b];
+      int nbits = CODE_LENGTHS[b];
+
+      current <<= nbits;
+      current |= code;
+      n += nbits;
+
+      while (n >= 8) {
+        n -= 8;
+        out.write(((int) (current >> n)));
+      }
+    }
+
+    if (n > 0) {
+      current <<= (8 - n);
+      current |= (0xFF >>> n);
+      out.write((int) current);
+    }
+  }
+
+  int encodedLength(byte[] bytes) {
+    long len = 0;
+
+    for (int i = 0; i < bytes.length; i++) {
+      int b = bytes[i] & 0xFF;
+      len += CODE_LENGTHS[b];
+    }
+
+    return (int) ((len + 7) >> 3);
+  }
+
+  byte[] decode(byte[] buf) throws IOException {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    Node node = root;
+    int current = 0;
+    int nbits = 0;
+    for (int i = 0; i < buf.length; i++) {
+      int b = buf[i] & 0xFF;
+      current = (current << 8) | b;
+      nbits += 8;
+      while (nbits >= 8) {
+        int c = (current >>> (nbits - 8)) & 0xFF;
+        node = node.children[c];
+        if (node.children == null) {
+          // terminal node
+          baos.write(node.symbol);
+          nbits -= node.terminalBits;
+          node = root;
+        } else {
+          // non-terminal node
+          nbits -= 8;
+        }
+      }
+    }
+
+    while (nbits > 0) {
+      int c = (current << (8 - nbits)) & 0xFF;
+      node = node.children[c];
+      if (node.children != null || node.terminalBits > nbits) {
+        break;
+      }
+      baos.write(node.symbol);
+      nbits -= node.terminalBits;
+      node = root;
+    }
+
+    return baos.toByteArray();
+  }
+
+  private void buildTree() {
+    for (int i = 0; i < CODE_LENGTHS.length; i++) {
+      addCode(i, CODES[i], CODE_LENGTHS[i]);
+    }
+  }
+
+  private void addCode(int sym, int code, byte len) {
+    Node terminal = new Node(sym, len);
+
+    Node current = root;
+    while (len > 8) {
+      len -= 8;
+      int i = ((code >>> len) & 0xFF);
+      if (current.children == null) {
+        throw new IllegalStateException("invalid dictionary: prefix not unique");
+      }
+      if (current.children[i] == null) {
+        current.children[i] = new Node();
+      }
+      current = current.children[i];
+    }
+
+    int shift = 8 - len;
+    int start = (code << shift) & 0xFF;
+    int end = 1 << shift;
+    for (int i = start; i < start + end; i++) {
+      current.children[i] = terminal;
+    }
+  }
+
+  private static final class Node {
+
+    // Null if terminal.
+    private final Node[] children;
+
+    // Terminal nodes have a symbol.
+    private final int symbol;
+
+    // Number of bits represented in the terminal node.
+    private final int terminalBits;
+
+    /** Construct an internal node. */
+    Node() {
+      this.children = new Node[256];
+      this.symbol = 0; // Not read.
+      this.terminalBits = 0; // Not read.
+    }
+
+    /**
+     * Construct a terminal node.
+     *
+     * @param symbol symbol the node represents
+     * @param bits length of Huffman code in bits
+     */
+    Node(int symbol, int bits) {
+      this.children = null;
+      this.symbol = symbol;
+      int b = bits & 0x07;
+      this.terminalBits = b == 0 ? 8 : b;
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/NameValueBlockReader.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/NameValueBlockReader.java
new file mode 100644
index 0000000..8c84927
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/NameValueBlockReader.java
@@ -0,0 +1,119 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.framed;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.ForwardingSource;
+import com.android.okhttp.okio.InflaterSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Source;
+
+/**
+ * Reads a SPDY/3 Name/Value header block. This class is made complicated by the
+ * requirement that we're strict with which bytes we put in the compressed bytes
+ * buffer. We need to put all compressed bytes into that buffer -- but no other
+ * bytes.
+ */
+class NameValueBlockReader {
+  /** This source transforms compressed bytes into uncompressed bytes. */
+  private final InflaterSource inflaterSource;
+
+  /**
+   * How many compressed bytes must be read into inflaterSource before
+   * {@link #readNameValueBlock} returns.
+   */
+  private int compressedLimit;
+
+  /** This source holds inflated bytes. */
+  private final BufferedSource source;
+
+  public NameValueBlockReader(BufferedSource source) {
+    // Limit the inflater input stream to only those bytes in the Name/Value
+    // block. We cut the inflater off at its source because we can't predict the
+    // ratio of compressed bytes to uncompressed bytes.
+    Source throttleSource = new ForwardingSource(source) {
+      @Override public long read(Buffer sink, long byteCount) throws IOException {
+        if (compressedLimit == 0) return -1; // Out of data for the current block.
+        long read = super.read(sink, Math.min(byteCount, compressedLimit));
+        if (read == -1) return -1;
+        compressedLimit -= read;
+        return read;
+      }
+    };
+
+    // Subclass inflater to install a dictionary when it's needed.
+    Inflater inflater = new Inflater() {
+      @Override public int inflate(byte[] buffer, int offset, int count)
+          throws DataFormatException {
+        int result = super.inflate(buffer, offset, count);
+        if (result == 0 && needsDictionary()) {
+          setDictionary(Spdy3.DICTIONARY);
+          result = super.inflate(buffer, offset, count);
+        }
+        return result;
+      }
+    };
+
+    this.inflaterSource = new InflaterSource(throttleSource, inflater);
+    this.source = Okio.buffer(inflaterSource);
+  }
+
+  public List<Header> readNameValueBlock(int length) throws IOException {
+    this.compressedLimit += length;
+
+    int numberOfPairs = source.readInt();
+    if (numberOfPairs < 0) throw new IOException("numberOfPairs < 0: " + numberOfPairs);
+    if (numberOfPairs > 1024) throw new IOException("numberOfPairs > 1024: " + numberOfPairs);
+
+    List<Header> entries = new ArrayList<>(numberOfPairs);
+    for (int i = 0; i < numberOfPairs; i++) {
+      ByteString name = readByteString().toAsciiLowercase();
+      ByteString values = readByteString();
+      if (name.size() == 0) throw new IOException("name.size == 0");
+      entries.add(new Header(name, values));
+    }
+
+    doneReading();
+    return entries;
+  }
+
+  private ByteString readByteString() throws IOException {
+    int length = source.readInt();
+    return source.readByteString(length);
+  }
+
+  private void doneReading() throws IOException {
+    // Move any outstanding unread bytes into the inflater. One side-effect of
+    // deflate compression is that sometimes there are bytes remaining in the
+    // stream after we've consumed all of the content.
+    if (compressedLimit > 0) {
+      inflaterSource.refill();
+      if (compressedLimit != 0) throw new IOException("compressedLimit > 0: " + compressedLimit);
+    }
+  }
+
+  public void close() throws IOException {
+    source.close();
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Ping.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Ping.java
new file mode 100644
index 0000000..42eb248
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Ping.java
@@ -0,0 +1,73 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.framed;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A locally-originated ping.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Ping {
+  private final CountDownLatch latch = new CountDownLatch(1);
+  private long sent = -1;
+  private long received = -1;
+
+  Ping() {
+  }
+
+  void send() {
+    if (sent != -1) throw new IllegalStateException();
+    sent = System.nanoTime();
+  }
+
+  void receive() {
+    if (received != -1 || sent == -1) throw new IllegalStateException();
+    received = System.nanoTime();
+    latch.countDown();
+  }
+
+  void cancel() {
+    if (received != -1 || sent == -1) throw new IllegalStateException();
+    received = sent - 1;
+    latch.countDown();
+  }
+
+  /**
+   * Returns the round trip time for this ping in nanoseconds, waiting for the
+   * response to arrive if necessary. Returns -1 if the response was
+   * canceled.
+   */
+  public long roundTripTime() throws InterruptedException {
+    latch.await();
+    return received - sent;
+  }
+
+  /**
+   * Returns the round trip time for this ping in nanoseconds, or -1 if the
+   * response was canceled, or -2 if the timeout elapsed before the round
+   * trip completed.
+   */
+  public long roundTripTime(long timeout, TimeUnit unit) throws InterruptedException {
+    if (latch.await(timeout, unit)) {
+      return received - sent;
+    } else {
+      return -2;
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/PushObserver.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/PushObserver.java
new file mode 100644
index 0000000..8c71514
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/PushObserver.java
@@ -0,0 +1,97 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.framed;
+
+import java.io.IOException;
+import java.util.List;
+import com.android.okhttp.okio.BufferedSource;
+
+/**
+ * {@link com.android.okhttp.Protocol#HTTP_2 HTTP/2} only.
+ * Processes server-initiated HTTP requests on the client. Implementations must
+ * quickly dispatch callbacks to avoid creating a bottleneck.
+ *
+ * <p>While {@link #onReset} may occur at any time, the following callbacks are
+ * expected in order, correlated by stream ID.
+ * <ul>
+ *   <li>{@link #onRequest}</li>
+ *   <li>{@link #onHeaders} (unless canceled)</li>
+ *   <li>{@link #onData} (optional sequence of data frames)</li>
+ * </ul>
+ *
+ * <p>As a stream ID is scoped to a single HTTP/2 connection, implementations
+ * which target multiple connections should expect repetition of stream IDs.
+ *
+ * <p>Return true to request cancellation of a pushed stream.  Note that this
+ * does not guarantee future frames won't arrive on the stream ID.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface PushObserver {
+  /**
+   * Describes the request that the server intends to push a response for.
+   *
+   * @param streamId server-initiated stream ID: an even number.
+   * @param requestHeaders minimally includes {@code :method}, {@code :scheme},
+   * {@code :authority}, and (@code :path}.
+   */
+  boolean onRequest(int streamId, List<Header> requestHeaders);
+
+  /**
+   * The response headers corresponding to a pushed request.  When {@code last}
+   * is true, there are no data frames to follow.
+   *
+   * @param streamId server-initiated stream ID: an even number.
+   * @param responseHeaders minimally includes {@code :status}.
+   * @param last when true, there is no response data.
+   */
+  boolean onHeaders(int streamId, List<Header> responseHeaders, boolean last);
+
+  /**
+   * A chunk of response data corresponding to a pushed request.  This data
+   * must either be read or skipped.
+   *
+   * @param streamId server-initiated stream ID: an even number.
+   * @param source location of data corresponding with this stream ID.
+   * @param byteCount number of bytes to read or skip from the source.
+   * @param last when true, there are no data frames to follow.
+   */
+  boolean onData(int streamId, BufferedSource source, int byteCount, boolean last)
+      throws IOException;
+
+  /** Indicates the reason why this stream was canceled. */
+  void onReset(int streamId, ErrorCode errorCode);
+
+  PushObserver CANCEL = new PushObserver() {
+
+    @Override public boolean onRequest(int streamId, List<Header> requestHeaders) {
+      return true;
+    }
+
+    @Override public boolean onHeaders(int streamId, List<Header> responseHeaders, boolean last) {
+      return true;
+    }
+
+    @Override public boolean onData(int streamId, BufferedSource source, int byteCount,
+        boolean last) throws IOException {
+      source.skip(byteCount);
+      return true;
+    }
+
+    @Override public void onReset(int streamId, ErrorCode errorCode) {
+    }
+  };
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Settings.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Settings.java
new file mode 100644
index 0000000..223de47
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Settings.java
@@ -0,0 +1,241 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.framed;
+
+import java.util.Arrays;
+
+/**
+ * Settings describe characteristics of the sending peer, which are used by the receiving peer.
+ * Settings are {@link FramedConnection connection} scoped.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Settings {
+  /**
+   * From the SPDY/3 and HTTP/2 specs, the default initial window size for all
+   * streams is 64 KiB. (Chrome 25 uses 10 MiB).
+   */
+  static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024;
+
+  /** Peer request to clear durable settings. */
+  static final int FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1;
+
+  /** Sent by servers only. The peer requests this setting persisted for future connections. */
+  static final int PERSIST_VALUE = 0x1;
+  /** Sent by clients only. The client is reminding the server of a persisted value. */
+  static final int PERSISTED = 0x2;
+
+  /** spdy/3: Sender's estimate of max incoming kbps. */
+  static final int UPLOAD_BANDWIDTH = 1;
+  /** HTTP/2: Size in bytes of the table used to decode the sender's header blocks. */
+  static final int HEADER_TABLE_SIZE = 1;
+  /** spdy/3: Sender's estimate of max outgoing kbps. */
+  static final int DOWNLOAD_BANDWIDTH = 2;
+  /** HTTP/2: The peer must not send a PUSH_PROMISE frame when this is 0. */
+  static final int ENABLE_PUSH = 2;
+  /** spdy/3: Sender's estimate of millis between sending a request and receiving a response. */
+  static final int ROUND_TRIP_TIME = 3;
+  /** Sender's maximum number of concurrent streams. */
+  static final int MAX_CONCURRENT_STREAMS = 4;
+  /** spdy/3: Current CWND in Packets. */
+  static final int CURRENT_CWND = 5;
+  /** HTTP/2: Size in bytes of the largest frame payload the sender will accept. */
+  static final int MAX_FRAME_SIZE = 5;
+  /** spdy/3: Retransmission rate. Percentage */
+  static final int DOWNLOAD_RETRANS_RATE = 6;
+  /** HTTP/2: Advisory only. Size in bytes of the largest header list the sender will accept. */
+  static final int MAX_HEADER_LIST_SIZE = 6;
+  /** Window size in bytes. */
+  static final int INITIAL_WINDOW_SIZE = 7;
+  /** spdy/3: Size of the client certificate vector. Unsupported. */
+  static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 8;
+  /** Flow control options. */
+  static final int FLOW_CONTROL_OPTIONS = 10;
+
+  /** Total number of settings. */
+  static final int COUNT = 10;
+
+  /** If set, flow control is disabled for streams directed to the sender of these settings. */
+  static final int FLOW_CONTROL_OPTIONS_DISABLED = 0x1;
+
+  /** Bitfield of which flags that values. */
+  private int set;
+
+  /** Bitfield of flags that have {@link #PERSIST_VALUE}. */
+  private int persistValue;
+
+  /** Bitfield of flags that have {@link #PERSISTED}. */
+  private int persisted;
+
+  /** Flag values. */
+  private final int[] values = new int[COUNT];
+
+  void clear() {
+    set = persistValue = persisted = 0;
+    Arrays.fill(values, 0);
+  }
+
+  Settings set(int id, int idFlags, int value) {
+    if (id >= values.length) {
+      return this; // Discard unknown settings.
+    }
+
+    int bit = 1 << id;
+    set |= bit;
+    if ((idFlags & PERSIST_VALUE) != 0) {
+      persistValue |= bit;
+    } else {
+      persistValue &= ~bit;
+    }
+    if ((idFlags & PERSISTED) != 0) {
+      persisted |= bit;
+    } else {
+      persisted &= ~bit;
+    }
+
+    values[id] = value;
+    return this;
+  }
+
+  /** Returns true if a value has been assigned for the setting {@code id}. */
+  boolean isSet(int id) {
+    int bit = 1 << id;
+    return (set & bit) != 0;
+  }
+
+  /** Returns the value for the setting {@code id}, or 0 if unset. */
+  int get(int id) {
+    return values[id];
+  }
+
+  /** Returns the flags for the setting {@code id}, or 0 if unset. */
+  int flags(int id) {
+    int result = 0;
+    if (isPersisted(id)) result |= Settings.PERSISTED;
+    if (persistValue(id)) result |= Settings.PERSIST_VALUE;
+    return result;
+  }
+
+  /** Returns the number of settings that have values assigned. */
+  int size() {
+    return Integer.bitCount(set);
+  }
+
+  /** spdy/3 only. */
+  int getUploadBandwidth(int defaultValue) {
+    int bit = 1 << UPLOAD_BANDWIDTH;
+    return (bit & set) != 0 ? values[UPLOAD_BANDWIDTH] : defaultValue;
+  }
+
+  /** HTTP/2 only. Returns -1 if unset. */
+  int getHeaderTableSize() {
+    int bit = 1 << HEADER_TABLE_SIZE;
+    return (bit & set) != 0 ? values[HEADER_TABLE_SIZE] : -1;
+  }
+
+  /** spdy/3 only. */
+  int getDownloadBandwidth(int defaultValue) {
+    int bit = 1 << DOWNLOAD_BANDWIDTH;
+    return (bit & set) != 0 ? values[DOWNLOAD_BANDWIDTH] : defaultValue;
+  }
+
+  /** HTTP/2 only. */
+  // TODO: honor this setting in HTTP/2.
+  boolean getEnablePush(boolean defaultValue) {
+    int bit = 1 << ENABLE_PUSH;
+    return ((bit & set) != 0 ? values[ENABLE_PUSH] : defaultValue ? 1 : 0) == 1;
+  }
+
+  /** spdy/3 only. */
+  int getRoundTripTime(int defaultValue) {
+    int bit = 1 << ROUND_TRIP_TIME;
+    return (bit & set) != 0 ? values[ROUND_TRIP_TIME] : defaultValue;
+  }
+
+  // TODO: honor this setting in spdy/3 and HTTP/2.
+  int getMaxConcurrentStreams(int defaultValue) {
+    int bit = 1 << MAX_CONCURRENT_STREAMS;
+    return (bit & set) != 0 ? values[MAX_CONCURRENT_STREAMS] : defaultValue;
+  }
+
+  /** spdy/3 only. */
+  int getCurrentCwnd(int defaultValue) {
+    int bit = 1 << CURRENT_CWND;
+    return (bit & set) != 0 ? values[CURRENT_CWND] : defaultValue;
+  }
+
+  /** HTTP/2 only. */
+  int getMaxFrameSize(int defaultValue) {
+    int bit = 1 << MAX_FRAME_SIZE;
+    return (bit & set) != 0 ? values[MAX_FRAME_SIZE] : defaultValue;
+  }
+
+  /** spdy/3 only. */
+  int getDownloadRetransRate(int defaultValue) {
+    int bit = 1 << DOWNLOAD_RETRANS_RATE;
+    return (bit & set) != 0 ? values[DOWNLOAD_RETRANS_RATE] : defaultValue;
+  }
+
+  /** HTTP/2 only. */
+  int getMaxHeaderListSize(int defaultValue) {
+    int bit = 1 << MAX_HEADER_LIST_SIZE;
+    return (bit & set) != 0 ? values[MAX_HEADER_LIST_SIZE] : defaultValue;
+  }
+
+  int getInitialWindowSize(int defaultValue) {
+    int bit = 1 << INITIAL_WINDOW_SIZE;
+    return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : defaultValue;
+  }
+
+  /** spdy/3 only. */
+  int getClientCertificateVectorSize(int defaultValue) {
+    int bit = 1 << CLIENT_CERTIFICATE_VECTOR_SIZE;
+    return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue;
+  }
+
+  // TODO: honor this setting in spdy/3 and HTTP/2.
+  boolean isFlowControlDisabled() {
+    int bit = 1 << FLOW_CONTROL_OPTIONS;
+    int value = (bit & set) != 0 ? values[FLOW_CONTROL_OPTIONS] : 0;
+    return (value & FLOW_CONTROL_OPTIONS_DISABLED) != 0;
+  }
+
+  /**
+   * Returns true if this user agent should use this setting in future spdy/3
+   * connections to the same host.
+   */
+  boolean persistValue(int id) {
+    int bit = 1 << id;
+    return (persistValue & bit) != 0;
+  }
+
+  /** Returns true if this setting was persisted. */
+  boolean isPersisted(int id) {
+    int bit = 1 << id;
+    return (persisted & bit) != 0;
+  }
+
+  /**
+   * Writes {@code other} into this. If any setting is populated by this and
+   * {@code other}, the value and flags from {@code other} will be kept.
+   */
+  void merge(Settings other) {
+    for (int i = 0; i < COUNT; i++) {
+      if (!other.isSet(i)) continue;
+      set(i, other.flags(i), other.get(i));
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Spdy3.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Spdy3.java
new file mode 100644
index 0000000..d792cd8
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Spdy3.java
@@ -0,0 +1,493 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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 com.android.okhttp.internal.framed;
+
+import com.android.okhttp.Protocol;
+import com.android.okhttp.internal.Util;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.ProtocolException;
+import java.util.List;
+import java.util.zip.Deflater;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.DeflaterSink;
+import com.android.okhttp.okio.Okio;
+
+/**
+ * Read and write spdy/3.1 frames.
+ * http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Spdy3 implements Variant {
+
+  @Override public Protocol getProtocol() {
+    return Protocol.SPDY_3;
+  }
+
+  static final int TYPE_DATA = 0x0;
+  static final int TYPE_SYN_STREAM = 0x1;
+  static final int TYPE_SYN_REPLY = 0x2;
+  static final int TYPE_RST_STREAM = 0x3;
+  static final int TYPE_SETTINGS = 0x4;
+  static final int TYPE_PING = 0x6;
+  static final int TYPE_GOAWAY = 0x7;
+  static final int TYPE_HEADERS = 0x8;
+  static final int TYPE_WINDOW_UPDATE = 0x9;
+
+  static final int FLAG_FIN = 0x1;
+  static final int FLAG_UNIDIRECTIONAL = 0x2;
+
+  static final int VERSION = 3;
+
+  static final byte[] DICTIONARY;
+  static {
+    try {
+      DICTIONARY = ("\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004hea"
+          + "d\u0000\u0000\u0000\u0004post\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006dele"
+          + "te\u0000\u0000\u0000\u0005trace\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000"
+          + "\u000Eaccept-charset\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Fa"
+          + "ccept-language\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000"
+          + "\u0000\u0000\u0005allow\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-co"
+          + "ntrol\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000"
+          + "\u0000\u0010content-encoding\u0000\u0000\u0000\u0010content-language\u0000\u0000"
+          + "\u0000\u000Econtent-length\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000"
+          + "\u000Bcontent-md5\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type"
+          + "\u0000\u0000\u0000\u0004date\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expe"
+          + "ct\u0000\u0000\u0000\u0007expires\u0000\u0000\u0000\u0004from\u0000\u0000\u0000"
+          + "\u0004host\u0000\u0000\u0000\bif-match\u0000\u0000\u0000\u0011if-modified-since"
+          + "\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range\u0000\u0000\u0000"
+          + "\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified\u0000\u0000\u0000\blocati"
+          + "on\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma\u0000\u0000\u0000"
+          + "\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization\u0000\u0000"
+          + "\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after"
+          + "\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trai"
+          + "ler\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000"
+          + "\u0000\u0000\nuser-agent\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via"
+          + "\u0000\u0000\u0000\u0007warning\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000"
+          + "\u0000\u0006method\u0000\u0000\u0000\u0003get\u0000\u0000\u0000\u0006status\u0000"
+          + "\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version\u0000\u0000\u0000\bHTTP/1.1"
+          + "\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public\u0000\u0000\u0000\nset-coo"
+          + "kie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin100101201202205206300"
+          + "302303304305306307402405406407408409410411412413414415416417502504505203 Non-Authori"
+          + "tative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized"
+          + "403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Un"
+          + "availableJan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Th"
+          + "u, Fri, Sat, Sun, GMTchunked,text/html,image/png,image/jpg,image/gif,application/xml"
+          + ",application/xhtml+xml,text/plain,text/javascript,publicprivatemax-age=gzip,deflate,"
+          + "sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.").getBytes(Util.UTF_8.name());
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError();
+    }
+  }
+
+  @Override public FrameReader newReader(BufferedSource source, boolean client) {
+    return new Reader(source, client);
+  }
+
+  @Override public FrameWriter newWriter(BufferedSink sink, boolean client) {
+    return new Writer(sink, client);
+  }
+
+  /** Read spdy/3 frames. */
+  static final class Reader implements FrameReader {
+    private final BufferedSource source;
+    private final boolean client;
+    private final NameValueBlockReader headerBlockReader;
+
+    Reader(BufferedSource source, boolean client) {
+      this.source = source;
+      this.headerBlockReader = new NameValueBlockReader(this.source);
+      this.client = client;
+    }
+
+    @Override public void readConnectionPreface() {
+    }
+
+    /**
+     * Send the next frame to {@code handler}. Returns true unless there are no
+     * more frames on the stream.
+     */
+    @Override public boolean nextFrame(Handler handler) throws IOException {
+      int w1;
+      int w2;
+      try {
+        w1 = source.readInt();
+        w2 = source.readInt();
+      } catch (IOException e) {
+        return false; // This might be a normal socket close.
+      }
+
+      boolean control = (w1 & 0x80000000) != 0;
+      int flags = (w2 & 0xff000000) >>> 24;
+      int length = (w2 & 0xffffff);
+
+      if (control) {
+        int version = (w1 & 0x7fff0000) >>> 16;
+        int type = (w1 & 0xffff);
+
+        if (version != 3) {
+          throw new ProtocolException("version != 3: " + version);
+        }
+
+        switch (type) {
+          case TYPE_SYN_STREAM:
+            readSynStream(handler, flags, length);
+            return true;
+
+          case TYPE_SYN_REPLY:
+            readSynReply(handler, flags, length);
+            return true;
+
+          case TYPE_RST_STREAM:
+            readRstStream(handler, flags, length);
+            return true;
+
+          case TYPE_SETTINGS:
+            readSettings(handler, flags, length);
+            return true;
+
+          case TYPE_PING:
+            readPing(handler, flags, length);
+            return true;
+
+          case TYPE_GOAWAY:
+            readGoAway(handler, flags, length);
+            return true;
+
+          case TYPE_HEADERS:
+            readHeaders(handler, flags, length);
+            return true;
+
+          case TYPE_WINDOW_UPDATE:
+            readWindowUpdate(handler, flags, length);
+            return true;
+
+          default:
+            source.skip(length);
+            return true;
+        }
+      } else {
+        int streamId = w1 & 0x7fffffff;
+        boolean inFinished = (flags & FLAG_FIN) != 0;
+        handler.data(inFinished, streamId, source, length);
+        return true;
+      }
+    }
+
+    private void readSynStream(Handler handler, int flags, int length) throws IOException {
+      int w1 = source.readInt();
+      int w2 = source.readInt();
+      int streamId = w1 & 0x7fffffff;
+      int associatedStreamId = w2 & 0x7fffffff;
+      source.readShort(); // int priority = (s3 & 0xe000) >>> 13; int slot = s3 & 0xff;
+      List<Header> headerBlock = headerBlockReader.readNameValueBlock(length - 10);
+
+      boolean inFinished = (flags & FLAG_FIN) != 0;
+      boolean outFinished = (flags & FLAG_UNIDIRECTIONAL) != 0;
+      handler.headers(outFinished, inFinished, streamId, associatedStreamId, headerBlock,
+          HeadersMode.SPDY_SYN_STREAM);
+    }
+
+    private void readSynReply(Handler handler, int flags, int length) throws IOException {
+      int w1 = source.readInt();
+      int streamId = w1 & 0x7fffffff;
+      List<Header> headerBlock = headerBlockReader.readNameValueBlock(length - 4);
+      boolean inFinished = (flags & FLAG_FIN) != 0;
+      handler.headers(false, inFinished, streamId, -1, headerBlock, HeadersMode.SPDY_REPLY);
+    }
+
+    private void readRstStream(Handler handler, int flags, int length) throws IOException {
+      if (length != 8) throw ioException("TYPE_RST_STREAM length: %d != 8", length);
+      int streamId = source.readInt() & 0x7fffffff;
+      int errorCodeInt = source.readInt();
+      ErrorCode errorCode = ErrorCode.fromSpdy3Rst(errorCodeInt);
+      if (errorCode == null) {
+        throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
+      }
+      handler.rstStream(streamId, errorCode);
+    }
+
+    private void readHeaders(Handler handler, int flags, int length) throws IOException {
+      int w1 = source.readInt();
+      int streamId = w1 & 0x7fffffff;
+      List<Header> headerBlock = headerBlockReader.readNameValueBlock(length - 4);
+      handler.headers(false, false, streamId, -1, headerBlock, HeadersMode.SPDY_HEADERS);
+    }
+
+    private void readWindowUpdate(Handler handler, int flags, int length) throws IOException {
+      if (length != 8) throw ioException("TYPE_WINDOW_UPDATE length: %d != 8", length);
+      int w1 = source.readInt();
+      int w2 = source.readInt();
+      int streamId = w1 & 0x7fffffff;
+      long increment = w2 & 0x7fffffff;
+      if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
+      handler.windowUpdate(streamId, increment);
+    }
+
+    private void readPing(Handler handler, int flags, int length) throws IOException {
+      if (length != 4) throw ioException("TYPE_PING length: %d != 4", length);
+      int id = source.readInt();
+      boolean ack = client == ((id & 1) == 1);
+      handler.ping(ack, id, 0);
+    }
+
+    private void readGoAway(Handler handler, int flags, int length) throws IOException {
+      if (length != 8) throw ioException("TYPE_GOAWAY length: %d != 8", length);
+      int lastGoodStreamId = source.readInt() & 0x7fffffff;
+      int errorCodeInt = source.readInt();
+      ErrorCode errorCode = ErrorCode.fromSpdyGoAway(errorCodeInt);
+      if (errorCode == null) {
+        throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
+      }
+      handler.goAway(lastGoodStreamId, errorCode, ByteString.EMPTY);
+    }
+
+    private void readSettings(Handler handler, int flags, int length) throws IOException {
+      int numberOfEntries = source.readInt();
+      if (length != 4 + 8 * numberOfEntries) {
+        throw ioException("TYPE_SETTINGS length: %d != 4 + 8 * %d", length, numberOfEntries);
+      }
+      Settings settings = new Settings();
+      for (int i = 0; i < numberOfEntries; i++) {
+        int w1 = source.readInt();
+        int value = source.readInt();
+        int idFlags = (w1 & 0xff000000) >>> 24;
+        int id = w1 & 0xffffff;
+        settings.set(id, idFlags, value);
+      }
+      boolean clearPrevious = (flags & Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) != 0;
+      handler.settings(clearPrevious, settings);
+    }
+
+    private static IOException ioException(String message, Object... args) throws IOException {
+      throw new IOException(String.format(message, args));
+    }
+
+    @Override public void close() throws IOException {
+      headerBlockReader.close();
+    }
+  }
+
+  /** Write spdy/3 frames. */
+  static final class Writer implements FrameWriter {
+    private final BufferedSink sink;
+    private final Buffer headerBlockBuffer;
+    private final BufferedSink headerBlockOut;
+    private final boolean client;
+    private boolean closed;
+
+    Writer(BufferedSink sink, boolean client) {
+      this.sink = sink;
+      this.client = client;
+
+      Deflater deflater = new Deflater();
+      deflater.setDictionary(DICTIONARY);
+      headerBlockBuffer = new Buffer();
+      headerBlockOut = Okio.buffer(new DeflaterSink(headerBlockBuffer, deflater));
+    }
+
+    @Override public void ackSettings(Settings peerSettings) {
+      // Do nothing: no ACK for SPDY/3 settings.
+    }
+
+    @Override
+    public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
+        throws IOException {
+      // Do nothing: no push promise for SPDY/3.
+    }
+
+    @Override public synchronized void connectionPreface() {
+      // Do nothing: no connection preface for SPDY/3.
+    }
+
+    @Override public synchronized void flush() throws IOException {
+      if (closed) throw new IOException("closed");
+      sink.flush();
+    }
+
+    @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
+        int streamId, int associatedStreamId, List<Header> headerBlock)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      writeNameValueBlockToBuffer(headerBlock);
+      int length = (int) (10 + headerBlockBuffer.size());
+      int type = TYPE_SYN_STREAM;
+      int flags = (outFinished ? FLAG_FIN : 0) | (inFinished ? FLAG_UNIDIRECTIONAL : 0);
+
+      int unused = 0;
+      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      sink.writeInt(streamId & 0x7fffffff);
+      sink.writeInt(associatedStreamId & 0x7fffffff);
+      sink.writeShort((unused & 0x7) << 13 | (unused & 0x1f) << 8 | (unused & 0xff));
+      sink.writeAll(headerBlockBuffer);
+      sink.flush();
+    }
+
+    @Override public synchronized void synReply(boolean outFinished, int streamId,
+        List<Header> headerBlock) throws IOException {
+      if (closed) throw new IOException("closed");
+      writeNameValueBlockToBuffer(headerBlock);
+      int type = TYPE_SYN_REPLY;
+      int flags = (outFinished ? FLAG_FIN : 0);
+      int length = (int) (headerBlockBuffer.size() + 4);
+
+      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      sink.writeInt(streamId & 0x7fffffff);
+      sink.writeAll(headerBlockBuffer);
+      sink.flush();
+    }
+
+    @Override public synchronized void headers(int streamId, List<Header> headerBlock)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      writeNameValueBlockToBuffer(headerBlock);
+      int flags = 0;
+      int type = TYPE_HEADERS;
+      int length = (int) (headerBlockBuffer.size() + 4);
+
+      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      sink.writeInt(streamId & 0x7fffffff);
+      sink.writeAll(headerBlockBuffer);
+    }
+
+    @Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      if (errorCode.spdyRstCode == -1) throw new IllegalArgumentException();
+      int flags = 0;
+      int type = TYPE_RST_STREAM;
+      int length = 8;
+      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      sink.writeInt(streamId & 0x7fffffff);
+      sink.writeInt(errorCode.spdyRstCode);
+      sink.flush();
+    }
+
+    @Override public int maxDataLength() {
+      return 16383;
+    }
+
+    @Override public synchronized void data(boolean outFinished, int streamId, Buffer source,
+        int byteCount) throws IOException {
+      int flags = (outFinished ? FLAG_FIN : 0);
+      sendDataFrame(streamId, flags, source, byteCount);
+    }
+
+    void sendDataFrame(int streamId, int flags, Buffer buffer, int byteCount)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      if (byteCount > 0xffffffL) {
+        throw new IllegalArgumentException("FRAME_TOO_LARGE max size is 16Mib: " + byteCount);
+      }
+      sink.writeInt(streamId & 0x7fffffff);
+      sink.writeInt((flags & 0xff) << 24 | byteCount & 0xffffff);
+      if (byteCount > 0) {
+        sink.write(buffer, byteCount);
+      }
+    }
+
+    private void writeNameValueBlockToBuffer(List<Header> headerBlock) throws IOException {
+      headerBlockOut.writeInt(headerBlock.size());
+      for (int i = 0, size = headerBlock.size(); i < size; i++) {
+        ByteString name = headerBlock.get(i).name;
+        headerBlockOut.writeInt(name.size());
+        headerBlockOut.write(name);
+        ByteString value = headerBlock.get(i).value;
+        headerBlockOut.writeInt(value.size());
+        headerBlockOut.write(value);
+      }
+      headerBlockOut.flush();
+    }
+
+    @Override public synchronized void settings(Settings settings) throws IOException {
+      if (closed) throw new IOException("closed");
+      int type = TYPE_SETTINGS;
+      int flags = 0;
+      int size = settings.size();
+      int length = 4 + size * 8;
+      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      sink.writeInt(size);
+      for (int i = 0; i <= Settings.COUNT; i++) {
+        if (!settings.isSet(i)) continue;
+        int settingsFlags = settings.flags(i);
+        sink.writeInt((settingsFlags & 0xff) << 24 | (i & 0xffffff));
+        sink.writeInt(settings.get(i));
+      }
+      sink.flush();
+    }
+
+    @Override public synchronized void ping(boolean reply, int payload1, int payload2)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      boolean payloadIsReply = client != ((payload1 & 1) == 1);
+      if (reply != payloadIsReply) throw new IllegalArgumentException("payload != reply");
+      int type = TYPE_PING;
+      int flags = 0;
+      int length = 4;
+      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      sink.writeInt(payload1);
+      sink.flush();
+    }
+
+    @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode,
+        byte[] ignored) throws IOException {
+      if (closed) throw new IOException("closed");
+      if (errorCode.spdyGoAwayCode == -1) {
+        throw new IllegalArgumentException("errorCode.spdyGoAwayCode == -1");
+      }
+      int type = TYPE_GOAWAY;
+      int flags = 0;
+      int length = 8;
+      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      sink.writeInt(lastGoodStreamId);
+      sink.writeInt(errorCode.spdyGoAwayCode);
+      sink.flush();
+    }
+
+    @Override public synchronized void windowUpdate(int streamId, long increment)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      if (increment == 0 || increment > 0x7fffffffL) {
+        throw new IllegalArgumentException(
+            "windowSizeIncrement must be between 1 and 0x7fffffff: " + increment);
+      }
+      int type = TYPE_WINDOW_UPDATE;
+      int flags = 0;
+      int length = 8;
+      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
+      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+      sink.writeInt(streamId);
+      sink.writeInt((int) increment);
+      sink.flush();
+    }
+
+    @Override public synchronized void close() throws IOException {
+      closed = true;
+      Util.closeAll(sink, headerBlockOut);
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Variant.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Variant.java
new file mode 100644
index 0000000..92f319c
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/framed/Variant.java
@@ -0,0 +1,39 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.framed;
+
+import com.android.okhttp.Protocol;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+
+/** A version and dialect of the framed socket protocol. 
+ * @hide This class is not part of the Android public SDK API*/
+public interface Variant {
+
+  /** The protocol as selected using ALPN. */
+  Protocol getProtocol();
+
+  /**
+   * @param client true if this is the HTTP client's reader, reading frames from a server.
+   */
+  FrameReader newReader(BufferedSource source, boolean client);
+
+  /**
+   * @param client true if this is the HTTP client's writer, writing frames to a server.
+   */
+  FrameWriter newWriter(BufferedSink sink, boolean client);
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/AuthenticatorAdapter.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/AuthenticatorAdapter.java
new file mode 100644
index 0000000..48257f3
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/AuthenticatorAdapter.java
@@ -0,0 +1,89 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.http;
+
+import com.android.okhttp.Authenticator;
+import com.android.okhttp.Challenge;
+import com.android.okhttp.Credentials;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import java.io.IOException;
+import java.net.Authenticator.RequestorType;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.util.List;
+
+/** Adapts {@link java.net.Authenticator} to {@link com.android.okhttp.Authenticator}. 
+ * @hide This class is not part of the Android public SDK API*/
+public final class AuthenticatorAdapter implements Authenticator {
+  /** Uses the global authenticator to get the password. */
+  public static final Authenticator INSTANCE = new AuthenticatorAdapter();
+
+  @Override public Request authenticate(Proxy proxy, Response response) throws IOException {
+    List<Challenge> challenges = response.challenges();
+    Request request = response.request();
+    HttpUrl url = request.httpUrl();
+    for (int i = 0, size = challenges.size(); i < size; i++) {
+      Challenge challenge = challenges.get(i);
+      if (!"Basic".equalsIgnoreCase(challenge.getScheme())) continue;
+
+      PasswordAuthentication auth = java.net.Authenticator.requestPasswordAuthentication(
+          url.host(), getConnectToInetAddress(proxy, url), url.port(), url.scheme(),
+          challenge.getRealm(), challenge.getScheme(), url.url(), RequestorType.SERVER);
+      if (auth == null) continue;
+
+      String credential = Credentials.basic(auth.getUserName(), new String(auth.getPassword()));
+      return request.newBuilder()
+          .header("Authorization", credential)
+          .build();
+    }
+    return null;
+
+  }
+
+  @Override public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
+    List<Challenge> challenges = response.challenges();
+    Request request = response.request();
+    HttpUrl url = request.httpUrl();
+    for (int i = 0, size = challenges.size(); i < size; i++) {
+      Challenge challenge = challenges.get(i);
+      if (!"Basic".equalsIgnoreCase(challenge.getScheme())) continue;
+
+      InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
+      PasswordAuthentication auth = java.net.Authenticator.requestPasswordAuthentication(
+          proxyAddress.getHostName(), getConnectToInetAddress(proxy, url), proxyAddress.getPort(),
+          url.scheme(), challenge.getRealm(), challenge.getScheme(), url.url(),
+          RequestorType.PROXY);
+      if (auth == null) continue;
+
+      String credential = Credentials.basic(auth.getUserName(), new String(auth.getPassword()));
+      return request.newBuilder()
+          .header("Proxy-Authorization", credential)
+          .build();
+    }
+    return null;
+  }
+
+  private InetAddress getConnectToInetAddress(Proxy proxy, HttpUrl url) throws IOException {
+    return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
+        ? ((InetSocketAddress) proxy.address()).getAddress()
+        : InetAddress.getByName(url.host());
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheRequest.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheRequest.java
new file mode 100644
index 0000000..f419884
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheRequest.java
@@ -0,0 +1,28 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.http;
+
+import java.io.IOException;
+import com.android.okhttp.okio.Sink;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface CacheRequest {
+  Sink body() throws IOException;
+  void abort();
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheStrategy.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheStrategy.java
new file mode 100644
index 0000000..de544db
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/CacheStrategy.java
@@ -0,0 +1,309 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal.http;
+
+import com.android.okhttp.CacheControl;
+import com.android.okhttp.Headers;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import java.util.Date;
+
+import static com.android.okhttp.internal.http.StatusLine.HTTP_PERM_REDIRECT;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
+import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
+import static java.net.HttpURLConnection.HTTP_GONE;
+import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
+import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
+import static java.net.HttpURLConnection.HTTP_NOT_AUTHORITATIVE;
+import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
+import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static java.net.HttpURLConnection.HTTP_REQ_TOO_LONG;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+/**
+ * Given a request and cached response, this figures out whether to use the
+ * network, the cache, or both.
+ *
+ * <p>Selecting a cache strategy may add conditions to the request (like the
+ * "If-Modified-Since" header for conditional GETs) or warnings to the cached
+ * response (if the cached data is potentially stale).
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class CacheStrategy {
+  /** The request to send on the network, or null if this call doesn't use the network. */
+  public final Request networkRequest;
+
+  /** The cached response to return or validate; or null if this call doesn't use a cache. */
+  public final Response cacheResponse;
+
+  private CacheStrategy(Request networkRequest, Response cacheResponse) {
+    this.networkRequest = networkRequest;
+    this.cacheResponse = cacheResponse;
+  }
+
+  /**
+   * Returns true if {@code response} can be stored to later serve another
+   * request.
+   */
+  public static boolean isCacheable(Response response, Request request) {
+    // Always go to network for uncacheable response codes (RFC 7231 section 6.1),
+    // This implementation doesn't support caching partial content.
+    switch (response.code()) {
+      case HTTP_OK:
+      case HTTP_NOT_AUTHORITATIVE:
+      case HTTP_NO_CONTENT:
+      case HTTP_MULT_CHOICE:
+      case HTTP_MOVED_PERM:
+      case HTTP_NOT_FOUND:
+      case HTTP_BAD_METHOD:
+      case HTTP_GONE:
+      case HTTP_REQ_TOO_LONG:
+      case HTTP_NOT_IMPLEMENTED:
+      case HTTP_PERM_REDIRECT:
+      // These codes can be cached unless headers forbid it.
+      break;
+
+      case HTTP_MOVED_TEMP:
+      case HTTP_TEMP_REDIRECT:
+        // These codes can only be cached with the right response headers.
+        // http://tools.ietf.org/html/rfc7234#section-3
+        // s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
+        if (response.header("Expires") != null
+            || response.cacheControl().maxAgeSeconds() != -1
+            || response.cacheControl().isPublic()
+            || response.cacheControl().isPrivate()) {
+          break;
+        }
+        // Fall-through.
+
+      default:
+        // All other codes cannot be cached.
+        return false;
+    }
+
+    // A 'no-store' directive on request or response prevents the response from being cached.
+    return !response.cacheControl().noStore() && !request.cacheControl().noStore();
+  }
+
+  /**
+   * @hide This class is not part of the Android public SDK API
+   */
+  public static class Factory {
+    final long nowMillis;
+    final Request request;
+    final Response cacheResponse;
+
+    /** The server's time when the cached response was served, if known. */
+    private Date servedDate;
+    private String servedDateString;
+
+    /** The last modified date of the cached response, if known. */
+    private Date lastModified;
+    private String lastModifiedString;
+
+    /**
+     * The expiration date of the cached response, if known. If both this field
+     * and the max age are set, the max age is preferred.
+     */
+    private Date expires;
+
+    /**
+     * Extension header set by OkHttp specifying the timestamp when the cached
+     * HTTP request was first initiated.
+     */
+    private long sentRequestMillis;
+
+    /**
+     * Extension header set by OkHttp specifying the timestamp when the cached
+     * HTTP response was first received.
+     */
+    private long receivedResponseMillis;
+
+    /** Etag of the cached response. */
+    private String etag;
+
+    /** Age of the cached response. */
+    private int ageSeconds = -1;
+
+    public Factory(long nowMillis, Request request, Response cacheResponse) {
+      this.nowMillis = nowMillis;
+      this.request = request;
+      this.cacheResponse = cacheResponse;
+
+      if (cacheResponse != null) {
+        Headers headers = cacheResponse.headers();
+        for (int i = 0, size = headers.size(); i < size; i++) {
+          String fieldName = headers.name(i);
+          String value = headers.value(i);
+          if ("Date".equalsIgnoreCase(fieldName)) {
+            servedDate = HttpDate.parse(value);
+            servedDateString = value;
+          } else if ("Expires".equalsIgnoreCase(fieldName)) {
+            expires = HttpDate.parse(value);
+          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
+            lastModified = HttpDate.parse(value);
+            lastModifiedString = value;
+          } else if ("ETag".equalsIgnoreCase(fieldName)) {
+            etag = value;
+          } else if ("Age".equalsIgnoreCase(fieldName)) {
+            ageSeconds = HeaderParser.parseSeconds(value, -1);
+          } else if (OkHeaders.SENT_MILLIS.equalsIgnoreCase(fieldName)) {
+            sentRequestMillis = Long.parseLong(value);
+          } else if (OkHeaders.RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
+            receivedResponseMillis = Long.parseLong(value);
+          }
+        }
+      }
+    }
+
+    /**
+     * Returns a strategy to satisfy {@code request} using the a cached response
+     * {@code response}.
+     */
+    public CacheStrategy get() {
+      CacheStrategy candidate = getCandidate();
+
+      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
+        // We're forbidden from using the network and the cache is insufficient.
+        return new CacheStrategy(null, null);
+      }
+
+      return candidate;
+    }
+
+    /** Returns a strategy to use assuming the request can use the network. */
+    private CacheStrategy getCandidate() {
+      // No cached response.
+      if (cacheResponse == null) {
+        return new CacheStrategy(request, null);
+      }
+
+      // Drop the cached response if it's missing a required handshake.
+      if (request.isHttps() && cacheResponse.handshake() == null) {
+        return new CacheStrategy(request, null);
+      }
+
+      // If this response shouldn't have been stored, it should never be used
+      // as a response source. This check should be redundant as long as the
+      // persistence store is well-behaved and the rules are constant.
+      if (!isCacheable(cacheResponse, request)) {
+        return new CacheStrategy(request, null);
+      }
+
+      CacheControl requestCaching = request.cacheControl();
+      if (requestCaching.noCache() || hasConditions(request)) {
+        return new CacheStrategy(request, null);
+      }
+
+      long ageMillis = cacheResponseAge();
+      long freshMillis = computeFreshnessLifetime();
+
+      if (requestCaching.maxAgeSeconds() != -1) {
+        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
+      }
+
+      long minFreshMillis = 0;
+      if (requestCaching.minFreshSeconds() != -1) {
+        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
+      }
+
+      long maxStaleMillis = 0;
+      CacheControl responseCaching = cacheResponse.cacheControl();
+      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
+        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
+      }
+
+      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
+        Response.Builder builder = cacheResponse.newBuilder();
+        if (ageMillis + minFreshMillis >= freshMillis) {
+          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
+        }
+        long oneDayMillis = 24 * 60 * 60 * 1000L;
+        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
+          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
+        }
+        return new CacheStrategy(null, builder.build());
+      }
+
+      Request.Builder conditionalRequestBuilder = request.newBuilder();
+
+      if (etag != null) {
+        conditionalRequestBuilder.header("If-None-Match", etag);
+      } else if (lastModified != null) {
+        conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString);
+      } else if (servedDate != null) {
+        conditionalRequestBuilder.header("If-Modified-Since", servedDateString);
+      }
+
+      Request conditionalRequest = conditionalRequestBuilder.build();
+      return hasConditions(conditionalRequest)
+          ? new CacheStrategy(conditionalRequest, cacheResponse)
+          : new CacheStrategy(conditionalRequest, null);
+    }
+
+    /**
+     * Returns the number of milliseconds that the response was fresh for,
+     * starting from the served date.
+     */
+    private long computeFreshnessLifetime() {
+      CacheControl responseCaching = cacheResponse.cacheControl();
+      if (responseCaching.maxAgeSeconds() != -1) {
+        return SECONDS.toMillis(responseCaching.maxAgeSeconds());
+      } else if (expires != null) {
+        long servedMillis = servedDate != null
+            ? servedDate.getTime()
+            : receivedResponseMillis;
+        long delta = expires.getTime() - servedMillis;
+        return delta > 0 ? delta : 0;
+      } else if (lastModified != null
+          && cacheResponse.request().httpUrl().query() == null) {
+        // As recommended by the HTTP RFC and implemented in Firefox, the
+        // max age of a document should be defaulted to 10% of the
+        // document's age at the time it was served. Default expiration
+        // dates aren't used for URIs containing a query.
+        long servedMillis = servedDate != null
+            ? servedDate.getTime()
+            : sentRequestMillis;
+        long delta = servedMillis - lastModified.getTime();
+        return delta > 0 ? (delta / 10) : 0;
+      }
+      return 0;
+    }
+
+    /**
+     * Returns the current age of the response, in milliseconds. The calculation
+     * is specified by RFC 2616, 13.2.3 Age Calculations.
+     */
+    private long cacheResponseAge() {
+      long apparentReceivedAge = servedDate != null
+          ? Math.max(0, receivedResponseMillis - servedDate.getTime())
+          : 0;
+      long receivedAge = ageSeconds != -1
+          ? Math.max(apparentReceivedAge, SECONDS.toMillis(ageSeconds))
+          : apparentReceivedAge;
+      long responseDuration = receivedResponseMillis - sentRequestMillis;
+      long residentDuration = nowMillis - receivedResponseMillis;
+      return receivedAge + responseDuration + residentDuration;
+    }
+
+    /**
+     * Returns true if computeFreshnessLifetime used a heuristic. If we used a
+     * heuristic to serve a cached response older than 24 hours, we are required
+     * to attach a warning.
+     */
+    private boolean isFreshnessLifetimeHeuristic() {
+      return cacheResponse.cacheControl().maxAgeSeconds() == -1 && expires == null;
+    }
+
+    /**
+     * Returns true if the request contains conditions that save the server from
+     * sending a response that the client has locally. When a request is enqueued
+     * with its own conditions, the built-in response cache won't be used.
+     */
+    private static boolean hasConditions(Request request) {
+      return request.header("If-Modified-Since") != null || request.header("If-None-Match") != null;
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HeaderParser.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HeaderParser.java
new file mode 100644
index 0000000..7d398ea
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HeaderParser.java
@@ -0,0 +1,73 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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 com.android.okhttp.internal.http;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HeaderParser {
+  /**
+   * Returns the next index in {@code input} at or after {@code pos} that
+   * contains a character from {@code characters}. Returns the input length if
+   * none of the requested characters can be found.
+   */
+  public static int skipUntil(String input, int pos, String characters) {
+    for (; pos < input.length(); pos++) {
+      if (characters.indexOf(input.charAt(pos)) != -1) {
+        break;
+      }
+    }
+    return pos;
+  }
+
+  /**
+   * Returns the next non-whitespace character in {@code input} that is white
+   * space. Result is undefined if input contains newline characters.
+   */
+  public static int skipWhitespace(String input, int pos) {
+    for (; pos < input.length(); pos++) {
+      char c = input.charAt(pos);
+      if (c != ' ' && c != '\t') {
+        break;
+      }
+    }
+    return pos;
+  }
+
+  /**
+   * Returns {@code value} as a positive integer, or 0 if it is negative, or
+   * {@code defaultValue} if it cannot be parsed.
+   */
+  public static int parseSeconds(String value, int defaultValue) {
+    try {
+      long seconds = Long.parseLong(value);
+      if (seconds > Integer.MAX_VALUE) {
+        return Integer.MAX_VALUE;
+      } else if (seconds < 0) {
+        return 0;
+      } else {
+        return (int) seconds;
+      }
+    } catch (NumberFormatException e) {
+      return defaultValue;
+    }
+  }
+
+  private HeaderParser() {
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http1xStream.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http1xStream.java
new file mode 100644
index 0000000..a925746
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http1xStream.java
@@ -0,0 +1,508 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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 com.android.okhttp.internal.http;
+
+import com.android.okhttp.Headers;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.ResponseBody;
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.io.RealConnection;
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.ProtocolException;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.ForwardingTimeout;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+import com.android.okhttp.okio.Timeout;
+
+import static com.android.okhttp.internal.Util.checkOffsetAndCount;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_CONTINUE;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ * A socket connection that can be used to send HTTP/1.1 messages. This class
+ * strictly enforces the following lifecycle:
+ * <ol>
+ *   <li>{@link #writeRequest Send request headers}.
+ *   <li>Open a sink to write the request body. Either {@link
+ *       #newFixedLengthSink fixed-length} or {@link #newChunkedSink chunked}.
+ *   <li>Write to and then close that sink.
+ *   <li>{@link #readResponse Read response headers}.
+ *   <li>Open a source to read the response body. Either {@link
+ *       #newFixedLengthSource fixed-length}, {@link #newChunkedSource chunked}
+ *       or {@link #newUnknownLengthSource unknown length}.
+ *   <li>Read from and close that source.
+ * </ol>
+ * <p>Exchanges that do not have a request body may skip creating and closing
+ * the request body. Exchanges that do not have a response body can call {@link
+ * #newFixedLengthSource(long) newFixedLengthSource(0)} and may skip reading and
+ * closing that source.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Http1xStream implements HttpStream {
+  private static final int STATE_IDLE = 0; // Idle connections are ready to write request headers.
+  private static final int STATE_OPEN_REQUEST_BODY = 1;
+  private static final int STATE_WRITING_REQUEST_BODY = 2;
+  private static final int STATE_READ_RESPONSE_HEADERS = 3;
+  private static final int STATE_OPEN_RESPONSE_BODY = 4;
+  private static final int STATE_READING_RESPONSE_BODY = 5;
+  private static final int STATE_CLOSED = 6;
+
+  /** The stream allocation that owns this stream. May be null for HTTPS proxy tunnels. */
+  private final StreamAllocation streamAllocation;
+  private final BufferedSource source;
+  private final BufferedSink sink;
+  private HttpEngine httpEngine;
+  private int state = STATE_IDLE;
+
+  public Http1xStream(StreamAllocation streamAllocation, BufferedSource source, BufferedSink sink) {
+    this.streamAllocation = streamAllocation;
+    this.source = source;
+    this.sink = sink;
+  }
+
+  @Override public void setHttpEngine(HttpEngine httpEngine) {
+    this.httpEngine = httpEngine;
+  }
+
+  @Override public Sink createRequestBody(Request request, long contentLength) throws IOException {
+    if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
+      // Stream a request body of unknown length.
+      return newChunkedSink();
+    }
+
+    if (contentLength != -1) {
+      // Stream a request body of a known length.
+      return newFixedLengthSink(contentLength);
+    }
+
+    throw new IllegalStateException(
+        "Cannot stream a request body without chunked encoding or a known content length!");
+  }
+
+  @Override public void cancel() {
+    RealConnection connection = streamAllocation.connection();
+    if (connection != null) connection.cancel();
+  }
+
+  /**
+   * Prepares the HTTP headers and sends them to the server.
+   *
+   * <p>For streaming requests with a body, headers must be prepared
+   * <strong>before</strong> the output stream has been written to. Otherwise
+   * the body would need to be buffered!
+   *
+   * <p>For non-streaming requests with a body, headers must be prepared
+   * <strong>after</strong> the output stream has been written to and closed.
+   * This ensures that the {@code Content-Length} header field receives the
+   * proper value.
+   */
+  @Override public void writeRequestHeaders(Request request) throws IOException {
+    httpEngine.writingRequestHeaders();
+    String requestLine = RequestLine.get(
+        request, httpEngine.getConnection().getRoute().getProxy().type());
+    writeRequest(request.headers(), requestLine);
+  }
+
+  @Override public Response.Builder readResponseHeaders() throws IOException {
+    return readResponse();
+  }
+
+  @Override public ResponseBody openResponseBody(Response response) throws IOException {
+    Source source = getTransferStream(response);
+    return new RealResponseBody(response.headers(), Okio.buffer(source));
+  }
+
+  private Source getTransferStream(Response response) throws IOException {
+    if (!HttpEngine.hasBody(response)) {
+      return newFixedLengthSource(0);
+    }
+
+    if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
+      return newChunkedSource(httpEngine);
+    }
+
+    long contentLength = OkHeaders.contentLength(response);
+    if (contentLength != -1) {
+      return newFixedLengthSource(contentLength);
+    }
+
+    // Wrap the input stream from the connection (rather than just returning
+    // "socketIn" directly here), so that we can control its use after the
+    // reference escapes.
+    return newUnknownLengthSource();
+  }
+
+  /** Returns true if this connection is closed. */
+  public boolean isClosed() {
+    return state == STATE_CLOSED;
+  }
+
+  @Override public void finishRequest() throws IOException {
+    sink.flush();
+  }
+
+  /** Returns bytes of a request header for sending on an HTTP transport. */
+  public void writeRequest(Headers headers, String requestLine) throws IOException {
+    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
+    sink.writeUtf8(requestLine).writeUtf8("\r\n");
+    for (int i = 0, size = headers.size(); i < size; i ++) {
+      sink.writeUtf8(headers.name(i))
+          .writeUtf8(": ")
+          .writeUtf8(headers.value(i))
+          .writeUtf8("\r\n");
+    }
+    sink.writeUtf8("\r\n");
+    state = STATE_OPEN_REQUEST_BODY;
+  }
+
+  /** Parses bytes of a response header from an HTTP transport. */
+  public Response.Builder readResponse() throws IOException {
+    if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
+      throw new IllegalStateException("state: " + state);
+    }
+
+    try {
+      while (true) {
+        StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
+
+        Response.Builder responseBuilder = new Response.Builder()
+            .protocol(statusLine.protocol)
+            .code(statusLine.code)
+            .message(statusLine.message)
+            .headers(readHeaders());
+
+        if (statusLine.code != HTTP_CONTINUE) {
+          state = STATE_OPEN_RESPONSE_BODY;
+          return responseBuilder;
+        }
+      }
+    } catch (EOFException e) {
+      // Provide more context if the server ends the stream before sending a response.
+      IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
+      exception.initCause(e);
+      throw exception;
+    }
+  }
+
+  /** Reads headers or trailers. */
+  public Headers readHeaders() throws IOException {
+    Headers.Builder headers = new Headers.Builder();
+    // parse the result headers until the first blank line
+    for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) {
+      Internal.instance.addLenient(headers, line);
+    }
+    return headers.build();
+  }
+
+  public Sink newChunkedSink() {
+    if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
+    state = STATE_WRITING_REQUEST_BODY;
+    return new ChunkedSink();
+  }
+
+  public Sink newFixedLengthSink(long contentLength) {
+    if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
+    state = STATE_WRITING_REQUEST_BODY;
+    return new FixedLengthSink(contentLength);
+  }
+
+  @Override public void writeRequestBody(RetryableSink requestBody) throws IOException {
+    if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
+    state = STATE_READ_RESPONSE_HEADERS;
+    requestBody.writeToSocket(sink);
+  }
+
+  public Source newFixedLengthSource(long length) throws IOException {
+    if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+    state = STATE_READING_RESPONSE_BODY;
+    return new FixedLengthSource(length);
+  }
+
+  public Source newChunkedSource(HttpEngine httpEngine) throws IOException {
+    if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+    state = STATE_READING_RESPONSE_BODY;
+    return new ChunkedSource(httpEngine);
+  }
+
+  public Source newUnknownLengthSource() throws IOException {
+    if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+    if (streamAllocation == null) throw new IllegalStateException("streamAllocation == null");
+    state = STATE_READING_RESPONSE_BODY;
+    streamAllocation.noNewStreams();
+    return new UnknownLengthSource();
+  }
+
+  /**
+   * Sets the delegate of {@code timeout} to {@link Timeout#NONE} and resets its underlying timeout
+   * to the default configuration. Use this to avoid unexpected sharing of timeouts between pooled
+   * connections.
+   */
+  private void detachTimeout(ForwardingTimeout timeout) {
+    Timeout oldDelegate = timeout.delegate();
+    timeout.setDelegate(Timeout.NONE);
+    oldDelegate.clearDeadline();
+    oldDelegate.clearTimeout();
+  }
+
+  /** An HTTP body with a fixed length known in advance. */
+  private final class FixedLengthSink implements Sink {
+    private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout());
+    private boolean closed;
+    private long bytesRemaining;
+
+    private FixedLengthSink(long bytesRemaining) {
+      this.bytesRemaining = bytesRemaining;
+    }
+
+    @Override public Timeout timeout() {
+      return timeout;
+    }
+
+    @Override public void write(Buffer source, long byteCount) throws IOException {
+      if (closed) throw new IllegalStateException("closed");
+      checkOffsetAndCount(source.size(), 0, byteCount);
+      if (byteCount > bytesRemaining) {
+        throw new ProtocolException("expected " + bytesRemaining
+            + " bytes but received " + byteCount);
+      }
+      sink.write(source, byteCount);
+      bytesRemaining -= byteCount;
+    }
+
+    @Override public void flush() throws IOException {
+      if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
+      sink.flush();
+    }
+
+    @Override public void close() throws IOException {
+      if (closed) return;
+      closed = true;
+      if (bytesRemaining > 0) throw new ProtocolException("unexpected end of stream");
+      detachTimeout(timeout);
+      state = STATE_READ_RESPONSE_HEADERS;
+    }
+  }
+
+  /**
+   * An HTTP body with alternating chunk sizes and chunk bodies. It is the
+   * caller's responsibility to buffer chunks; typically by using a buffered
+   * sink with this sink.
+   */
+  private final class ChunkedSink implements Sink {
+    private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout());
+    private boolean closed;
+
+    @Override public Timeout timeout() {
+      return timeout;
+    }
+
+    @Override public void write(Buffer source, long byteCount) throws IOException {
+      if (closed) throw new IllegalStateException("closed");
+      if (byteCount == 0) return;
+
+      sink.writeHexadecimalUnsignedLong(byteCount);
+      sink.writeUtf8("\r\n");
+      sink.write(source, byteCount);
+      sink.writeUtf8("\r\n");
+    }
+
+    @Override public synchronized void flush() throws IOException {
+      if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
+      sink.flush();
+    }
+
+    @Override public synchronized void close() throws IOException {
+      if (closed) return;
+      closed = true;
+      sink.writeUtf8("0\r\n\r\n");
+      detachTimeout(timeout);
+      state = STATE_READ_RESPONSE_HEADERS;
+    }
+  }
+
+  private abstract class AbstractSource implements Source {
+    protected final ForwardingTimeout timeout = new ForwardingTimeout(source.timeout());
+    protected boolean closed;
+
+    @Override public Timeout timeout() {
+      return timeout;
+    }
+
+    /**
+     * Closes the cache entry and makes the socket available for reuse. This
+     * should be invoked when the end of the body has been reached.
+     */
+    protected final void endOfInput() throws IOException {
+      if (state != STATE_READING_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+
+      detachTimeout(timeout);
+
+      state = STATE_CLOSED;
+      if (streamAllocation != null) {
+        streamAllocation.streamFinished(Http1xStream.this);
+      }
+    }
+
+    protected final void unexpectedEndOfInput() {
+      if (state == STATE_CLOSED) return;
+
+      state = STATE_CLOSED;
+      if (streamAllocation != null) {
+        streamAllocation.noNewStreams();
+        streamAllocation.streamFinished(Http1xStream.this);
+      }
+    }
+  }
+
+  /** An HTTP body with a fixed length specified in advance. */
+  private class FixedLengthSource extends AbstractSource {
+    private long bytesRemaining;
+
+    public FixedLengthSource(long length) throws IOException {
+      bytesRemaining = length;
+      if (bytesRemaining == 0) {
+        endOfInput();
+      }
+    }
+
+    @Override public long read(Buffer sink, long byteCount) throws IOException {
+      if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+      if (closed) throw new IllegalStateException("closed");
+      if (bytesRemaining == 0) return -1;
+
+      long read = source.read(sink, Math.min(bytesRemaining, byteCount));
+      if (read == -1) {
+        unexpectedEndOfInput(); // The server didn't supply the promised content length.
+        throw new ProtocolException("unexpected end of stream");
+      }
+
+      bytesRemaining -= read;
+      if (bytesRemaining == 0) {
+        endOfInput();
+      }
+      return read;
+    }
+
+    @Override public void close() throws IOException {
+      if (closed) return;
+
+      if (bytesRemaining != 0
+          && !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
+        unexpectedEndOfInput();
+      }
+
+      closed = true;
+    }
+  }
+
+  /** An HTTP body with alternating chunk sizes and chunk bodies. */
+  private class ChunkedSource extends AbstractSource {
+    private static final long NO_CHUNK_YET = -1L;
+    private long bytesRemainingInChunk = NO_CHUNK_YET;
+    private boolean hasMoreChunks = true;
+    private final HttpEngine httpEngine;
+
+    ChunkedSource(HttpEngine httpEngine) throws IOException {
+      this.httpEngine = httpEngine;
+    }
+
+    @Override public long read(Buffer sink, long byteCount) throws IOException {
+      if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+      if (closed) throw new IllegalStateException("closed");
+      if (!hasMoreChunks) return -1;
+
+      if (bytesRemainingInChunk == 0 || bytesRemainingInChunk == NO_CHUNK_YET) {
+        readChunkSize();
+        if (!hasMoreChunks) return -1;
+      }
+
+      long read = source.read(sink, Math.min(byteCount, bytesRemainingInChunk));
+      if (read == -1) {
+        unexpectedEndOfInput(); // The server didn't supply the promised chunk length.
+        throw new ProtocolException("unexpected end of stream");
+      }
+      bytesRemainingInChunk -= read;
+      return read;
+    }
+
+    private void readChunkSize() throws IOException {
+      // Read the suffix of the previous chunk.
+      if (bytesRemainingInChunk != NO_CHUNK_YET) {
+        source.readUtf8LineStrict();
+      }
+      try {
+        bytesRemainingInChunk = source.readHexadecimalUnsignedLong();
+        String extensions = source.readUtf8LineStrict().trim();
+        if (bytesRemainingInChunk < 0 || (!extensions.isEmpty() && !extensions.startsWith(";"))) {
+          throw new ProtocolException("expected chunk size and optional extensions but was \""
+              + bytesRemainingInChunk + extensions + "\"");
+        }
+      } catch (NumberFormatException e) {
+        throw new ProtocolException(e.getMessage());
+      }
+      if (bytesRemainingInChunk == 0L) {
+        hasMoreChunks = false;
+        httpEngine.receiveHeaders(readHeaders());
+        endOfInput();
+      }
+    }
+
+    @Override public void close() throws IOException {
+      if (closed) return;
+      if (hasMoreChunks && !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
+        unexpectedEndOfInput();
+      }
+      closed = true;
+    }
+  }
+
+  /** An HTTP message body terminated by the end of the underlying stream. */
+  private class UnknownLengthSource extends AbstractSource {
+    private boolean inputExhausted;
+
+    @Override public long read(Buffer sink, long byteCount)
+        throws IOException {
+      if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+      if (closed) throw new IllegalStateException("closed");
+      if (inputExhausted) return -1;
+
+      long read = source.read(sink, byteCount);
+      if (read == -1) {
+        inputExhausted = true;
+        endOfInput();
+        return -1;
+      }
+      return read;
+    }
+
+    @Override public void close() throws IOException {
+      if (closed) return;
+      if (!inputExhausted) {
+        unexpectedEndOfInput();
+      }
+      closed = true;
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http2xStream.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http2xStream.java
new file mode 100644
index 0000000..64908e0
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/Http2xStream.java
@@ -0,0 +1,298 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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 com.android.okhttp.internal.http;
+
+import com.android.okhttp.Headers;
+import com.android.okhttp.Protocol;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.ResponseBody;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.framed.ErrorCode;
+import com.android.okhttp.internal.framed.FramedConnection;
+import com.android.okhttp.internal.framed.FramedStream;
+import com.android.okhttp.internal.framed.Header;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import com.android.okhttp.okio.ByteString;
+import com.android.okhttp.okio.ForwardingSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+
+import static com.android.okhttp.internal.framed.Header.RESPONSE_STATUS;
+import static com.android.okhttp.internal.framed.Header.TARGET_AUTHORITY;
+import static com.android.okhttp.internal.framed.Header.TARGET_HOST;
+import static com.android.okhttp.internal.framed.Header.TARGET_METHOD;
+import static com.android.okhttp.internal.framed.Header.TARGET_PATH;
+import static com.android.okhttp.internal.framed.Header.TARGET_SCHEME;
+import static com.android.okhttp.internal.framed.Header.VERSION;
+
+/** An HTTP stream for HTTP/2 and SPDY. 
+ * @hide This class is not part of the Android public SDK API*/
+public final class Http2xStream implements HttpStream {
+  private static final ByteString CONNECTION = ByteString.encodeUtf8("connection");
+  private static final ByteString HOST = ByteString.encodeUtf8("host");
+  private static final ByteString KEEP_ALIVE = ByteString.encodeUtf8("keep-alive");
+  private static final ByteString PROXY_CONNECTION = ByteString.encodeUtf8("proxy-connection");
+  private static final ByteString TRANSFER_ENCODING = ByteString.encodeUtf8("transfer-encoding");
+  private static final ByteString TE = ByteString.encodeUtf8("te");
+  private static final ByteString ENCODING = ByteString.encodeUtf8("encoding");
+  private static final ByteString UPGRADE = ByteString.encodeUtf8("upgrade");
+
+  /** See http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-3.2.1-Request. */
+  private static final List<ByteString> SPDY_3_SKIPPED_REQUEST_HEADERS = Util.immutableList(
+      CONNECTION,
+      HOST,
+      KEEP_ALIVE,
+      PROXY_CONNECTION,
+      TRANSFER_ENCODING,
+      TARGET_METHOD,
+      TARGET_PATH,
+      TARGET_SCHEME,
+      TARGET_AUTHORITY,
+      TARGET_HOST,
+      VERSION);
+  private static final List<ByteString> SPDY_3_SKIPPED_RESPONSE_HEADERS = Util.immutableList(
+      CONNECTION,
+      HOST,
+      KEEP_ALIVE,
+      PROXY_CONNECTION,
+      TRANSFER_ENCODING);
+
+  /** See http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3. */
+  private static final List<ByteString> HTTP_2_SKIPPED_REQUEST_HEADERS = Util.immutableList(
+      CONNECTION,
+      HOST,
+      KEEP_ALIVE,
+      PROXY_CONNECTION,
+      TE,
+      TRANSFER_ENCODING,
+      ENCODING,
+      UPGRADE,
+      TARGET_METHOD,
+      TARGET_PATH,
+      TARGET_SCHEME,
+      TARGET_AUTHORITY,
+      TARGET_HOST,
+      VERSION);
+  private static final List<ByteString> HTTP_2_SKIPPED_RESPONSE_HEADERS = Util.immutableList(
+      CONNECTION,
+      HOST,
+      KEEP_ALIVE,
+      PROXY_CONNECTION,
+      TE,
+      TRANSFER_ENCODING,
+      ENCODING,
+      UPGRADE);
+
+  private final StreamAllocation streamAllocation;
+  private final FramedConnection framedConnection;
+  private HttpEngine httpEngine;
+  private FramedStream stream;
+
+  public Http2xStream(StreamAllocation streamAllocation, FramedConnection framedConnection) {
+    this.streamAllocation = streamAllocation;
+    this.framedConnection = framedConnection;
+  }
+
+  @Override public void setHttpEngine(HttpEngine httpEngine) {
+    this.httpEngine = httpEngine;
+  }
+
+  @Override public Sink createRequestBody(Request request, long contentLength) throws IOException {
+    return stream.getSink();
+  }
+
+  @Override public void writeRequestHeaders(Request request) throws IOException {
+    if (stream != null) return;
+
+    httpEngine.writingRequestHeaders();
+    boolean permitsRequestBody = httpEngine.permitsRequestBody(request);
+    List<Header> requestHeaders = framedConnection.getProtocol() == Protocol.HTTP_2
+        ? http2HeadersList(request)
+        : spdy3HeadersList(request);
+    boolean hasResponseBody = true;
+    stream = framedConnection.newStream(requestHeaders, permitsRequestBody, hasResponseBody);
+    stream.readTimeout().timeout(httpEngine.client.getReadTimeout(), TimeUnit.MILLISECONDS);
+    stream.writeTimeout().timeout(httpEngine.client.getWriteTimeout(), TimeUnit.MILLISECONDS);
+  }
+
+  @Override public void writeRequestBody(RetryableSink requestBody) throws IOException {
+    requestBody.writeToSocket(stream.getSink());
+  }
+
+  @Override public void finishRequest() throws IOException {
+    stream.getSink().close();
+  }
+
+  @Override public Response.Builder readResponseHeaders() throws IOException {
+    return framedConnection.getProtocol() == Protocol.HTTP_2
+        ? readHttp2HeadersList(stream.getResponseHeaders())
+        : readSpdy3HeadersList(stream.getResponseHeaders());
+  }
+
+  /**
+   * Returns a list of alternating names and values containing a SPDY request.
+   * Names are all lowercase. No names are repeated. If any name has multiple
+   * values, they are concatenated using "\0" as a delimiter.
+   */
+  public static List<Header> spdy3HeadersList(Request request) {
+    Headers headers = request.headers();
+    List<Header> result = new ArrayList<>(headers.size() + 5);
+    result.add(new Header(TARGET_METHOD, request.method()));
+    result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.httpUrl())));
+    result.add(new Header(VERSION, "HTTP/1.1"));
+    result.add(new Header(TARGET_HOST, Util.hostHeader(request.httpUrl(), false)));
+    result.add(new Header(TARGET_SCHEME, request.httpUrl().scheme()));
+
+    Set<ByteString> names = new LinkedHashSet<>();
+    for (int i = 0, size = headers.size(); i < size; i++) {
+      // header names must be lowercase.
+      ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US));
+
+      // Drop headers that are forbidden when layering HTTP over SPDY.
+      if (SPDY_3_SKIPPED_REQUEST_HEADERS.contains(name)) continue;
+
+      // If we haven't seen this name before, add the pair to the end of the list...
+      String value = headers.value(i);
+      if (names.add(name)) {
+        result.add(new Header(name, value));
+        continue;
+      }
+
+      // ...otherwise concatenate the existing values and this value.
+      for (int j = 0; j < result.size(); j++) {
+        if (result.get(j).name.equals(name)) {
+          String concatenated = joinOnNull(result.get(j).value.utf8(), value);
+          result.set(j, new Header(name, concatenated));
+          break;
+        }
+      }
+    }
+    return result;
+  }
+
+  private static String joinOnNull(String first, String second) {
+    return new StringBuilder(first).append('\0').append(second).toString();
+  }
+
+  public static List<Header> http2HeadersList(Request request) {
+    Headers headers = request.headers();
+    List<Header> result = new ArrayList<>(headers.size() + 4);
+    result.add(new Header(TARGET_METHOD, request.method()));
+    result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.httpUrl())));
+    result.add(new Header(TARGET_AUTHORITY, Util.hostHeader(request.httpUrl(), false))); // Optional
+    result.add(new Header(TARGET_SCHEME, request.httpUrl().scheme()));
+
+    for (int i = 0, size = headers.size(); i < size; i++) {
+      // header names must be lowercase.
+      ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US));
+      if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name)) {
+        result.add(new Header(name, headers.value(i)));
+      }
+    }
+    return result;
+  }
+
+  /** Returns headers for a name value block containing a SPDY response. */
+  public static Response.Builder readSpdy3HeadersList(List<Header> headerBlock) throws IOException {
+    String status = null;
+    String version = "HTTP/1.1";
+    Headers.Builder headersBuilder = new Headers.Builder();
+    for (int i = 0, size = headerBlock.size(); i < size; i++) {
+      ByteString name = headerBlock.get(i).name;
+
+      String values = headerBlock.get(i).value.utf8();
+      for (int start = 0; start < values.length(); ) {
+        int end = values.indexOf('\0', start);
+        if (end == -1) {
+          end = values.length();
+        }
+        String value = values.substring(start, end);
+        if (name.equals(RESPONSE_STATUS)) {
+          status = value;
+        } else if (name.equals(VERSION)) {
+          version = value;
+        } else if (!SPDY_3_SKIPPED_RESPONSE_HEADERS.contains(name)) {
+          headersBuilder.add(name.utf8(), value);
+        }
+        start = end + 1;
+      }
+    }
+    if (status == null) throw new ProtocolException("Expected ':status' header not present");
+
+    StatusLine statusLine = StatusLine.parse(version + " " + status);
+    return new Response.Builder()
+        .protocol(Protocol.SPDY_3)
+        .code(statusLine.code)
+        .message(statusLine.message)
+        .headers(headersBuilder.build());
+  }
+
+  /** Returns headers for a name value block containing an HTTP/2 response. */
+  public static Response.Builder readHttp2HeadersList(List<Header> headerBlock) throws IOException {
+    String status = null;
+
+    Headers.Builder headersBuilder = new Headers.Builder();
+    for (int i = 0, size = headerBlock.size(); i < size; i++) {
+      ByteString name = headerBlock.get(i).name;
+
+      String value = headerBlock.get(i).value.utf8();
+      if (name.equals(RESPONSE_STATUS)) {
+        status = value;
+      } else if (!HTTP_2_SKIPPED_RESPONSE_HEADERS.contains(name)) {
+        headersBuilder.add(name.utf8(), value);
+      }
+    }
+    if (status == null) throw new ProtocolException("Expected ':status' header not present");
+
+    StatusLine statusLine = StatusLine.parse("HTTP/1.1 " + status);
+    return new Response.Builder()
+        .protocol(Protocol.HTTP_2)
+        .code(statusLine.code)
+        .message(statusLine.message)
+        .headers(headersBuilder.build());
+  }
+
+  @Override public ResponseBody openResponseBody(Response response) throws IOException {
+    Source source = new StreamFinishingSource(stream.getSource());
+    return new RealResponseBody(response.headers(), Okio.buffer(source));
+  }
+
+  @Override public void cancel() {
+    if (stream != null) stream.closeLater(ErrorCode.CANCEL);
+  }
+
+  class StreamFinishingSource extends ForwardingSource {
+    public StreamFinishingSource(Source delegate) {
+      super(delegate);
+    }
+
+    @Override public void close() throws IOException {
+      streamAllocation.streamFinished(Http2xStream.this);
+      super.close();
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpDate.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpDate.java
new file mode 100644
index 0000000..52265c2
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpDate.java
@@ -0,0 +1,121 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2011 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 com.android.okhttp.internal.http;
+
+import java.text.DateFormat;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Best-effort parser for HTTP dates.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpDate {
+
+  private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
+
+  /**
+   * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such
+   * cookies are on the fast path.
+   */
+  private static final ThreadLocal<DateFormat> STANDARD_DATE_FORMAT =
+      new ThreadLocal<DateFormat>() {
+        @Override protected DateFormat initialValue() {
+          // RFC 2616 specified: RFC 822, updated by RFC 1123 format with fixed GMT.
+          DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
+          rfc1123.setLenient(false);
+          rfc1123.setTimeZone(GMT);
+          return rfc1123;
+        }
+      };
+
+  /** If we fail to parse a date in a non-standard format, try each of these formats in sequence. */
+  private static final String[] BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS = new String[] {
+      // HTTP formats required by RFC2616 but with any timezone.
+      "EEE, dd MMM yyyy HH:mm:ss zzz", // RFC 822, updated by RFC 1123 with any TZ
+      "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 850, obsoleted by RFC 1036 with any TZ.
+      "EEE MMM d HH:mm:ss yyyy", // ANSI C's asctime() format
+       // Alternative formats.
+      "EEE, dd-MMM-yyyy HH:mm:ss z",
+      "EEE, dd-MMM-yyyy HH-mm-ss z",
+      "EEE, dd MMM yy HH:mm:ss z",
+      "EEE dd-MMM-yyyy HH:mm:ss z",
+      "EEE dd MMM yyyy HH:mm:ss z",
+      "EEE dd-MMM-yyyy HH-mm-ss z",
+      "EEE dd-MMM-yy HH:mm:ss z",
+      "EEE dd MMM yy HH:mm:ss z",
+      "EEE,dd-MMM-yy HH:mm:ss z",
+      "EEE,dd-MMM-yyyy HH:mm:ss z",
+      "EEE, dd-MM-yyyy HH:mm:ss z",
+
+      /* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */
+      "EEE MMM d yyyy HH:mm:ss z",
+  };
+
+  private static final DateFormat[] BROWSER_COMPATIBLE_DATE_FORMATS =
+      new DateFormat[BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length];
+
+  /** Returns the date for {@code value}. Returns null if the value couldn't be parsed. */
+  public static Date parse(String value) {
+    if (value.length() == 0) {
+      return null;
+    }
+
+    ParsePosition position = new ParsePosition(0);
+    Date result = STANDARD_DATE_FORMAT.get().parse(value, position);
+    if (position.getIndex() == value.length()) {
+      // STANDARD_DATE_FORMAT must match exactly; all text must be consumed, e.g. no ignored
+      // non-standard trailing "+01:00". Those cases are covered below.
+      return result;
+    }
+    synchronized (BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS) {
+      for (int i = 0, count = BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length; i < count; i++) {
+        DateFormat format = BROWSER_COMPATIBLE_DATE_FORMATS[i];
+        if (format == null) {
+          format = new SimpleDateFormat(BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS[i], Locale.US);
+          // Set the timezone to use when interpreting formats that don't have a timezone. GMT is
+          // specified by RFC 2616.
+          format.setTimeZone(GMT);
+          BROWSER_COMPATIBLE_DATE_FORMATS[i] = format;
+        }
+        position.setIndex(0);
+        result = format.parse(value, position);
+        if (position.getIndex() != 0) {
+          // Something was parsed. It's possible the entire string was not consumed but we ignore
+          // that. If any of the BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS ended in "'GMT'" we'd have
+          // to also check that position.getIndex() == value.length() otherwise parsing might have
+          // terminated early, ignoring things like "+01:00". Leaving this as != 0 means that any
+          // trailing junk is ignored.
+          return result;
+        }
+      }
+    }
+    return null;
+  }
+
+  /** Returns the string for {@code value}. */
+  public static String format(Date value) {
+    return STANDARD_DATE_FORMAT.get().format(value);
+  }
+
+  private HttpDate() {
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpEngine.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpEngine.java
new file mode 100644
index 0000000..0ef78ed
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpEngine.java
@@ -0,0 +1,989 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp.internal.http;
+
+import com.android.okhttp.Address;
+import com.android.okhttp.CertificatePinner;
+import com.android.okhttp.Connection;
+import com.android.okhttp.Headers;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.Interceptor;
+import com.android.okhttp.MediaType;
+import com.android.okhttp.OkHttpClient;
+import com.android.okhttp.Protocol;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.ResponseBody;
+import com.android.okhttp.Route;
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.InternalCache;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.Version;
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.GzipSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+import com.android.okhttp.okio.Timeout;
+
+import static com.android.okhttp.internal.Util.closeQuietly;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_CONTINUE;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_PERM_REDIRECT;
+import static com.android.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
+import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
+import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
+import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ * Handles a single HTTP request/response pair. Each HTTP engine follows this
+ * lifecycle:
+ * <ol>
+ * <li>It is created.
+ * <li>The HTTP request message is sent with sendRequest(). Once the request
+ * is sent it is an error to modify the request headers. After
+ * sendRequest() has been called the request body can be written to if
+ * it exists.
+ * <li>The HTTP response message is read with readResponse(). After the
+ * response has been read the response headers and body can be read.
+ * All responses have a response body input stream, though in some
+ * instances this stream is empty.
+ * </ol>
+ *
+ * <p>The request and response may be served by the HTTP response cache, by the
+ * network, or by both in the event of a conditional GET.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpEngine {
+  /**
+   * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
+   * curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
+   */
+  public static final int MAX_FOLLOW_UPS = 20;
+
+  private static final ResponseBody EMPTY_BODY = new ResponseBody() {
+    @Override public MediaType contentType() {
+      return null;
+    }
+    @Override public long contentLength() {
+      return 0;
+    }
+    @Override public BufferedSource source() {
+      return new Buffer();
+    }
+  };
+
+  final OkHttpClient client;
+
+  public final StreamAllocation streamAllocation;
+  private final Response priorResponse;
+  private HttpStream httpStream;
+
+  /** The time when the request headers were written, or -1 if they haven't been written yet. */
+  long sentRequestMillis = -1;
+
+  /**
+   * True if this client added an "Accept-Encoding: gzip" header field and is
+   * therefore responsible for also decompressing the transfer stream.
+   */
+  private boolean transparentGzip;
+
+  /**
+   * True if the request body must be completely buffered before transmission;
+   * false if it can be streamed. Buffering has two advantages: we don't need
+   * the content-length in advance and we can retransmit if necessary. The
+   * upside of streaming is that we can save memory.
+   */
+  public final boolean bufferRequestBody;
+
+  /**
+   * The original application-provided request. Never modified by OkHttp. When
+   * follow-up requests are necessary, they are derived from this request.
+   */
+  private final Request userRequest;
+
+  /**
+   * The request to send on the network, or null for no network request. This is
+   * derived from the user request, and customized to support OkHttp features
+   * like compression and caching.
+   */
+  private Request networkRequest;
+
+  /**
+   * The cached response, or null if the cache doesn't exist or cannot be used
+   * for this request. Conditional caching means this may be non-null even when
+   * the network request is non-null. Never modified by OkHttp.
+   */
+  private Response cacheResponse;
+
+  /**
+   * The user-visible response. This is derived from either the network
+   * response, cache response, or both. It is customized to support OkHttp
+   * features like compression and caching.
+   */
+  private Response userResponse;
+
+  private Sink requestBodyOut;
+  private BufferedSink bufferedRequestBody;
+  private final boolean callerWritesRequestBody;
+  private final boolean forWebSocket;
+
+  /** The cache request currently being populated from a network response. */
+  private CacheRequest storeRequest;
+  private CacheStrategy cacheStrategy;
+
+  /**
+   * @param request the HTTP request without a body. The body must be written via the engine's
+   *     request body stream.
+   * @param callerWritesRequestBody true for the {@code HttpURLConnection}-style interaction
+   *     model where control flow is returned to the calling application to write the request body
+   *     before the response body is readable.
+   */
+  public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
+      boolean callerWritesRequestBody, boolean forWebSocket, StreamAllocation streamAllocation,
+      RetryableSink requestBodyOut, Response priorResponse) {
+    this.client = client;
+    this.userRequest = request;
+    this.bufferRequestBody = bufferRequestBody;
+    this.callerWritesRequestBody = callerWritesRequestBody;
+    this.forWebSocket = forWebSocket;
+    this.streamAllocation = streamAllocation != null
+        ? streamAllocation
+        : new StreamAllocation(client.getConnectionPool(), createAddress(client, request));
+    this.requestBodyOut = requestBodyOut;
+    this.priorResponse = priorResponse;
+  }
+
+  /**
+   * Figures out what the response source will be, and opens a socket to that
+   * source if necessary. Prepares the request headers and gets ready to start
+   * writing the request body if it exists.
+   *
+   * @throws RequestException if there was a problem with request setup. Unrecoverable.
+   * @throws RouteException if the was a problem during connection via a specific route. Sometimes
+   *     recoverable. See {@link #recover(RouteException)}.
+   * @throws IOException if there was a problem while making a request. Sometimes recoverable. See
+   *     {@link #recover(IOException)}.
+   *
+   */
+  public void sendRequest() throws RequestException, RouteException, IOException {
+    if (cacheStrategy != null) return; // Already sent.
+    if (httpStream != null) throw new IllegalStateException();
+
+    Request request = networkRequest(userRequest);
+
+    InternalCache responseCache = Internal.instance.internalCache(client);
+    Response cacheCandidate = responseCache != null
+        ? responseCache.get(request)
+        : null;
+
+    long now = System.currentTimeMillis();
+    cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
+    networkRequest = cacheStrategy.networkRequest;
+    cacheResponse = cacheStrategy.cacheResponse;
+
+    if (responseCache != null) {
+      responseCache.trackResponse(cacheStrategy);
+    }
+
+    if (cacheCandidate != null && cacheResponse == null) {
+      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
+    }
+
+    if (networkRequest != null) {
+      httpStream = connect();
+      httpStream.setHttpEngine(this);
+
+      // If the caller's control flow writes the request body, we need to create that stream
+      // immediately. And that means we need to immediately write the request headers, so we can
+      // start streaming the request body. (We may already have a request body if we're retrying a
+      // failed POST.)
+      if (callerWritesRequestBody && permitsRequestBody(networkRequest) && requestBodyOut == null) {
+        long contentLength = OkHeaders.contentLength(request);
+        if (bufferRequestBody) {
+          if (contentLength > Integer.MAX_VALUE) {
+            throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
+                + "setChunkedStreamingMode() for requests larger than 2 GiB.");
+          }
+
+          if (contentLength != -1) {
+            // Buffer a request body of a known length.
+            httpStream.writeRequestHeaders(networkRequest);
+            requestBodyOut = new RetryableSink((int) contentLength);
+          } else {
+            // Buffer a request body of an unknown length. Don't write request
+            // headers until the entire body is ready; otherwise we can't set the
+            // Content-Length header correctly.
+            requestBodyOut = new RetryableSink();
+          }
+        } else {
+          httpStream.writeRequestHeaders(networkRequest);
+          requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
+        }
+      }
+
+    } else {
+      if (cacheResponse != null) {
+        // We have a valid cached response. Promote it to the user response immediately.
+        this.userResponse = cacheResponse.newBuilder()
+            .request(userRequest)
+            .priorResponse(stripBody(priorResponse))
+            .cacheResponse(stripBody(cacheResponse))
+            .build();
+      } else {
+        // We're forbidden from using the network, and the cache is insufficient.
+        this.userResponse = new Response.Builder()
+            .request(userRequest)
+            .priorResponse(stripBody(priorResponse))
+            .protocol(Protocol.HTTP_1_1)
+            .code(504)
+            .message("Unsatisfiable Request (only-if-cached)")
+            .body(EMPTY_BODY)
+            .build();
+      }
+
+      userResponse = unzip(userResponse);
+    }
+  }
+
+  private HttpStream connect() throws RouteException, RequestException, IOException {
+    boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET");
+    return streamAllocation.newStream(client.getConnectTimeout(),
+        client.getReadTimeout(), client.getWriteTimeout(),
+        client.getRetryOnConnectionFailure(), doExtensiveHealthChecks);
+  }
+
+  private static Response stripBody(Response response) {
+    return response != null && response.body() != null
+        ? response.newBuilder().body(null).build()
+        : response;
+  }
+
+
+  /**
+   * Called immediately before the transport transmits HTTP request headers.
+   * This is used to observe the sent time should the request be cached.
+   */
+  public void writingRequestHeaders() {
+    if (sentRequestMillis != -1) throw new IllegalStateException();
+    sentRequestMillis = System.currentTimeMillis();
+  }
+
+  boolean permitsRequestBody(Request request) {
+    return HttpMethod.permitsRequestBody(request.method());
+  }
+
+  /** Returns the request body or null if this request doesn't have a body. */
+  public Sink getRequestBody() {
+    if (cacheStrategy == null) throw new IllegalStateException();
+    return requestBodyOut;
+  }
+
+  public BufferedSink getBufferedRequestBody() {
+    BufferedSink result = bufferedRequestBody;
+    if (result != null) return result;
+    Sink requestBody = getRequestBody();
+    return requestBody != null
+        ? (bufferedRequestBody = Okio.buffer(requestBody))
+        : null;
+  }
+
+  public boolean hasResponse() {
+    return userResponse != null;
+  }
+
+  public Request getRequest() {
+    return userRequest;
+  }
+
+  /** Returns the engine's response. */
+  // TODO: the returned body will always be null.
+  public Response getResponse() {
+    if (userResponse == null) throw new IllegalStateException();
+    return userResponse;
+  }
+
+  public Connection getConnection() {
+    return streamAllocation.connection();
+  }
+
+  /**
+   * Attempt to recover from failure to connect via a route. Returns a new HTTP engine
+   * that should be used for the retry if there are other routes to try, or null if
+   * there are no more routes to try.
+   */
+  public HttpEngine recover(RouteException e) {
+    if (!streamAllocation.recover(e)) {
+      return null;
+    }
+
+    if (!client.getRetryOnConnectionFailure()) {
+      return null;
+    }
+
+    StreamAllocation streamAllocation = close();
+
+    // For failure recovery, use the same route selector with a new connection.
+    return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
+        forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse);
+  }
+
+  /**
+   * Report and attempt to recover from a failure to communicate with a server. Returns a new
+   * HTTP engine that should be used for the retry if {@code e} is recoverable, or null if
+   * the failure is permanent. Requests with a body can only be recovered if the
+   * body is buffered.
+   */
+  public HttpEngine recover(IOException e, Sink requestBodyOut) {
+    if (!streamAllocation.recover(e, requestBodyOut)) {
+      return null;
+    }
+
+    if (!client.getRetryOnConnectionFailure()) {
+      return null;
+    }
+
+    StreamAllocation streamAllocation = close();
+
+    // For failure recovery, use the same route selector with a new connection.
+    return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
+        forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse);
+  }
+
+  public HttpEngine recover(IOException e) {
+    return recover(e, requestBodyOut);
+  }
+
+  private void maybeCache() throws IOException {
+    InternalCache responseCache = Internal.instance.internalCache(client);
+    if (responseCache == null) return;
+
+    // Should we cache this response for this request?
+    if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
+      if (HttpMethod.invalidatesCache(networkRequest.method())) {
+        try {
+          responseCache.remove(networkRequest);
+        } catch (IOException ignored) {
+          // The cache cannot be written.
+        }
+      }
+      return;
+    }
+
+    // Offer this request to the cache.
+    storeRequest = responseCache.put(stripBody(userResponse));
+  }
+
+  /**
+   * Configure the socket connection to be either pooled or closed when it is
+   * either exhausted or closed. If it is unneeded when this is called, it will
+   * be released immediately.
+   */
+  public void releaseStreamAllocation() throws IOException {
+    streamAllocation.release();
+  }
+
+  /**
+   * Immediately closes the socket connection if it's currently held by this engine. Use this to
+   * interrupt an in-flight request from any thread. It's the caller's responsibility to close the
+   * request body and response body streams; otherwise resources may be leaked.
+   *
+   * <p>This method is safe to be called concurrently, but provides limited guarantees. If a
+   * transport layer connection has been established (such as a HTTP/2 stream) that is terminated.
+   * Otherwise if a socket connection is being established, that is terminated.
+   */
+  public void cancel() {
+    streamAllocation.cancel();
+  }
+
+  /**
+   * Release any resources held by this engine. Returns the stream allocation held by this engine,
+   * which itself must be used or released.
+   */
+  public StreamAllocation close() {
+    if (bufferedRequestBody != null) {
+      // This also closes the wrapped requestBodyOut.
+      closeQuietly(bufferedRequestBody);
+    } else if (requestBodyOut != null) {
+      closeQuietly(requestBodyOut);
+    }
+
+    if (userResponse != null) {
+      closeQuietly(userResponse.body());
+    } else {
+      // If this engine never achieved a response body, its stream allocation is dead.
+      streamAllocation.connectionFailed();
+    }
+
+    return streamAllocation;
+  }
+
+  /**
+   * Returns a new response that does gzip decompression on {@code response}, if transparent gzip
+   * was both offered by OkHttp and used by the origin server.
+   *
+   * <p>In addition to decompression, this will also strip the corresponding headers. We strip the
+   * Content-Encoding header to prevent the application from attempting to double decompress. We
+   * strip the Content-Length header because it is the length of the compressed content, but the
+   * application is only interested in the length of the uncompressed content.
+   *
+   * <p>This method should only be used for non-empty response bodies. Response codes like "304 Not
+   * Modified" can include "Content-Encoding: gzip" without a response body and we will crash if we
+   * attempt to decompress the zero-byte source.
+   */
+  private Response unzip(final Response response) throws IOException {
+    if (!transparentGzip || !"gzip".equalsIgnoreCase(userResponse.header("Content-Encoding"))) {
+      return response;
+    }
+
+    if (response.body() == null) {
+      return response;
+    }
+
+    GzipSource responseBody = new GzipSource(response.body().source());
+    Headers strippedHeaders = response.headers().newBuilder()
+        .removeAll("Content-Encoding")
+        .removeAll("Content-Length")
+        .build();
+    return response.newBuilder()
+        .headers(strippedHeaders)
+        .body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)))
+        .build();
+  }
+
+  /**
+   * Returns true if the response must have a (possibly 0-length) body.
+   * See RFC 2616 section 4.3.
+   */
+  public static boolean hasBody(Response response) {
+    // HEAD requests never yield a body regardless of the response headers.
+    if (response.request().method().equals("HEAD")) {
+      return false;
+    }
+
+    int responseCode = response.code();
+    if ((responseCode < HTTP_CONTINUE || responseCode >= 200)
+        && responseCode != HTTP_NO_CONTENT
+        && responseCode != HTTP_NOT_MODIFIED) {
+      return true;
+    }
+
+    // If the Content-Length or Transfer-Encoding headers disagree with the
+    // response code, the response is malformed. For best compatibility, we
+    // honor the headers.
+    if (OkHeaders.contentLength(response) != -1
+        || "chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Populates request with defaults and cookies.
+   *
+   * <p>This client doesn't specify a default {@code Accept} header because it
+   * doesn't know what content types the application is interested in.
+   */
+  private Request networkRequest(Request request) throws IOException {
+    Request.Builder result = request.newBuilder();
+
+    if (request.header("Host") == null) {
+      result.header("Host", Util.hostHeader(request.httpUrl(), false));
+    }
+
+    if (request.header("Connection") == null) {
+      result.header("Connection", "Keep-Alive");
+    }
+
+    if (request.header("Accept-Encoding") == null) {
+      transparentGzip = true;
+      result.header("Accept-Encoding", "gzip");
+    }
+
+    CookieHandler cookieHandler = client.getCookieHandler();
+    if (cookieHandler != null) {
+      // Capture the request headers added so far so that they can be offered to the CookieHandler.
+      // This is mostly to stay close to the RI; it is unlikely any of the headers above would
+      // affect cookie choice besides "Host".
+      Map<String, List<String>> headers = OkHeaders.toMultimap(result.build().headers(), null);
+
+      Map<String, List<String>> cookies = cookieHandler.get(request.uri(), headers);
+
+      // Add any new cookies to the request.
+      OkHeaders.addCookies(result, cookies);
+    }
+
+    if (request.header("User-Agent") == null) {
+      result.header("User-Agent", Version.userAgent());
+    }
+
+    return result.build();
+  }
+
+  /**
+   * Flushes the remaining request header and body, parses the HTTP response
+   * headers and starts reading the HTTP response body if it exists.
+   */
+  public void readResponse() throws IOException {
+    if (userResponse != null) {
+      return; // Already ready.
+    }
+    if (networkRequest == null && cacheResponse == null) {
+      throw new IllegalStateException("call sendRequest() first!");
+    }
+    if (networkRequest == null) {
+      return; // No network response to read.
+    }
+
+    Response networkResponse;
+
+    if (forWebSocket) {
+      httpStream.writeRequestHeaders(networkRequest);
+      networkResponse = readNetworkResponse();
+
+    } else if (!callerWritesRequestBody) {
+      networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
+
+    } else {
+      // Emit the request body's buffer so that everything is in requestBodyOut.
+      if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
+        bufferedRequestBody.emit();
+      }
+
+      // Emit the request headers if we haven't yet. We might have just learned the Content-Length.
+      if (sentRequestMillis == -1) {
+        if (OkHeaders.contentLength(networkRequest) == -1
+            && requestBodyOut instanceof RetryableSink) {
+          long contentLength = ((RetryableSink) requestBodyOut).contentLength();
+          networkRequest = networkRequest.newBuilder()
+              .header("Content-Length", Long.toString(contentLength))
+              .build();
+        }
+        httpStream.writeRequestHeaders(networkRequest);
+      }
+
+      // Write the request body to the socket.
+      if (requestBodyOut != null) {
+        if (bufferedRequestBody != null) {
+          // This also closes the wrapped requestBodyOut.
+          bufferedRequestBody.close();
+        } else {
+          requestBodyOut.close();
+        }
+        if (requestBodyOut instanceof RetryableSink) {
+          httpStream.writeRequestBody((RetryableSink) requestBodyOut);
+        }
+      }
+
+      networkResponse = readNetworkResponse();
+    }
+
+    receiveHeaders(networkResponse.headers());
+
+    // If we have a cache response too, then we're doing a conditional get.
+    if (cacheResponse != null) {
+      if (validate(cacheResponse, networkResponse)) {
+        userResponse = cacheResponse.newBuilder()
+            .request(userRequest)
+            .priorResponse(stripBody(priorResponse))
+            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
+            .cacheResponse(stripBody(cacheResponse))
+            .networkResponse(stripBody(networkResponse))
+            .build();
+        networkResponse.body().close();
+        releaseStreamAllocation();
+
+        // Update the cache after combining headers but before stripping the
+        // Content-Encoding header (as performed by initContentStream()).
+        InternalCache responseCache = Internal.instance.internalCache(client);
+        responseCache.trackConditionalCacheHit();
+        responseCache.update(cacheResponse, stripBody(userResponse));
+        userResponse = unzip(userResponse);
+        return;
+      } else {
+        closeQuietly(cacheResponse.body());
+      }
+    }
+
+    userResponse = networkResponse.newBuilder()
+        .request(userRequest)
+        .priorResponse(stripBody(priorResponse))
+        .cacheResponse(stripBody(cacheResponse))
+        .networkResponse(stripBody(networkResponse))
+        .build();
+
+    if (hasBody(userResponse)) {
+      maybeCache();
+      userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
+    }
+  }
+
+  class NetworkInterceptorChain implements Interceptor.Chain {
+    private final int index;
+    private final Request request;
+    private int calls;
+
+    NetworkInterceptorChain(int index, Request request) {
+      this.index = index;
+      this.request = request;
+    }
+
+    @Override public Connection connection() {
+      return streamAllocation.connection();
+    }
+
+    @Override public Request request() {
+      return request;
+    }
+
+    @Override public Response proceed(Request request) throws IOException {
+      calls++;
+
+      if (index > 0) {
+        Interceptor caller = client.networkInterceptors().get(index - 1);
+        Address address = connection().getRoute().getAddress();
+
+        // Confirm that the interceptor uses the connection we've already prepared.
+        if (!request.httpUrl().host().equals(address.getUriHost())
+            || request.httpUrl().port() != address.getUriPort()) {
+          throw new IllegalStateException("network interceptor " + caller
+              + " must retain the same host and port");
+        }
+
+        // Confirm that this is the interceptor's first call to chain.proceed().
+        if (calls > 1) {
+          throw new IllegalStateException("network interceptor " + caller
+              + " must call proceed() exactly once");
+        }
+      }
+
+      if (index < client.networkInterceptors().size()) {
+        // There's another interceptor in the chain. Call that.
+        NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request);
+        Interceptor interceptor = client.networkInterceptors().get(index);
+        Response interceptedResponse = interceptor.intercept(chain);
+
+        // Confirm that the interceptor made the required call to chain.proceed().
+        if (chain.calls != 1) {
+          throw new IllegalStateException("network interceptor " + interceptor
+              + " must call proceed() exactly once");
+        }
+        if (interceptedResponse == null) {
+          throw new NullPointerException("network interceptor " + interceptor
+              + " returned null");
+        }
+
+        return interceptedResponse;
+      }
+
+      httpStream.writeRequestHeaders(request);
+
+      //Update the networkRequest with the possibly updated interceptor request.
+      networkRequest = request;
+
+      if (permitsRequestBody(request) && request.body() != null) {
+        Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
+        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
+        request.body().writeTo(bufferedRequestBody);
+        bufferedRequestBody.close();
+      }
+
+      Response response = readNetworkResponse();
+
+      int code = response.code();
+      if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
+        throw new ProtocolException(
+            "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
+      }
+
+      return response;
+    }
+  }
+
+  private Response readNetworkResponse() throws IOException {
+    httpStream.finishRequest();
+
+    Response networkResponse = httpStream.readResponseHeaders()
+        .request(networkRequest)
+        .handshake(streamAllocation.connection().getHandshake())
+        .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
+        .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
+        .build();
+
+    if (!forWebSocket) {
+      networkResponse = networkResponse.newBuilder()
+          .body(httpStream.openResponseBody(networkResponse))
+          .build();
+    }
+
+    if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))
+        || "close".equalsIgnoreCase(networkResponse.header("Connection"))) {
+      streamAllocation.noNewStreams();
+    }
+
+    return networkResponse;
+  }
+
+  /**
+   * Returns a new source that writes bytes to {@code cacheRequest} as they are read by the source
+   * consumer. This is careful to discard bytes left over when the stream is closed; otherwise we
+   * may never exhaust the source stream and therefore not complete the cached response.
+   */
+  private Response cacheWritingResponse(final CacheRequest cacheRequest, Response response)
+      throws IOException {
+    // Some apps return a null body; for compatibility we treat that like a null cache request.
+    if (cacheRequest == null) return response;
+    Sink cacheBodyUnbuffered = cacheRequest.body();
+    if (cacheBodyUnbuffered == null) return response;
+
+    final BufferedSource source = response.body().source();
+    final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);
+
+    Source cacheWritingSource = new Source() {
+      boolean cacheRequestClosed;
+
+      @Override public long read(Buffer sink, long byteCount) throws IOException {
+        long bytesRead;
+        try {
+          bytesRead = source.read(sink, byteCount);
+        } catch (IOException e) {
+          if (!cacheRequestClosed) {
+            cacheRequestClosed = true;
+            cacheRequest.abort(); // Failed to write a complete cache response.
+          }
+          throw e;
+        }
+
+        if (bytesRead == -1) {
+          if (!cacheRequestClosed) {
+            cacheRequestClosed = true;
+            cacheBody.close(); // The cache response is complete!
+          }
+          return -1;
+        }
+
+        sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);
+        cacheBody.emitCompleteSegments();
+        return bytesRead;
+      }
+
+      @Override public Timeout timeout() {
+        return source.timeout();
+      }
+
+      @Override public void close() throws IOException {
+        if (!cacheRequestClosed
+            && !Util.discard(this, HttpStream.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
+          cacheRequestClosed = true;
+          cacheRequest.abort();
+        }
+        source.close();
+      }
+    };
+
+    return response.newBuilder()
+        .body(new RealResponseBody(response.headers(), Okio.buffer(cacheWritingSource)))
+        .build();
+  }
+
+  /**
+   * Returns true if {@code cached} should be used; false if {@code network}
+   * response should be used.
+   */
+  private static boolean validate(Response cached, Response network) {
+    if (network.code() == HTTP_NOT_MODIFIED) {
+      return true;
+    }
+
+    // The HTTP spec says that if the network's response is older than our
+    // cached response, we may return the cache's response. Like Chrome (but
+    // unlike Firefox), this client prefers to return the newer response.
+    Date lastModified = cached.headers().getDate("Last-Modified");
+    if (lastModified != null) {
+      Date networkLastModified = network.headers().getDate("Last-Modified");
+      if (networkLastModified != null
+          && networkLastModified.getTime() < lastModified.getTime()) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Combines cached headers with a network headers as defined by RFC 2616,
+   * 13.5.3.
+   */
+  private static Headers combine(Headers cachedHeaders, Headers networkHeaders) throws IOException {
+    Headers.Builder result = new Headers.Builder();
+
+    for (int i = 0, size = cachedHeaders.size(); i < size; i++) {
+      String fieldName = cachedHeaders.name(i);
+      String value = cachedHeaders.value(i);
+      if ("Warning".equalsIgnoreCase(fieldName) && value.startsWith("1")) {
+        continue; // Drop 100-level freshness warnings.
+      }
+      if (!OkHeaders.isEndToEnd(fieldName) || networkHeaders.get(fieldName) == null) {
+        result.add(fieldName, value);
+      }
+    }
+
+    for (int i = 0, size = networkHeaders.size(); i < size; i++) {
+      String fieldName = networkHeaders.name(i);
+      if ("Content-Length".equalsIgnoreCase(fieldName)) {
+        continue; // Ignore content-length headers of validating responses.
+      }
+      if (OkHeaders.isEndToEnd(fieldName)) {
+        result.add(fieldName, networkHeaders.value(i));
+      }
+    }
+
+    return result.build();
+  }
+
+  public void receiveHeaders(Headers headers) throws IOException {
+    CookieHandler cookieHandler = client.getCookieHandler();
+    if (cookieHandler != null) {
+      cookieHandler.put(userRequest.uri(), OkHeaders.toMultimap(headers, null));
+    }
+  }
+
+  /**
+   * Figures out the HTTP request to make in response to receiving this engine's
+   * response. This will either add authentication headers or follow redirects.
+   * If a follow-up is either unnecessary or not applicable, this returns null.
+   */
+  public Request followUpRequest() throws IOException {
+    if (userResponse == null) throw new IllegalStateException();
+    Connection connection = streamAllocation.connection();
+    Route route = connection != null
+        ? connection.getRoute()
+        : null;
+    Proxy selectedProxy = route != null
+        ? route.getProxy()
+        : client.getProxy();
+    int responseCode = userResponse.code();
+
+    final String method = userRequest.method();
+    switch (responseCode) {
+      case HTTP_PROXY_AUTH:
+        if (selectedProxy.type() != Proxy.Type.HTTP) {
+          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
+        }
+        // fall-through
+      case HTTP_UNAUTHORIZED:
+        return OkHeaders.processAuthHeader(client.getAuthenticator(), userResponse, selectedProxy);
+
+      case HTTP_PERM_REDIRECT:
+      case HTTP_TEMP_REDIRECT:
+        // "If the 307 or 308 status code is received in response to a request other than GET
+        // or HEAD, the user agent MUST NOT automatically redirect the request"
+        if (!method.equals("GET") && !method.equals("HEAD")) {
+            return null;
+        }
+        // fall-through
+      case HTTP_MULT_CHOICE:
+      case HTTP_MOVED_PERM:
+      case HTTP_MOVED_TEMP:
+      case HTTP_SEE_OTHER:
+        // Does the client allow redirects?
+        if (!client.getFollowRedirects()) return null;
+
+        String location = userResponse.header("Location");
+        if (location == null) return null;
+        HttpUrl url = userRequest.httpUrl().resolve(location);
+
+        // Don't follow redirects to unsupported protocols.
+        if (url == null) return null;
+
+        // If configured, don't follow redirects between SSL and non-SSL.
+        boolean sameScheme = url.scheme().equals(userRequest.httpUrl().scheme());
+        if (!sameScheme && !client.getFollowSslRedirects()) return null;
+
+        // Redirects don't include a request body.
+        Request.Builder requestBuilder = userRequest.newBuilder();
+        if (HttpMethod.permitsRequestBody(method)) {
+          if (HttpMethod.redirectsToGet(method)) {
+            requestBuilder.method("GET", null);
+          } else {
+            requestBuilder.method(method, null);
+          }
+          requestBuilder.removeHeader("Transfer-Encoding");
+          requestBuilder.removeHeader("Content-Length");
+          requestBuilder.removeHeader("Content-Type");
+        }
+
+        // When redirecting across hosts, drop all authentication headers. This
+        // is potentially annoying to the application layer since they have no
+        // way to retain them.
+        if (!sameConnection(url)) {
+          requestBuilder.removeHeader("Authorization");
+        }
+
+        return requestBuilder.url(url).build();
+
+      default:
+        return null;
+    }
+  }
+
+  /**
+   * Returns true if an HTTP request for {@code followUp} can reuse the
+   * connection used by this engine.
+   */
+  public boolean sameConnection(HttpUrl followUp) {
+    HttpUrl url = userRequest.httpUrl();
+    return url.host().equals(followUp.host())
+        && url.port() == followUp.port()
+        && url.scheme().equals(followUp.scheme());
+  }
+
+  private static Address createAddress(OkHttpClient client, Request request) {
+    SSLSocketFactory sslSocketFactory = null;
+    HostnameVerifier hostnameVerifier = null;
+    CertificatePinner certificatePinner = null;
+    if (request.isHttps()) {
+      sslSocketFactory = client.getSslSocketFactory();
+      hostnameVerifier = client.getHostnameVerifier();
+      certificatePinner = client.getCertificatePinner();
+    }
+
+    return new Address(request.httpUrl().host(), request.httpUrl().port(), client.getDns(),
+        client.getSocketFactory(), sslSocketFactory, hostnameVerifier, certificatePinner,
+        client.getAuthenticator(), client.getProxy(), client.getProtocols(),
+        client.getConnectionSpecs(), client.getProxySelector());
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpMethod.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpMethod.java
new file mode 100644
index 0000000..5bd7b48
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpMethod.java
@@ -0,0 +1,55 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.http;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class HttpMethod {
+  public static boolean invalidatesCache(String method) {
+    return method.equals("POST")
+        || method.equals("PATCH")
+        || method.equals("PUT")
+        || method.equals("DELETE")
+        || method.equals("MOVE");     // WebDAV
+  }
+
+  public static boolean requiresRequestBody(String method) {
+    return method.equals("POST")
+        || method.equals("PUT")
+        || method.equals("PATCH")
+        || method.equals("PROPPATCH") // WebDAV
+        || method.equals("REPORT");   // CalDAV/CardDAV (defined in WebDAV Versioning)
+  }
+
+  public static boolean permitsRequestBody(String method) {
+    return requiresRequestBody(method)
+        || method.equals("OPTIONS")
+        || method.equals("DELETE")    // Permitted as spec is ambiguous.
+        || method.equals("PROPFIND")  // (WebDAV) without body: request <allprop/>
+        || method.equals("MKCOL")     // (WebDAV) may contain a body, but behaviour is unspecified
+        || method.equals("LOCK");     // (WebDAV) body: create lock, without body: refresh lock
+  }
+
+  public static boolean redirectsToGet(String method) {
+    // All requests but PROPFIND should redirect to a GET request.
+    return !method.equals("PROPFIND");
+  }
+
+  private HttpMethod() {
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpStream.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpStream.java
new file mode 100644
index 0000000..ccc026d
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/HttpStream.java
@@ -0,0 +1,65 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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 com.android.okhttp.internal.http;
+
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.ResponseBody;
+import java.io.IOException;
+import com.android.okhttp.okio.Sink;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface HttpStream {
+  /**
+   * The timeout to use while discarding a stream of input data. Since this is
+   * used for connection reuse, this timeout should be significantly less than
+   * the time it takes to establish a new connection.
+   */
+  int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
+
+  /** Returns an output stream where the request body can be streamed. */
+  Sink createRequestBody(Request request, long contentLength) throws IOException;
+
+  /** This should update the HTTP engine's sentRequestMillis field. */
+  void writeRequestHeaders(Request request) throws IOException;
+
+  /**
+   * Sends the request body returned by {@link #createRequestBody} to the
+   * remote peer.
+   */
+  void writeRequestBody(RetryableSink requestBody) throws IOException;
+
+  /** Flush the request to the underlying socket. */
+  void finishRequest() throws IOException;
+
+  /** Read and return response headers. */
+  Response.Builder readResponseHeaders() throws IOException;
+
+  /** Returns a stream that reads the response body. */
+  ResponseBody openResponseBody(Response response) throws IOException;
+
+  void setHttpEngine(HttpEngine httpEngine);
+
+  /**
+   * Cancel this stream. Resources held by this stream will be cleaned up, though not synchronously.
+   * That may happen later by the connection pool thread.
+   */
+  void cancel();
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/OkHeaders.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/OkHeaders.java
new file mode 100644
index 0000000..f192dcb
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/OkHeaders.java
@@ -0,0 +1,292 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal.http;
+
+import com.android.okhttp.Authenticator;
+import com.android.okhttp.Challenge;
+import com.android.okhttp.Headers;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.internal.Platform;
+import java.io.IOException;
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import static com.android.okhttp.internal.Util.equal;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+
+/** Headers and utilities for internal use by OkHttp. 
+ * @hide This class is not part of the Android public SDK API*/
+public final class OkHeaders {
+  private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() {
+    // @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
+    @Override public int compare(String a, String b) {
+      if (a == b) {
+        return 0;
+      } else if (a == null) {
+        return -1;
+      } else if (b == null) {
+        return 1;
+      } else {
+        return String.CASE_INSENSITIVE_ORDER.compare(a, b);
+      }
+    }
+  };
+
+  static final String PREFIX = Platform.get().getPrefix();
+
+  /**
+   * Synthetic response header: the local time when the request was sent.
+   */
+  public static final String SENT_MILLIS = PREFIX + "-Sent-Millis";
+
+  /**
+   * Synthetic response header: the local time when the response was received.
+   */
+  public static final String RECEIVED_MILLIS = PREFIX + "-Received-Millis";
+
+  /**
+   * Synthetic response header: the selected
+   * {@link com.android.okhttp.Protocol protocol} ("spdy/3.1", "http/1.1", etc).
+   */
+  public static final String SELECTED_PROTOCOL = PREFIX + "-Selected-Protocol";
+
+  /** Synthetic response header: the location from which the response was loaded. */
+  public static final String RESPONSE_SOURCE = PREFIX + "-Response-Source";
+
+  private OkHeaders() {
+  }
+
+  public static long contentLength(Request request) {
+    return contentLength(request.headers());
+  }
+
+  public static long contentLength(Response response) {
+    return contentLength(response.headers());
+  }
+
+  public static long contentLength(Headers headers) {
+    return stringToLong(headers.get("Content-Length"));
+  }
+
+  private static long stringToLong(String s) {
+    if (s == null) return -1;
+    try {
+      return Long.parseLong(s);
+    } catch (NumberFormatException e) {
+      return -1;
+    }
+  }
+
+  /**
+   * Returns an immutable map containing each field to its list of values.
+   *
+   * @param valueForNullKey the request line for requests, or the status line
+   *     for responses. If non-null, this value is mapped to the null key.
+   */
+  public static Map<String, List<String>> toMultimap(Headers headers, String valueForNullKey) {
+    Map<String, List<String>> result = new TreeMap<>(FIELD_NAME_COMPARATOR);
+    for (int i = 0, size = headers.size(); i < size; i++) {
+      String fieldName = headers.name(i);
+      String value = headers.value(i);
+
+      List<String> allValues = new ArrayList<>();
+      List<String> otherValues = result.get(fieldName);
+      if (otherValues != null) {
+        allValues.addAll(otherValues);
+      }
+      allValues.add(value);
+      result.put(fieldName, Collections.unmodifiableList(allValues));
+    }
+    if (valueForNullKey != null) {
+      result.put(null, Collections.unmodifiableList(Collections.singletonList(valueForNullKey)));
+    }
+    return Collections.unmodifiableMap(result);
+  }
+
+  public static void addCookies(Request.Builder builder, Map<String, List<String>> cookieHeaders) {
+    for (Map.Entry<String, List<String>> entry : cookieHeaders.entrySet()) {
+      String key = entry.getKey();
+      if (("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key))
+          && !entry.getValue().isEmpty()) {
+        builder.addHeader(key, buildCookieHeader(entry.getValue()));
+      }
+    }
+  }
+
+  /**
+   * Send all cookies in one big header, as recommended by
+   * <a href="http://tools.ietf.org/html/rfc6265#section-4.2.1">RFC 6265</a>.
+   */
+  private static String buildCookieHeader(List<String> cookies) {
+    if (cookies.size() == 1) return cookies.get(0);
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0, size = cookies.size(); i < size; i++) {
+      if (i > 0) sb.append("; ");
+      sb.append(cookies.get(i));
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Returns true if none of the Vary headers have changed between {@code
+   * cachedRequest} and {@code newRequest}.
+   */
+  public static boolean varyMatches(
+      Response cachedResponse, Headers cachedRequest, Request newRequest) {
+    for (String field : varyFields(cachedResponse)) {
+      if (!equal(cachedRequest.values(field), newRequest.headers(field))) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns true if a Vary header contains an asterisk. Such responses cannot
+   * be cached.
+   */
+  public static boolean hasVaryAll(Response response) {
+    return hasVaryAll(response.headers());
+  }
+
+  /**
+   * Returns true if a Vary header contains an asterisk. Such responses cannot
+   * be cached.
+   */
+  public static boolean hasVaryAll(Headers responseHeaders) {
+    return varyFields(responseHeaders).contains("*");
+  }
+
+  private static Set<String> varyFields(Response response) {
+    return varyFields(response.headers());
+  }
+
+  /**
+   * Returns the names of the request headers that need to be checked for
+   * equality when caching.
+   */
+  public static Set<String> varyFields(Headers responseHeaders) {
+    Set<String> result = Collections.emptySet();
+    for (int i = 0, size = responseHeaders.size(); i < size; i++) {
+      if (!"Vary".equalsIgnoreCase(responseHeaders.name(i))) continue;
+
+      String value = responseHeaders.value(i);
+      if (result.isEmpty()) {
+        result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+      }
+      for (String varyField : value.split(",")) {
+        result.add(varyField.trim());
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns the subset of the headers in {@code response}'s request that
+   * impact the content of response's body.
+   */
+  public static Headers varyHeaders(Response response) {
+    // Use the request headers sent over the network, since that's what the
+    // response varies on. Otherwise OkHttp-supplied headers like
+    // "Accept-Encoding: gzip" may be lost.
+    Headers requestHeaders = response.networkResponse().request().headers();
+    Headers responseHeaders = response.headers();
+    return varyHeaders(requestHeaders, responseHeaders);
+  }
+
+  /**
+   * Returns the subset of the headers in {@code requestHeaders} that
+   * impact the content of response's body.
+   */
+  public static Headers varyHeaders(Headers requestHeaders, Headers responseHeaders) {
+    Set<String> varyFields = varyFields(responseHeaders);
+    if (varyFields.isEmpty()) return new Headers.Builder().build();
+
+    Headers.Builder result = new Headers.Builder();
+    for (int i = 0, size = requestHeaders.size(); i < size; i++) {
+      String fieldName = requestHeaders.name(i);
+      if (varyFields.contains(fieldName)) {
+        result.add(fieldName, requestHeaders.value(i));
+      }
+    }
+    return result.build();
+  }
+
+  /**
+   * Returns true if {@code fieldName} is an end-to-end HTTP header, as
+   * defined by RFC 2616, 13.5.1.
+   */
+  static boolean isEndToEnd(String fieldName) {
+    return !"Connection".equalsIgnoreCase(fieldName)
+        && !"Keep-Alive".equalsIgnoreCase(fieldName)
+        && !"Proxy-Authenticate".equalsIgnoreCase(fieldName)
+        && !"Proxy-Authorization".equalsIgnoreCase(fieldName)
+        && !"TE".equalsIgnoreCase(fieldName)
+        && !"Trailers".equalsIgnoreCase(fieldName)
+        && !"Transfer-Encoding".equalsIgnoreCase(fieldName)
+        && !"Upgrade".equalsIgnoreCase(fieldName);
+  }
+
+  /**
+   * Parse RFC 2617 challenges. This API is only interested in the scheme
+   * name and realm.
+   */
+  public static List<Challenge> parseChallenges(Headers responseHeaders, String challengeHeader) {
+    // auth-scheme = token
+    // auth-param  = token "=" ( token | quoted-string )
+    // challenge   = auth-scheme 1*SP 1#auth-param
+    // realm       = "realm" "=" realm-value
+    // realm-value = quoted-string
+    List<Challenge> result = new ArrayList<>();
+    for (int i = 0, size = responseHeaders.size(); i < size; i++) {
+      if (!challengeHeader.equalsIgnoreCase(responseHeaders.name(i))) {
+        continue;
+      }
+      String value = responseHeaders.value(i);
+      int pos = 0;
+      while (pos < value.length()) {
+        int tokenStart = pos;
+        pos = HeaderParser.skipUntil(value, pos, " ");
+
+        String scheme = value.substring(tokenStart, pos).trim();
+        pos = HeaderParser.skipWhitespace(value, pos);
+
+        // TODO: This currently only handles schemes with a 'realm' parameter;
+        //       It needs to be fixed to handle any scheme and any parameters
+        //       http://code.google.com/p/android/issues/detail?id=11140
+
+        if (!value.regionMatches(true, pos, "realm=\"", 0, "realm=\"".length())) {
+          break; // Unexpected challenge parameter; give up!
+        }
+
+        pos += "realm=\"".length();
+        int realmStart = pos;
+        pos = HeaderParser.skipUntil(value, pos, "\"");
+        String realm = value.substring(realmStart, pos);
+        pos++; // Consume '"' close quote.
+        pos = HeaderParser.skipUntil(value, pos, ",");
+        pos++; // Consume ',' comma.
+        pos = HeaderParser.skipWhitespace(value, pos);
+        result.add(new Challenge(scheme, realm));
+      }
+    }
+    return result;
+  }
+
+  /**
+   * React to a failed authorization response by looking up new credentials.
+   * Returns a request for a subsequent attempt, or null if no further attempts
+   * should be made.
+   */
+  public static Request processAuthHeader(Authenticator authenticator, Response response,
+      Proxy proxy) throws IOException {
+    return response.code() == HTTP_PROXY_AUTH
+        ? authenticator.authenticateProxy(proxy, response)
+        : authenticator.authenticate(proxy, response);
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RealResponseBody.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RealResponseBody.java
new file mode 100644
index 0000000..5bd4840
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RealResponseBody.java
@@ -0,0 +1,48 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.http;
+
+import com.android.okhttp.Headers;
+import com.android.okhttp.MediaType;
+import com.android.okhttp.ResponseBody;
+import com.android.okhttp.okio.BufferedSource;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RealResponseBody extends ResponseBody {
+  private final Headers headers;
+  private final BufferedSource source;
+
+  public RealResponseBody(Headers headers, BufferedSource source) {
+    this.headers = headers;
+    this.source = source;
+  }
+
+  @Override public MediaType contentType() {
+    String contentType = headers.get("Content-Type");
+    return contentType != null ? MediaType.parse(contentType) : null;
+  }
+
+  @Override public long contentLength() {
+    return OkHeaders.contentLength(headers);
+  }
+
+  @Override public BufferedSource source() {
+    return source;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestException.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestException.java
new file mode 100644
index 0000000..729b8d5
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestException.java
@@ -0,0 +1,36 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.http;
+
+import java.io.IOException;
+
+/**
+ * Indicates a problem with interpreting a request. It may indicate there was a problem with the
+ * request itself, or the environment being used to interpret the request (network failure, etc.).
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RequestException extends Exception {
+
+  public RequestException(IOException cause) {
+    super(cause);
+  }
+
+  @Override
+  public IOException getCause() {
+    return (IOException) super.getCause();
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestLine.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestLine.java
new file mode 100644
index 0000000..fcfda0b
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RequestLine.java
@@ -0,0 +1,54 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal.http;
+
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.Request;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RequestLine {
+  private RequestLine() {
+  }
+
+  /**
+   * Returns the request status line, like "GET / HTTP/1.1". This is exposed
+   * to the application by {@link HttpURLConnection#getHeaderFields}, so it
+   * needs to be set even if the transport is SPDY.
+   */
+  static String get(Request request, Proxy.Type proxyType) {
+    StringBuilder result = new StringBuilder();
+    result.append(request.method());
+    result.append(' ');
+
+    if (includeAuthorityInRequestLine(request, proxyType)) {
+      result.append(request.httpUrl());
+    } else {
+      result.append(requestPath(request.httpUrl()));
+    }
+
+    result.append(" HTTP/1.1");
+    return result.toString();
+  }
+
+  /**
+   * Returns true if the request line should contain the full URL with host
+   * and port (like "GET http://android.com/foo HTTP/1.1") or only the path
+   * (like "GET /foo HTTP/1.1").
+   */
+  private static boolean includeAuthorityInRequestLine(Request request, Proxy.Type proxyType) {
+    return !request.isHttps() && proxyType == Proxy.Type.HTTP;
+  }
+
+  /**
+   * Returns the path to request, like the '/' in 'GET / HTTP/1.1'. Never empty,
+   * even if the request URL is. Includes the query component if it exists.
+   */
+  public static String requestPath(HttpUrl url) {
+    String path = url.encodedPath();
+    String query = url.encodedQuery();
+    return query != null ? (path + '?' + query) : path;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RetryableSink.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RetryableSink.java
new file mode 100644
index 0000000..2891079
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RetryableSink.java
@@ -0,0 +1,82 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2010 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 com.android.okhttp.internal.http;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+import com.android.okhttp.okio.Buffer;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Timeout;
+
+import static com.android.okhttp.internal.Util.checkOffsetAndCount;
+
+/**
+ * An HTTP request body that's completely buffered in memory. This allows
+ * the post body to be transparently re-sent if the HTTP request must be
+ * sent multiple times.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RetryableSink implements Sink {
+  private boolean closed;
+  private final int limit;
+  private final Buffer content = new Buffer();
+
+  public RetryableSink(int limit) {
+    this.limit = limit;
+  }
+
+  public RetryableSink() {
+    this(-1);
+  }
+
+  @Override public void close() throws IOException {
+    if (closed) return;
+    closed = true;
+    if (content.size() < limit) {
+      throw new ProtocolException(
+          "content-length promised " + limit + " bytes, but received " + content.size());
+    }
+  }
+
+  @Override public void write(Buffer source, long byteCount) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    checkOffsetAndCount(source.size(), 0, byteCount);
+    if (limit != -1 && content.size() > limit - byteCount) {
+      throw new ProtocolException("exceeded content-length limit of " + limit + " bytes");
+    }
+    content.write(source, byteCount);
+  }
+
+  @Override public void flush() throws IOException {
+  }
+
+  @Override public Timeout timeout() {
+    return Timeout.NONE;
+  }
+
+  public long contentLength() throws IOException {
+    return content.size();
+  }
+
+  public void writeToSocket(Sink socketOut) throws IOException {
+    // Copy the content; otherwise we won't have data to retry.
+    Buffer buffer = new Buffer();
+    content.copyTo(buffer, 0, content.size());
+    socketOut.write(buffer, buffer.size());
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteException.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteException.java
new file mode 100644
index 0000000..72ed5de
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteException.java
@@ -0,0 +1,63 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.http;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * An exception thrown to indicate a problem connecting via a single Route. Multiple attempts may
+ * have been made with alternative protocols, none of which were successful.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RouteException extends Exception {
+  private static final Method addSuppressedExceptionMethod;
+  static {
+    Method m;
+    try {
+      m = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class);
+    } catch (Exception e) {
+      m = null;
+    }
+    addSuppressedExceptionMethod = m;
+  }
+  private IOException lastException;
+
+  public RouteException(IOException cause) {
+    super(cause);
+    lastException = cause;
+  }
+
+  public IOException getLastConnectException() {
+    return lastException;
+  }
+
+  public void addConnectException(IOException e) {
+    addSuppressedIfPossible(e, lastException);
+    lastException = e;
+  }
+
+  private void addSuppressedIfPossible(IOException e, IOException suppressed) {
+    if (addSuppressedExceptionMethod != null) {
+      try {
+        addSuppressedExceptionMethod.invoke(e, suppressed);
+      } catch (InvocationTargetException | IllegalAccessException ignored) {
+      }
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteSelector.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteSelector.java
new file mode 100644
index 0000000..75ecf64
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/RouteSelector.java
@@ -0,0 +1,228 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.http;
+
+import com.android.okhttp.Address;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.Route;
+import com.android.okhttp.internal.RouteDatabase;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * Selects routes to connect to an origin server. Each connection requires a
+ * choice of proxy server, IP address, and TLS mode. Connections may also be
+ * recycled.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RouteSelector {
+  private final Address address;
+  private final RouteDatabase routeDatabase;
+
+  /* The most recently attempted route. */
+  private Proxy lastProxy;
+  private InetSocketAddress lastInetSocketAddress;
+
+  /* State for negotiating the next proxy to use. */
+  private List<Proxy> proxies = Collections.emptyList();
+  private int nextProxyIndex;
+
+  /* State for negotiating the next socket address to use. */
+  private List<InetSocketAddress> inetSocketAddresses = Collections.emptyList();
+  private int nextInetSocketAddressIndex;
+
+  /* State for negotiating failed routes */
+  private final List<Route> postponedRoutes = new ArrayList<>();
+
+  public RouteSelector(Address address, RouteDatabase routeDatabase) {
+    this.address = address;
+    this.routeDatabase = routeDatabase;
+
+    resetNextProxy(address.url(), address.getProxy());
+  }
+
+  /**
+   * Returns true if there's another route to attempt. Every address has at
+   * least one route.
+   */
+  public boolean hasNext() {
+    return hasNextInetSocketAddress()
+        || hasNextProxy()
+        || hasNextPostponed();
+  }
+
+  public Route next() throws IOException {
+    // Compute the next route to attempt.
+    if (!hasNextInetSocketAddress()) {
+      if (!hasNextProxy()) {
+        if (!hasNextPostponed()) {
+          throw new NoSuchElementException();
+        }
+        return nextPostponed();
+      }
+      lastProxy = nextProxy();
+    }
+    lastInetSocketAddress = nextInetSocketAddress();
+
+    Route route = new Route(address, lastProxy, lastInetSocketAddress);
+    if (routeDatabase.shouldPostpone(route)) {
+      postponedRoutes.add(route);
+      // We will only recurse in order to skip previously failed routes. They will be tried last.
+      return next();
+    }
+
+    return route;
+  }
+
+  /**
+   * Clients should invoke this method when they encounter a connectivity
+   * failure on a connection returned by this route selector.
+   */
+  public void connectFailed(Route failedRoute, IOException failure) {
+    if (failedRoute.getProxy().type() != Proxy.Type.DIRECT && address.getProxySelector() != null) {
+      // Tell the proxy selector when we fail to connect on a fresh connection.
+      address.getProxySelector().connectFailed(
+          address.url().uri(), failedRoute.getProxy().address(), failure);
+    }
+
+    routeDatabase.failed(failedRoute);
+  }
+
+  /** Prepares the proxy servers to try. */
+  private void resetNextProxy(HttpUrl url, Proxy proxy) {
+    if (proxy != null) {
+      // If the user specifies a proxy, try that and only that.
+      proxies = Collections.singletonList(proxy);
+    } else {
+      // Try each of the ProxySelector choices until one connection succeeds. If none succeed
+      // then we'll try a direct connection below.
+      proxies = new ArrayList<>();
+      List<Proxy> selectedProxies = address.getProxySelector().select(url.uri());
+      if (selectedProxies != null) proxies.addAll(selectedProxies);
+      // Finally try a direct connection. We only try it once!
+      proxies.removeAll(Collections.singleton(Proxy.NO_PROXY));
+      proxies.add(Proxy.NO_PROXY);
+    }
+    nextProxyIndex = 0;
+  }
+
+  /** Returns true if there's another proxy to try. */
+  private boolean hasNextProxy() {
+    return nextProxyIndex < proxies.size();
+  }
+
+  /** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */
+  private Proxy nextProxy() throws IOException {
+    if (!hasNextProxy()) {
+      throw new SocketException("No route to " + address.getUriHost()
+          + "; exhausted proxy configurations: " + proxies);
+    }
+    Proxy result = proxies.get(nextProxyIndex++);
+    resetNextInetSocketAddress(result);
+    return result;
+  }
+
+  /** Prepares the socket addresses to attempt for the current proxy or host. */
+  private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
+    // Clear the addresses. Necessary if getAllByName() below throws!
+    inetSocketAddresses = new ArrayList<>();
+
+    String socketHost;
+    int socketPort;
+    if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
+      socketHost = address.getUriHost();
+      socketPort = address.getUriPort();
+    } else {
+      SocketAddress proxyAddress = proxy.address();
+      if (!(proxyAddress instanceof InetSocketAddress)) {
+        throw new IllegalArgumentException(
+            "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
+      }
+      InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
+      socketHost = getHostString(proxySocketAddress);
+      socketPort = proxySocketAddress.getPort();
+    }
+
+    if (socketPort < 1 || socketPort > 65535) {
+      throw new SocketException("No route to " + socketHost + ":" + socketPort
+          + "; port is out of range");
+    }
+
+    if (proxy.type() == Proxy.Type.SOCKS) {
+      inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
+    } else {
+      // Try each address for best behavior in mixed IPv4/IPv6 environments.
+      List<InetAddress> addresses = address.getDns().lookup(socketHost);
+      for (int i = 0, size = addresses.size(); i < size; i++) {
+        InetAddress inetAddress = addresses.get(i);
+        inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
+      }
+    }
+
+    nextInetSocketAddressIndex = 0;
+  }
+
+  /**
+   * Obtain a "host" from an {@link InetSocketAddress}. This returns a string containing either an
+   * actual host name or a numeric IP address.
+   */
+  // Visible for testing
+  static String getHostString(InetSocketAddress socketAddress) {
+    InetAddress address = socketAddress.getAddress();
+    if (address == null) {
+      // The InetSocketAddress was specified with a string (either a numeric IP or a host name). If
+      // it is a name, all IPs for that name should be tried. If it is an IP address, only that IP
+      // address should be tried.
+      return socketAddress.getHostName();
+    }
+    // The InetSocketAddress has a specific address: we should only try that address. Therefore we
+    // return the address and ignore any host name that may be available.
+    return address.getHostAddress();
+  }
+
+  /** Returns true if there's another socket address to try. */
+  private boolean hasNextInetSocketAddress() {
+    return nextInetSocketAddressIndex < inetSocketAddresses.size();
+  }
+
+  /** Returns the next socket address to try. */
+  private InetSocketAddress nextInetSocketAddress() throws IOException {
+    if (!hasNextInetSocketAddress()) {
+      throw new SocketException("No route to " + address.getUriHost()
+          + "; exhausted inet socket addresses: " + inetSocketAddresses);
+    }
+    return inetSocketAddresses.get(nextInetSocketAddressIndex++);
+  }
+
+  /** Returns true if there is another postponed route to try. */
+  private boolean hasNextPostponed() {
+    return !postponedRoutes.isEmpty();
+  }
+
+  /** Returns the next postponed route to try. */
+  private Route nextPostponed() {
+    return postponedRoutes.remove(0);
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StatusLine.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StatusLine.java
new file mode 100644
index 0000000..45b52c3
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StatusLine.java
@@ -0,0 +1,92 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.okhttp.internal.http;
+
+import com.android.okhttp.Protocol;
+import com.android.okhttp.Response;
+import java.io.IOException;
+import java.net.ProtocolException;
+
+/** An HTTP response status line like "HTTP/1.1 200 OK". 
+ * @hide This class is not part of the Android public SDK API*/
+public final class StatusLine {
+  /** Numeric status code, 307: Temporary Redirect. */
+  public static final int HTTP_TEMP_REDIRECT = 307;
+  public static final int HTTP_PERM_REDIRECT = 308;
+  public static final int HTTP_CONTINUE = 100;
+
+  public final Protocol protocol;
+  public final int code;
+  public final String message;
+
+  public StatusLine(Protocol protocol, int code, String message) {
+    this.protocol = protocol;
+    this.code = code;
+    this.message = message;
+  }
+
+  public static StatusLine get(Response response) {
+    return new StatusLine(response.protocol(), response.code(), response.message());
+  }
+
+  public static StatusLine parse(String statusLine) throws IOException {
+    // H T T P / 1 . 1   2 0 0   T e m p o r a r y   R e d i r e c t
+    // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
+
+    // Parse protocol like "HTTP/1.1" followed by a space.
+    int codeStart;
+    Protocol protocol;
+    if (statusLine.startsWith("HTTP/1.")) {
+      if (statusLine.length() < 9 || statusLine.charAt(8) != ' ') {
+        throw new ProtocolException("Unexpected status line: " + statusLine);
+      }
+      int httpMinorVersion = statusLine.charAt(7) - '0';
+      codeStart = 9;
+      if (httpMinorVersion == 0) {
+        protocol = Protocol.HTTP_1_0;
+      } else if (httpMinorVersion == 1) {
+        protocol = Protocol.HTTP_1_1;
+      } else {
+        throw new ProtocolException("Unexpected status line: " + statusLine);
+      }
+    } else if (statusLine.startsWith("ICY ")) {
+      // Shoutcast uses ICY instead of "HTTP/1.0".
+      protocol = Protocol.HTTP_1_0;
+      codeStart = 4;
+    } else {
+      throw new ProtocolException("Unexpected status line: " + statusLine);
+    }
+
+    // Parse response code like "200". Always 3 digits.
+    if (statusLine.length() < codeStart + 3) {
+      throw new ProtocolException("Unexpected status line: " + statusLine);
+    }
+    int code;
+    try {
+      code = Integer.parseInt(statusLine.substring(codeStart, codeStart + 3));
+    } catch (NumberFormatException e) {
+      throw new ProtocolException("Unexpected status line: " + statusLine);
+    }
+
+    // Parse an optional response message like "OK" or "Not Modified". If it
+    // exists, it is separated from the response code by a space.
+    String message = "";
+    if (statusLine.length() > codeStart + 3) {
+      if (statusLine.charAt(codeStart + 3) != ' ') {
+        throw new ProtocolException("Unexpected status line: " + statusLine);
+      }
+      message = statusLine.substring(codeStart + 4);
+    }
+
+    return new StatusLine(protocol, code, message);
+  }
+
+  @Override public String toString() {
+    StringBuilder result = new StringBuilder();
+    result.append(protocol == Protocol.HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1");
+    result.append(' ').append(code);
+    if (message != null) {
+      result.append(' ').append(message);
+    }
+    return result.toString();
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StreamAllocation.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StreamAllocation.java
new file mode 100644
index 0000000..4199e90
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/http/StreamAllocation.java
@@ -0,0 +1,412 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.http;
+
+import com.android.okhttp.Address;
+import com.android.okhttp.ConnectionPool;
+import com.android.okhttp.Route;
+import com.android.okhttp.internal.Internal;
+import com.android.okhttp.internal.RouteDatabase;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.io.RealConnection;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.net.ProtocolException;
+import java.net.SocketTimeoutException;
+import java.security.cert.CertificateException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import com.android.okhttp.okio.Sink;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ * This class coordinates the relationship between three entities:
+ *
+ * <ul>
+ *   <li><strong>Connections:</strong> physical socket connections to remote servers. These are
+ *       potentially slow to establish so it is necessary to be able to cancel a connection
+ *       currently being connected.
+ *   <li><strong>Streams:</strong> logical HTTP request/response pairs that are layered on
+ *       connections. Each connection has its own allocation limit, which defines how many
+ *       concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream
+ *       at a time, SPDY and HTTP/2 typically carry multiple.
+ *   <li><strong>Calls:</strong> a logical sequence of streams, typically an initial request and
+ *       its follow up requests. We prefer to keep all streams of a single call on the same
+ *       connection for better behavior and locality.
+ * </ul>
+ *
+ * <p>Instances of this class act on behalf of the call, using one or more streams over one or
+ * more connections. This class has APIs to release each of the above resources:
+ *
+ * <ul>
+ *   <li>{@link #noNewStreams()} prevents the connection from being used for new streams in the
+ *       future. Use this after a {@code Connection: close} header, or when the connection may be
+ *       inconsistent.
+ *   <li>{@link #streamFinished streamFinished()} releases the active stream from this allocation.
+ *       Note that only one stream may be active at a given time, so it is necessary to call {@link
+ *       #streamFinished streamFinished()} before creating a subsequent stream with {@link
+ *       #newStream newStream()}.
+ *   <li>{@link #release()} removes the call's hold on the connection. Note that this won't
+ *       immediately free the connection if there is a stream still lingering. That happens when a
+ *       call is complete but its response body has yet to be fully consumed.
+ * </ul>
+ *
+ * <p>This class supports {@linkplain #cancel asynchronous canceling}. This is intended to have
+ * the smallest blast radius possible. If an HTTP/2 stream is active, canceling will cancel that
+ * stream but not the other streams sharing its connection. But if the TLS handshake is still in
+ * progress then canceling may break the entire connection.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class StreamAllocation {
+  public final Address address;
+  private final ConnectionPool connectionPool;
+
+  // State guarded by connectionPool.
+  private RouteSelector routeSelector;
+  private RealConnection connection;
+  private boolean released;
+  private boolean canceled;
+  private HttpStream stream;
+
+  public StreamAllocation(ConnectionPool connectionPool, Address address) {
+    this.connectionPool = connectionPool;
+    this.address = address;
+  }
+
+  public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout,
+      boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
+      throws RouteException, IOException {
+    try {
+      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
+          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
+
+      HttpStream resultStream;
+      if (resultConnection.framedConnection != null) {
+        resultStream = new Http2xStream(this, resultConnection.framedConnection);
+      } else {
+        resultConnection.getSocket().setSoTimeout(readTimeout);
+        resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
+        resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
+        resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink);
+      }
+
+      synchronized (connectionPool) {
+        resultConnection.streamCount++;
+        stream = resultStream;
+        return resultStream;
+      }
+    } catch (IOException e) {
+      throw new RouteException(e);
+    }
+  }
+
+  /**
+   * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
+   * until a healthy connection is found.
+   */
+  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
+      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
+      throws IOException, RouteException {
+    while (true) {
+      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
+          connectionRetryEnabled);
+
+      // If this is a brand new connection, we can skip the extensive health checks.
+      synchronized (connectionPool) {
+        if (candidate.streamCount == 0) {
+          return candidate;
+        }
+      }
+
+      // Otherwise do a potentially-slow check to confirm that the pooled connection is still good.
+      if (candidate.isHealthy(doExtensiveHealthChecks)) {
+        return candidate;
+      }
+
+      connectionFailed();
+    }
+  }
+
+  /**
+   * Returns a connection to host a new stream. This prefers the existing connection if it exists,
+   * then the pool, finally building a new connection.
+   */
+  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
+      boolean connectionRetryEnabled) throws IOException, RouteException {
+    synchronized (connectionPool) {
+      if (released) throw new IllegalStateException("released");
+      if (stream != null) throw new IllegalStateException("stream != null");
+      if (canceled) throw new IOException("Canceled");
+
+      RealConnection allocatedConnection = this.connection;
+      if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
+        return allocatedConnection;
+      }
+
+      // Attempt to get a connection from the pool.
+      RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
+      if (pooledConnection != null) {
+        this.connection = pooledConnection;
+        return pooledConnection;
+      }
+
+      // Attempt to create a connection.
+      if (routeSelector == null) {
+        routeSelector = new RouteSelector(address, routeDatabase());
+      }
+    }
+
+    Route route = routeSelector.next();
+    RealConnection newConnection = new RealConnection(route);
+    acquire(newConnection);
+
+    synchronized (connectionPool) {
+      Internal.instance.put(connectionPool, newConnection);
+      this.connection = newConnection;
+      if (canceled) throw new IOException("Canceled");
+    }
+
+    newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.getConnectionSpecs(),
+        connectionRetryEnabled);
+    routeDatabase().connected(newConnection.getRoute());
+
+    return newConnection;
+  }
+
+  public void streamFinished(HttpStream stream) {
+    synchronized (connectionPool) {
+      if (stream == null || stream != this.stream) {
+        throw new IllegalStateException("expected " + this.stream + " but was " + stream);
+      }
+    }
+    deallocate(false, false, true);
+  }
+
+  public HttpStream stream() {
+    synchronized (connectionPool) {
+      return stream;
+    }
+  }
+
+  private RouteDatabase routeDatabase() {
+    return Internal.instance.routeDatabase(connectionPool);
+  }
+
+  public synchronized RealConnection connection() {
+    return connection;
+  }
+
+  public void release() {
+    deallocate(false, true, false);
+  }
+
+  /** Forbid new streams from being created on the connection that hosts this allocation. */
+  public void noNewStreams() {
+    deallocate(true, false, false);
+  }
+
+  /**
+   * Releases resources held by this allocation. If sufficient resources are allocated, the
+   * connection will be detached or closed.
+   */
+  private void deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
+    RealConnection connectionToClose = null;
+    synchronized (connectionPool) {
+      if (streamFinished) {
+        this.stream = null;
+      }
+      if (released) {
+        this.released = true;
+      }
+      if (connection != null) {
+        if (noNewStreams) {
+          connection.noNewStreams = true;
+        }
+        if (this.stream == null && (this.released || connection.noNewStreams)) {
+          release(connection);
+          if (connection.streamCount > 0) {
+            routeSelector = null;
+          }
+          if (connection.allocations.isEmpty()) {
+            connection.idleAtNanos = System.nanoTime();
+            if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
+              connectionToClose = connection;
+            }
+          }
+          connection = null;
+        }
+      }
+    }
+    if (connectionToClose != null) {
+      Util.closeQuietly(connectionToClose.getSocket());
+    }
+  }
+
+  public void cancel() {
+    HttpStream streamToCancel;
+    RealConnection connectionToCancel;
+    synchronized (connectionPool) {
+      canceled = true;
+      streamToCancel = stream;
+      connectionToCancel = connection;
+    }
+    if (streamToCancel != null) {
+      streamToCancel.cancel();
+    } else if (connectionToCancel != null) {
+      connectionToCancel.cancel();
+    }
+  }
+
+  private void connectionFailed(IOException e) {
+    synchronized (connectionPool) {
+      if (routeSelector != null) {
+        if (connection.streamCount == 0) {
+          // Record the failure on a fresh route.
+          Route failedRoute = connection.getRoute();
+          routeSelector.connectFailed(failedRoute, e);
+        } else {
+          // We saw a failure on a recycled connection, reset this allocation with a fresh route.
+          routeSelector = null;
+        }
+      }
+    }
+    connectionFailed();
+  }
+
+  /** Finish the current stream and prevent new streams from being created. */
+  public void connectionFailed() {
+    deallocate(true, false, true);
+  }
+
+  /**
+   * Use this allocation to hold {@code connection}. Each call to this must be paired with a call to
+   * {@link #release} on the same connection.
+   */
+  public void acquire(RealConnection connection) {
+    connection.allocations.add(new WeakReference<>(this));
+  }
+
+  /** Remove this allocation from the connection's list of allocations. */
+  private void release(RealConnection connection) {
+    for (int i = 0, size = connection.allocations.size(); i < size; i++) {
+      Reference<StreamAllocation> reference = connection.allocations.get(i);
+      if (reference.get() == this) {
+        connection.allocations.remove(i);
+        return;
+      }
+    }
+    throw new IllegalStateException();
+  }
+
+  public boolean recover(RouteException e) {
+    // Android-changed: Canceled StreamAllocations can never recover http://b/33763156
+    if (canceled) {
+      return false;
+    }
+    if (connection != null) {
+      connectionFailed(e.getLastConnectException());
+    }
+
+    if ((routeSelector != null && !routeSelector.hasNext()) // No more routes to attempt.
+        || !isRecoverable(e)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  public boolean recover(IOException e, Sink requestBodyOut) {
+    if (connection != null) {
+      int streamCount = connection.streamCount;
+      connectionFailed(e);
+
+      if (streamCount == 1) {
+        // This isn't a recycled connection.
+        // TODO(jwilson): find a better way for this.
+        return false;
+      }
+    }
+
+    boolean canRetryRequestBody = requestBodyOut == null || requestBodyOut instanceof RetryableSink;
+    if ((routeSelector != null && !routeSelector.hasNext()) // No more routes to attempt.
+        || !isRecoverable(e)
+        || !canRetryRequestBody) {
+      return false;
+    }
+
+    return true;
+  }
+
+  private boolean isRecoverable(IOException e) {
+    // If there was a protocol problem, don't recover.
+    if (e instanceof ProtocolException) {
+      return false;
+    }
+
+    // If there was an interruption or timeout, don't recover.
+    if (e instanceof InterruptedIOException) {
+      return false;
+    }
+
+    return true;
+  }
+
+  private boolean isRecoverable(RouteException e) {
+    // Problems with a route may mean the connection can be retried with a new route, or may
+    // indicate a client-side or server-side issue that should not be retried. To tell, we must look
+    // at the cause.
+
+    IOException ioe = e.getLastConnectException();
+
+    // If there was a protocol problem, don't recover.
+    if (ioe instanceof ProtocolException) {
+      return false;
+    }
+
+    // If there was an interruption don't recover, but if there was a timeout
+    // we should try the next route (if there is one).
+    if (ioe instanceof InterruptedIOException) {
+      return ioe instanceof SocketTimeoutException;
+    }
+
+    // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
+    // again with a different route.
+    if (ioe instanceof SSLHandshakeException) {
+      // If the problem was a CertificateException from the X509TrustManager,
+      // do not retry.
+      if (ioe.getCause() instanceof CertificateException) {
+        return false;
+      }
+    }
+    if (ioe instanceof SSLPeerUnverifiedException) {
+      // e.g. a certificate pinning error.
+      return false;
+    }
+
+    // An example of one we might want to retry with a different route is a problem connecting to a
+    // proxy and would manifest as a standard IOException. Unless it is one we know we should not
+    // retry, we return true and try a new route.
+    return true;
+  }
+
+  @Override public String toString() {
+    return address.toString();
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/FileSystem.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/FileSystem.java
new file mode 100644
index 0000000..3bdcbc9
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/FileSystem.java
@@ -0,0 +1,139 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.io;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Sink;
+import com.android.okhttp.okio.Source;
+
+/**
+ * Access to read and write files on a hierarchical data store. Most callers should use the {@link
+ * #SYSTEM} implementation, which uses the host machine's local file system. Alternate
+ * implementations may be used to inject faults (for testing) or to transform stored data (to add
+ * encryption, for example).
+ *
+ * <p>All operations on a file system are racy. For example, guarding a call to {@link #source}
+ * with {@link #exists} does not guarantee that {@link FileNotFoundException} will not be thrown.
+ * The file may be moved between the two calls!
+ *
+ * <p>This interface is less ambitious than {@link java.nio.file.FileSystem} introduced in Java 7.
+ * It lacks important features like file watching, metadata, permissions, and disk space
+ * information. In exchange for these limitations, this interface is easier to implement and works
+ * on all versions of Java and Android.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface FileSystem {
+  /** The host machine's local file system. */
+  FileSystem SYSTEM = new FileSystem() {
+    @Override public Source source(File file) throws FileNotFoundException {
+      return Okio.source(file);
+    }
+
+    @Override public Sink sink(File file) throws FileNotFoundException {
+      try {
+        return Okio.sink(file);
+      } catch (FileNotFoundException e) {
+        // Maybe the parent directory doesn't exist? Try creating it first.
+        file.getParentFile().mkdirs();
+        return Okio.sink(file);
+      }
+    }
+
+    @Override public Sink appendingSink(File file) throws FileNotFoundException {
+      try {
+        return Okio.appendingSink(file);
+      } catch (FileNotFoundException e) {
+        // Maybe the parent directory doesn't exist? Try creating it first.
+        file.getParentFile().mkdirs();
+        return Okio.appendingSink(file);
+      }
+    }
+
+    @Override public void delete(File file) throws IOException {
+      // If delete() fails, make sure it's because the file didn't exist!
+      if (!file.delete() && file.exists()) {
+        throw new IOException("failed to delete " + file);
+      }
+    }
+
+    @Override public boolean exists(File file) throws IOException {
+      return file.exists();
+    }
+
+    @Override public long size(File file) {
+      return file.length();
+    }
+
+    @Override public void rename(File from, File to) throws IOException {
+      delete(to);
+      if (!from.renameTo(to)) {
+        throw new IOException("failed to rename " + from + " to " + to);
+      }
+    }
+
+    @Override public void deleteContents(File directory) throws IOException {
+      File[] files = directory.listFiles();
+      if (files == null) {
+        throw new IOException("not a readable directory: " + directory);
+      }
+      for (File file : files) {
+        if (file.isDirectory()) {
+          deleteContents(file);
+        }
+        if (!file.delete()) {
+          throw new IOException("failed to delete " + file);
+        }
+      }
+    }
+  };
+
+  /** Reads from {@code file}. */
+  Source source(File file) throws FileNotFoundException;
+
+  /**
+   * Writes to {@code file}, discarding any data already present. Creates parent directories if
+   * necessary.
+   */
+  Sink sink(File file) throws FileNotFoundException;
+
+  /**
+   * Writes to {@code file}, appending if data is already present. Creates parent directories if
+   * necessary.
+   */
+  Sink appendingSink(File file) throws FileNotFoundException;
+
+  /** Deletes {@code file} if it exists. Throws if the file exists and cannot be deleted. */
+  void delete(File file) throws IOException;
+
+  /** Returns true if {@code file} exists on the file system. */
+  boolean exists(File file) throws IOException;
+
+  /** Returns the number of bytes stored in {@code file}, or 0 if it does not exist. */
+  long size(File file);
+
+  /** Renames {@code from} to {@code to}. Throws if the file cannot be renamed. */
+  void rename(File from, File to) throws IOException;
+
+  /**
+   * Recursively delete the contents of {@code directory}. Throws an IOException if any file could
+   * not be deleted, or if {@code dir} is not a readable directory.
+   */
+  void deleteContents(File directory) throws IOException;
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/RealConnection.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/RealConnection.java
new file mode 100644
index 0000000..719890e
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/io/RealConnection.java
@@ -0,0 +1,411 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp.internal.io;
+
+import com.android.okhttp.Address;
+import com.android.okhttp.CertificatePinner;
+import com.android.okhttp.Connection;
+import com.android.okhttp.ConnectionSpec;
+import com.android.okhttp.Handshake;
+import com.android.okhttp.HttpUrl;
+import com.android.okhttp.Protocol;
+import com.android.okhttp.Request;
+import com.android.okhttp.Response;
+import com.android.okhttp.Route;
+import com.android.okhttp.internal.ConnectionSpecSelector;
+import com.android.okhttp.internal.Platform;
+import com.android.okhttp.internal.Util;
+import com.android.okhttp.internal.Version;
+import com.android.okhttp.internal.framed.FramedConnection;
+import com.android.okhttp.internal.http.Http1xStream;
+import com.android.okhttp.internal.http.OkHeaders;
+import com.android.okhttp.internal.http.RouteException;
+import com.android.okhttp.internal.http.StreamAllocation;
+import com.android.okhttp.internal.tls.CertificateChainCleaner;
+import com.android.okhttp.internal.tls.OkHostnameVerifier;
+import com.android.okhttp.internal.tls.TrustRootIndex;
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.net.ConnectException;
+import java.net.Proxy;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.net.UnknownServiceException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+import com.android.okhttp.okio.BufferedSink;
+import com.android.okhttp.okio.BufferedSource;
+import com.android.okhttp.okio.Okio;
+import com.android.okhttp.okio.Source;
+
+import static com.android.okhttp.internal.Util.closeQuietly;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RealConnection implements Connection {
+  private final Route route;
+
+  /** The low-level TCP socket. */
+  private Socket rawSocket;
+
+  /**
+   * The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
+   * {@link #rawSocket} itself if this connection does not use SSL.
+   */
+  public Socket socket;
+  private Handshake handshake;
+  private Protocol protocol;
+  public volatile FramedConnection framedConnection;
+  public int streamCount;
+  public BufferedSource source;
+  public BufferedSink sink;
+  public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
+  public boolean noNewStreams;
+  public long idleAtNanos = Long.MAX_VALUE;
+
+  public RealConnection(Route route) {
+    this.route = route;
+  }
+
+  public void connect(int connectTimeout, int readTimeout, int writeTimeout,
+      List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
+    if (protocol != null) throw new IllegalStateException("already connected");
+
+    RouteException routeException = null;
+    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
+    Proxy proxy = route.getProxy();
+    Address address = route.getAddress();
+
+    if (route.getAddress().getSslSocketFactory() == null
+        && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
+      throw new RouteException(new UnknownServiceException(
+          "CLEARTEXT communication not supported: " + connectionSpecs));
+    }
+
+    while (protocol == null) {
+      try {
+        rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
+            ? address.getSocketFactory().createSocket()
+            : new Socket(proxy);
+        connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
+      } catch (IOException e) {
+        Util.closeQuietly(socket);
+        Util.closeQuietly(rawSocket);
+        socket = null;
+        rawSocket = null;
+        source = null;
+        sink = null;
+        handshake = null;
+        protocol = null;
+
+        if (routeException == null) {
+          routeException = new RouteException(e);
+        } else {
+          routeException.addConnectException(e);
+        }
+
+        if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
+          throw routeException;
+        }
+      }
+    }
+  }
+
+  /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
+  private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
+      ConnectionSpecSelector connectionSpecSelector) throws IOException {
+    rawSocket.setSoTimeout(readTimeout);
+    try {
+      Platform.get().connectSocket(rawSocket, route.getSocketAddress(), connectTimeout);
+    } catch (ConnectException e) {
+      throw new ConnectException("Failed to connect to " + route.getSocketAddress());
+    }
+    source = Okio.buffer(Okio.source(rawSocket));
+    sink = Okio.buffer(Okio.sink(rawSocket));
+
+    if (route.getAddress().getSslSocketFactory() != null) {
+      connectTls(readTimeout, writeTimeout, connectionSpecSelector);
+    } else {
+      protocol = Protocol.HTTP_1_1;
+      socket = rawSocket;
+    }
+
+    if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
+      socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.
+
+      FramedConnection framedConnection = new FramedConnection.Builder(true)
+          .socket(socket, route.getAddress().url().host(), source, sink)
+          .protocol(protocol)
+          .build();
+      framedConnection.sendConnectionPreface();
+
+      // Only assign the framed connection once the preface has been sent successfully.
+      this.framedConnection = framedConnection;
+    }
+  }
+
+  private void connectTls(int readTimeout, int writeTimeout,
+      ConnectionSpecSelector connectionSpecSelector) throws IOException {
+    if (route.requiresTunnel()) {
+      createTunnel(readTimeout, writeTimeout);
+    }
+
+    Address address = route.getAddress();
+    SSLSocketFactory sslSocketFactory = address.getSslSocketFactory();
+    boolean success = false;
+    SSLSocket sslSocket = null;
+    try {
+      // Create the wrapper over the connected socket.
+      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
+          rawSocket, address.getUriHost(), address.getUriPort(), true /* autoClose */);
+
+      // Configure the socket's ciphers, TLS versions, and extensions.
+      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
+      if (connectionSpec.supportsTlsExtensions()) {
+        Platform.get().configureTlsExtensions(
+            sslSocket, address.getUriHost(), address.getProtocols());
+      }
+
+      // Force handshake. This can throw!
+      sslSocket.startHandshake();
+      Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
+
+      // Verify that the socket's certificates are acceptable for the target host.
+      if (!address.getHostnameVerifier().verify(address.getUriHost(), sslSocket.getSession())) {
+        X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
+        throw new SSLPeerUnverifiedException("Hostname " + address.getUriHost() + " not verified:"
+            + "\n    certificate: " + CertificatePinner.pin(cert)
+            + "\n    DN: " + cert.getSubjectDN().getName()
+            + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
+      }
+
+      // Check that the certificate pinner is satisfied by the certificates presented.
+      if (address.getCertificatePinner() != CertificatePinner.DEFAULT) {
+        TrustRootIndex trustRootIndex = trustRootIndex(address.getSslSocketFactory());
+        List<Certificate> certificates = new CertificateChainCleaner(trustRootIndex)
+            .clean(unverifiedHandshake.peerCertificates());
+        address.getCertificatePinner().check(address.getUriHost(), certificates);
+      }
+
+      // Success! Save the handshake and the ALPN protocol.
+      String maybeProtocol = connectionSpec.supportsTlsExtensions()
+          ? Platform.get().getSelectedProtocol(sslSocket)
+          : null;
+      socket = sslSocket;
+      source = Okio.buffer(Okio.source(socket));
+      sink = Okio.buffer(Okio.sink(socket));
+      handshake = unverifiedHandshake;
+      protocol = maybeProtocol != null
+          ? Protocol.get(maybeProtocol)
+          : Protocol.HTTP_1_1;
+      success = true;
+    } catch (AssertionError e) {
+      if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
+      throw e;
+    } finally {
+      if (sslSocket != null) {
+        Platform.get().afterHandshake(sslSocket);
+      }
+      if (!success) {
+        closeQuietly(sslSocket);
+      }
+    }
+  }
+
+  private static SSLSocketFactory lastSslSocketFactory;
+  private static TrustRootIndex lastTrustRootIndex;
+
+  /**
+   * Returns a trust root index for {@code sslSocketFactory}. This uses a static, single-element
+   * cache to avoid redoing reflection and SSL indexing in the common case where most SSL
+   * connections use the same SSL socket factory.
+   */
+  private static synchronized TrustRootIndex trustRootIndex(SSLSocketFactory sslSocketFactory) {
+    if (sslSocketFactory != lastSslSocketFactory) {
+      X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
+      lastTrustRootIndex = Platform.get().trustRootIndex(trustManager);
+      lastSslSocketFactory = sslSocketFactory;
+    }
+    return lastTrustRootIndex;
+  }
+
+  /**
+   * To make an HTTPS connection over an HTTP proxy, send an unencrypted
+   * CONNECT request to create the proxy connection. This may need to be
+   * retried if the proxy requires authorization.
+   */
+  private void createTunnel(int readTimeout, int writeTimeout) throws IOException {
+    // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
+    Request tunnelRequest = createTunnelRequest();
+    HttpUrl url = tunnelRequest.httpUrl();
+    String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
+    while (true) {
+      Http1xStream tunnelConnection = new Http1xStream(null, source, sink);
+      source.timeout().timeout(readTimeout, MILLISECONDS);
+      sink.timeout().timeout(writeTimeout, MILLISECONDS);
+      tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
+      tunnelConnection.finishRequest();
+      Response response = tunnelConnection.readResponse().request(tunnelRequest).build();
+      // The response body from a CONNECT should be empty, but if it is not then we should consume
+      // it before proceeding.
+      long contentLength = OkHeaders.contentLength(response);
+      if (contentLength == -1L) {
+        contentLength = 0L;
+      }
+      Source body = tunnelConnection.newFixedLengthSource(contentLength);
+      Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
+      body.close();
+
+      switch (response.code()) {
+        case HTTP_OK:
+          // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If
+          // that happens, then we will have buffered bytes that are needed by the SSLSocket!
+          // This check is imperfect: it doesn't tell us whether a handshake will succeed, just
+          // that it will almost certainly fail because the proxy has sent unexpected data.
+          if (!source.buffer().exhausted() || !sink.buffer().exhausted()) {
+            throw new IOException("TLS tunnel buffered too many bytes!");
+          }
+          return;
+
+        case HTTP_PROXY_AUTH:
+          tunnelRequest = OkHeaders.processAuthHeader(
+              route.getAddress().getAuthenticator(), response, route.getProxy());
+          if (tunnelRequest != null) continue;
+          throw new IOException("Failed to authenticate with proxy");
+
+        default:
+          throw new IOException(
+              "Unexpected response code for CONNECT: " + response.code());
+      }
+    }
+  }
+
+  /**
+   * Returns a request that creates a TLS tunnel via an HTTP proxy, or null if
+   * no tunnel is necessary. Everything in the tunnel request is sent
+   * unencrypted to the proxy server, so tunnels include only the minimum set of
+   * headers. This avoids sending potentially sensitive data like HTTP cookies
+   * to the proxy unencrypted.
+   */
+  private Request createTunnelRequest() throws IOException {
+    return new Request.Builder()
+        .url(route.getAddress().url())
+        .header("Host", Util.hostHeader(route.getAddress().url(), true))
+        .header("Proxy-Connection", "Keep-Alive")
+        .header("User-Agent", Version.userAgent()) // For HTTP/1.0 proxies like Squid.
+        .build();
+  }
+
+  /** Returns true if {@link #connect} has been attempted on this connection. */
+  boolean isConnected() {
+    return protocol != null;
+  }
+
+  @Override public Route getRoute() {
+    return route;
+  }
+
+  public void cancel() {
+    // Close the raw socket so we don't end up doing synchronous I/O.
+    Util.closeQuietly(rawSocket);
+  }
+
+  @Override public Socket getSocket() {
+    return socket;
+  }
+
+  public int allocationLimit() {
+    FramedConnection framedConnection = this.framedConnection;
+    return framedConnection != null
+        ? framedConnection.maxConcurrentStreams()
+        : 1;
+  }
+
+  /** Returns true if this connection is ready to host new streams. */
+  public boolean isHealthy(boolean doExtensiveChecks) {
+    if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
+      return false;
+    }
+
+    if (framedConnection != null) {
+      return true; // TODO: check framedConnection.shutdown.
+    }
+
+    if (doExtensiveChecks) {
+      try {
+        int readTimeout = socket.getSoTimeout();
+        try {
+          socket.setSoTimeout(1);
+          if (source.exhausted()) {
+            return false; // Stream is exhausted; socket is closed.
+          }
+          return true;
+        } finally {
+          socket.setSoTimeout(readTimeout);
+        }
+      } catch (SocketTimeoutException ignored) {
+        // Read timed out; socket is good.
+      } catch (IOException e) {
+        return false; // Couldn't read; socket is closed.
+      }
+    }
+
+    return true;
+  }
+
+  @Override public Handshake getHandshake() {
+    return handshake;
+  }
+
+  /**
+   * Returns true if this is a SPDY connection. Such connections can be used
+   * in multiple HTTP requests simultaneously.
+   */
+  public boolean isMultiplexed() {
+    return framedConnection != null;
+  }
+
+  @Override public Protocol getProtocol() {
+    return protocol != null ? protocol : Protocol.HTTP_1_1;
+  }
+
+  @Override public String toString() {
+    return "Connection{"
+        + route.getAddress().url().host() + ":" + route.getAddress().url().port()
+        + ", proxy="
+        + route.getProxy()
+        + " hostAddress="
+        + route.getSocketAddress()
+        + " cipherSuite="
+        + (handshake != null ? handshake.cipherSuite() : "none")
+        + " protocol="
+        + protocol
+        + '}';
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/AndroidTrustRootIndex.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/AndroidTrustRootIndex.java
new file mode 100644
index 0000000..207985e
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/AndroidTrustRootIndex.java
@@ -0,0 +1,67 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.tls;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * A index of trusted root certificates that exploits knowledge of Android implementation details.
+ * This class is potentially much faster to initialize than {@link RealTrustRootIndex} because
+ * it doesn't need to load and index trusted CA certificates.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class AndroidTrustRootIndex implements TrustRootIndex {
+  private final X509TrustManager trustManager;
+  private final Method findByIssuerAndSignatureMethod;
+
+  public AndroidTrustRootIndex(
+      X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) {
+    this.findByIssuerAndSignatureMethod = findByIssuerAndSignatureMethod;
+    this.trustManager = trustManager;
+  }
+
+  @Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) {
+    try {
+      TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke(
+          trustManager, cert);
+      return trustAnchor != null
+          ? trustAnchor.getTrustedCert()
+          : null;
+    } catch (IllegalAccessException e) {
+      throw new AssertionError();
+    } catch (InvocationTargetException e) {
+      return null;
+    }
+  }
+
+  public static TrustRootIndex get(X509TrustManager trustManager) {
+    // From org.conscrypt.TrustManagerImpl, we want the method with this signature:
+    // private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert);
+    try {
+      Method method = trustManager.getClass().getDeclaredMethod(
+          "findTrustAnchorByIssuerAndSignature", X509Certificate.class);
+      method.setAccessible(true);
+      return new AndroidTrustRootIndex(trustManager, method);
+    } catch (NoSuchMethodException e) {
+      return null;
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/CertificateChainCleaner.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/CertificateChainCleaner.java
new file mode 100644
index 0000000..6bcdc25
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/CertificateChainCleaner.java
@@ -0,0 +1,119 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp.internal.tls;
+
+import java.security.GeneralSecurityException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import javax.net.ssl.SSLPeerUnverifiedException;
+
+/**
+ * Computes the effective certificate chain from the raw array returned by Java's built in TLS APIs.
+ * Cleaning a chain returns a list of certificates where the first element is {@code chain[0]}, each
+ * certificate is signed by the certificate that follows, and the last certificate is a trusted CA
+ * certificate.
+ *
+ * <p>Use of the chain cleaner is necessary to omit unexpected certificates that aren't relevant to
+ * the TLS handshake and to extract the trusted CA certificate for the benefit of certificate
+ * pinning.
+ *
+ * <p>This class includes code from <a href="https://conscrypt.org/">Conscrypt's</a> {@code
+ * TrustManagerImpl} and {@code TrustedCertificateIndex}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class CertificateChainCleaner {
+  /** The maximum number of signers in a chain. We use 9 for consistency with OpenSSL. */
+  private static final int MAX_SIGNERS = 9;
+
+  private final TrustRootIndex trustRootIndex;
+
+  public CertificateChainCleaner(TrustRootIndex trustRootIndex) {
+    this.trustRootIndex = trustRootIndex;
+  }
+
+  /**
+   * Returns a cleaned chain for {@code chain}.
+   *
+   * <p>This method throws if the complete chain to a trusted CA certificate cannot be constructed.
+   * This is unexpected unless the trust root index in this class has a different trust manager than
+   * what was used to establish {@code chain}.
+   */
+  public List<Certificate> clean(List<Certificate> chain) throws SSLPeerUnverifiedException {
+    Deque<Certificate> queue = new ArrayDeque<>(chain);
+    List<Certificate> result = new ArrayList<>();
+    result.add(queue.removeFirst());
+    boolean foundTrustedCertificate = false;
+
+    followIssuerChain:
+    for (int c = 0; c < MAX_SIGNERS; c++) {
+      X509Certificate toVerify = (X509Certificate) result.get(result.size() - 1);
+
+      // If this cert has been signed by a trusted cert, use that. Add the trusted certificate to
+      // the end of the chain unless it's already present. (That would happen if the first
+      // certificate in the chain is itself a self-signed and trusted CA certificate.)
+      X509Certificate trustedCert = trustRootIndex.findByIssuerAndSignature(toVerify);
+      if (trustedCert != null) {
+        if (result.size() > 1 || !toVerify.equals(trustedCert)) {
+          result.add(trustedCert);
+        }
+        if (verifySignature(trustedCert, trustedCert)) {
+          return result; // The self-signed cert is a root CA. We're done.
+        }
+        foundTrustedCertificate = true;
+        continue;
+      }
+
+      // Search for the certificate in the chain that signed this certificate. This is typically the
+      // next element in the chain, but it could be any element.
+      for (Iterator<Certificate> i = queue.iterator(); i.hasNext(); ) {
+        X509Certificate signingCert = (X509Certificate) i.next();
+        if (verifySignature(toVerify, signingCert)) {
+          i.remove();
+          result.add(signingCert);
+          continue followIssuerChain;
+        }
+      }
+
+      // We've reached the end of the chain. If any cert in the chain is trusted, we're done.
+      if (foundTrustedCertificate) {
+        return result;
+      }
+
+      // The last link isn't trusted. Fail.
+      throw new SSLPeerUnverifiedException("Failed to find a trusted cert that signed " + toVerify);
+    }
+
+    throw new SSLPeerUnverifiedException("Certificate chain too long: " + result);
+  }
+
+  /** Returns true if {@code toVerify} was signed by {@code signingCert}'s public key. */
+  private boolean verifySignature(X509Certificate toVerify, X509Certificate signingCert) {
+    if (!toVerify.getIssuerDN().equals(signingCert.getSubjectDN())) return false;
+    try {
+      toVerify.verify(signingCert.getPublicKey());
+      return true;
+    } catch (GeneralSecurityException verifyFailed) {
+      return false;
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/DistinguishedNameParser.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/DistinguishedNameParser.java
new file mode 100644
index 0000000..15f0a6a
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/DistinguishedNameParser.java
@@ -0,0 +1,408 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp.internal.tls;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A distinguished name (DN) parser. This parser only supports extracting a
+ * string value from a DN. It doesn't support values in the hex-string style.
+ */
+final class DistinguishedNameParser {
+  private final String dn;
+  private final int length;
+  private int pos;
+  private int beg;
+  private int end;
+
+  /** Temporary variable to store positions of the currently parsed item. */
+  private int cur;
+
+  /** Distinguished name characters. */
+  private char[] chars;
+
+  public DistinguishedNameParser(X500Principal principal) {
+    // RFC2253 is used to ensure we get attributes in the reverse
+    // order of the underlying ASN.1 encoding, so that the most
+    // significant values of repeated attributes occur first.
+    this.dn = principal.getName(X500Principal.RFC2253);
+    this.length = this.dn.length();
+  }
+
+  // gets next attribute type: (ALPHA 1*keychar) / oid
+  private String nextAT() {
+    // skip preceding space chars, they can present after
+    // comma or semicolon (compatibility with RFC 1779)
+    for (; pos < length && chars[pos] == ' '; pos++) {
+    }
+    if (pos == length) {
+      return null; // reached the end of DN
+    }
+
+    // mark the beginning of attribute type
+    beg = pos;
+
+    // attribute type chars
+    pos++;
+    for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
+      // we don't follow exact BNF syntax here:
+      // accept any char except space and '='
+    }
+    if (pos >= length) {
+      throw new IllegalStateException("Unexpected end of DN: " + dn);
+    }
+
+    // mark the end of attribute type
+    end = pos;
+
+    // skip trailing space chars between attribute type and '='
+    // (compatibility with RFC 1779)
+    if (chars[pos] == ' ') {
+      for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {
+      }
+
+      if (chars[pos] != '=' || pos == length) {
+        throw new IllegalStateException("Unexpected end of DN: " + dn);
+      }
+    }
+
+    pos++; //skip '=' char
+
+    // skip space chars between '=' and attribute value
+    // (compatibility with RFC 1779)
+    for (; pos < length && chars[pos] == ' '; pos++) {
+    }
+
+    // in case of oid attribute type skip its prefix: "oid." or "OID."
+    // (compatibility with RFC 1779)
+    if ((end - beg > 4) && (chars[beg + 3] == '.')
+        && (chars[beg] == 'O' || chars[beg] == 'o')
+        && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
+        && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
+      beg += 4;
+    }
+
+    return new String(chars, beg, end - beg);
+  }
+
+  // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
+  private String quotedAV() {
+    pos++;
+    beg = pos;
+    end = beg;
+    while (true) {
+
+      if (pos == length) {
+        throw new IllegalStateException("Unexpected end of DN: " + dn);
+      }
+
+      if (chars[pos] == '"') {
+        // enclosing quotation was found
+        pos++;
+        break;
+      } else if (chars[pos] == '\\') {
+        chars[end] = getEscaped();
+      } else {
+        // shift char: required for string with escaped chars
+        chars[end] = chars[pos];
+      }
+      pos++;
+      end++;
+    }
+
+    // skip trailing space chars before comma or semicolon.
+    // (compatibility with RFC 1779)
+    for (; pos < length && chars[pos] == ' '; pos++) {
+    }
+
+    return new String(chars, beg, end - beg);
+  }
+
+  // gets hex string attribute value: "#" hexstring
+  private String hexAV() {
+    if (pos + 4 >= length) {
+      // encoded byte array  must be not less then 4 c
+      throw new IllegalStateException("Unexpected end of DN: " + dn);
+    }
+
+    beg = pos; // store '#' position
+    pos++;
+    while (true) {
+
+      // check for end of attribute value
+      // looks for space and component separators
+      if (pos == length || chars[pos] == '+' || chars[pos] == ','
+          || chars[pos] == ';') {
+        end = pos;
+        break;
+      }
+
+      if (chars[pos] == ' ') {
+        end = pos;
+        pos++;
+        // skip trailing space chars before comma or semicolon.
+        // (compatibility with RFC 1779)
+        for (; pos < length && chars[pos] == ' '; pos++) {
+        }
+        break;
+      } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
+        chars[pos] += 32; //to low case
+      }
+
+      pos++;
+    }
+
+    // verify length of hex string
+    // encoded byte array  must be not less then 4 and must be even number
+    int hexLen = end - beg; // skip first '#' char
+    if (hexLen < 5 || (hexLen & 1) == 0) {
+      throw new IllegalStateException("Unexpected end of DN: " + dn);
+    }
+
+    // get byte encoding from string representation
+    byte[] encoded = new byte[hexLen / 2];
+    for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
+      encoded[i] = (byte) getByte(p);
+    }
+
+    return new String(chars, beg, hexLen);
+  }
+
+  // gets string attribute value: *( stringchar / pair )
+  private String escapedAV() {
+    beg = pos;
+    end = pos;
+    while (true) {
+      if (pos >= length) {
+        // the end of DN has been found
+        return new String(chars, beg, end - beg);
+      }
+
+      switch (chars[pos]) {
+        case '+':
+        case ',':
+        case ';':
+          // separator char has been found
+          return new String(chars, beg, end - beg);
+        case '\\':
+          // escaped char
+          chars[end++] = getEscaped();
+          pos++;
+          break;
+        case ' ':
+          // need to figure out whether space defines
+          // the end of attribute value or not
+          cur = end;
+
+          pos++;
+          chars[end++] = ' ';
+
+          for (; pos < length && chars[pos] == ' '; pos++) {
+            chars[end++] = ' ';
+          }
+          if (pos == length || chars[pos] == ',' || chars[pos] == '+'
+              || chars[pos] == ';') {
+            // separator char or the end of DN has been found
+            return new String(chars, beg, cur - beg);
+          }
+          break;
+        default:
+          chars[end++] = chars[pos];
+          pos++;
+      }
+    }
+  }
+
+  // returns escaped char
+  private char getEscaped() {
+    pos++;
+    if (pos == length) {
+      throw new IllegalStateException("Unexpected end of DN: " + dn);
+    }
+
+    switch (chars[pos]) {
+      case '"':
+      case '\\':
+      case ',':
+      case '=':
+      case '+':
+      case '<':
+      case '>':
+      case '#':
+      case ';':
+      case ' ':
+      case '*':
+      case '%':
+      case '_':
+        //FIXME: escaping is allowed only for leading or trailing space char
+        return chars[pos];
+      default:
+        // RFC doesn't explicitly say that escaped hex pair is
+        // interpreted as UTF-8 char. It only contains an example of such DN.
+        return getUTF8();
+    }
+  }
+
+  // decodes UTF-8 char
+  // see http://www.unicode.org for UTF-8 bit distribution table
+  private char getUTF8() {
+    int res = getByte(pos);
+    pos++; //FIXME tmp
+
+    if (res < 128) { // one byte: 0-7F
+      return (char) res;
+    } else if (res >= 192 && res <= 247) {
+
+      int count;
+      if (res <= 223) { // two bytes: C0-DF
+        count = 1;
+        res = res & 0x1F;
+      } else if (res <= 239) { // three bytes: E0-EF
+        count = 2;
+        res = res & 0x0F;
+      } else { // four bytes: F0-F7
+        count = 3;
+        res = res & 0x07;
+      }
+
+      int b;
+      for (int i = 0; i < count; i++) {
+        pos++;
+        if (pos == length || chars[pos] != '\\') {
+          return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+        }
+        pos++;
+
+        b = getByte(pos);
+        pos++; //FIXME tmp
+        if ((b & 0xC0) != 0x80) {
+          return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+        }
+
+        res = (res << 6) + (b & 0x3F);
+      }
+      return (char) res;
+    } else {
+      return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+    }
+  }
+
+  // Returns byte representation of a char pair
+  // The char pair is composed of DN char in
+  // specified 'position' and the next char
+  // According to BNF syntax:
+  // hexchar    = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
+  //                    / "a" / "b" / "c" / "d" / "e" / "f"
+  private int getByte(int position) {
+    if (position + 1 >= length) {
+      throw new IllegalStateException("Malformed DN: " + dn);
+    }
+
+    int b1, b2;
+
+    b1 = chars[position];
+    if (b1 >= '0' && b1 <= '9') {
+      b1 = b1 - '0';
+    } else if (b1 >= 'a' && b1 <= 'f') {
+      b1 = b1 - 87; // 87 = 'a' - 10
+    } else if (b1 >= 'A' && b1 <= 'F') {
+      b1 = b1 - 55; // 55 = 'A' - 10
+    } else {
+      throw new IllegalStateException("Malformed DN: " + dn);
+    }
+
+    b2 = chars[position + 1];
+    if (b2 >= '0' && b2 <= '9') {
+      b2 = b2 - '0';
+    } else if (b2 >= 'a' && b2 <= 'f') {
+      b2 = b2 - 87; // 87 = 'a' - 10
+    } else if (b2 >= 'A' && b2 <= 'F') {
+      b2 = b2 - 55; // 55 = 'A' - 10
+    } else {
+      throw new IllegalStateException("Malformed DN: " + dn);
+    }
+
+    return (b1 << 4) + b2;
+  }
+
+  /**
+   * Parses the DN and returns the most significant attribute value
+   * for an attribute type, or null if none found.
+   *
+   * @param attributeType attribute type to look for (e.g. "ca")
+   */
+  public String findMostSpecific(String attributeType) {
+    // Initialize internal state.
+    pos = 0;
+    beg = 0;
+    end = 0;
+    cur = 0;
+    chars = dn.toCharArray();
+
+    String attType = nextAT();
+    if (attType == null) {
+      return null;
+    }
+    while (true) {
+      String attValue = "";
+
+      if (pos == length) {
+        return null;
+      }
+
+      switch (chars[pos]) {
+        case '"':
+          attValue = quotedAV();
+          break;
+        case '#':
+          attValue = hexAV();
+          break;
+        case '+':
+        case ',':
+        case ';': // compatibility with RFC 1779: semicolon can separate RDNs
+          //empty attribute value
+          break;
+        default:
+          attValue = escapedAV();
+      }
+
+      // Values are ordered from most specific to least specific
+      // due to the RFC2253 formatting. So take the first match
+      // we see.
+      if (attributeType.equalsIgnoreCase(attType)) {
+        return attValue;
+      }
+
+      if (pos >= length) {
+        return null;
+      }
+
+      if (chars[pos] == ',' || chars[pos] == ';') {
+      } else if (chars[pos] != '+') {
+        throw new IllegalStateException("Malformed DN: " + dn);
+      }
+
+      pos++;
+      attType = nextAT();
+      if (attType == null) {
+        throw new IllegalStateException("Malformed DN: " + dn);
+      }
+    }
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/OkHostnameVerifier.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/OkHostnameVerifier.java
new file mode 100644
index 0000000..3cdd1bf
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/OkHostnameVerifier.java
@@ -0,0 +1,257 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.android.okhttp.internal.tls;
+
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+
+/**
+ * A HostnameVerifier consistent with <a
+ * href="http://www.ietf.org/rfc/rfc2818.txt">RFC 2818</a>.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class OkHostnameVerifier implements HostnameVerifier {
+  public static final OkHostnameVerifier INSTANCE = new OkHostnameVerifier();
+
+  /**
+   * Quick and dirty pattern to differentiate IP addresses from hostnames. This
+   * is an approximation of Android's private InetAddress#isNumeric API.
+   *
+   * <p>This matches IPv6 addresses as a hex string containing at least one
+   * colon, and possibly including dots after the first colon. It matches IPv4
+   * addresses as strings containing only decimal digits and dots. This pattern
+   * matches strings like "a:.23" and "54" that are neither IP addresses nor
+   * hostnames; they will be verified as IP addresses (which is a more strict
+   * verification).
+   */
+  private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile(
+      "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)");
+
+  private static final int ALT_DNS_NAME = 2;
+  private static final int ALT_IPA_NAME = 7;
+
+  private OkHostnameVerifier() {
+  }
+
+  @Override
+  public boolean verify(String host, SSLSession session) {
+    try {
+      Certificate[] certificates = session.getPeerCertificates();
+      return verify(host, (X509Certificate) certificates[0]);
+    } catch (SSLException e) {
+      return false;
+    }
+  }
+
+  public boolean verify(String host, X509Certificate certificate) {
+    return verifyAsIpAddress(host)
+        ? verifyIpAddress(host, certificate)
+        : verifyHostName(host, certificate);
+  }
+
+  static boolean verifyAsIpAddress(String host) {
+    return VERIFY_AS_IP_ADDRESS.matcher(host).matches();
+  }
+
+  /**
+   * Returns true if {@code certificate} matches {@code ipAddress}.
+   */
+  private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) {
+    List<String> altNames = getSubjectAltNames(certificate, ALT_IPA_NAME);
+    for (int i = 0, size = altNames.size(); i < size; i++) {
+      if (ipAddress.equalsIgnoreCase(altNames.get(i))) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if {@code certificate} matches {@code hostName}.
+   */
+  private boolean verifyHostName(String hostName, X509Certificate certificate) {
+    hostName = hostName.toLowerCase(Locale.US);
+    boolean hasDns = false;
+    List<String> altNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
+    for (int i = 0, size = altNames.size(); i < size; i++) {
+      hasDns = true;
+      if (verifyHostName(hostName, altNames.get(i))) {
+        return true;
+      }
+    }
+
+    // BEGIN Android-removed: Ignore common name in hostname verification. http://b/70278814
+    /*
+    if (!hasDns) {
+      X500Principal principal = certificate.getSubjectX500Principal();
+      // RFC 2818 advises using the most specific name for matching.
+      String cn = new DistinguishedNameParser(principal).findMostSpecific("cn");
+      if (cn != null) {
+        return verifyHostName(hostName, cn);
+      }
+    }
+    */
+    // END Android-removed: Ignore common name in hostname verification. http://b/70278814
+
+    return false;
+  }
+
+  public static List<String> allSubjectAltNames(X509Certificate certificate) {
+    List<String> altIpaNames = getSubjectAltNames(certificate, ALT_IPA_NAME);
+    List<String> altDnsNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
+    List<String> result = new ArrayList<>(altIpaNames.size() + altDnsNames.size());
+    result.addAll(altIpaNames);
+    result.addAll(altDnsNames);
+    return result;
+  }
+
+  private static List<String> getSubjectAltNames(X509Certificate certificate, int type) {
+    List<String> result = new ArrayList<>();
+    try {
+      Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames();
+      if (subjectAltNames == null) {
+        return Collections.emptyList();
+      }
+      for (Object subjectAltName : subjectAltNames) {
+        List<?> entry = (List<?>) subjectAltName;
+        if (entry == null || entry.size() < 2) {
+          continue;
+        }
+        Integer altNameType = (Integer) entry.get(0);
+        if (altNameType == null) {
+          continue;
+        }
+        if (altNameType == type) {
+          String altName = (String) entry.get(1);
+          if (altName != null) {
+            result.add(altName);
+          }
+        }
+      }
+      return result;
+    } catch (CertificateParsingException e) {
+      return Collections.emptyList();
+    }
+  }
+
+  /**
+   * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern}.
+   *
+   * @param hostName lower-case host name.
+   * @param pattern domain name pattern from certificate. May be a wildcard pattern such as
+   *        {@code *.android.com}.
+   */
+  private boolean verifyHostName(String hostName, String pattern) {
+    // Basic sanity checks
+    // Check length == 0 instead of .isEmpty() to support Java 5.
+    if ((hostName == null) || (hostName.length() == 0) || (hostName.startsWith("."))
+        || (hostName.endsWith(".."))) {
+      // Invalid domain name
+      return false;
+    }
+    if ((pattern == null) || (pattern.length() == 0) || (pattern.startsWith("."))
+        || (pattern.endsWith(".."))) {
+      // Invalid pattern/domain name
+      return false;
+    }
+
+    // Normalize hostName and pattern by turning them into absolute domain names if they are not
+    // yet absolute. This is needed because server certificates do not normally contain absolute
+    // names or patterns, but they should be treated as absolute. At the same time, any hostName
+    // presented to this method should also be treated as absolute for the purposes of matching
+    // to the server certificate.
+    //   www.android.com  matches www.android.com
+    //   www.android.com  matches www.android.com.
+    //   www.android.com. matches www.android.com.
+    //   www.android.com. matches www.android.com
+    if (!hostName.endsWith(".")) {
+      hostName += '.';
+    }
+    if (!pattern.endsWith(".")) {
+      pattern += '.';
+    }
+    // hostName and pattern are now absolute domain names.
+
+    pattern = pattern.toLowerCase(Locale.US);
+    // hostName and pattern are now in lower case -- domain names are case-insensitive.
+
+    if (!pattern.contains("*")) {
+      // Not a wildcard pattern -- hostName and pattern must match exactly.
+      return hostName.equals(pattern);
+    }
+    // Wildcard pattern
+
+    // WILDCARD PATTERN RULES:
+    // 1. Asterisk (*) is only permitted in the left-most domain name label and must be the
+    //    only character in that label (i.e., must match the whole left-most label).
+    //    For example, *.example.com is permitted, while *a.example.com, a*.example.com,
+    //    a*b.example.com, a.*.example.com are not permitted.
+    // 2. Asterisk (*) cannot match across domain name labels.
+    //    For example, *.example.com matches test.example.com but does not match
+    //    sub.test.example.com.
+    // 3. Wildcard patterns for single-label domain names are not permitted.
+
+    if ((!pattern.startsWith("*.")) || (pattern.indexOf('*', 1) != -1)) {
+      // Asterisk (*) is only permitted in the left-most domain name label and must be the only
+      // character in that label
+      return false;
+    }
+
+    // Optimization: check whether hostName is too short to match the pattern. hostName must be at
+    // least as long as the pattern because asterisk must match the whole left-most label and
+    // hostName starts with a non-empty label. Thus, asterisk has to match one or more characters.
+    if (hostName.length() < pattern.length()) {
+      // hostName too short to match the pattern.
+      return false;
+    }
+
+    if ("*.".equals(pattern)) {
+      // Wildcard pattern for single-label domain name -- not permitted.
+      return false;
+    }
+
+    // hostName must end with the region of pattern following the asterisk.
+    String suffix = pattern.substring(1);
+    if (!hostName.endsWith(suffix)) {
+      // hostName does not end with the suffix
+      return false;
+    }
+
+    // Check that asterisk did not match across domain name labels.
+    int suffixStartIndexInHostName = hostName.length() - suffix.length();
+    if ((suffixStartIndexInHostName > 0)
+        && (hostName.lastIndexOf('.', suffixStartIndexInHostName - 1) != -1)) {
+      // Asterisk is matching across domain name labels -- not permitted.
+      return false;
+    }
+
+    // hostName matches pattern
+    return true;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/RealTrustRootIndex.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/RealTrustRootIndex.java
new file mode 100644
index 0000000..f4696a5
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/RealTrustRootIndex.java
@@ -0,0 +1,62 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.tls;
+
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class RealTrustRootIndex implements TrustRootIndex {
+  private final Map<X500Principal, List<X509Certificate>> subjectToCaCerts;
+
+  public RealTrustRootIndex(X509Certificate... caCerts) {
+    subjectToCaCerts = new LinkedHashMap<>();
+    for (X509Certificate caCert : caCerts) {
+      X500Principal subject = caCert.getSubjectX500Principal();
+      List<X509Certificate> subjectCaCerts = subjectToCaCerts.get(subject);
+      if (subjectCaCerts == null) {
+        subjectCaCerts = new ArrayList<>(1);
+        subjectToCaCerts.put(subject, subjectCaCerts);
+      }
+      subjectCaCerts.add(caCert);
+    }
+  }
+
+  @Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) {
+    X500Principal issuer = cert.getIssuerX500Principal();
+    List<X509Certificate> subjectCaCerts = subjectToCaCerts.get(issuer);
+    if (subjectCaCerts == null) return null;
+
+    for (X509Certificate caCert : subjectCaCerts) {
+      PublicKey publicKey = caCert.getPublicKey();
+      try {
+        cert.verify(publicKey);
+        return caCert;
+      } catch (Exception ignored) {
+      }
+    }
+
+    return null;
+  }
+}
diff --git a/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/TrustRootIndex.java b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/TrustRootIndex.java
new file mode 100644
index 0000000..86111a8
--- /dev/null
+++ b/repackaged/okhttp/src/main/java/com/android/okhttp/internal/tls/TrustRootIndex.java
@@ -0,0 +1,27 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * 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 com.android.okhttp.internal.tls;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface TrustRootIndex {
+  /** Returns the trusted CA certificate that signed {@code cert}. */
+  X509Certificate findByIssuerAndSignature(X509Certificate cert);
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/AsyncTimeout.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/AsyncTimeout.java
new file mode 100644
index 0000000..e801ae7
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/AsyncTimeout.java
@@ -0,0 +1,334 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+/**
+ * This timeout uses a background thread to take action exactly when the timeout
+ * occurs. Use this to implement timeouts where they aren't supported natively,
+ * such as to sockets that are blocked on writing.
+ *
+ * <p>Subclasses should override {@link #timedOut} to take action when a timeout
+ * occurs. This method will be invoked by the shared watchdog thread so it
+ * should not do any long-running operations. Otherwise we risk starving other
+ * timeouts from being triggered.
+ *
+ * <p>Use {@link #sink} and {@link #source} to apply this timeout to a stream.
+ * The returned value will apply the timeout to each operation on the wrapped
+ * stream.
+ *
+ * <p>Callers should call {@link #enter} before doing work that is subject to
+ * timeouts, and {@link #exit} afterwards. The return value of {@link #exit}
+ * indicates whether a timeout was triggered. Note that the call to {@link
+ * #timedOut} is asynchronous, and may be called after {@link #exit}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class AsyncTimeout extends Timeout {
+  /**
+   * The watchdog thread processes a linked list of pending timeouts, sorted in
+   * the order to be triggered. This class synchronizes on AsyncTimeout.class.
+   * This lock guards the queue.
+   *
+   * <p>Head's 'next' points to the first element of the linked list. The first
+   * element is the next node to time out, or null if the queue is empty. The
+   * head is null until the watchdog thread is started.
+   */
+  private static AsyncTimeout head;
+
+  /** True if this node is currently in the queue. */
+  private boolean inQueue;
+
+  /** The next node in the linked list. */
+  private AsyncTimeout next;
+
+  /** If scheduled, this is the time that the watchdog should time this out. */
+  private long timeoutAt;
+
+  public final void enter() {
+    if (inQueue) throw new IllegalStateException("Unbalanced enter/exit");
+    long timeoutNanos = timeoutNanos();
+    boolean hasDeadline = hasDeadline();
+    if (timeoutNanos == 0 && !hasDeadline) {
+      return; // No timeout and no deadline? Don't bother with the queue.
+    }
+    inQueue = true;
+    scheduleTimeout(this, timeoutNanos, hasDeadline);
+  }
+
+  private static synchronized void scheduleTimeout(
+      AsyncTimeout node, long timeoutNanos, boolean hasDeadline) {
+    // Start the watchdog thread and create the head node when the first timeout is scheduled.
+    if (head == null) {
+      head = new AsyncTimeout();
+      new Watchdog().start();
+    }
+
+    long now = System.nanoTime();
+    if (timeoutNanos != 0 && hasDeadline) {
+      // Compute the earliest event; either timeout or deadline. Because nanoTime can wrap around,
+      // Math.min() is undefined for absolute values, but meaningful for relative ones.
+      node.timeoutAt = now + Math.min(timeoutNanos, node.deadlineNanoTime() - now);
+    } else if (timeoutNanos != 0) {
+      node.timeoutAt = now + timeoutNanos;
+    } else if (hasDeadline) {
+      node.timeoutAt = node.deadlineNanoTime();
+    } else {
+      throw new AssertionError();
+    }
+
+    // Insert the node in sorted order.
+    long remainingNanos = node.remainingNanos(now);
+    for (AsyncTimeout prev = head; true; prev = prev.next) {
+      if (prev.next == null || remainingNanos < prev.next.remainingNanos(now)) {
+        node.next = prev.next;
+        prev.next = node;
+        if (prev == head) {
+          AsyncTimeout.class.notify(); // Wake up the watchdog when inserting at the front.
+        }
+        break;
+      }
+    }
+  }
+
+  /** Returns true if the timeout occurred. */
+  public final boolean exit() {
+    if (!inQueue) return false;
+    inQueue = false;
+    return cancelScheduledTimeout(this);
+  }
+
+  /** Returns true if the timeout occurred. */
+  private static synchronized boolean cancelScheduledTimeout(AsyncTimeout node) {
+    // Remove the node from the linked list.
+    for (AsyncTimeout prev = head; prev != null; prev = prev.next) {
+      if (prev.next == node) {
+        prev.next = node.next;
+        node.next = null;
+        return false;
+      }
+    }
+
+    // The node wasn't found in the linked list: it must have timed out!
+    return true;
+  }
+
+  /**
+   * Returns the amount of time left until the time out. This will be negative
+   * if the timeout has elapsed and the timeout should occur immediately.
+   */
+  private long remainingNanos(long now) {
+    return timeoutAt - now;
+  }
+
+  /**
+   * Invoked by the watchdog thread when the time between calls to {@link
+   * #enter()} and {@link #exit()} has exceeded the timeout.
+   */
+  protected void timedOut() {
+  }
+
+  /**
+   * Returns a new sink that delegates to {@code sink}, using this to implement
+   * timeouts. This works best if {@link #timedOut} is overridden to interrupt
+   * {@code sink}'s current operation.
+   */
+  public final Sink sink(final Sink sink) {
+    return new Sink() {
+      @Override public void write(Buffer source, long byteCount) throws IOException {
+        boolean throwOnTimeout = false;
+        enter();
+        try {
+          sink.write(source, byteCount);
+          throwOnTimeout = true;
+        } catch (IOException e) {
+          throw exit(e);
+        } finally {
+          exit(throwOnTimeout);
+        }
+      }
+
+      @Override public void flush() throws IOException {
+        boolean throwOnTimeout = false;
+        enter();
+        try {
+          sink.flush();
+          throwOnTimeout = true;
+        } catch (IOException e) {
+          throw exit(e);
+        } finally {
+          exit(throwOnTimeout);
+        }
+      }
+
+      @Override public void close() throws IOException {
+        boolean throwOnTimeout = false;
+        enter();
+        try {
+          sink.close();
+          throwOnTimeout = true;
+        } catch (IOException e) {
+          throw exit(e);
+        } finally {
+          exit(throwOnTimeout);
+        }
+      }
+
+      @Override public Timeout timeout() {
+        return AsyncTimeout.this;
+      }
+
+      @Override public String toString() {
+        return "AsyncTimeout.sink(" + sink + ")";
+      }
+    };
+  }
+
+  /**
+   * Returns a new source that delegates to {@code source}, using this to
+   * implement timeouts. This works best if {@link #timedOut} is overridden to
+   * interrupt {@code sink}'s current operation.
+   */
+  public final Source source(final Source source) {
+    return new Source() {
+      @Override public long read(Buffer sink, long byteCount) throws IOException {
+        boolean throwOnTimeout = false;
+        enter();
+        try {
+          long result = source.read(sink, byteCount);
+          throwOnTimeout = true;
+          return result;
+        } catch (IOException e) {
+          throw exit(e);
+        } finally {
+          exit(throwOnTimeout);
+        }
+      }
+
+      @Override public void close() throws IOException {
+        boolean throwOnTimeout = false;
+        try {
+          source.close();
+          throwOnTimeout = true;
+        } catch (IOException e) {
+          throw exit(e);
+        } finally {
+          exit(throwOnTimeout);
+        }
+      }
+
+      @Override public Timeout timeout() {
+        return AsyncTimeout.this;
+      }
+
+      @Override public String toString() {
+        return "AsyncTimeout.source(" + source + ")";
+      }
+    };
+  }
+
+  /**
+   * Throws an IOException if {@code throwOnTimeout} is {@code true} and a
+   * timeout occurred. See {@link #newTimeoutException(java.io.IOException)}
+   * for the type of exception thrown.
+   */
+  final void exit(boolean throwOnTimeout) throws IOException {
+    boolean timedOut = exit();
+    if (timedOut && throwOnTimeout) throw newTimeoutException(null);
+  }
+
+  /**
+   * Returns either {@code cause} or an IOException that's caused by
+   * {@code cause} if a timeout occurred. See
+   * {@link #newTimeoutException(java.io.IOException)} for the type of
+   * exception returned.
+   */
+  final IOException exit(IOException cause) throws IOException {
+    if (!exit()) return cause;
+    return newTimeoutException(cause);
+  }
+
+  /**
+   * Returns an {@link IOException} to represent a timeout. By default this method returns
+   * {@link java.io.InterruptedIOException}. If {@code cause} is non-null it is set as the cause of
+   * the returned exception.
+   */
+  protected IOException newTimeoutException(IOException cause) {
+    InterruptedIOException e = new InterruptedIOException("timeout");
+    if (cause != null) {
+      e.initCause(cause);
+    }
+    return e;
+  }
+
+  private static final class Watchdog extends Thread {
+    public Watchdog() {
+      super("Okio Watchdog");
+      setDaemon(true);
+    }
+
+    public void run() {
+      while (true) {
+        try {
+          AsyncTimeout timedOut = awaitTimeout();
+
+          // Didn't find a node to interrupt. Try again.
+          if (timedOut == null) continue;
+
+          // Close the timed out node.
+          timedOut.timedOut();
+        } catch (InterruptedException ignored) {
+        }
+      }
+    }
+  }
+
+  /**
+   * Removes and returns the node at the head of the list, waiting for it to
+   * time out if necessary. Returns null if the situation changes while waiting:
+   * either a newer node is inserted at the head, or the node being waited on
+   * has been removed.
+   */
+  private static synchronized AsyncTimeout awaitTimeout() throws InterruptedException {
+    // Get the next eligible node.
+    AsyncTimeout node = head.next;
+
+    // The queue is empty. Wait for something to be enqueued.
+    if (node == null) {
+      AsyncTimeout.class.wait();
+      return null;
+    }
+
+    long waitNanos = node.remainingNanos(System.nanoTime());
+
+    // The head of the queue hasn't timed out yet. Await that.
+    if (waitNanos > 0) {
+      // Waiting is made complicated by the fact that we work in nanoseconds,
+      // but the API wants (millis, nanos) in two arguments.
+      long waitMillis = waitNanos / 1000000L;
+      waitNanos -= (waitMillis * 1000000L);
+      AsyncTimeout.class.wait(waitMillis, (int) waitNanos);
+      return null;
+    }
+
+    // The head of the queue has timed out. Remove it.
+    head.next = node.next;
+    node.next = null;
+    return node;
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Base64.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Base64.java
new file mode 100644
index 0000000..3260c96
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Base64.java
@@ -0,0 +1,163 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  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.
+ */
+
+/**
+ * @author Alexander Y. Kleymenov
+ */
+package com.android.okhttp.okio;
+
+import java.io.UnsupportedEncodingException;
+
+final class Base64 {
+  private Base64() {
+  }
+
+  public static byte[] decode(String in) {
+    // Ignore trailing '=' padding and whitespace from the input.
+    int limit = in.length();
+    for (; limit > 0; limit--) {
+      char c = in.charAt(limit - 1);
+      if (c != '=' && c != '\n' && c != '\r' && c != ' ' && c != '\t') {
+        break;
+      }
+    }
+
+    // If the input includes whitespace, this output array will be longer than necessary.
+    byte[] out = new byte[(int) (limit * 6L / 8L)];
+    int outCount = 0;
+    int inCount = 0;
+
+    int word = 0;
+    for (int pos = 0; pos < limit; pos++) {
+      char c = in.charAt(pos);
+
+      int bits;
+      if (c >= 'A' && c <= 'Z') {
+        // char ASCII value
+        //  A    65    0
+        //  Z    90    25 (ASCII - 65)
+        bits = c - 65;
+      } else if (c >= 'a' && c <= 'z') {
+        // char ASCII value
+        //  a    97    26
+        //  z    122   51 (ASCII - 71)
+        bits = c - 71;
+      } else if (c >= '0' && c <= '9') {
+        // char ASCII value
+        //  0    48    52
+        //  9    57    61 (ASCII + 4)
+        bits = c + 4;
+      } else if (c == '+' || c == '-') {
+        bits = 62;
+      } else if (c == '/' || c == '_') {
+        bits = 63;
+      } else if (c == '\n' || c == '\r' || c == ' ' || c == '\t') {
+        continue;
+      } else {
+        return null;
+      }
+
+      // Append this char's 6 bits to the word.
+      word = (word << 6) | (byte) bits;
+
+      // For every 4 chars of input, we accumulate 24 bits of output. Emit 3 bytes.
+      inCount++;
+      if (inCount % 4 == 0) {
+        out[outCount++] = (byte) (word >> 16);
+        out[outCount++] = (byte) (word >> 8);
+        out[outCount++] = (byte) word;
+      }
+    }
+
+    int lastWordChars = inCount % 4;
+    if (lastWordChars == 1) {
+      // We read 1 char followed by "===". But 6 bits is a truncated byte! Fail.
+      return null;
+    } else if (lastWordChars == 2) {
+      // We read 2 chars followed by "==". Emit 1 byte with 8 of those 12 bits.
+      word = word << 12;
+      out[outCount++] = (byte) (word >> 16);
+    } else if (lastWordChars == 3) {
+      // We read 3 chars, followed by "=". Emit 2 bytes for 16 of those 18 bits.
+      word = word << 6;
+      out[outCount++] = (byte) (word >> 16);
+      out[outCount++] = (byte) (word >> 8);
+    }
+
+    // If we sized our out array perfectly, we're done.
+    if (outCount == out.length) return out;
+
+    // Copy the decoded bytes to a new, right-sized array.
+    byte[] prefix = new byte[outCount];
+    System.arraycopy(out, 0, prefix, 0, outCount);
+    return prefix;
+  }
+
+  private static final byte[] MAP = new byte[] {
+      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+      'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+      'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
+      '5', '6', '7', '8', '9', '+', '/'
+  };
+
+  private static final byte[] URL_MAP = new byte[] {
+      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+      'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+      'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
+      '5', '6', '7', '8', '9', '-', '_'
+  };
+
+  public static String encode(byte[] in) {
+    return encode(in, MAP);
+  }
+
+  public static String encodeUrl(byte[] in) {
+    return encode(in, URL_MAP);
+  }
+
+  private static String encode(byte[] in, byte[] map) {
+    int length = (in.length + 2) * 4 / 3;
+    byte[] out = new byte[length];
+    int index = 0, end = in.length - in.length % 3;
+    for (int i = 0; i < end; i += 3) {
+      out[index++] = map[(in[i] & 0xff) >> 2];
+      out[index++] = map[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)];
+      out[index++] = map[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)];
+      out[index++] = map[(in[i + 2] & 0x3f)];
+    }
+    switch (in.length % 3) {
+      case 1:
+        out[index++] = map[(in[end] & 0xff) >> 2];
+        out[index++] = map[(in[end] & 0x03) << 4];
+        out[index++] = '=';
+        out[index++] = '=';
+        break;
+      case 2:
+        out[index++] = map[(in[end] & 0xff) >> 2];
+        out[index++] = map[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)];
+        out[index++] = map[((in[end + 1] & 0x0f) << 2)];
+        out[index++] = '=';
+        break;
+    }
+    try {
+      return new String(out, 0, index, "US-ASCII");
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError(e);
+    }
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Buffer.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Buffer.java
new file mode 100644
index 0000000..04752d8
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Buffer.java
@@ -0,0 +1,1439 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+import static com.android.okhttp.okio.Util.reverseBytesLong;
+
+/**
+ * A collection of bytes in memory.
+ *
+ * <p><strong>Moving data from one buffer to another is fast.</strong> Instead
+ * of copying bytes from one place in memory to another, this class just changes
+ * ownership of the underlying byte arrays.
+ *
+ * <p><strong>This buffer grows with your data.</strong> Just like ArrayList,
+ * each buffer starts small. It consumes only the memory it needs to.
+ *
+ * <p><strong>This buffer pools its byte arrays.</strong> When you allocate a
+ * byte array in Java, the runtime must zero-fill the requested array before
+ * returning it to you. Even if you're going to write over that space anyway.
+ * This class avoids zero-fill and GC churn by pooling byte arrays.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class Buffer implements BufferedSource, BufferedSink, Cloneable {
+  private static final byte[] DIGITS =
+      { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+  static final int REPLACEMENT_CHARACTER = '\ufffd';
+
+  Segment head;
+  long size;
+
+  public Buffer() {
+  }
+
+  /** Returns the number of bytes currently in this buffer. */
+  public long size() {
+    return size;
+  }
+
+  @Override public Buffer buffer() {
+    return this;
+  }
+
+  @Override public OutputStream outputStream() {
+    return new OutputStream() {
+      @Override public void write(int b) {
+        writeByte((byte) b);
+      }
+
+      @Override public void write(byte[] data, int offset, int byteCount) {
+        Buffer.this.write(data, offset, byteCount);
+      }
+
+      @Override public void flush() {
+      }
+
+      @Override public void close() {
+      }
+
+      @Override public String toString() {
+        return this + ".outputStream()";
+      }
+    };
+  }
+
+  @Override public Buffer emitCompleteSegments() {
+    return this; // Nowhere to emit to!
+  }
+
+  @Override public BufferedSink emit() {
+    return this; // Nowhere to emit to!
+  }
+
+  @Override public boolean exhausted() {
+    return size == 0;
+  }
+
+  @Override public void require(long byteCount) throws EOFException {
+    if (size < byteCount) throw new EOFException();
+  }
+
+  @Override public boolean request(long byteCount) {
+    return size >= byteCount;
+  }
+
+  @Override public InputStream inputStream() {
+    return new InputStream() {
+      @Override public int read() {
+        if (size > 0) return readByte() & 0xff;
+        return -1;
+      }
+
+      @Override public int read(byte[] sink, int offset, int byteCount) {
+        return Buffer.this.read(sink, offset, byteCount);
+      }
+
+      @Override public int available() {
+        return (int) Math.min(size, Integer.MAX_VALUE);
+      }
+
+      @Override public void close() {
+      }
+
+      @Override public String toString() {
+        return Buffer.this + ".inputStream()";
+      }
+    };
+  }
+
+  /** Copy the contents of this to {@code out}. */
+  public Buffer copyTo(OutputStream out) throws IOException {
+    return copyTo(out, 0, size);
+  }
+
+  /**
+   * Copy {@code byteCount} bytes from this, starting at {@code offset}, to
+   * {@code out}.
+   */
+  public Buffer copyTo(OutputStream out, long offset, long byteCount) throws IOException {
+    if (out == null) throw new IllegalArgumentException("out == null");
+    checkOffsetAndCount(size, offset, byteCount);
+    if (byteCount == 0) return this;
+
+    // Skip segments that we aren't copying from.
+    Segment s = head;
+    for (; offset >= (s.limit - s.pos); s = s.next) {
+      offset -= (s.limit - s.pos);
+    }
+
+    // Copy from one segment at a time.
+    for (; byteCount > 0; s = s.next) {
+      int pos = (int) (s.pos + offset);
+      int toCopy = (int) Math.min(s.limit - pos, byteCount);
+      out.write(s.data, pos, toCopy);
+      byteCount -= toCopy;
+      offset = 0;
+    }
+
+    return this;
+  }
+
+  /** Copy {@code byteCount} bytes from this, starting at {@code offset}, to {@code out}. */
+  public Buffer copyTo(Buffer out, long offset, long byteCount) {
+    if (out == null) throw new IllegalArgumentException("out == null");
+    checkOffsetAndCount(size, offset, byteCount);
+    if (byteCount == 0) return this;
+
+    out.size += byteCount;
+
+    // Skip segments that we aren't copying from.
+    Segment s = head;
+    for (; offset >= (s.limit - s.pos); s = s.next) {
+      offset -= (s.limit - s.pos);
+    }
+
+    // Copy one segment at a time.
+    for (; byteCount > 0; s = s.next) {
+      Segment copy = new Segment(s);
+      copy.pos += offset;
+      copy.limit = Math.min(copy.pos + (int) byteCount, copy.limit);
+      if (out.head == null) {
+        out.head = copy.next = copy.prev = copy;
+      } else {
+        out.head.prev.push(copy);
+      }
+      byteCount -= copy.limit - copy.pos;
+      offset = 0;
+    }
+
+    return this;
+  }
+
+  /** Write the contents of this to {@code out}. */
+  public Buffer writeTo(OutputStream out) throws IOException {
+    return writeTo(out, size);
+  }
+
+  /** Write {@code byteCount} bytes from this to {@code out}. */
+  public Buffer writeTo(OutputStream out, long byteCount) throws IOException {
+    if (out == null) throw new IllegalArgumentException("out == null");
+    checkOffsetAndCount(size, 0, byteCount);
+
+    Segment s = head;
+    while (byteCount > 0) {
+      int toCopy = (int) Math.min(byteCount, s.limit - s.pos);
+      out.write(s.data, s.pos, toCopy);
+
+      s.pos += toCopy;
+      size -= toCopy;
+      byteCount -= toCopy;
+
+      if (s.pos == s.limit) {
+        Segment toRecycle = s;
+        head = s = toRecycle.pop();
+        SegmentPool.recycle(toRecycle);
+      }
+    }
+
+    return this;
+  }
+
+  /** Read and exhaust bytes from {@code in} to this. */
+  public Buffer readFrom(InputStream in) throws IOException {
+    readFrom(in, Long.MAX_VALUE, true);
+    return this;
+  }
+
+  /** Read {@code byteCount} bytes from {@code in} to this. */
+  public Buffer readFrom(InputStream in, long byteCount) throws IOException {
+    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+    readFrom(in, byteCount, false);
+    return this;
+  }
+
+  private void readFrom(InputStream in, long byteCount, boolean forever) throws IOException {
+    if (in == null) throw new IllegalArgumentException("in == null");
+    while (byteCount > 0 || forever) {
+      Segment tail = writableSegment(1);
+      int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
+      int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
+      if (bytesRead == -1) {
+        if (forever) return;
+        throw new EOFException();
+      }
+      tail.limit += bytesRead;
+      size += bytesRead;
+      byteCount -= bytesRead;
+    }
+  }
+
+  /**
+   * Returns the number of bytes in segments that are not writable. This is the
+   * number of bytes that can be flushed immediately to an underlying sink
+   * without harming throughput.
+   */
+  public long completeSegmentByteCount() {
+    long result = size;
+    if (result == 0) return 0;
+
+    // Omit the tail if it's still writable.
+    Segment tail = head.prev;
+    if (tail.limit < Segment.SIZE && tail.owner) {
+      result -= tail.limit - tail.pos;
+    }
+
+    return result;
+  }
+
+  @Override public byte readByte() {
+    if (size == 0) throw new IllegalStateException("size == 0");
+
+    Segment segment = head;
+    int pos = segment.pos;
+    int limit = segment.limit;
+
+    byte[] data = segment.data;
+    byte b = data[pos++];
+    size -= 1;
+
+    if (pos == limit) {
+      head = segment.pop();
+      SegmentPool.recycle(segment);
+    } else {
+      segment.pos = pos;
+    }
+
+    return b;
+  }
+
+  /** Returns the byte at {@code pos}. */
+  public byte getByte(long pos) {
+    checkOffsetAndCount(size, pos, 1);
+    for (Segment s = head; true; s = s.next) {
+      int segmentByteCount = s.limit - s.pos;
+      if (pos < segmentByteCount) return s.data[s.pos + (int) pos];
+      pos -= segmentByteCount;
+    }
+  }
+
+  @Override public short readShort() {
+    if (size < 2) throw new IllegalStateException("size < 2: " + size);
+
+    Segment segment = head;
+    int pos = segment.pos;
+    int limit = segment.limit;
+
+    // If the short is split across multiple segments, delegate to readByte().
+    if (limit - pos < 2) {
+      int s = (readByte() & 0xff) << 8
+          |   (readByte() & 0xff);
+      return (short) s;
+    }
+
+    byte[] data = segment.data;
+    int s = (data[pos++] & 0xff) << 8
+        |   (data[pos++] & 0xff);
+    size -= 2;
+
+    if (pos == limit) {
+      head = segment.pop();
+      SegmentPool.recycle(segment);
+    } else {
+      segment.pos = pos;
+    }
+
+    return (short) s;
+  }
+
+  @Override public int readInt() {
+    if (size < 4) throw new IllegalStateException("size < 4: " + size);
+
+    Segment segment = head;
+    int pos = segment.pos;
+    int limit = segment.limit;
+
+    // If the int is split across multiple segments, delegate to readByte().
+    if (limit - pos < 4) {
+      return (readByte() & 0xff) << 24
+          |  (readByte() & 0xff) << 16
+          |  (readByte() & 0xff) <<  8
+          |  (readByte() & 0xff);
+    }
+
+    byte[] data = segment.data;
+    int i = (data[pos++] & 0xff) << 24
+        |   (data[pos++] & 0xff) << 16
+        |   (data[pos++] & 0xff) <<  8
+        |   (data[pos++] & 0xff);
+    size -= 4;
+
+    if (pos == limit) {
+      head = segment.pop();
+      SegmentPool.recycle(segment);
+    } else {
+      segment.pos = pos;
+    }
+
+    return i;
+  }
+
+  @Override public long readLong() {
+    if (size < 8) throw new IllegalStateException("size < 8: " + size);
+
+    Segment segment = head;
+    int pos = segment.pos;
+    int limit = segment.limit;
+
+    // If the long is split across multiple segments, delegate to readInt().
+    if (limit - pos < 8) {
+      return (readInt() & 0xffffffffL) << 32
+          |  (readInt() & 0xffffffffL);
+    }
+
+    byte[] data = segment.data;
+    long v = (data[pos++] & 0xffL) << 56
+        |    (data[pos++] & 0xffL) << 48
+        |    (data[pos++] & 0xffL) << 40
+        |    (data[pos++] & 0xffL) << 32
+        |    (data[pos++] & 0xffL) << 24
+        |    (data[pos++] & 0xffL) << 16
+        |    (data[pos++] & 0xffL) <<  8
+        |    (data[pos++] & 0xffL);
+    size -= 8;
+
+    if (pos == limit) {
+      head = segment.pop();
+      SegmentPool.recycle(segment);
+    } else {
+      segment.pos = pos;
+    }
+
+    return v;
+  }
+
+  @Override public short readShortLe() {
+    return Util.reverseBytesShort(readShort());
+  }
+
+  @Override public int readIntLe() {
+    return Util.reverseBytesInt(readInt());
+  }
+
+  @Override public long readLongLe() {
+    return Util.reverseBytesLong(readLong());
+  }
+
+  @Override public long readDecimalLong() {
+    if (size == 0) throw new IllegalStateException("size == 0");
+
+    // This value is always built negatively in order to accommodate Long.MIN_VALUE.
+    long value = 0;
+    int seen = 0;
+    boolean negative = false;
+    boolean done = false;
+
+    long overflowZone = Long.MIN_VALUE / 10;
+    long overflowDigit = (Long.MIN_VALUE % 10) + 1;
+
+    do {
+      Segment segment = head;
+
+      byte[] data = segment.data;
+      int pos = segment.pos;
+      int limit = segment.limit;
+
+      for (; pos < limit; pos++, seen++) {
+        byte b = data[pos];
+        if (b >= '0' && b <= '9') {
+          int digit = '0' - b;
+
+          // Detect when the digit would cause an overflow.
+          if (value < overflowZone || value == overflowZone && digit < overflowDigit) {
+            Buffer buffer = new Buffer().writeDecimalLong(value).writeByte(b);
+            if (!negative) buffer.readByte(); // Skip negative sign.
+            throw new NumberFormatException("Number too large: " + buffer.readUtf8());
+          }
+          value *= 10;
+          value += digit;
+        } else if (b == '-' && seen == 0) {
+          negative = true;
+          overflowDigit -= 1;
+        } else {
+          if (seen == 0) {
+            throw new NumberFormatException(
+                "Expected leading [0-9] or '-' character but was 0x" + Integer.toHexString(b));
+          }
+          // Set a flag to stop iteration. We still need to run through segment updating below.
+          done = true;
+          break;
+        }
+      }
+
+      if (pos == limit) {
+        head = segment.pop();
+        SegmentPool.recycle(segment);
+      } else {
+        segment.pos = pos;
+      }
+    } while (!done && head != null);
+
+    size -= seen;
+    return negative ? value : -value;
+  }
+
+  @Override public long readHexadecimalUnsignedLong() {
+    if (size == 0) throw new IllegalStateException("size == 0");
+
+    long value = 0;
+    int seen = 0;
+    boolean done = false;
+
+    do {
+      Segment segment = head;
+
+      byte[] data = segment.data;
+      int pos = segment.pos;
+      int limit = segment.limit;
+
+      for (; pos < limit; pos++, seen++) {
+        int digit;
+
+        byte b = data[pos];
+        if (b >= '0' && b <= '9') {
+          digit = b - '0';
+        } else if (b >= 'a' && b <= 'f') {
+          digit = b - 'a' + 10;
+        } else if (b >= 'A' && b <= 'F') {
+          digit = b - 'A' + 10; // We never write uppercase, but we support reading it.
+        } else {
+          if (seen == 0) {
+            throw new NumberFormatException(
+                "Expected leading [0-9a-fA-F] character but was 0x" + Integer.toHexString(b));
+          }
+          // Set a flag to stop iteration. We still need to run through segment updating below.
+          done = true;
+          break;
+        }
+
+        // Detect when the shift will overflow.
+        if ((value & 0xf000000000000000L) != 0) {
+          Buffer buffer = new Buffer().writeHexadecimalUnsignedLong(value).writeByte(b);
+          throw new NumberFormatException("Number too large: " + buffer.readUtf8());
+        }
+
+        value <<= 4;
+        value |= digit;
+      }
+
+      if (pos == limit) {
+        head = segment.pop();
+        SegmentPool.recycle(segment);
+      } else {
+        segment.pos = pos;
+      }
+    } while (!done && head != null);
+
+    size -= seen;
+    return value;
+  }
+
+  @Override public ByteString readByteString() {
+    return new ByteString(readByteArray());
+  }
+
+  @Override public ByteString readByteString(long byteCount) throws EOFException {
+    return new ByteString(readByteArray(byteCount));
+  }
+
+  @Override public void readFully(Buffer sink, long byteCount) throws EOFException {
+    if (size < byteCount) {
+      sink.write(this, size); // Exhaust ourselves.
+      throw new EOFException();
+    }
+    sink.write(this, byteCount);
+  }
+
+  @Override public long readAll(Sink sink) throws IOException {
+    long byteCount = size;
+    if (byteCount > 0) {
+      sink.write(this, byteCount);
+    }
+    return byteCount;
+  }
+
+  @Override public String readUtf8() {
+    try {
+      return readString(size, Util.UTF_8);
+    } catch (EOFException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  @Override public String readUtf8(long byteCount) throws EOFException {
+    return readString(byteCount, Util.UTF_8);
+  }
+
+  @Override public String readString(Charset charset) {
+    try {
+      return readString(size, charset);
+    } catch (EOFException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  @Override public String readString(long byteCount, Charset charset) throws EOFException {
+    checkOffsetAndCount(size, 0, byteCount);
+    if (charset == null) throw new IllegalArgumentException("charset == null");
+    if (byteCount > Integer.MAX_VALUE) {
+      throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
+    }
+    if (byteCount == 0) return "";
+
+    Segment s = head;
+    if (s.pos + byteCount > s.limit) {
+      // If the string spans multiple segments, delegate to readBytes().
+      return new String(readByteArray(byteCount), charset);
+    }
+
+    String result = new String(s.data, s.pos, (int) byteCount, charset);
+    s.pos += byteCount;
+    size -= byteCount;
+
+    if (s.pos == s.limit) {
+      head = s.pop();
+      SegmentPool.recycle(s);
+    }
+
+    return result;
+  }
+
+  @Override public String readUtf8Line() throws EOFException {
+    long newline = indexOf((byte) '\n');
+
+    if (newline == -1) {
+      return size != 0 ? readUtf8(size) : null;
+    }
+
+    return readUtf8Line(newline);
+  }
+
+  @Override public String readUtf8LineStrict() throws EOFException {
+    long newline = indexOf((byte) '\n');
+    if (newline == -1) {
+      Buffer data = new Buffer();
+      copyTo(data, 0, Math.min(32, size));
+      throw new EOFException("\\n not found: size=" + size()
+          + " content=" + data.readByteString().hex() + "...");
+    }
+    return readUtf8Line(newline);
+  }
+
+  String readUtf8Line(long newline) throws EOFException {
+    if (newline > 0 && getByte(newline - 1) == '\r') {
+      // Read everything until '\r\n', then skip the '\r\n'.
+      String result = readUtf8((newline - 1));
+      skip(2);
+      return result;
+
+    } else {
+      // Read everything until '\n', then skip the '\n'.
+      String result = readUtf8(newline);
+      skip(1);
+      return result;
+    }
+  }
+
+  @Override public int readUtf8CodePoint() throws EOFException {
+    if (size == 0) throw new EOFException();
+
+    byte b0 = getByte(0);
+    int codePoint;
+    int byteCount;
+    int min;
+
+    if ((b0 & 0x80) == 0) {
+      // 0xxxxxxx.
+      codePoint = b0 & 0x7f;
+      byteCount = 1; // 7 bits (ASCII).
+      min = 0x0;
+
+    } else if ((b0 & 0xe0) == 0xc0) {
+      // 0x110xxxxx
+      codePoint = b0 & 0x1f;
+      byteCount = 2; // 11 bits (5 + 6).
+      min = 0x80;
+
+    } else if ((b0 & 0xf0) == 0xe0) {
+      // 0x1110xxxx
+      codePoint = b0 & 0x0f;
+      byteCount = 3; // 16 bits (4 + 6 + 6).
+      min = 0x800;
+
+    } else if ((b0 & 0xf8) == 0xf0) {
+      // 0x11110xxx
+      codePoint = b0 & 0x07;
+      byteCount = 4; // 21 bits (3 + 6 + 6 + 6).
+      min = 0x10000;
+
+    } else {
+      // We expected the first byte of a code point but got something else.
+      skip(1);
+      return REPLACEMENT_CHARACTER;
+    }
+
+    if (size < byteCount) {
+      throw new EOFException("size < " + byteCount + ": " + size
+          + " (to read code point prefixed 0x" + Integer.toHexString(b0) + ")");
+    }
+
+    // Read the continuation bytes. If we encounter a non-continuation byte, the sequence consumed
+    // thus far is truncated and is decoded as the replacement character. That non-continuation byte
+    // is left in the stream for processing by the next call to readUtf8CodePoint().
+    for (int i = 1; i < byteCount; i++) {
+      byte b = getByte(i);
+      if ((b & 0xc0) == 0x80) {
+        // 0x10xxxxxx
+        codePoint <<= 6;
+        codePoint |= b & 0x3f;
+      } else {
+        skip(i);
+        return REPLACEMENT_CHARACTER;
+      }
+    }
+
+    skip(byteCount);
+
+    if (codePoint > 0x10ffff) {
+      return REPLACEMENT_CHARACTER; // Reject code points larger than the Unicode maximum.
+    }
+
+    if (codePoint >= 0xd800 && codePoint <= 0xdfff) {
+      return REPLACEMENT_CHARACTER; // Reject partial surrogates.
+    }
+
+    if (codePoint < min) {
+      return REPLACEMENT_CHARACTER; // Reject overlong code points.
+    }
+
+    return codePoint;
+  }
+
+  @Override public byte[] readByteArray() {
+    try {
+      return readByteArray(size);
+    } catch (EOFException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  @Override public byte[] readByteArray(long byteCount) throws EOFException {
+    checkOffsetAndCount(size, 0, byteCount);
+    if (byteCount > Integer.MAX_VALUE) {
+      throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
+    }
+
+    byte[] result = new byte[(int) byteCount];
+    readFully(result);
+    return result;
+  }
+
+  @Override public int read(byte[] sink) {
+    return read(sink, 0, sink.length);
+  }
+
+  @Override public void readFully(byte[] sink) throws EOFException {
+    int offset = 0;
+    while (offset < sink.length) {
+      int read = read(sink, offset, sink.length - offset);
+      if (read == -1) throw new EOFException();
+      offset += read;
+    }
+  }
+
+  @Override public int read(byte[] sink, int offset, int byteCount) {
+    checkOffsetAndCount(sink.length, offset, byteCount);
+
+    Segment s = head;
+    if (s == null) return -1;
+    int toCopy = Math.min(byteCount, s.limit - s.pos);
+    System.arraycopy(s.data, s.pos, sink, offset, toCopy);
+
+    s.pos += toCopy;
+    size -= toCopy;
+
+    if (s.pos == s.limit) {
+      head = s.pop();
+      SegmentPool.recycle(s);
+    }
+
+    return toCopy;
+  }
+
+  /**
+   * Discards all bytes in this buffer. Calling this method when you're done
+   * with a buffer will return its segments to the pool.
+   */
+  public void clear() {
+    try {
+      skip(size);
+    } catch (EOFException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /** Discards {@code byteCount} bytes from the head of this buffer. */
+  @Override public void skip(long byteCount) throws EOFException {
+    while (byteCount > 0) {
+      if (head == null) throw new EOFException();
+
+      int toSkip = (int) Math.min(byteCount, head.limit - head.pos);
+      size -= toSkip;
+      byteCount -= toSkip;
+      head.pos += toSkip;
+
+      if (head.pos == head.limit) {
+        Segment toRecycle = head;
+        head = toRecycle.pop();
+        SegmentPool.recycle(toRecycle);
+      }
+    }
+  }
+
+  @Override public Buffer write(ByteString byteString) {
+    if (byteString == null) throw new IllegalArgumentException("byteString == null");
+    byteString.write(this);
+    return this;
+  }
+
+  @Override public Buffer writeUtf8(String string) {
+    return writeUtf8(string, 0, string.length());
+  }
+
+  @Override public Buffer writeUtf8(String string, int beginIndex, int endIndex) {
+    if (string == null) throw new IllegalArgumentException("string == null");
+    if (beginIndex < 0) throw new IllegalAccessError("beginIndex < 0: " + beginIndex);
+    if (endIndex < beginIndex) {
+      throw new IllegalArgumentException("endIndex < beginIndex: " + endIndex + " < " + beginIndex);
+    }
+    if (endIndex > string.length()) {
+      throw new IllegalArgumentException(
+          "endIndex > string.length: " + endIndex + " > " + string.length());
+    }
+
+    // Transcode a UTF-16 Java String to UTF-8 bytes.
+    for (int i = beginIndex; i < endIndex;) {
+      int c = string.charAt(i);
+
+      if (c < 0x80) {
+        Segment tail = writableSegment(1);
+        byte[] data = tail.data;
+        int segmentOffset = tail.limit - i;
+        int runLimit = Math.min(endIndex, Segment.SIZE - segmentOffset);
+
+        // Emit a 7-bit character with 1 byte.
+        data[segmentOffset + i++] = (byte) c; // 0xxxxxxx
+
+        // Fast-path contiguous runs of ASCII characters. This is ugly, but yields a ~4x performance
+        // improvement over independent calls to writeByte().
+        while (i < runLimit) {
+          c = string.charAt(i);
+          if (c >= 0x80) break;
+          data[segmentOffset + i++] = (byte) c; // 0xxxxxxx
+        }
+
+        int runSize = i + segmentOffset - tail.limit; // Equivalent to i - (previous i).
+        tail.limit += runSize;
+        size += runSize;
+
+      } else if (c < 0x800) {
+        // Emit a 11-bit character with 2 bytes.
+        writeByte(c >>  6        | 0xc0); // 110xxxxx
+        writeByte(c       & 0x3f | 0x80); // 10xxxxxx
+        i++;
+
+      } else if (c < 0xd800 || c > 0xdfff) {
+        // Emit a 16-bit character with 3 bytes.
+        writeByte(c >> 12        | 0xe0); // 1110xxxx
+        writeByte(c >>  6 & 0x3f | 0x80); // 10xxxxxx
+        writeByte(c       & 0x3f | 0x80); // 10xxxxxx
+        i++;
+
+      } else {
+        // c is a surrogate. Make sure it is a high surrogate & that its successor is a low
+        // surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement character.
+        int low = i + 1 < endIndex ? string.charAt(i + 1) : 0;
+        if (c > 0xdbff || low < 0xdc00 || low > 0xdfff) {
+          writeByte('?');
+          i++;
+          continue;
+        }
+
+        // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits)
+        // UTF-16 low surrogate:  110111yyyyyyyyyy (10 bits)
+        // Unicode code point:    00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits)
+        int codePoint = 0x010000 + ((c & ~0xd800) << 10 | low & ~0xdc00);
+
+        // Emit a 21-bit character with 4 bytes.
+        writeByte(codePoint >> 18        | 0xf0); // 11110xxx
+        writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx
+        writeByte(codePoint >>  6 & 0x3f | 0x80); // 10xxyyyy
+        writeByte(codePoint       & 0x3f | 0x80); // 10yyyyyy
+        i += 2;
+      }
+    }
+
+    return this;
+  }
+
+  @Override public Buffer writeUtf8CodePoint(int codePoint) {
+    if (codePoint < 0x80) {
+      // Emit a 7-bit code point with 1 byte.
+      writeByte(codePoint);
+
+    } else if (codePoint < 0x800) {
+      // Emit a 11-bit code point with 2 bytes.
+      writeByte(codePoint >>  6        | 0xc0); // 110xxxxx
+      writeByte(codePoint       & 0x3f | 0x80); // 10xxxxxx
+
+    } else if (codePoint < 0x10000) {
+      if (codePoint >= 0xd800 && codePoint <= 0xdfff) {
+        throw new IllegalArgumentException(
+            "Unexpected code point: " + Integer.toHexString(codePoint));
+      }
+
+      // Emit a 16-bit code point with 3 bytes.
+      writeByte(codePoint >> 12        | 0xe0); // 1110xxxx
+      writeByte(codePoint >>  6 & 0x3f | 0x80); // 10xxxxxx
+      writeByte(codePoint       & 0x3f | 0x80); // 10xxxxxx
+
+    } else if (codePoint <= 0x10ffff) {
+      // Emit a 21-bit code point with 4 bytes.
+      writeByte(codePoint >> 18        | 0xf0); // 11110xxx
+      writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx
+      writeByte(codePoint >>  6 & 0x3f | 0x80); // 10xxxxxx
+      writeByte(codePoint       & 0x3f | 0x80); // 10xxxxxx
+
+    } else {
+      throw new IllegalArgumentException(
+          "Unexpected code point: " + Integer.toHexString(codePoint));
+    }
+
+    return this;
+  }
+
+  @Override public Buffer writeString(String string, Charset charset) {
+    return writeString(string, 0, string.length(), charset);
+  }
+
+  @Override
+  public Buffer writeString(String string, int beginIndex, int endIndex, Charset charset) {
+    if (string == null) throw new IllegalArgumentException("string == null");
+    if (beginIndex < 0) throw new IllegalAccessError("beginIndex < 0: " + beginIndex);
+    if (endIndex < beginIndex) {
+      throw new IllegalArgumentException("endIndex < beginIndex: " + endIndex + " < " + beginIndex);
+    }
+    if (endIndex > string.length()) {
+      throw new IllegalArgumentException(
+          "endIndex > string.length: " + endIndex + " > " + string.length());
+    }
+    if (charset == null) throw new IllegalArgumentException("charset == null");
+    if (charset.equals(Util.UTF_8)) return writeUtf8(string);
+    byte[] data = string.substring(beginIndex, endIndex).getBytes(charset);
+    return write(data, 0, data.length);
+  }
+
+  @Override public Buffer write(byte[] source) {
+    if (source == null) throw new IllegalArgumentException("source == null");
+    return write(source, 0, source.length);
+  }
+
+  @Override public Buffer write(byte[] source, int offset, int byteCount) {
+    if (source == null) throw new IllegalArgumentException("source == null");
+    checkOffsetAndCount(source.length, offset, byteCount);
+
+    int limit = offset + byteCount;
+    while (offset < limit) {
+      Segment tail = writableSegment(1);
+
+      int toCopy = Math.min(limit - offset, Segment.SIZE - tail.limit);
+      System.arraycopy(source, offset, tail.data, tail.limit, toCopy);
+
+      offset += toCopy;
+      tail.limit += toCopy;
+    }
+
+    size += byteCount;
+    return this;
+  }
+
+  @Override public long writeAll(Source source) throws IOException {
+    if (source == null) throw new IllegalArgumentException("source == null");
+    long totalBytesRead = 0;
+    for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {
+      totalBytesRead += readCount;
+    }
+    return totalBytesRead;
+  }
+
+  @Override public BufferedSink write(Source source, long byteCount) throws IOException {
+    while (byteCount > 0) {
+      long read = source.read(this, byteCount);
+      if (read == -1) throw new EOFException();
+      byteCount -= read;
+    }
+    return this;
+  }
+
+  @Override public Buffer writeByte(int b) {
+    Segment tail = writableSegment(1);
+    tail.data[tail.limit++] = (byte) b;
+    size += 1;
+    return this;
+  }
+
+  @Override public Buffer writeShort(int s) {
+    Segment tail = writableSegment(2);
+    byte[] data = tail.data;
+    int limit = tail.limit;
+    data[limit++] = (byte) ((s >>> 8) & 0xff);
+    data[limit++] = (byte)  (s        & 0xff);
+    tail.limit = limit;
+    size += 2;
+    return this;
+  }
+
+  @Override public Buffer writeShortLe(int s) {
+    return writeShort(Util.reverseBytesShort((short) s));
+  }
+
+  @Override public Buffer writeInt(int i) {
+    Segment tail = writableSegment(4);
+    byte[] data = tail.data;
+    int limit = tail.limit;
+    data[limit++] = (byte) ((i >>> 24) & 0xff);
+    data[limit++] = (byte) ((i >>> 16) & 0xff);
+    data[limit++] = (byte) ((i >>>  8) & 0xff);
+    data[limit++] = (byte)  (i         & 0xff);
+    tail.limit = limit;
+    size += 4;
+    return this;
+  }
+
+  @Override public Buffer writeIntLe(int i) {
+    return writeInt(Util.reverseBytesInt(i));
+  }
+
+  @Override public Buffer writeLong(long v) {
+    Segment tail = writableSegment(8);
+    byte[] data = tail.data;
+    int limit = tail.limit;
+    data[limit++] = (byte) ((v >>> 56L) & 0xff);
+    data[limit++] = (byte) ((v >>> 48L) & 0xff);
+    data[limit++] = (byte) ((v >>> 40L) & 0xff);
+    data[limit++] = (byte) ((v >>> 32L) & 0xff);
+    data[limit++] = (byte) ((v >>> 24L) & 0xff);
+    data[limit++] = (byte) ((v >>> 16L) & 0xff);
+    data[limit++] = (byte) ((v >>>  8L) & 0xff);
+    data[limit++] = (byte)  (v          & 0xff);
+    tail.limit = limit;
+    size += 8;
+    return this;
+  }
+
+  @Override public Buffer writeLongLe(long v) {
+    return writeLong(reverseBytesLong(v));
+  }
+
+  @Override public Buffer writeDecimalLong(long v) {
+    if (v == 0) {
+      // Both a shortcut and required since the following code can't handle zero.
+      return writeByte('0');
+    }
+
+    boolean negative = false;
+    if (v < 0) {
+      v = -v;
+      if (v < 0) { // Only true for Long.MIN_VALUE.
+        return writeUtf8("-9223372036854775808");
+      }
+      negative = true;
+    }
+
+    // Binary search for character width which favors matching lower numbers.
+    int width = //
+          v < 100000000L
+        ? v < 10000L
+        ? v < 100L
+        ? v < 10L ? 1 : 2
+        : v < 1000L ? 3 : 4
+        : v < 1000000L
+        ? v < 100000L ? 5 : 6
+        : v < 10000000L ? 7 : 8
+        : v < 1000000000000L
+        ? v < 10000000000L
+        ? v < 1000000000L ? 9 : 10
+        : v < 100000000000L ? 11 : 12
+        : v < 1000000000000000L
+        ? v < 10000000000000L ? 13
+        : v < 100000000000000L ? 14 : 15
+        : v < 100000000000000000L
+        ? v < 10000000000000000L ? 16 : 17
+        : v < 1000000000000000000L ? 18 : 19;
+    if (negative) {
+      ++width;
+    }
+
+    Segment tail = writableSegment(width);
+    byte[] data = tail.data;
+    int pos = tail.limit + width; // We write backwards from right to left.
+    while (v != 0) {
+      int digit = (int) (v % 10);
+      data[--pos] = DIGITS[digit];
+      v /= 10;
+    }
+    if (negative) {
+      data[--pos] = '-';
+    }
+
+    tail.limit += width;
+    this.size += width;
+    return this;
+  }
+
+  @Override public Buffer writeHexadecimalUnsignedLong(long v) {
+    if (v == 0) {
+      // Both a shortcut and required since the following code can't handle zero.
+      return writeByte('0');
+    }
+
+    int width = Long.numberOfTrailingZeros(Long.highestOneBit(v)) / 4 + 1;
+
+    Segment tail = writableSegment(width);
+    byte[] data = tail.data;
+    for (int pos = tail.limit + width - 1, start = tail.limit; pos >= start; pos--) {
+      data[pos] = DIGITS[(int) (v & 0xF)];
+      v >>>= 4;
+    }
+    tail.limit += width;
+    size += width;
+    return this;
+  }
+
+  /**
+   * Returns a tail segment that we can write at least {@code minimumCapacity}
+   * bytes to, creating it if necessary.
+   */
+  Segment writableSegment(int minimumCapacity) {
+    if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();
+
+    if (head == null) {
+      head = SegmentPool.take(); // Acquire a first segment.
+      return head.next = head.prev = head;
+    }
+
+    Segment tail = head.prev;
+    if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {
+      tail = tail.push(SegmentPool.take()); // Append a new empty segment to fill up.
+    }
+    return tail;
+  }
+
+  @Override public void write(Buffer source, long byteCount) {
+    // Move bytes from the head of the source buffer to the tail of this buffer
+    // while balancing two conflicting goals: don't waste CPU and don't waste
+    // memory.
+    //
+    //
+    // Don't waste CPU (ie. don't copy data around).
+    //
+    // Copying large amounts of data is expensive. Instead, we prefer to
+    // reassign entire segments from one buffer to the other.
+    //
+    //
+    // Don't waste memory.
+    //
+    // As an invariant, adjacent pairs of segments in a buffer should be at
+    // least 50% full, except for the head segment and the tail segment.
+    //
+    // The head segment cannot maintain the invariant because the application is
+    // consuming bytes from this segment, decreasing its level.
+    //
+    // The tail segment cannot maintain the invariant because the application is
+    // producing bytes, which may require new nearly-empty tail segments to be
+    // appended.
+    //
+    //
+    // Moving segments between buffers
+    //
+    // When writing one buffer to another, we prefer to reassign entire segments
+    // over copying bytes into their most compact form. Suppose we have a buffer
+    // with these segment levels [91%, 61%]. If we append a buffer with a
+    // single [72%] segment, that yields [91%, 61%, 72%]. No bytes are copied.
+    //
+    // Or suppose we have a buffer with these segment levels: [100%, 2%], and we
+    // want to append it to a buffer with these segment levels [99%, 3%]. This
+    // operation will yield the following segments: [100%, 2%, 99%, 3%]. That
+    // is, we do not spend time copying bytes around to achieve more efficient
+    // memory use like [100%, 100%, 4%].
+    //
+    // When combining buffers, we will compact adjacent buffers when their
+    // combined level doesn't exceed 100%. For example, when we start with
+    // [100%, 40%] and append [30%, 80%], the result is [100%, 70%, 80%].
+    //
+    //
+    // Splitting segments
+    //
+    // Occasionally we write only part of a source buffer to a sink buffer. For
+    // example, given a sink [51%, 91%], we may want to write the first 30% of
+    // a source [92%, 82%] to it. To simplify, we first transform the source to
+    // an equivalent buffer [30%, 62%, 82%] and then move the head segment,
+    // yielding sink [51%, 91%, 30%] and source [62%, 82%].
+
+    if (source == null) throw new IllegalArgumentException("source == null");
+    if (source == this) throw new IllegalArgumentException("source == this");
+    checkOffsetAndCount(source.size, 0, byteCount);
+
+    while (byteCount > 0) {
+      // Is a prefix of the source's head segment all that we need to move?
+      if (byteCount < (source.head.limit - source.head.pos)) {
+        Segment tail = head != null ? head.prev : null;
+        if (tail != null && tail.owner
+            && (byteCount + tail.limit - (tail.shared ? 0 : tail.pos) <= Segment.SIZE)) {
+          // Our existing segments are sufficient. Move bytes from source's head to our tail.
+          source.head.writeTo(tail, (int) byteCount);
+          source.size -= byteCount;
+          size += byteCount;
+          return;
+        } else {
+          // We're going to need another segment. Split the source's head
+          // segment in two, then move the first of those two to this buffer.
+          source.head = source.head.split((int) byteCount);
+        }
+      }
+
+      // Remove the source's head segment and append it to our tail.
+      Segment segmentToMove = source.head;
+      long movedByteCount = segmentToMove.limit - segmentToMove.pos;
+      source.head = segmentToMove.pop();
+      if (head == null) {
+        head = segmentToMove;
+        head.next = head.prev = head;
+      } else {
+        Segment tail = head.prev;
+        tail = tail.push(segmentToMove);
+        tail.compact();
+      }
+      source.size -= movedByteCount;
+      size += movedByteCount;
+      byteCount -= movedByteCount;
+    }
+  }
+
+  @Override public long read(Buffer sink, long byteCount) {
+    if (sink == null) throw new IllegalArgumentException("sink == null");
+    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+    if (size == 0) return -1L;
+    if (byteCount > size) byteCount = size;
+    sink.write(this, byteCount);
+    return byteCount;
+  }
+
+  @Override public long indexOf(byte b) {
+    return indexOf(b, 0);
+  }
+
+  /**
+   * Returns the index of {@code b} in this at or beyond {@code fromIndex}, or
+   * -1 if this buffer does not contain {@code b} in that range.
+   */
+  @Override public long indexOf(byte b, long fromIndex) {
+    if (fromIndex < 0) throw new IllegalArgumentException("fromIndex < 0");
+
+    Segment s = head;
+    if (s == null) return -1L;
+    long offset = 0L;
+    do {
+      int segmentByteCount = s.limit - s.pos;
+      if (fromIndex >= segmentByteCount) {
+        fromIndex -= segmentByteCount;
+      } else {
+        byte[] data = s.data;
+        for (int pos = (int) (s.pos + fromIndex), limit = s.limit; pos < limit; pos++) {
+          if (data[pos] == b) return offset + pos - s.pos;
+        }
+        fromIndex = 0;
+      }
+      offset += segmentByteCount;
+      s = s.next;
+    } while (s != head);
+    return -1L;
+  }
+
+  @Override public long indexOf(ByteString bytes) throws IOException {
+    return indexOf(bytes, 0);
+  }
+
+  @Override public long indexOf(ByteString bytes, long fromIndex) throws IOException {
+    if (bytes.size() == 0) throw new IllegalArgumentException("bytes is empty");
+    while (true) {
+      fromIndex = indexOf(bytes.getByte(0), fromIndex);
+      if (fromIndex == -1) {
+        return -1;
+      }
+      if (rangeEquals(fromIndex, bytes)) {
+        return fromIndex;
+      }
+      fromIndex++;
+    }
+  }
+
+  @Override public long indexOfElement(ByteString targetBytes) {
+    return indexOfElement(targetBytes, 0);
+  }
+
+  @Override public long indexOfElement(ByteString targetBytes, long fromIndex) {
+    if (fromIndex < 0) throw new IllegalArgumentException("fromIndex < 0");
+
+    Segment s = head;
+    if (s == null) return -1L;
+    long offset = 0L;
+    byte[] toFind = targetBytes.toByteArray();
+    do {
+      int segmentByteCount = s.limit - s.pos;
+      if (fromIndex >= segmentByteCount) {
+        fromIndex -= segmentByteCount;
+      } else {
+        byte[] data = s.data;
+        for (long pos = s.pos + fromIndex, limit = s.limit; pos < limit; pos++) {
+          byte b = data[(int) pos];
+          for (byte targetByte : toFind) {
+            if (b == targetByte) return offset + pos - s.pos;
+          }
+        }
+        fromIndex = 0;
+      }
+      offset += segmentByteCount;
+      s = s.next;
+    } while (s != head);
+    return -1L;
+  }
+
+  boolean rangeEquals(long offset, ByteString bytes) {
+    int byteCount = bytes.size();
+    if (size - offset < byteCount) {
+      return false;
+    }
+    for (int i = 0; i < byteCount; i++) {
+      if (getByte(offset + i) != bytes.getByte(i)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override public void flush() {
+  }
+
+  @Override public void close() {
+  }
+
+  @Override public Timeout timeout() {
+    return Timeout.NONE;
+  }
+
+  /** For testing. This returns the sizes of the segments in this buffer. */
+  List<Integer> segmentSizes() {
+    if (head == null) return Collections.emptyList();
+    List<Integer> result = new ArrayList<>();
+    result.add(head.limit - head.pos);
+    for (Segment s = head.next; s != head; s = s.next) {
+      result.add(s.limit - s.pos);
+    }
+    return result;
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof Buffer)) return false;
+    Buffer that = (Buffer) o;
+    if (size != that.size) return false;
+    if (size == 0) return true; // Both buffers are empty.
+
+    Segment sa = this.head;
+    Segment sb = that.head;
+    int posA = sa.pos;
+    int posB = sb.pos;
+
+    for (long pos = 0, count; pos < size; pos += count) {
+      count = Math.min(sa.limit - posA, sb.limit - posB);
+
+      for (int i = 0; i < count; i++) {
+        if (sa.data[posA++] != sb.data[posB++]) return false;
+      }
+
+      if (posA == sa.limit) {
+        sa = sa.next;
+        posA = sa.pos;
+      }
+
+      if (posB == sb.limit) {
+        sb = sb.next;
+        posB = sb.pos;
+      }
+    }
+
+    return true;
+  }
+
+  @Override public int hashCode() {
+    Segment s = head;
+    if (s == null) return 0;
+    int result = 1;
+    do {
+      for (int pos = s.pos, limit = s.limit; pos < limit; pos++) {
+        result = 31 * result + s.data[pos];
+      }
+      s = s.next;
+    } while (s != head);
+    return result;
+  }
+
+  @Override public String toString() {
+    if (size == 0) {
+      return "Buffer[size=0]";
+    }
+
+    if (size <= 16) {
+      ByteString data = clone().readByteString();
+      return String.format("Buffer[size=%s data=%s]", size, data.hex());
+    }
+
+    try {
+      MessageDigest md5 = MessageDigest.getInstance("MD5");
+      md5.update(head.data, head.pos, head.limit - head.pos);
+      for (Segment s = head.next; s != head; s = s.next) {
+        md5.update(s.data, s.pos, s.limit - s.pos);
+      }
+      return String.format("Buffer[size=%s md5=%s]",
+          size, ByteString.of(md5.digest()).hex());
+    } catch (NoSuchAlgorithmException e) {
+      throw new AssertionError();
+    }
+  }
+
+  /** Returns a deep copy of this buffer. */
+  @Override public Buffer clone() {
+    Buffer result = new Buffer();
+    if (size == 0) return result;
+
+    result.head = new Segment(head);
+    result.head.next = result.head.prev = result.head;
+    for (Segment s = head.next; s != head; s = s.next) {
+      result.head.prev.push(new Segment(s));
+    }
+    result.size = size;
+    return result;
+  }
+
+  /** Returns an immutable copy of this buffer as a byte string. */
+  public ByteString snapshot() {
+    if (size > Integer.MAX_VALUE) {
+      throw new IllegalArgumentException("size > Integer.MAX_VALUE: " + size);
+    }
+    return snapshot((int) size);
+  }
+
+  /**
+   * Returns an immutable copy of the first {@code byteCount} bytes of this buffer as a byte string.
+   */
+  public ByteString snapshot(int byteCount) {
+    if (byteCount == 0) return ByteString.EMPTY;
+    return new SegmentedByteString(this, byteCount);
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSink.java
new file mode 100644
index 0000000..7dd3f13
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSink.java
@@ -0,0 +1,119 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+/**
+ * A sink that keeps a buffer internally so that callers can do small writes
+ * without a performance penalty.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface BufferedSink extends Sink {
+  /** Returns this sink's internal buffer. */
+  Buffer buffer();
+
+  BufferedSink write(ByteString byteString) throws IOException;
+
+  /**
+   * Like {@link OutputStream#write(byte[])}, this writes a complete byte array to
+   * this sink.
+   */
+  BufferedSink write(byte[] source) throws IOException;
+
+  /**
+   * Like {@link OutputStream#write(byte[], int, int)}, this writes {@code byteCount}
+   * bytes of {@code source}, starting at {@code offset}.
+   */
+  BufferedSink write(byte[] source, int offset, int byteCount) throws IOException;
+
+  /**
+   * Removes all bytes from {@code source} and appends them to this sink. Returns the
+   * number of bytes read which will be 0 if {@code source} is exhausted.
+   */
+  long writeAll(Source source) throws IOException;
+
+  /** Removes {@code byteCount} bytes from {@code source} and appends them to this sink. */
+  BufferedSink write(Source source, long byteCount) throws IOException;
+
+  /** Encodes {@code string} in UTF-8 and writes it to this sink. */
+  BufferedSink writeUtf8(String string) throws IOException;
+
+  /**
+   * Encodes the characters at {@code beginIndex} up to {@code endIndex} from {@code string} in
+   * UTF-8 and writes it to this sink.
+   */
+  BufferedSink writeUtf8(String string, int beginIndex, int endIndex) throws IOException;
+
+  /** Encodes {@code codePoint} in UTF-8 and writes it to this sink. */
+  BufferedSink writeUtf8CodePoint(int codePoint) throws IOException;
+
+  /** Encodes {@code string} in {@code charset} and writes it to this sink. */
+  BufferedSink writeString(String string, Charset charset) throws IOException;
+
+  /**
+   * Encodes the characters at {@code beginIndex} up to {@code endIndex} from {@code string} in
+   * {@code charset} and writes it to this sink.
+   */
+  BufferedSink writeString(String string, int beginIndex, int endIndex, Charset charset)
+      throws IOException;
+
+  /** Writes a byte to this sink. */
+  BufferedSink writeByte(int b) throws IOException;
+
+  /** Writes a big-endian short to this sink using two bytes. */
+  BufferedSink writeShort(int s) throws IOException;
+
+  /** Writes a little-endian short to this sink using two bytes. */
+  BufferedSink writeShortLe(int s) throws IOException;
+
+  /** Writes a big-endian int to this sink using four bytes. */
+  BufferedSink writeInt(int i) throws IOException;
+
+  /** Writes a little-endian int to this sink using four bytes. */
+  BufferedSink writeIntLe(int i) throws IOException;
+
+  /** Writes a big-endian long to this sink using eight bytes. */
+  BufferedSink writeLong(long v) throws IOException;
+
+  /** Writes a little-endian long to this sink using eight bytes. */
+  BufferedSink writeLongLe(long v) throws IOException;
+
+  /** Writes a long to this sink in signed decimal form (i.e., as a string in base 10). */
+  BufferedSink writeDecimalLong(long v) throws IOException;
+
+  /** Writes a long to this sink in hexadecimal form (i.e., as a string in base 16). */
+  BufferedSink writeHexadecimalUnsignedLong(long v) throws IOException;
+
+  /**
+   * Writes complete segments to the underlying sink, if one exists. Like {@link #flush}, but
+   * weaker. Use this to limit the memory held in the buffer to a single segment.
+   */
+  BufferedSink emitCompleteSegments() throws IOException;
+
+  /**
+   * Writes all buffered data to the underlying sink, if one exists. Like {@link #flush}, but
+   * weaker. Call this before this buffered sink goes out of scope so that its data can reach its
+   * destination.
+   */
+  BufferedSink emit() throws IOException;
+
+  /** Returns an output stream that writes to this sink. */
+  OutputStream outputStream();
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSource.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSource.java
new file mode 100644
index 0000000..a2fdad8
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/BufferedSource.java
@@ -0,0 +1,253 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+/**
+ * A source that keeps a buffer internally so that callers can do small reads
+ * without a performance penalty. It also allows clients to read ahead,
+ * buffering as much as necessary before consuming input.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface BufferedSource extends Source {
+  /** Returns this source's internal buffer. */
+  Buffer buffer();
+
+  /**
+   * Returns true if there are no more bytes in this source. This will block
+   * until there are bytes to read or the source is definitely exhausted.
+   */
+  boolean exhausted() throws IOException;
+
+  /**
+   * Returns when the buffer contains at least {@code byteCount} bytes. Throws
+   * an {@link java.io.EOFException} if the source is exhausted before the
+   * required bytes can be read.
+   */
+  void require(long byteCount) throws IOException;
+
+  /**
+   * Returns true when the buffer contains at least {@code byteCount} bytes,
+   * expanding it as necessary. Returns false if the source is exhausted before
+   * the requested bytes can be read.
+   */
+  boolean request(long byteCount) throws IOException;
+
+  /** Removes a byte from this source and returns it. */
+  byte readByte() throws IOException;
+
+  /** Removes two bytes from this source and returns a big-endian short. */
+  short readShort() throws IOException;
+
+  /** Removes two bytes from this source and returns a little-endian short. */
+  short readShortLe() throws IOException;
+
+  /** Removes four bytes from this source and returns a big-endian int. */
+  int readInt() throws IOException;
+
+  /** Removes four bytes from this source and returns a little-endian int. */
+  int readIntLe() throws IOException;
+
+  /** Removes eight bytes from this source and returns a big-endian long. */
+  long readLong() throws IOException;
+
+  /** Removes eight bytes from this source and returns a little-endian long. */
+  long readLongLe() throws IOException;
+
+  /**
+   * Reads a long from this source in signed decimal form (i.e., as a string in base 10 with
+   * optional leading '-'). This will iterate until a non-digit character is found.
+   *
+   * @throws NumberFormatException if the found digits do not fit into a {@code long} or a decimal
+   * number was not present.
+   */
+  long readDecimalLong() throws IOException;
+
+  /**
+   * Reads a long form this source in hexadecimal form (i.e., as a string in base 16). This will
+   * iterate until a non-hexadecimal character is found.
+   *
+   * @throws NumberFormatException if the found hexadecimal does not fit into a {@code long} or
+   * hexadecimal was not found.
+   */
+  long readHexadecimalUnsignedLong() throws IOException;
+
+  /**
+   * Reads and discards {@code byteCount} bytes from this source. Throws an
+   * {@link java.io.EOFException} if the source is exhausted before the
+   * requested bytes can be skipped.
+   */
+  void skip(long byteCount) throws IOException;
+
+  /** Removes all bytes bytes from this and returns them as a byte string. */
+  ByteString readByteString() throws IOException;
+
+  /** Removes {@code byteCount} bytes from this and returns them as a byte string. */
+  ByteString readByteString(long byteCount) throws IOException;
+
+  /** Removes all bytes from this and returns them as a byte array. */
+  byte[] readByteArray() throws IOException;
+
+  /** Removes {@code byteCount} bytes from this and returns them as a byte array. */
+  byte[] readByteArray(long byteCount) throws IOException;
+
+  /**
+   * Removes up to {@code sink.length} bytes from this and copies them into {@code sink}.
+   * Returns the number of bytes read, or -1 if this source is exhausted.
+   */
+  int read(byte[] sink) throws IOException;
+
+  /**
+   * Removes exactly {@code sink.length} bytes from this and copies them into {@code sink}.
+   * Throws an {@link java.io.EOFException} if the requested number of bytes cannot be read.
+   */
+  void readFully(byte[] sink) throws IOException;
+
+  /**
+   * Removes up to {@code byteCount} bytes from this and copies them into {@code sink} at
+   * {@code offset}. Returns the number of bytes read, or -1 if this source is exhausted.
+   */
+  int read(byte[] sink, int offset, int byteCount) throws IOException;
+
+  /**
+   * Removes exactly {@code byteCount} bytes from this and appends them to
+   * {@code sink}. Throws an {@link java.io.EOFException} if the requested
+   * number of bytes cannot be read.
+   */
+  void readFully(Buffer sink, long byteCount) throws IOException;
+
+  /**
+   * Removes all bytes from this and appends them to {@code sink}. Returns the
+   * total number of bytes written to {@code sink} which will be 0 if this is
+   * exhausted.
+   */
+  long readAll(Sink sink) throws IOException;
+
+  /** Removes all bytes from this, decodes them as UTF-8, and returns the string. */
+  String readUtf8() throws IOException;
+
+  /**
+   * Removes {@code byteCount} bytes from this, decodes them as UTF-8, and
+   * returns the string.
+   */
+  String readUtf8(long byteCount) throws IOException;
+
+  /**
+   * Removes and returns characters up to but not including the next line break.
+   * A line break is either {@code "\n"} or {@code "\r\n"}; these characters are
+   * not included in the result.
+   *
+   * <p><strong>On the end of the stream this method returns null,</strong> just
+   * like {@link java.io.BufferedReader}. If the source doesn't end with a line
+   * break then an implicit line break is assumed. Null is returned once the
+   * source is exhausted. Use this for human-generated data, where a trailing
+   * line break is optional.
+   */
+  String readUtf8Line() throws IOException;
+
+  /**
+   * Removes and returns characters up to but not including the next line break.
+   * A line break is either {@code "\n"} or {@code "\r\n"}; these characters are
+   * not included in the result.
+   *
+   * <p><strong>On the end of the stream this method throws.</strong> Every call
+   * must consume either '\r\n' or '\n'. If these characters are absent in the
+   * stream, an {@link java.io.EOFException} is thrown. Use this for
+   * machine-generated data where a missing line break implies truncated input.
+   */
+  String readUtf8LineStrict() throws IOException;
+
+  /**
+   * Removes and returns a single UTF-8 code point, reading between 1 and 4 bytes as necessary.
+   *
+   * <p>If this source is exhausted before a complete code point can be read, this throws an {@link
+   * java.io.EOFException} and consumes no input.
+   *
+   * <p>If this source doesn't start with a properly-encoded UTF-8 code point, this method will
+   * remove 1 or more non-UTF-8 bytes and return the replacement character ({@code U+FFFD}). This
+   * covers encoding problems (the input is not properly-encoded UTF-8), characters out of range
+   * (beyond the 0x10ffff limit of Unicode), code points for UTF-16 surrogates (U+d800..U+dfff) and
+   * overlong encodings (such as {@code 0xc080} for the NUL character in modified UTF-8).
+   */
+  int readUtf8CodePoint() throws IOException;
+
+  /**
+   * Removes all bytes from this, decodes them as {@code charset}, and returns
+   * the string.
+   */
+  String readString(Charset charset) throws IOException;
+
+  /**
+   * Removes {@code byteCount} bytes from this, decodes them as {@code charset},
+   * and returns the string.
+   */
+  String readString(long byteCount, Charset charset) throws IOException;
+
+  /**
+   * Returns the index of the first {@code b} in the buffer. This expands the
+   * buffer as necessary until {@code b} is found. This reads an unbounded
+   * number of bytes into the buffer. Returns -1 if the stream is exhausted
+   * before the requested byte is found.
+   */
+  long indexOf(byte b) throws IOException;
+
+  /**
+   * Returns the index of the first {@code b} in the buffer at or after {@code
+   * fromIndex}. This expands the buffer as necessary until {@code b} is found.
+   * This reads an unbounded number of bytes into the buffer. Returns -1 if the
+   * stream is exhausted before the requested byte is found.
+   */
+  long indexOf(byte b, long fromIndex) throws IOException;
+
+  /**
+   * Returns the index of the first match for {@code bytes} in the buffer. This expands the buffer
+   * as necessary until {@code bytes} is found. This reads an unbounded number of bytes into the
+   * buffer. Returns -1 if the stream is exhausted before the requested bytes are found.
+   */
+  long indexOf(ByteString bytes) throws IOException;
+
+  /**
+   * Returns the index of the first match for {@code bytes} in the buffer at or after {@code
+   * fromIndex}. This expands the buffer as necessary until {@code bytes} is found. This reads an
+   * unbounded number of bytes into the buffer. Returns -1 if the stream is exhausted before the
+   * requested bytes are found.
+   */
+  long indexOf(ByteString bytes, long fromIndex) throws IOException;
+
+  /**
+   * Returns the index of the first byte in {@code targetBytes} in the buffer.
+   * This expands the buffer as necessary until a target byte is found. This
+   * reads an unbounded number of bytes into the buffer. Returns -1 if the
+   * stream is exhausted before the requested byte is found.
+   */
+  long indexOfElement(ByteString targetBytes) throws IOException;
+
+  /**
+   * Returns the index of the first byte in {@code targetBytes} in the buffer
+   * at or after {@code fromIndex}. This expands the buffer as necessary until
+   * a target byte is found. This reads an unbounded number of bytes into the
+   * buffer. Returns -1 if the stream is exhausted before the requested byte is
+   * found.
+   */
+  long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException;
+
+  /** Returns an input stream that reads from this source. */
+  InputStream inputStream();
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ByteString.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ByteString.java
new file mode 100644
index 0000000..ade1ea2
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ByteString.java
@@ -0,0 +1,381 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright 2014 Square Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import static com.android.okhttp.okio.Util.arrayRangeEquals;
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+
+/**
+ * An immutable sequence of bytes.
+ *
+ * <p>Byte strings compare lexicographically as a sequence of <strong>unsigned</strong> bytes. That
+ * is, the byte string {@code ff} sorts after {@code 00}. This is counter to the sort order of the
+ * corresponding bytes, where {@code -1} sorts before {@code 0}.
+ *
+ * <p><strong>Full disclosure:</strong> this class provides untrusted input and output streams with
+ * raw access to the underlying byte array. A hostile stream implementation could keep a reference
+ * to the mutable byte string, violating the immutable guarantee of this class. For this reason a
+ * byte string's immutability guarantee cannot be relied upon for security in applets and other
+ * environments that run both trusted and untrusted code in the same process.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class ByteString implements Serializable, Comparable<ByteString> {
+  static final char[] HEX_DIGITS =
+      { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+  private static final long serialVersionUID = 1L;
+
+  /** A singleton empty {@code ByteString}. */
+  public static final ByteString EMPTY = ByteString.of();
+
+  final byte[] data;
+  transient int hashCode; // Lazily computed; 0 if unknown.
+  transient String utf8; // Lazily computed.
+
+  ByteString(byte[] data) {
+    this.data = data; // Trusted internal constructor doesn't clone data.
+  }
+
+  /**
+   * Returns a new byte string containing a clone of the bytes of {@code data}.
+   */
+  public static ByteString of(byte... data) {
+    if (data == null) throw new IllegalArgumentException("data == null");
+    return new ByteString(data.clone());
+  }
+
+  /**
+   * Returns a new byte string containing a copy of {@code byteCount} bytes of {@code data} starting
+   * at {@code offset}.
+   */
+  public static ByteString of(byte[] data, int offset, int byteCount) {
+    if (data == null) throw new IllegalArgumentException("data == null");
+    checkOffsetAndCount(data.length, offset, byteCount);
+
+    byte[] copy = new byte[byteCount];
+    System.arraycopy(data, offset, copy, 0, byteCount);
+    return new ByteString(copy);
+  }
+
+  /** Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. */
+  public static ByteString encodeUtf8(String s) {
+    if (s == null) throw new IllegalArgumentException("s == null");
+    ByteString byteString = new ByteString(s.getBytes(Util.UTF_8));
+    byteString.utf8 = s;
+    return byteString;
+  }
+
+  /** Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. */
+  public String utf8() {
+    String result = utf8;
+    // We don't care if we double-allocate in racy code.
+    return result != null ? result : (utf8 = new String(data, Util.UTF_8));
+  }
+
+  /**
+   * Returns this byte string encoded as <a
+   * href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a>. In violation of the
+   * RFC, the returned string does not wrap lines at 76 columns.
+   */
+  public String base64() {
+    return Base64.encode(data);
+  }
+
+  /** Returns the MD5 hash of this byte string. */
+  public ByteString md5() {
+    return digest("MD5");
+  }
+
+  /** Returns the SHA-256 hash of this byte string. */
+  public ByteString sha256() {
+    return digest("SHA-256");
+  }
+
+  private ByteString digest(String digest) {
+    try {
+      return ByteString.of(MessageDigest.getInstance(digest).digest(data));
+    } catch (NoSuchAlgorithmException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /**
+   * Returns this byte string encoded as <a href="http://www.ietf.org/rfc/rfc4648.txt">URL-safe
+   * Base64</a>.
+   */
+  public String base64Url() {
+    return Base64.encodeUrl(data);
+  }
+
+  /**
+   * Decodes the Base64-encoded bytes and returns their value as a byte string.
+   * Returns null if {@code base64} is not a Base64-encoded sequence of bytes.
+   */
+  public static ByteString decodeBase64(String base64) {
+    if (base64 == null) throw new IllegalArgumentException("base64 == null");
+    byte[] decoded = Base64.decode(base64);
+    return decoded != null ? new ByteString(decoded) : null;
+  }
+
+  /** Returns this byte string encoded in hexadecimal. */
+  public String hex() {
+    char[] result = new char[data.length * 2];
+    int c = 0;
+    for (byte b : data) {
+      result[c++] = HEX_DIGITS[(b >> 4) & 0xf];
+      result[c++] = HEX_DIGITS[b & 0xf];
+    }
+    return new String(result);
+  }
+
+  /** Decodes the hex-encoded bytes and returns their value a byte string. */
+  public static ByteString decodeHex(String hex) {
+    if (hex == null) throw new IllegalArgumentException("hex == null");
+    if (hex.length() % 2 != 0) throw new IllegalArgumentException("Unexpected hex string: " + hex);
+
+    byte[] result = new byte[hex.length() / 2];
+    for (int i = 0; i < result.length; i++) {
+      int d1 = decodeHexDigit(hex.charAt(i * 2)) << 4;
+      int d2 = decodeHexDigit(hex.charAt(i * 2 + 1));
+      result[i] = (byte) (d1 + d2);
+    }
+    return of(result);
+  }
+
+  private static int decodeHexDigit(char c) {
+    if (c >= '0' && c <= '9') return c - '0';
+    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+    throw new IllegalArgumentException("Unexpected hex digit: " + c);
+  }
+
+  /**
+   * Reads {@code count} bytes from {@code in} and returns the result.
+   *
+   * @throws java.io.EOFException if {@code in} has fewer than {@code count}
+   *     bytes to read.
+   */
+  public static ByteString read(InputStream in, int byteCount) throws IOException {
+    if (in == null) throw new IllegalArgumentException("in == null");
+    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+
+    byte[] result = new byte[byteCount];
+    for (int offset = 0, read; offset < byteCount; offset += read) {
+      read = in.read(result, offset, byteCount - offset);
+      if (read == -1) throw new EOFException();
+    }
+    return new ByteString(result);
+  }
+
+  /**
+   * Returns a byte string equal to this byte string, but with the bytes 'A'
+   * through 'Z' replaced with the corresponding byte in 'a' through 'z'.
+   * Returns this byte string if it contains no bytes in 'A' through 'Z'.
+   */
+  public ByteString toAsciiLowercase() {
+    // Search for an uppercase character. If we don't find one, return this.
+    for (int i = 0; i < data.length; i++) {
+      byte c = data[i];
+      if (c < 'A' || c > 'Z') continue;
+
+      // If we reach this point, this string is not not lowercase. Create and
+      // return a new byte string.
+      byte[] lowercase = data.clone();
+      lowercase[i++] = (byte) (c - ('A' - 'a'));
+      for (; i < lowercase.length; i++) {
+        c = lowercase[i];
+        if (c < 'A' || c > 'Z') continue;
+        lowercase[i] = (byte) (c - ('A' - 'a'));
+      }
+      return new ByteString(lowercase);
+    }
+    return this;
+  }
+
+  /**
+   * Returns a byte string equal to this byte string, but with the bytes 'a'
+   * through 'z' replaced with the corresponding byte in 'A' through 'Z'.
+   * Returns this byte string if it contains no bytes in 'a' through 'z'.
+   */
+  public ByteString toAsciiUppercase() {
+    // Search for an lowercase character. If we don't find one, return this.
+    for (int i = 0; i < data.length; i++) {
+      byte c = data[i];
+      if (c < 'a' || c > 'z') continue;
+
+      // If we reach this point, this string is not not uppercase. Create and
+      // return a new byte string.
+      byte[] lowercase = data.clone();
+      lowercase[i++] = (byte) (c - ('a' - 'A'));
+      for (; i < lowercase.length; i++) {
+        c = lowercase[i];
+        if (c < 'a' || c > 'z') continue;
+        lowercase[i] = (byte) (c - ('a' - 'A'));
+      }
+      return new ByteString(lowercase);
+    }
+    return this;
+  }
+
+  /**
+   * Returns a byte string that is a substring of this byte string, beginning at the specified
+   * index until the end of this string. Returns this byte string if {@code beginIndex} is 0.
+   */
+  public ByteString substring(int beginIndex) {
+    return substring(beginIndex, data.length);
+  }
+
+  /**
+   * Returns a byte string that is a substring of this byte string, beginning at the specified
+   * {@code beginIndex} and ends at the specified {@code endIndex}. Returns this byte string if
+   * {@code beginIndex} is 0 and {@code endIndex} is the length of this byte string.
+   */
+  public ByteString substring(int beginIndex, int endIndex) {
+    if (beginIndex < 0) throw new IllegalArgumentException("beginIndex < 0");
+    if (endIndex > data.length) {
+      throw new IllegalArgumentException("endIndex > length(" + data.length + ")");
+    }
+
+    int subLen = endIndex - beginIndex;
+    if (subLen < 0) throw new IllegalArgumentException("endIndex < beginIndex");
+
+    if ((beginIndex == 0) && (endIndex == data.length)) {
+      return this;
+    }
+
+    byte[] copy = new byte[subLen];
+    System.arraycopy(data, beginIndex, copy, 0, subLen);
+    return new ByteString(copy);
+  }
+
+  /** Returns the byte at {@code pos}. */
+  public byte getByte(int pos) {
+    return data[pos];
+  }
+
+  /**
+   * Returns the number of bytes in this ByteString.
+   */
+  public int size() {
+    return data.length;
+  }
+
+  /**
+   * Returns a byte array containing a copy of the bytes in this {@code ByteString}.
+   */
+  public byte[] toByteArray() {
+    return data.clone();
+  }
+
+  /** Writes the contents of this byte string to {@code out}. */
+  public void write(OutputStream out) throws IOException {
+    if (out == null) throw new IllegalArgumentException("out == null");
+    out.write(data);
+  }
+
+  /** Writes the contents of this byte string to {@code buffer}. */
+  void write(Buffer buffer) {
+    buffer.write(data, 0, data.length);
+  }
+
+  /**
+   * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of
+   * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is
+   * out of bounds.
+   */
+  public boolean rangeEquals(int offset, ByteString other, int otherOffset, int byteCount) {
+    return other.rangeEquals(otherOffset, this.data, offset, byteCount);
+  }
+
+  /**
+   * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of
+   * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is
+   * out of bounds.
+   */
+  public boolean rangeEquals(int offset, byte[] other, int otherOffset, int byteCount) {
+    return offset <= data.length - byteCount
+        && otherOffset <= other.length - byteCount
+        && arrayRangeEquals(data, offset, other, otherOffset, byteCount);
+  }
+
+  @Override public boolean equals(Object o) {
+    if (o == this) return true;
+    return o instanceof ByteString
+        && ((ByteString) o).size() == data.length
+        && ((ByteString) o).rangeEquals(0, data, 0, data.length);
+  }
+
+  @Override public int hashCode() {
+    int result = hashCode;
+    return result != 0 ? result : (hashCode = Arrays.hashCode(data));
+  }
+
+  @Override public int compareTo(ByteString byteString) {
+    int sizeA = size();
+    int sizeB = byteString.size();
+    for (int i = 0, size = Math.min(sizeA, sizeB); i < size; i++) {
+      int byteA = getByte(i) & 0xff;
+      int byteB = byteString.getByte(i) & 0xff;
+      if (byteA == byteB) continue;
+      return byteA < byteB ? -1 : 1;
+    }
+    if (sizeA == sizeB) return 0;
+    return sizeA < sizeB ? -1 : 1;
+  }
+
+  @Override public String toString() {
+    if (data.length == 0) {
+      return "ByteString[size=0]";
+    }
+
+    if (data.length <= 16) {
+      return String.format("ByteString[size=%s data=%s]", data.length, hex());
+    }
+
+    return String.format("ByteString[size=%s md5=%s]", data.length, md5().hex());
+  }
+
+  private void readObject(ObjectInputStream in) throws IOException {
+    int dataLength = in.readInt();
+    ByteString byteString = ByteString.read(in, dataLength);
+    try {
+      Field field = ByteString.class.getDeclaredField("data");
+      field.setAccessible(true);
+      field.set(this, byteString.data);
+    } catch (NoSuchFieldException e) {
+      throw new AssertionError();
+    } catch (IllegalAccessException e) {
+      throw new AssertionError();
+    }
+  }
+
+  private void writeObject(ObjectOutputStream out) throws IOException {
+    out.writeInt(data.length);
+    out.write(data);
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/DeflaterSink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/DeflaterSink.java
new file mode 100644
index 0000000..7df783e
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/DeflaterSink.java
@@ -0,0 +1,160 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.util.zip.Deflater;
+
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+
+/**
+ * A sink that uses <a href="http://tools.ietf.org/html/rfc1951">DEFLATE</a> to
+ * compress data written to another source.
+ *
+ * <h3>Sync flush</h3>
+ * Aggressive flushing of this stream may result in reduced compression. Each
+ * call to {@link #flush} immediately compresses all currently-buffered data;
+ * this early compression may be less effective than compression performed
+ * without flushing.
+ *
+ * <p>This is equivalent to using {@link Deflater} with the sync flush option.
+ * This class does not offer any partial flush mechanism. For best performance,
+ * only call {@link #flush} when application behavior requires it.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class DeflaterSink implements Sink {
+  private final BufferedSink sink;
+  private final Deflater deflater;
+  private boolean closed;
+
+  public DeflaterSink(Sink sink, Deflater deflater) {
+    this(Okio.buffer(sink), deflater);
+  }
+
+  /**
+   * This package-private constructor shares a buffer with its trusted caller.
+   * In general we can't share a BufferedSource because the deflater holds input
+   * bytes until they are inflated.
+   */
+  DeflaterSink(BufferedSink sink, Deflater deflater) {
+    if (sink == null) throw new IllegalArgumentException("source == null");
+    if (deflater == null) throw new IllegalArgumentException("inflater == null");
+    this.sink = sink;
+    this.deflater = deflater;
+  }
+
+  @Override public void write(Buffer source, long byteCount)
+      throws IOException {
+    checkOffsetAndCount(source.size, 0, byteCount);
+    while (byteCount > 0) {
+      // Share bytes from the head segment of 'source' with the deflater.
+      Segment head = source.head;
+      int toDeflate = (int) Math.min(byteCount, head.limit - head.pos);
+      deflater.setInput(head.data, head.pos, toDeflate);
+
+      // Deflate those bytes into sink.
+      deflate(false);
+
+      // Mark those bytes as read.
+      source.size -= toDeflate;
+      head.pos += toDeflate;
+      if (head.pos == head.limit) {
+        source.head = head.pop();
+        SegmentPool.recycle(head);
+      }
+
+      byteCount -= toDeflate;
+    }
+  }
+
+  // ANDROID-BEGIN
+  // @IgnoreJRERequirement
+  // ANDROID-END
+  private void deflate(boolean syncFlush) throws IOException {
+    Buffer buffer = sink.buffer();
+    while (true) {
+      Segment s = buffer.writableSegment(1);
+
+      // The 4-parameter overload of deflate() doesn't exist in the RI until
+      // Java 1.7, and is public (although with @hide) on Android since 2.3.
+      // The @hide tag means that this code won't compile against the Android
+      // 2.3 SDK, but it will run fine there.
+      int deflated = syncFlush
+          ? deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH)
+          : deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit);
+
+      if (deflated > 0) {
+        s.limit += deflated;
+        buffer.size += deflated;
+        sink.emitCompleteSegments();
+      } else if (deflater.needsInput()) {
+        if (s.pos == s.limit) {
+          // We allocated a tail segment, but didn't end up needing it. Recycle!
+          buffer.head = s.pop();
+          SegmentPool.recycle(s);
+        }
+        return;
+      }
+    }
+  }
+
+  @Override public void flush() throws IOException {
+    deflate(true);
+    sink.flush();
+  }
+
+  void finishDeflate() throws IOException {
+    deflater.finish();
+    deflate(false);
+  }
+
+  @Override public void close() throws IOException {
+    if (closed) return;
+
+    // Emit deflated data to the underlying sink. If this fails, we still need
+    // to close the deflater and the sink; otherwise we risk leaking resources.
+    Throwable thrown = null;
+    try {
+      finishDeflate();
+    } catch (Throwable e) {
+      thrown = e;
+    }
+
+    try {
+      deflater.end();
+    } catch (Throwable e) {
+      if (thrown == null) thrown = e;
+    }
+
+    try {
+      sink.close();
+    } catch (Throwable e) {
+      if (thrown == null) thrown = e;
+    }
+    closed = true;
+
+    if (thrown != null) Util.sneakyRethrow(thrown);
+  }
+
+  @Override public Timeout timeout() {
+    return sink.timeout();
+  }
+
+  @Override public String toString() {
+    return "DeflaterSink(" + sink + ")";
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSink.java
new file mode 100644
index 0000000..5dcad18
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSink.java
@@ -0,0 +1,55 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+
+/** A {@link Sink} which forwards calls to another. Useful for subclassing. 
+ * @hide This class is not part of the Android public SDK API*/
+public abstract class ForwardingSink implements Sink {
+  private final Sink delegate;
+
+  public ForwardingSink(Sink delegate) {
+    if (delegate == null) throw new IllegalArgumentException("delegate == null");
+    this.delegate = delegate;
+  }
+
+  /** {@link Sink} to which this instance is delegating. */
+  public final Sink delegate() {
+    return delegate;
+  }
+
+  @Override public void write(Buffer source, long byteCount) throws IOException {
+    delegate.write(source, byteCount);
+  }
+
+  @Override public void flush() throws IOException {
+    delegate.flush();
+  }
+
+  @Override public Timeout timeout() {
+    return delegate.timeout();
+  }
+
+  @Override public void close() throws IOException {
+    delegate.close();
+  }
+
+  @Override public String toString() {
+    return getClass().getSimpleName() + "(" + delegate.toString() + ")";
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSource.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSource.java
new file mode 100644
index 0000000..12fd15e
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingSource.java
@@ -0,0 +1,51 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+
+/** A {@link Source} which forwards calls to another. Useful for subclassing. 
+ * @hide This class is not part of the Android public SDK API*/
+public abstract class ForwardingSource implements Source {
+  private final Source delegate;
+
+  public ForwardingSource(Source delegate) {
+    if (delegate == null) throw new IllegalArgumentException("delegate == null");
+    this.delegate = delegate;
+  }
+
+  /** {@link Source} to which this instance is delegating. */
+  public final Source delegate() {
+    return delegate;
+  }
+
+  @Override public long read(Buffer sink, long byteCount) throws IOException {
+    return delegate.read(sink, byteCount);
+  }
+
+  @Override public Timeout timeout() {
+    return delegate.timeout();
+  }
+
+  @Override public void close() throws IOException {
+    delegate.close();
+  }
+
+  @Override public String toString() {
+    return getClass().getSimpleName() + "(" + delegate.toString() + ")";
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingTimeout.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingTimeout.java
new file mode 100644
index 0000000..8f6e890
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/ForwardingTimeout.java
@@ -0,0 +1,74 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/** A {@link Timeout} which forwards calls to another. Useful for subclassing. 
+ * @hide This class is not part of the Android public SDK API*/
+public class ForwardingTimeout extends Timeout {
+  private Timeout delegate;
+
+  public ForwardingTimeout(Timeout delegate) {
+    if (delegate == null) throw new IllegalArgumentException("delegate == null");
+    this.delegate = delegate;
+  }
+
+  /** {@link Timeout} instance to which this instance is currently delegating. */
+  public final Timeout delegate() {
+    return delegate;
+  }
+
+  public final ForwardingTimeout setDelegate(Timeout delegate) {
+    if (delegate == null) throw new IllegalArgumentException("delegate == null");
+    this.delegate = delegate;
+    return this;
+  }
+
+  @Override public Timeout timeout(long timeout, TimeUnit unit) {
+    return delegate.timeout(timeout, unit);
+  }
+
+  @Override public long timeoutNanos() {
+    return delegate.timeoutNanos();
+  }
+
+  @Override public boolean hasDeadline() {
+    return delegate.hasDeadline();
+  }
+
+  @Override public long deadlineNanoTime() {
+    return delegate.deadlineNanoTime();
+  }
+
+  @Override public Timeout deadlineNanoTime(long deadlineNanoTime) {
+    return delegate.deadlineNanoTime(deadlineNanoTime);
+  }
+
+  @Override public Timeout clearTimeout() {
+    return delegate.clearTimeout();
+  }
+
+  @Override public Timeout clearDeadline() {
+    return delegate.clearDeadline();
+  }
+
+  @Override public void throwIfReached() throws IOException {
+    delegate.throwIfReached();
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSink.java
new file mode 100644
index 0000000..a2d02b2
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSink.java
@@ -0,0 +1,139 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.util.zip.CRC32;
+import java.util.zip.Deflater;
+
+import static java.util.zip.Deflater.DEFAULT_COMPRESSION;
+
+/**
+ * A sink that uses <a href="http://www.ietf.org/rfc/rfc1952.txt">GZIP</a> to
+ * compress written data to another sink.
+ *
+ * <h3>Sync flush</h3>
+ * Aggressive flushing of this stream may result in reduced compression. Each
+ * call to {@link #flush} immediately compresses all currently-buffered data;
+ * this early compression may be less effective than compression performed
+ * without flushing.
+ *
+ * <p>This is equivalent to using {@link Deflater} with the sync flush option.
+ * This class does not offer any partial flush mechanism. For best performance,
+ * only call {@link #flush} when application behavior requires it.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class GzipSink implements Sink {
+  /** Sink into which the GZIP format is written. */
+  private final BufferedSink sink;
+
+  /** The deflater used to compress the body. */
+  private final Deflater deflater;
+
+  /**
+   * The deflater sink takes care of moving data between decompressed source and
+   * compressed sink buffers.
+   */
+  private final DeflaterSink deflaterSink;
+
+  private boolean closed;
+
+  /** Checksum calculated for the compressed body. */
+  private final CRC32 crc = new CRC32();
+
+  public GzipSink(Sink sink) {
+    if (sink == null) throw new IllegalArgumentException("sink == null");
+    this.deflater = new Deflater(DEFAULT_COMPRESSION, true /* No wrap */);
+    this.sink = Okio.buffer(sink);
+    this.deflaterSink = new DeflaterSink(this.sink, deflater);
+
+    writeHeader();
+  }
+
+  @Override public void write(Buffer source, long byteCount) throws IOException {
+    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+    if (byteCount == 0) return;
+
+    updateCrc(source, byteCount);
+    deflaterSink.write(source, byteCount);
+  }
+
+  @Override public void flush() throws IOException {
+    deflaterSink.flush();
+  }
+
+  @Override public Timeout timeout() {
+    return sink.timeout();
+  }
+
+  @Override public void close() throws IOException {
+    if (closed) return;
+
+    // This method delegates to the DeflaterSink for finishing the deflate process
+    // but keeps responsibility for releasing the deflater's resources. This is
+    // necessary because writeFooter needs to query the processed byte count which
+    // only works when the deflater is still open.
+
+    Throwable thrown = null;
+    try {
+      deflaterSink.finishDeflate();
+      writeFooter();
+    } catch (Throwable e) {
+      thrown = e;
+    }
+
+    try {
+      deflater.end();
+    } catch (Throwable e) {
+      if (thrown == null) thrown = e;
+    }
+
+    try {
+      sink.close();
+    } catch (Throwable e) {
+      if (thrown == null) thrown = e;
+    }
+    closed = true;
+
+    if (thrown != null) Util.sneakyRethrow(thrown);
+  }
+
+  private void writeHeader() {
+    // Write the Gzip header directly into the buffer for the sink to avoid handling IOException.
+    Buffer buffer = this.sink.buffer();
+    buffer.writeShort(0x1f8b); // Two-byte Gzip ID.
+    buffer.writeByte(0x08); // 8 == Deflate compression method.
+    buffer.writeByte(0x00); // No flags.
+    buffer.writeInt(0x00); // No modification time.
+    buffer.writeByte(0x00); // No extra flags.
+    buffer.writeByte(0x00); // No OS.
+  }
+
+  private void writeFooter() throws IOException {
+    sink.writeIntLe((int) crc.getValue()); // CRC of original data.
+    sink.writeIntLe(deflater.getTotalIn()); // Length of original data.
+  }
+
+  /** Updates the CRC with the given bytes. */
+  private void updateCrc(Buffer buffer, long byteCount) {
+    for (Segment head = buffer.head; byteCount > 0; head = head.next) {
+      int segmentLength = (int) Math.min(byteCount, head.limit - head.pos);
+      crc.update(head.data, head.pos, segmentLength);
+      byteCount -= segmentLength;
+    }
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSource.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSource.java
new file mode 100644
index 0000000..f9586c0
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/GzipSource.java
@@ -0,0 +1,211 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.zip.CRC32;
+import java.util.zip.Inflater;
+
+/**
+ * A source that uses <a href="http://www.ietf.org/rfc/rfc1952.txt">GZIP</a> to
+ * decompress data read from another source.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class GzipSource implements Source {
+  private static final byte FHCRC = 1;
+  private static final byte FEXTRA = 2;
+  private static final byte FNAME = 3;
+  private static final byte FCOMMENT = 4;
+
+  private static final byte SECTION_HEADER = 0;
+  private static final byte SECTION_BODY = 1;
+  private static final byte SECTION_TRAILER = 2;
+  private static final byte SECTION_DONE = 3;
+
+  /** The current section. Always progresses forward. */
+  private int section = SECTION_HEADER;
+
+  /**
+   * Our source should yield a GZIP header (which we consume directly), followed
+   * by deflated bytes (which we consume via an InflaterSource), followed by a
+   * GZIP trailer (which we also consume directly).
+   */
+  private final BufferedSource source;
+
+  /** The inflater used to decompress the deflated body. */
+  private final Inflater inflater;
+
+  /**
+   * The inflater source takes care of moving data between compressed source and
+   * decompressed sink buffers.
+   */
+  private final InflaterSource inflaterSource;
+
+  /** Checksum used to check both the GZIP header and decompressed body. */
+  private final CRC32 crc = new CRC32();
+
+  public GzipSource(Source source) {
+    if (source == null) throw new IllegalArgumentException("source == null");
+    this.inflater = new Inflater(true);
+    this.source = Okio.buffer(source);
+    this.inflaterSource = new InflaterSource(this.source, inflater);
+  }
+
+  @Override public long read(Buffer sink, long byteCount) throws IOException {
+    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+    if (byteCount == 0) return 0;
+
+    // If we haven't consumed the header, we must consume it before anything else.
+    if (section == SECTION_HEADER) {
+      consumeHeader();
+      section = SECTION_BODY;
+    }
+
+    // Attempt to read at least a byte of the body. If we do, we're done.
+    if (section == SECTION_BODY) {
+      long offset = sink.size;
+      long result = inflaterSource.read(sink, byteCount);
+      if (result != -1) {
+        updateCrc(sink, offset, result);
+        return result;
+      }
+      section = SECTION_TRAILER;
+    }
+
+    // The body is exhausted; time to read the trailer. We always consume the
+    // trailer before returning a -1 exhausted result; that way if you read to
+    // the end of a GzipSource you guarantee that the CRC has been checked.
+    if (section == SECTION_TRAILER) {
+      consumeTrailer();
+      section = SECTION_DONE;
+
+      // Gzip streams self-terminate: they return -1 before their underlying
+      // source returns -1. Here we attempt to force the underlying stream to
+      // return -1 which may trigger it to release its resources. If it doesn't
+      // return -1, then our Gzip data finished prematurely!
+      if (!source.exhausted()) {
+        throw new IOException("gzip finished without exhausting source");
+      }
+    }
+
+    return -1;
+  }
+
+  private void consumeHeader() throws IOException {
+    // Read the 10-byte header. We peek at the flags byte first so we know if we
+    // need to CRC the entire header. Then we read the magic ID1ID2 sequence.
+    // We can skip everything else in the first 10 bytes.
+    // +---+---+---+---+---+---+---+---+---+---+
+    // |ID1|ID2|CM |FLG|     MTIME     |XFL|OS | (more-->)
+    // +---+---+---+---+---+---+---+---+---+---+
+    source.require(10);
+    byte flags = source.buffer().getByte(3);
+    boolean fhcrc = ((flags >> FHCRC) & 1) == 1;
+    if (fhcrc) updateCrc(source.buffer(), 0, 10);
+
+    short id1id2 = source.readShort();
+    checkEqual("ID1ID2", (short) 0x1f8b, id1id2);
+    source.skip(8);
+
+    // Skip optional extra fields.
+    // +---+---+=================================+
+    // | XLEN  |...XLEN bytes of "extra field"...| (more-->)
+    // +---+---+=================================+
+    if (((flags >> FEXTRA) & 1) == 1) {
+      source.require(2);
+      if (fhcrc) updateCrc(source.buffer(), 0, 2);
+      int xlen = source.buffer().readShortLe();
+      source.require(xlen);
+      if (fhcrc) updateCrc(source.buffer(), 0, xlen);
+      source.skip(xlen);
+    }
+
+    // Skip an optional 0-terminated name.
+    // +=========================================+
+    // |...original file name, zero-terminated...| (more-->)
+    // +=========================================+
+    if (((flags >> FNAME) & 1) == 1) {
+      long index = source.indexOf((byte) 0);
+      if (index == -1) throw new EOFException();
+      if (fhcrc) updateCrc(source.buffer(), 0, index + 1);
+      source.skip(index + 1);
+    }
+
+    // Skip an optional 0-terminated comment.
+    // +===================================+
+    // |...file comment, zero-terminated...| (more-->)
+    // +===================================+
+    if (((flags >> FCOMMENT) & 1) == 1) {
+      long index = source.indexOf((byte) 0);
+      if (index == -1) throw new EOFException();
+      if (fhcrc) updateCrc(source.buffer(), 0, index + 1);
+      source.skip(index + 1);
+    }
+
+    // Confirm the optional header CRC.
+    // +---+---+
+    // | CRC16 |
+    // +---+---+
+    if (fhcrc) {
+      checkEqual("FHCRC", source.readShortLe(), (short) crc.getValue());
+      crc.reset();
+    }
+  }
+
+  private void consumeTrailer() throws IOException {
+    // Read the eight-byte trailer. Confirm the body's CRC and size.
+    // +---+---+---+---+---+---+---+---+
+    // |     CRC32     |     ISIZE     |
+    // +---+---+---+---+---+---+---+---+
+    checkEqual("CRC", source.readIntLe(), (int) crc.getValue());
+    checkEqual("ISIZE", source.readIntLe(), inflater.getTotalOut());
+  }
+
+  @Override public Timeout timeout() {
+    return source.timeout();
+  }
+
+  @Override public void close() throws IOException {
+    inflaterSource.close();
+  }
+
+  /** Updates the CRC with the given bytes. */
+  private void updateCrc(Buffer buffer, long offset, long byteCount) {
+    // Skip segments that we aren't checksumming.
+    Segment s = buffer.head;
+    for (; offset >= (s.limit - s.pos); s = s.next) {
+      offset -= (s.limit - s.pos);
+    }
+
+    // Checksum one segment at a time.
+    for (; byteCount > 0; s = s.next) {
+      int pos = (int) (s.pos + offset);
+      int toUpdate = (int) Math.min(s.limit - pos, byteCount);
+      crc.update(s.data, pos, toUpdate);
+      byteCount -= toUpdate;
+      offset = 0;
+    }
+  }
+
+  private void checkEqual(String name, int expected, int actual) throws IOException {
+    if (actual != expected) {
+      throw new IOException(String.format(
+          "%s: actual 0x%08x != expected 0x%08x", name, actual, expected));
+    }
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/InflaterSource.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/InflaterSource.java
new file mode 100644
index 0000000..f94fe9f
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/InflaterSource.java
@@ -0,0 +1,130 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ * A source that uses <a href="http://tools.ietf.org/html/rfc1951">DEFLATE</a>
+ * to decompress data read from another source.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class InflaterSource implements Source {
+  private final BufferedSource source;
+  private final Inflater inflater;
+
+  /**
+   * When we call Inflater.setInput(), the inflater keeps our byte array until
+   * it needs input again. This tracks how many bytes the inflater is currently
+   * holding on to.
+   */
+  private int bufferBytesHeldByInflater;
+  private boolean closed;
+
+  public InflaterSource(Source source, Inflater inflater) {
+    this(Okio.buffer(source), inflater);
+  }
+
+  /**
+   * This package-private constructor shares a buffer with its trusted caller.
+   * In general we can't share a BufferedSource because the inflater holds input
+   * bytes until they are inflated.
+   */
+  InflaterSource(BufferedSource source, Inflater inflater) {
+    if (source == null) throw new IllegalArgumentException("source == null");
+    if (inflater == null) throw new IllegalArgumentException("inflater == null");
+    this.source = source;
+    this.inflater = inflater;
+  }
+
+  @Override public long read(
+      Buffer sink, long byteCount) throws IOException {
+    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+    if (closed) throw new IllegalStateException("closed");
+    if (byteCount == 0) return 0;
+
+    while (true) {
+      boolean sourceExhausted = refill();
+
+      // Decompress the inflater's compressed data into the sink.
+      try {
+        Segment tail = sink.writableSegment(1);
+        int bytesInflated = inflater.inflate(tail.data, tail.limit, Segment.SIZE - tail.limit);
+        if (bytesInflated > 0) {
+          tail.limit += bytesInflated;
+          sink.size += bytesInflated;
+          return bytesInflated;
+        }
+        if (inflater.finished() || inflater.needsDictionary()) {
+          releaseInflatedBytes();
+          if (tail.pos == tail.limit) {
+            // We allocated a tail segment, but didn't end up needing it. Recycle!
+            sink.head = tail.pop();
+            SegmentPool.recycle(tail);
+          }
+          return -1;
+        }
+        if (sourceExhausted) throw new EOFException("source exhausted prematurely");
+      } catch (DataFormatException e) {
+        throw new IOException(e);
+      }
+    }
+  }
+
+  /**
+   * Refills the inflater with compressed data if it needs input. (And only if
+   * it needs input). Returns true if the inflater required input but the source
+   * was exhausted.
+   */
+  public boolean refill() throws IOException {
+    if (!inflater.needsInput()) return false;
+
+    releaseInflatedBytes();
+    if (inflater.getRemaining() != 0) throw new IllegalStateException("?"); // TODO: possible?
+
+    // If there are compressed bytes in the source, assign them to the inflater.
+    if (source.exhausted()) return true;
+
+    // Assign buffer bytes to the inflater.
+    Segment head = source.buffer().head;
+    bufferBytesHeldByInflater = head.limit - head.pos;
+    inflater.setInput(head.data, head.pos, bufferBytesHeldByInflater);
+    return false;
+  }
+
+  /** When the inflater has processed compressed data, remove it from the buffer. */
+  private void releaseInflatedBytes() throws IOException {
+    if (bufferBytesHeldByInflater == 0) return;
+    int toRelease = bufferBytesHeldByInflater - inflater.getRemaining();
+    bufferBytesHeldByInflater -= toRelease;
+    source.skip(toRelease);
+  }
+
+  @Override public Timeout timeout() {
+    return source.timeout();
+  }
+
+  @Override public void close() throws IOException {
+    if (closed) return;
+    inflater.end();
+    closed = true;
+    source.close();
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Okio.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Okio.java
new file mode 100644
index 0000000..d1dd017
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Okio.java
@@ -0,0 +1,247 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+
+/** Essential APIs for working with Okio. 
+ * @hide This class is not part of the Android public SDK API*/
+public final class Okio {
+  private static final Logger logger = Logger.getLogger(Okio.class.getName());
+
+  private Okio() {
+  }
+
+  /**
+   * Returns a new source that buffers reads from {@code source}. The returned
+   * source will perform bulk reads into its in-memory buffer. Use this wherever
+   * you read a source to get an ergonomic and efficient access to data.
+   */
+  public static BufferedSource buffer(Source source) {
+    if (source == null) throw new IllegalArgumentException("source == null");
+    return new RealBufferedSource(source);
+  }
+
+  /**
+   * Returns a new sink that buffers writes to {@code sink}. The returned sink
+   * will batch writes to {@code sink}. Use this wherever you write to a sink to
+   * get an ergonomic and efficient access to data.
+   */
+  public static BufferedSink buffer(Sink sink) {
+    if (sink == null) throw new IllegalArgumentException("sink == null");
+    return new RealBufferedSink(sink);
+  }
+
+  /** Returns a sink that writes to {@code out}. */
+  public static Sink sink(OutputStream out) {
+    return sink(out, new Timeout());
+  }
+
+  private static Sink sink(final OutputStream out, final Timeout timeout) {
+    if (out == null) throw new IllegalArgumentException("out == null");
+    if (timeout == null) throw new IllegalArgumentException("timeout == null");
+
+    return new Sink() {
+      @Override public void write(Buffer source, long byteCount) throws IOException {
+        checkOffsetAndCount(source.size, 0, byteCount);
+        while (byteCount > 0) {
+          timeout.throwIfReached();
+          Segment head = source.head;
+          int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
+          out.write(head.data, head.pos, toCopy);
+
+          head.pos += toCopy;
+          byteCount -= toCopy;
+          source.size -= toCopy;
+
+          if (head.pos == head.limit) {
+            source.head = head.pop();
+            SegmentPool.recycle(head);
+          }
+        }
+      }
+
+      @Override public void flush() throws IOException {
+        out.flush();
+      }
+
+      @Override public void close() throws IOException {
+        out.close();
+      }
+
+      @Override public Timeout timeout() {
+        return timeout;
+      }
+
+      @Override public String toString() {
+        return "sink(" + out + ")";
+      }
+    };
+  }
+
+  /**
+   * Returns a sink that writes to {@code socket}. Prefer this over {@link
+   * #sink(OutputStream)} because this method honors timeouts. When the socket
+   * write times out, the socket is asynchronously closed by a watchdog thread.
+   */
+  public static Sink sink(Socket socket) throws IOException {
+    if (socket == null) throw new IllegalArgumentException("socket == null");
+    AsyncTimeout timeout = timeout(socket);
+    Sink sink = sink(socket.getOutputStream(), timeout);
+    return timeout.sink(sink);
+  }
+
+  /** Returns a source that reads from {@code in}. */
+  public static Source source(InputStream in) {
+    return source(in, new Timeout());
+  }
+
+  private static Source source(final InputStream in, final Timeout timeout) {
+    if (in == null) throw new IllegalArgumentException("in == null");
+    if (timeout == null) throw new IllegalArgumentException("timeout == null");
+
+    return new Source() {
+      @Override public long read(Buffer sink, long byteCount) throws IOException {
+        if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+        if (byteCount == 0) return 0;
+        try {
+          timeout.throwIfReached();
+          Segment tail = sink.writableSegment(1);
+          int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
+          int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
+          if (bytesRead == -1) return -1;
+          tail.limit += bytesRead;
+          sink.size += bytesRead;
+          return bytesRead;
+        } catch (AssertionError e) {
+          if (isAndroidGetsocknameError(e)) throw new IOException(e);
+          throw e;
+        }
+      }
+
+      @Override public void close() throws IOException {
+        in.close();
+      }
+
+      @Override public Timeout timeout() {
+        return timeout;
+      }
+
+      @Override public String toString() {
+        return "source(" + in + ")";
+      }
+    };
+  }
+
+  /** Returns a source that reads from {@code file}. */
+  public static Source source(File file) throws FileNotFoundException {
+    if (file == null) throw new IllegalArgumentException("file == null");
+    return source(new FileInputStream(file));
+  }
+
+  // ANDROID-BEGIN
+  //  /** Returns a source that reads from {@code path}. */
+  //  @IgnoreJRERequirement // Should only be invoked on Java 7+.
+  //  public static Source source(Path path, OpenOption... options) throws IOException {
+  //    if (path == null) throw new IllegalArgumentException("path == null");
+  //    return source(Files.newInputStream(path, options));
+  //  }
+  // ANDROID-END
+
+  /** Returns a sink that writes to {@code file}. */
+  public static Sink sink(File file) throws FileNotFoundException {
+    if (file == null) throw new IllegalArgumentException("file == null");
+    return sink(new FileOutputStream(file));
+  }
+
+  /** Returns a sink that appends to {@code file}. */
+  public static Sink appendingSink(File file) throws FileNotFoundException {
+    if (file == null) throw new IllegalArgumentException("file == null");
+    return sink(new FileOutputStream(file, true));
+  }
+
+  // ANDROID-BEGIN
+  //  /** Returns a sink that writes to {@code path}. */
+  //  @IgnoreJRERequirement // Should only be invoked on Java 7+.
+  //  public static Sink sink(Path path, OpenOption... options) throws IOException {
+  //    if (path == null) throw new IllegalArgumentException("path == null");
+  //    return sink(Files.newOutputStream(path, options));
+  //  }
+  // ANDROID-END
+
+  /**
+   * Returns a source that reads from {@code socket}. Prefer this over {@link
+   * #source(InputStream)} because this method honors timeouts. When the socket
+   * read times out, the socket is asynchronously closed by a watchdog thread.
+   */
+  public static Source source(Socket socket) throws IOException {
+    if (socket == null) throw new IllegalArgumentException("socket == null");
+    AsyncTimeout timeout = timeout(socket);
+    Source source = source(socket.getInputStream(), timeout);
+    return timeout.source(source);
+  }
+
+  private static AsyncTimeout timeout(final Socket socket) {
+    return new AsyncTimeout() {
+      @Override protected IOException newTimeoutException(IOException cause) {
+        InterruptedIOException ioe = new SocketTimeoutException("timeout");
+        if (cause != null) {
+          ioe.initCause(cause);
+        }
+        return ioe;
+      }
+
+      @Override protected void timedOut() {
+        try {
+          socket.close();
+        } catch (Exception e) {
+          logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
+        } catch (AssertionError e) {
+          if (isAndroidGetsocknameError(e)) {
+            // Catch this exception due to a Firmware issue up to android 4.2.2
+            // https://code.google.com/p/android/issues/detail?id=54072
+            logger.log(Level.WARNING, "Failed to close timed out socket " + socket, e);
+          } else {
+            throw e;
+          }
+        }
+      }
+    };
+  }
+
+  /**
+   * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2.
+   * https://code.google.com/p/android/issues/detail?id=54072
+   */
+  private static boolean isAndroidGetsocknameError(AssertionError e) {
+    return e.getCause() != null && e.getMessage() != null
+        && e.getMessage().contains("getsockname failed");
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSink.java
new file mode 100644
index 0000000..410e122
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSink.java
@@ -0,0 +1,258 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+final class RealBufferedSink implements BufferedSink {
+  public final Buffer buffer;
+  public final Sink sink;
+  private boolean closed;
+
+  public RealBufferedSink(Sink sink, Buffer buffer) {
+    if (sink == null) throw new IllegalArgumentException("sink == null");
+    this.buffer = buffer;
+    this.sink = sink;
+  }
+
+  public RealBufferedSink(Sink sink) {
+    this(sink, new Buffer());
+  }
+
+  @Override public Buffer buffer() {
+    return buffer;
+  }
+
+  @Override public void write(Buffer source, long byteCount)
+      throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.write(source, byteCount);
+    emitCompleteSegments();
+  }
+
+  @Override public BufferedSink write(ByteString byteString) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.write(byteString);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeUtf8(String string) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeUtf8(string);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeUtf8(String string, int beginIndex, int endIndex)
+      throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeUtf8(string, beginIndex, endIndex);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeUtf8CodePoint(int codePoint) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeUtf8CodePoint(codePoint);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeString(String string, Charset charset) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeString(string, charset);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeString(String string, int beginIndex, int endIndex,
+      Charset charset) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeString(string, beginIndex, endIndex, charset);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink write(byte[] source) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.write(source);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink write(byte[] source, int offset, int byteCount) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.write(source, offset, byteCount);
+    return emitCompleteSegments();
+  }
+
+  @Override public long writeAll(Source source) throws IOException {
+    if (source == null) throw new IllegalArgumentException("source == null");
+    long totalBytesRead = 0;
+    for (long readCount; (readCount = source.read(buffer, Segment.SIZE)) != -1; ) {
+      totalBytesRead += readCount;
+      emitCompleteSegments();
+    }
+    return totalBytesRead;
+  }
+
+  @Override public BufferedSink write(Source source, long byteCount) throws IOException {
+    while (byteCount > 0) {
+      long read = source.read(buffer, byteCount);
+      if (read == -1) throw new EOFException();
+      byteCount -= read;
+      emitCompleteSegments();
+    }
+    return this;
+  }
+
+  @Override public BufferedSink writeByte(int b) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeByte(b);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeShort(int s) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeShort(s);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeShortLe(int s) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeShortLe(s);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeInt(int i) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeInt(i);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeIntLe(int i) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeIntLe(i);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeLong(long v) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeLong(v);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeLongLe(long v) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeLongLe(v);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeDecimalLong(long v) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeDecimalLong(v);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink writeHexadecimalUnsignedLong(long v) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    buffer.writeHexadecimalUnsignedLong(v);
+    return emitCompleteSegments();
+  }
+
+  @Override public BufferedSink emitCompleteSegments() throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    long byteCount = buffer.completeSegmentByteCount();
+    if (byteCount > 0) sink.write(buffer, byteCount);
+    return this;
+  }
+
+  @Override public BufferedSink emit() throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    long byteCount = buffer.size();
+    if (byteCount > 0) sink.write(buffer, byteCount);
+    return this;
+  }
+
+  @Override public OutputStream outputStream() {
+    return new OutputStream() {
+      @Override public void write(int b) throws IOException {
+        if (closed) throw new IOException("closed");
+        buffer.writeByte((byte) b);
+        emitCompleteSegments();
+      }
+
+      @Override public void write(byte[] data, int offset, int byteCount) throws IOException {
+        if (closed) throw new IOException("closed");
+        buffer.write(data, offset, byteCount);
+        emitCompleteSegments();
+      }
+
+      @Override public void flush() throws IOException {
+        // For backwards compatibility, a flush() on a closed stream is a no-op.
+        if (!closed) {
+          RealBufferedSink.this.flush();
+        }
+      }
+
+      @Override public void close() throws IOException {
+        RealBufferedSink.this.close();
+      }
+
+      @Override public String toString() {
+        return RealBufferedSink.this + ".outputStream()";
+      }
+    };
+  }
+
+  @Override public void flush() throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    if (buffer.size > 0) {
+      sink.write(buffer, buffer.size);
+    }
+    sink.flush();
+  }
+
+  @Override public void close() throws IOException {
+    if (closed) return;
+
+    // Emit buffered data to the underlying sink. If this fails, we still need
+    // to close the sink; otherwise we risk leaking resources.
+    Throwable thrown = null;
+    try {
+      if (buffer.size > 0) {
+        sink.write(buffer, buffer.size);
+      }
+    } catch (Throwable e) {
+      thrown = e;
+    }
+
+    try {
+      sink.close();
+    } catch (Throwable e) {
+      if (thrown == null) thrown = e;
+    }
+    closed = true;
+
+    if (thrown != null) Util.sneakyRethrow(thrown);
+  }
+
+  @Override public Timeout timeout() {
+    return sink.timeout();
+  }
+
+  @Override public String toString() {
+    return "buffer(" + sink + ")";
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSource.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSource.java
new file mode 100644
index 0000000..5e49106
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/RealBufferedSource.java
@@ -0,0 +1,408 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+
+final class RealBufferedSource implements BufferedSource {
+  public final Buffer buffer;
+  public final Source source;
+  private boolean closed;
+
+  public RealBufferedSource(Source source, Buffer buffer) {
+    if (source == null) throw new IllegalArgumentException("source == null");
+    this.buffer = buffer;
+    this.source = source;
+  }
+
+  public RealBufferedSource(Source source) {
+    this(source, new Buffer());
+  }
+
+  @Override public Buffer buffer() {
+    return buffer;
+  }
+
+  @Override public long read(Buffer sink, long byteCount) throws IOException {
+    if (sink == null) throw new IllegalArgumentException("sink == null");
+    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+    if (closed) throw new IllegalStateException("closed");
+
+    if (buffer.size == 0) {
+      long read = source.read(buffer, Segment.SIZE);
+      if (read == -1) return -1;
+    }
+
+    long toRead = Math.min(byteCount, buffer.size);
+    return buffer.read(sink, toRead);
+  }
+
+  @Override public boolean exhausted() throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    return buffer.exhausted() && source.read(buffer, Segment.SIZE) == -1;
+  }
+
+  @Override public void require(long byteCount) throws IOException {
+    if (!request(byteCount)) throw new EOFException();
+  }
+
+  @Override public boolean request(long byteCount) throws IOException {
+    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+    if (closed) throw new IllegalStateException("closed");
+    while (buffer.size < byteCount) {
+      if (source.read(buffer, Segment.SIZE) == -1) return false;
+    }
+    return true;
+  }
+
+  @Override public byte readByte() throws IOException {
+    require(1);
+    return buffer.readByte();
+  }
+
+  @Override public ByteString readByteString() throws IOException {
+    buffer.writeAll(source);
+    return buffer.readByteString();
+  }
+
+  @Override public ByteString readByteString(long byteCount) throws IOException {
+    require(byteCount);
+    return buffer.readByteString(byteCount);
+  }
+
+  @Override public byte[] readByteArray() throws IOException {
+    buffer.writeAll(source);
+    return buffer.readByteArray();
+  }
+
+  @Override public byte[] readByteArray(long byteCount) throws IOException {
+    require(byteCount);
+    return buffer.readByteArray(byteCount);
+  }
+
+  @Override public int read(byte[] sink) throws IOException {
+    return read(sink, 0, sink.length);
+  }
+
+  @Override public void readFully(byte[] sink) throws IOException {
+    try {
+      require(sink.length);
+    } catch (EOFException e) {
+      // The underlying source is exhausted. Copy the bytes we got before rethrowing.
+      int offset = 0;
+      while (buffer.size > 0) {
+        int read = buffer.read(sink, offset, (int) buffer.size);
+        if (read == -1) throw new AssertionError();
+        offset += read;
+      }
+      throw e;
+    }
+    buffer.readFully(sink);
+  }
+
+  @Override public int read(byte[] sink, int offset, int byteCount) throws IOException {
+    checkOffsetAndCount(sink.length, offset, byteCount);
+
+    if (buffer.size == 0) {
+      long read = source.read(buffer, Segment.SIZE);
+      if (read == -1) return -1;
+    }
+
+    int toRead = (int) Math.min(byteCount, buffer.size);
+    return buffer.read(sink, offset, toRead);
+  }
+
+  @Override public void readFully(Buffer sink, long byteCount) throws IOException {
+    try {
+      require(byteCount);
+    } catch (EOFException e) {
+      // The underlying source is exhausted. Copy the bytes we got before rethrowing.
+      sink.writeAll(buffer);
+      throw e;
+    }
+    buffer.readFully(sink, byteCount);
+  }
+
+  @Override public long readAll(Sink sink) throws IOException {
+    if (sink == null) throw new IllegalArgumentException("sink == null");
+
+    long totalBytesWritten = 0;
+    while (source.read(buffer, Segment.SIZE) != -1) {
+      long emitByteCount = buffer.completeSegmentByteCount();
+      if (emitByteCount > 0) {
+        totalBytesWritten += emitByteCount;
+        sink.write(buffer, emitByteCount);
+      }
+    }
+    if (buffer.size() > 0) {
+      totalBytesWritten += buffer.size();
+      sink.write(buffer, buffer.size());
+    }
+    return totalBytesWritten;
+  }
+
+  @Override public String readUtf8() throws IOException {
+    buffer.writeAll(source);
+    return buffer.readUtf8();
+  }
+
+  @Override public String readUtf8(long byteCount) throws IOException {
+    require(byteCount);
+    return buffer.readUtf8(byteCount);
+  }
+
+  @Override public String readString(Charset charset) throws IOException {
+    if (charset == null) throw new IllegalArgumentException("charset == null");
+
+    buffer.writeAll(source);
+    return buffer.readString(charset);
+  }
+
+  @Override public String readString(long byteCount, Charset charset) throws IOException {
+    require(byteCount);
+    if (charset == null) throw new IllegalArgumentException("charset == null");
+    return buffer.readString(byteCount, charset);
+  }
+
+  @Override public String readUtf8Line() throws IOException {
+    long newline = indexOf((byte) '\n');
+
+    if (newline == -1) {
+      return buffer.size != 0 ? readUtf8(buffer.size) : null;
+    }
+
+    return buffer.readUtf8Line(newline);
+  }
+
+  @Override public String readUtf8LineStrict() throws IOException {
+    long newline = indexOf((byte) '\n');
+    if (newline == -1L) {
+      Buffer data = new Buffer();
+      buffer.copyTo(data, 0, Math.min(32, buffer.size()));
+      throw new EOFException("\\n not found: size=" + buffer.size()
+          + " content=" + data.readByteString().hex() + "...");
+    }
+    return buffer.readUtf8Line(newline);
+  }
+
+  @Override public int readUtf8CodePoint() throws IOException {
+    require(1);
+
+    byte b0 = buffer.getByte(0);
+    if ((b0 & 0xe0) == 0xc0) {
+      require(2);
+    } else if ((b0 & 0xf0) == 0xe0) {
+      require(3);
+    } else if ((b0 & 0xf8) == 0xf0) {
+      require(4);
+    }
+
+    return buffer.readUtf8CodePoint();
+  }
+
+  @Override public short readShort() throws IOException {
+    require(2);
+    return buffer.readShort();
+  }
+
+  @Override public short readShortLe() throws IOException {
+    require(2);
+    return buffer.readShortLe();
+  }
+
+  @Override public int readInt() throws IOException {
+    require(4);
+    return buffer.readInt();
+  }
+
+  @Override public int readIntLe() throws IOException {
+    require(4);
+    return buffer.readIntLe();
+  }
+
+  @Override public long readLong() throws IOException {
+    require(8);
+    return buffer.readLong();
+  }
+
+  @Override public long readLongLe() throws IOException {
+    require(8);
+    return buffer.readLongLe();
+  }
+
+  @Override public long readDecimalLong() throws IOException {
+    require(1);
+
+    for (int pos = 0; request(pos + 1); pos++) {
+      byte b = buffer.getByte(pos);
+      if ((b < '0' || b > '9') && (pos != 0 || b != '-')) {
+        // Non-digit, or non-leading negative sign.
+        if (pos == 0) {
+          throw new NumberFormatException(String.format(
+              "Expected leading [0-9] or '-' character but was %#x", b));
+        }
+        break;
+      }
+    }
+
+    return buffer.readDecimalLong();
+  }
+
+  @Override public long readHexadecimalUnsignedLong() throws IOException {
+    require(1);
+
+    for (int pos = 0; request(pos + 1); pos++) {
+      byte b = buffer.getByte(pos);
+      if ((b < '0' || b > '9') && (b < 'a' || b > 'f') && (b < 'A' || b > 'F')) {
+        // Non-digit, or non-leading negative sign.
+        if (pos == 0) {
+          throw new NumberFormatException(String.format(
+              "Expected leading [0-9a-fA-F] character but was %#x", b));
+        }
+        break;
+      }
+    }
+
+    return buffer.readHexadecimalUnsignedLong();
+  }
+
+  @Override public void skip(long byteCount) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    while (byteCount > 0) {
+      if (buffer.size == 0 && source.read(buffer, Segment.SIZE) == -1) {
+        throw new EOFException();
+      }
+      long toSkip = Math.min(byteCount, buffer.size());
+      buffer.skip(toSkip);
+      byteCount -= toSkip;
+    }
+  }
+
+  @Override public long indexOf(byte b) throws IOException {
+    return indexOf(b, 0);
+  }
+
+  @Override public long indexOf(byte b, long fromIndex) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    while (fromIndex >= buffer.size) {
+      if (source.read(buffer, Segment.SIZE) == -1) return -1L;
+    }
+    long index;
+    while ((index = buffer.indexOf(b, fromIndex)) == -1) {
+      fromIndex = buffer.size;
+      if (source.read(buffer, Segment.SIZE) == -1) return -1L;
+    }
+    return index;
+  }
+
+  @Override public long indexOf(ByteString bytes) throws IOException {
+    return indexOf(bytes, 0);
+  }
+
+  @Override public long indexOf(ByteString bytes, long fromIndex) throws IOException {
+    if (bytes.size() == 0) throw new IllegalArgumentException("bytes is empty");
+    while (true) {
+      fromIndex = indexOf(bytes.getByte(0), fromIndex);
+      if (fromIndex == -1) {
+        return -1;
+      }
+      if (rangeEquals(fromIndex, bytes)) {
+        return fromIndex;
+      }
+      fromIndex++;
+    }
+  }
+
+  @Override public long indexOfElement(ByteString targetBytes) throws IOException {
+    return indexOfElement(targetBytes, 0);
+  }
+
+  @Override public long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException {
+    if (closed) throw new IllegalStateException("closed");
+    while (fromIndex >= buffer.size) {
+      if (source.read(buffer, Segment.SIZE) == -1) return -1L;
+    }
+    long index;
+    while ((index = buffer.indexOfElement(targetBytes, fromIndex)) == -1) {
+      fromIndex = buffer.size;
+      if (source.read(buffer, Segment.SIZE) == -1) return -1L;
+    }
+    return index;
+  }
+
+  private boolean rangeEquals(long offset, ByteString bytes) throws IOException {
+    return request(offset + bytes.size()) && buffer.rangeEquals(offset, bytes);
+  }
+
+  @Override public InputStream inputStream() {
+    return new InputStream() {
+      @Override public int read() throws IOException {
+        if (closed) throw new IOException("closed");
+        if (buffer.size == 0) {
+          long count = source.read(buffer, Segment.SIZE);
+          if (count == -1) return -1;
+        }
+        return buffer.readByte() & 0xff;
+      }
+
+      @Override public int read(byte[] data, int offset, int byteCount) throws IOException {
+        if (closed) throw new IOException("closed");
+        checkOffsetAndCount(data.length, offset, byteCount);
+
+        if (buffer.size == 0) {
+          long count = source.read(buffer, Segment.SIZE);
+          if (count == -1) return -1;
+        }
+
+        return buffer.read(data, offset, byteCount);
+      }
+
+      @Override public int available() throws IOException {
+        if (closed) throw new IOException("closed");
+        return (int) Math.min(buffer.size, Integer.MAX_VALUE);
+      }
+
+      @Override public void close() throws IOException {
+        RealBufferedSource.this.close();
+      }
+
+      @Override public String toString() {
+        return RealBufferedSource.this + ".inputStream()";
+      }
+    };
+  }
+
+  @Override public void close() throws IOException {
+    if (closed) return;
+    closed = true;
+    source.close();
+    buffer.clear();
+  }
+
+  @Override public Timeout timeout() {
+    return source.timeout();
+  }
+
+  @Override public String toString() {
+    return "buffer(" + source + ")";
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Segment.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Segment.java
new file mode 100644
index 0000000..5056a4f
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Segment.java
@@ -0,0 +1,150 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+/**
+ * A segment of a buffer.
+ *
+ * <p>Each segment in a buffer is a circularly-linked list node referencing the following and
+ * preceding segments in the buffer.
+ *
+ * <p>Each segment in the pool is a singly-linked list node referencing the rest of segments in the
+ * pool.
+ *
+ * <p>The underlying byte arrays of segments may be shared between buffers and byte strings. When a
+ * segment's byte array is shared the segment may not be recycled, nor may its byte data be changed.
+ * The lone exception is that the owner segment is allowed to append to the segment, writing data at
+ * {@code limit} and beyond. There is a single owning segment for each byte array. Positions,
+ * limits, prev, and next references are not shared.
+ */
+final class Segment {
+  /** The size of all segments in bytes. */
+  static final int SIZE = 8192;
+
+  final byte[] data;
+
+  /** The next byte of application data byte to read in this segment. */
+  int pos;
+
+  /** The first byte of available data ready to be written to. */
+  int limit;
+
+  /** True if other segments or byte strings use the same byte array. */
+  boolean shared;
+
+  /** True if this segment owns the byte array and can append to it, extending {@code limit}. */
+  boolean owner;
+
+  /** Next segment in a linked or circularly-linked list. */
+  Segment next;
+
+  /** Previous segment in a circularly-linked list. */
+  Segment prev;
+
+  Segment() {
+    this.data = new byte[SIZE];
+    this.owner = true;
+    this.shared = false;
+  }
+
+  Segment(Segment shareFrom) {
+    this(shareFrom.data, shareFrom.pos, shareFrom.limit);
+    shareFrom.shared = true;
+  }
+
+  Segment(byte[] data, int pos, int limit) {
+    this.data = data;
+    this.pos = pos;
+    this.limit = limit;
+    this.owner = false;
+    this.shared = true;
+  }
+
+  /**
+   * Removes this segment of a circularly-linked list and returns its successor.
+   * Returns null if the list is now empty.
+   */
+  public Segment pop() {
+    Segment result = next != this ? next : null;
+    prev.next = next;
+    next.prev = prev;
+    next = null;
+    prev = null;
+    return result;
+  }
+
+  /**
+   * Appends {@code segment} after this segment in the circularly-linked list.
+   * Returns the pushed segment.
+   */
+  public Segment push(Segment segment) {
+    segment.prev = this;
+    segment.next = next;
+    next.prev = segment;
+    next = segment;
+    return segment;
+  }
+
+  /**
+   * Splits this head of a circularly-linked list into two segments. The first
+   * segment contains the data in {@code [pos..pos+byteCount)}. The second
+   * segment contains the data in {@code [pos+byteCount..limit)}. This can be
+   * useful when moving partial segments from one buffer to another.
+   *
+   * <p>Returns the new head of the circularly-linked list.
+   */
+  public Segment split(int byteCount) {
+    if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
+    Segment prefix = new Segment(this);
+    prefix.limit = prefix.pos + byteCount;
+    pos += byteCount;
+    prev.push(prefix);
+    return prefix;
+  }
+
+  /**
+   * Call this when the tail and its predecessor may both be less than half
+   * full. This will copy data so that segments can be recycled.
+   */
+  public void compact() {
+    if (prev == this) throw new IllegalStateException();
+    if (!prev.owner) return; // Cannot compact: prev isn't writable.
+    int byteCount = limit - pos;
+    int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
+    if (byteCount > availableByteCount) return; // Cannot compact: not enough writable space.
+    writeTo(prev, byteCount);
+    pop();
+    SegmentPool.recycle(this);
+  }
+
+  /** Moves {@code byteCount} bytes from this segment to {@code sink}. */
+  public void writeTo(Segment sink, int byteCount) {
+    if (!sink.owner) throw new IllegalArgumentException();
+    if (sink.limit + byteCount > SIZE) {
+      // We can't fit byteCount bytes at the sink's current position. Shift sink first.
+      if (sink.shared) throw new IllegalArgumentException();
+      if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
+      System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
+      sink.limit -= sink.pos;
+      sink.pos = 0;
+    }
+
+    System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
+    sink.limit += byteCount;
+    pos += byteCount;
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentPool.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentPool.java
new file mode 100644
index 0000000..fdc1395
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentPool.java
@@ -0,0 +1,61 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+/**
+ * A collection of unused segments, necessary to avoid GC churn and zero-fill.
+ * This pool is a thread-safe static singleton.
+ */
+final class SegmentPool {
+  /** The maximum number of bytes to pool. */
+  // TODO: Is 64 KiB a good maximum size? Do we ever have that many idle segments?
+  static final long MAX_SIZE = 64 * 1024; // 64 KiB.
+
+  /** Singly-linked list of segments. */
+  static Segment next;
+
+  /** Total bytes in this pool. */
+  static long byteCount;
+
+  private SegmentPool() {
+  }
+
+  static Segment take() {
+    synchronized (SegmentPool.class) {
+      if (next != null) {
+        Segment result = next;
+        next = result.next;
+        result.next = null;
+        byteCount -= Segment.SIZE;
+        return result;
+      }
+    }
+    return new Segment(); // Pool is empty. Don't zero-fill while holding a lock.
+  }
+
+  static void recycle(Segment segment) {
+    if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
+    if (segment.shared) return; // This segment cannot be recycled.
+    synchronized (SegmentPool.class) {
+      if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full.
+      byteCount += Segment.SIZE;
+      segment.next = next;
+      segment.pos = segment.limit = 0;
+      next = segment;
+    }
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentedByteString.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentedByteString.java
new file mode 100644
index 0000000..bb2dd98
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/SegmentedByteString.java
@@ -0,0 +1,259 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+import static com.android.okhttp.okio.Util.arrayRangeEquals;
+import static com.android.okhttp.okio.Util.checkOffsetAndCount;
+
+/**
+ * An immutable byte string composed of segments of byte arrays. This class exists to implement
+ * efficient snapshots of buffers. It is implemented as an array of segments, plus a directory in
+ * two halves that describes how the segments compose this byte string.
+ *
+ * <p>The first half of the directory is the cumulative byte count covered by each segment. The
+ * element at {@code directory[0]} contains the number of bytes held in {@code segments[0]}; the
+ * element at {@code directory[1]} contains the number of bytes held in {@code segments[0] +
+ * segments[1]}, and so on. The element at {@code directory[segments.length - 1]} contains the total
+ * size of this byte string. The first half of the directory is always monotonically increasing.
+ *
+ * <p>The second half of the directory is the offset in {@code segments} of the first content byte.
+ * Bytes preceding this offset are unused, as are bytes beyond the segment's effective size.
+ *
+ * <p>Suppose we have a byte string, {@code [A, B, C, D, E, F, G, H, I, J, K, L, M]} that is stored
+ * across three byte arrays: {@code [x, x, x, x, A, B, C, D, E, x, x, x]}, {@code [x, F, G]}, and
+ * {@code [H, I, J, K, L, M, x, x, x, x, x, x]}. The three byte arrays would be stored in {@code
+ * segments} in order. Since the arrays contribute 5, 2, and 6 elements respectively, the directory
+ * starts with {@code [5, 7, 13} to hold the cumulative total at each position. Since the offsets
+ * into the arrays are 4, 1, and 0 respectively, the directory ends with {@code 4, 1, 0]}.
+ * Concatenating these two halves, the complete directory is {@code [5, 7, 13, 4, 1, 0]}.
+ *
+ * <p>This structure is chosen so that the segment holding a particular offset can be found by
+ * binary search. We use one array rather than two for the directory as a micro-optimization.
+ */
+final class SegmentedByteString extends ByteString {
+  transient final byte[][] segments;
+  transient final int[] directory;
+
+  SegmentedByteString(Buffer buffer, int byteCount) {
+    super(null);
+    checkOffsetAndCount(buffer.size, 0, byteCount);
+
+    // Walk through the buffer to count how many segments we'll need.
+    int offset = 0;
+    int segmentCount = 0;
+    for (Segment s = buffer.head; offset < byteCount; s = s.next) {
+      if (s.limit == s.pos) {
+        throw new AssertionError("s.limit == s.pos"); // Empty segment. This should not happen!
+      }
+      offset += s.limit - s.pos;
+      segmentCount++;
+    }
+
+    // Walk through the buffer again to assign segments and build the directory.
+    this.segments = new byte[segmentCount][];
+    this.directory = new int[segmentCount * 2];
+    offset = 0;
+    segmentCount = 0;
+    for (Segment s = buffer.head; offset < byteCount; s = s.next) {
+      segments[segmentCount] = s.data;
+      offset += s.limit - s.pos;
+      directory[segmentCount] = offset;
+      directory[segmentCount + segments.length] = s.pos;
+      s.shared = true;
+      segmentCount++;
+    }
+  }
+
+  @Override public String utf8() {
+    return toByteString().utf8();
+  }
+
+  @Override public String base64() {
+    return toByteString().base64();
+  }
+
+  @Override public String hex() {
+    return toByteString().hex();
+  }
+
+  @Override public ByteString toAsciiLowercase() {
+    return toByteString().toAsciiLowercase();
+  }
+
+  @Override public ByteString toAsciiUppercase() {
+    return toByteString().toAsciiUppercase();
+  }
+
+  @Override public ByteString md5() {
+    return toByteString().md5();
+  }
+
+  @Override public ByteString sha256() {
+    return toByteString().sha256();
+  }
+
+  @Override public String base64Url() {
+    return toByteString().base64Url();
+  }
+
+  @Override public ByteString substring(int beginIndex) {
+    return toByteString().substring(beginIndex);
+  }
+
+  @Override public ByteString substring(int beginIndex, int endIndex) {
+    return toByteString().substring(beginIndex, endIndex);
+  }
+
+  @Override public byte getByte(int pos) {
+    checkOffsetAndCount(directory[segments.length - 1], pos, 1);
+    int segment = segment(pos);
+    int segmentOffset = segment == 0 ? 0 : directory[segment - 1];
+    int segmentPos = directory[segment + segments.length];
+    return segments[segment][pos - segmentOffset + segmentPos];
+  }
+
+  /** Returns the index of the segment that contains the byte at {@code pos}. */
+  private int segment(int pos) {
+    // Search for (pos + 1) instead of (pos) because the directory holds sizes, not indexes.
+    int i = Arrays.binarySearch(directory, 0, segments.length, pos + 1);
+    return i >= 0 ? i : ~i; // If i is negative, bitflip to get the insert position.
+  }
+
+  @Override public int size() {
+    return directory[segments.length - 1];
+  }
+
+  @Override public byte[] toByteArray() {
+    byte[] result = new byte[directory[segments.length - 1]];
+    int segmentOffset = 0;
+    for (int s = 0, segmentCount = segments.length; s < segmentCount; s++) {
+      int segmentPos = directory[segmentCount + s];
+      int nextSegmentOffset = directory[s];
+      System.arraycopy(segments[s], segmentPos, result, segmentOffset,
+          nextSegmentOffset - segmentOffset);
+      segmentOffset = nextSegmentOffset;
+    }
+    return result;
+  }
+
+  @Override public void write(OutputStream out) throws IOException {
+    if (out == null) throw new IllegalArgumentException("out == null");
+    int segmentOffset = 0;
+    for (int s = 0, segmentCount = segments.length; s < segmentCount; s++) {
+      int segmentPos = directory[segmentCount + s];
+      int nextSegmentOffset = directory[s];
+      out.write(segments[s], segmentPos, nextSegmentOffset - segmentOffset);
+      segmentOffset = nextSegmentOffset;
+    }
+  }
+
+  @Override void write(Buffer buffer) {
+    int segmentOffset = 0;
+    for (int s = 0, segmentCount = segments.length; s < segmentCount; s++) {
+      int segmentPos = directory[segmentCount + s];
+      int nextSegmentOffset = directory[s];
+      Segment segment = new Segment(segments[s], segmentPos,
+          segmentPos + nextSegmentOffset - segmentOffset);
+      if (buffer.head == null) {
+        buffer.head = segment.next = segment.prev = segment;
+      } else {
+        buffer.head.prev.push(segment);
+      }
+      segmentOffset = nextSegmentOffset;
+    }
+    buffer.size += segmentOffset;
+  }
+
+  @Override public boolean rangeEquals(
+      int offset, ByteString other, int otherOffset, int byteCount) {
+    if (offset > size() - byteCount) return false;
+    // Go segment-by-segment through this, passing arrays to other's rangeEquals().
+    for (int s = segment(offset); byteCount > 0; s++) {
+      int segmentOffset = s == 0 ? 0 : directory[s - 1];
+      int segmentSize = directory[s] - segmentOffset;
+      int stepSize = Math.min(byteCount, segmentOffset + segmentSize - offset);
+      int segmentPos = directory[segments.length + s];
+      int arrayOffset = offset - segmentOffset + segmentPos;
+      if (!other.rangeEquals(otherOffset, segments[s], arrayOffset, stepSize)) return false;
+      offset += stepSize;
+      otherOffset += stepSize;
+      byteCount -= stepSize;
+    }
+    return true;
+  }
+
+  @Override public boolean rangeEquals(int offset, byte[] other, int otherOffset, int byteCount) {
+    if (offset > size() - byteCount || otherOffset > other.length - byteCount) return false;
+    // Go segment-by-segment through this, comparing ranges of arrays.
+    for (int s = segment(offset); byteCount > 0; s++) {
+      int segmentOffset = s == 0 ? 0 : directory[s - 1];
+      int segmentSize = directory[s] - segmentOffset;
+      int stepSize = Math.min(byteCount, segmentOffset + segmentSize - offset);
+      int segmentPos = directory[segments.length + s];
+      int arrayOffset = offset - segmentOffset + segmentPos;
+      if (!arrayRangeEquals(segments[s], arrayOffset, other, otherOffset, stepSize)) return false;
+      offset += stepSize;
+      otherOffset += stepSize;
+      byteCount -= stepSize;
+    }
+    return true;
+  }
+
+  /** Returns a copy as a non-segmented byte string. */
+  private ByteString toByteString() {
+    return new ByteString(toByteArray());
+  }
+
+  @Override public boolean equals(Object o) {
+    if (o == this) return true;
+    return o instanceof ByteString
+        && ((ByteString) o).size() == size()
+        && rangeEquals(0, ((ByteString) o), 0, size());
+  }
+
+  @Override public int hashCode() {
+    int result = hashCode;
+    if (result != 0) return result;
+
+    // Equivalent to Arrays.hashCode(toByteArray()).
+    result = 1;
+    int segmentOffset = 0;
+    for (int s = 0, segmentCount = segments.length; s < segmentCount; s++) {
+      byte[] segment = segments[s];
+      int segmentPos = directory[segmentCount + s];
+      int nextSegmentOffset = directory[s];
+      int segmentSize = nextSegmentOffset - segmentOffset;
+      for (int i = segmentPos, limit = segmentPos + segmentSize; i < limit; i++) {
+        result = (31 * result) + segment[i];
+      }
+      segmentOffset = nextSegmentOffset;
+    }
+    return (hashCode = result);
+  }
+
+  @Override public String toString() {
+    return toByteString().toString();
+  }
+
+  private Object writeReplace() {
+    return toByteString();
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Sink.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Sink.java
new file mode 100644
index 0000000..e0a4eea
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Sink.java
@@ -0,0 +1,69 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+
+/**
+ * Receives a stream of bytes. Use this interface to write data wherever it's
+ * needed: to the network, storage, or a buffer in memory. Sinks may be layered
+ * to transform received data, such as to compress, encrypt, throttle, or add
+ * protocol framing.
+ *
+ * <p>Most application code shouldn't operate on a sink directly, but rather
+ * {@link BufferedSink} which is both more efficient and more convenient. Use
+ * {@link Okio#buffer(Sink)} to wrap any sink with a buffer.
+ *
+ * <p>Sinks are easy to test: just use an {@link Buffer} in your tests, and
+ * read from it to confirm it received the data that was expected.
+ *
+ * <h3>Comparison with OutputStream</h3>
+ * This interface is functionally equivalent to {@link java.io.OutputStream}.
+ *
+ * <p>{@code OutputStream} requires multiple layers when emitted data is
+ * heterogeneous: a {@code DataOutputStream} for primitive values, a {@code
+ * BufferedOutputStream} for buffering, and {@code OutputStreamWriter} for
+ * charset encoding. This class uses {@code BufferedSink} for all of the above.
+ *
+ * <p>Sink is also easier to layer: there is no {@linkplain
+ * java.io.OutputStream#write(int) single-byte write} method that is awkward to
+ * implement efficiently.
+ *
+ * <h3>Interop with OutputStream</h3>
+ * Use {@link Okio#sink} to adapt an {@code OutputStream} to a sink. Use {@link
+ * BufferedSink#outputStream} to adapt a sink to an {@code OutputStream}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Sink extends Closeable, Flushable {
+  /** Removes {@code byteCount} bytes from {@code source} and appends them to this. */
+  void write(Buffer source, long byteCount) throws IOException;
+
+  /** Pushes all buffered bytes to their final destination. */
+  @Override void flush() throws IOException;
+
+  /** Returns the timeout for this sink. */
+  Timeout timeout();
+
+  /**
+   * Pushes all buffered bytes to their final destination and releases the
+   * resources held by this sink. It is an error to write a closed sink. It is
+   * safe to close a sink more than once.
+   */
+  @Override void close() throws IOException;
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Source.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Source.java
new file mode 100644
index 0000000..6af20cd
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Source.java
@@ -0,0 +1,80 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Supplies a stream of bytes. Use this interface to read data from wherever
+ * it's located: from the network, storage, or a buffer in memory. Sources may
+ * be layered to transform supplied data, such as to decompress, decrypt, or
+ * remove protocol framing.
+ *
+ * <p>Most applications shouldn't operate on a source directly, but rather
+ * {@link BufferedSource} which is both more efficient and more convenient. Use
+ * {@link Okio#buffer(Source)} to wrap any source with a buffer.
+ *
+ * <p>Sources are easy to test: just use an {@link Buffer} in your tests, and
+ * fill it with the data your application is to read.
+ *
+ * <h3>Comparison with InputStream</h3>
+ * This interface is functionally equivalent to {@link java.io.InputStream}.
+ *
+ * <p>{@code InputStream} requires multiple layers when consumed data is
+ * heterogeneous: a {@code DataInputStream} for primitive values, a {@code
+ * BufferedInputStream} for buffering, and {@code InputStreamReader} for
+ * strings. This class uses {@code BufferedSource} for all of the above.
+ *
+ * <p>Source avoids the impossible-to-implement {@linkplain
+ * java.io.InputStream#available available()} method. Instead callers specify
+ * how many bytes they {@link BufferedSource#require require}.
+ *
+ * <p>Source omits the unsafe-to-compose {@linkplain java.io.InputStream#mark
+ * mark and reset} state that's tracked by {@code InputStream}; callers instead
+ * just buffer what they need.
+ *
+ * <p>When implementing a source, you need not worry about the {@linkplain
+ * java.io.InputStream#read single-byte read} method that is awkward to
+ * implement efficiently and that returns one of 257 possible values.
+ *
+ * <p>And source has a stronger {@code skip} method: {@link BufferedSource#skip}
+ * won't return prematurely.
+ *
+ * <h3>Interop with InputStream</h3>
+ * Use {@link Okio#source} to adapt an {@code InputStream} to a source. Use
+ * {@link BufferedSource#inputStream} to adapt a source to an {@code
+ * InputStream}.
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Source extends Closeable {
+  /**
+   * Removes at least 1, and up to {@code byteCount} bytes from this and appends
+   * them to {@code sink}. Returns the number of bytes read, or -1 if this
+   * source is exhausted.
+   */
+  long read(Buffer sink, long byteCount) throws IOException;
+
+  /** Returns the timeout for this source. */
+  Timeout timeout();
+
+  /**
+   * Closes this source and releases the resources held by this source. It is an
+   * error to read a closed source. It is safe to close a source more than once.
+   */
+  @Override void close() throws IOException;
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Timeout.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Timeout.java
new file mode 100644
index 0000000..7b757d2
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Timeout.java
@@ -0,0 +1,154 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A policy on how much time to spend on a task before giving up. When a task
+ * times out, it is left in an unspecified state and should be abandoned. For
+ * example, if reading from a source times out, that source should be closed and
+ * the read should be retried later. If writing to a sink times out, the same
+ * rules apply: close the sink and retry later.
+ *
+ * <h3>Timeouts and Deadlines</h3>
+ * This class offers two complementary controls to define a timeout policy.
+ *
+ * <p><strong>Timeouts</strong> specify the maximum time to wait for a single
+ * operation to complete. Timeouts are typically used to detect problems like
+ * network partitions. For example, if a remote peer doesn't return <i>any</i>
+ * data for ten seconds, we may assume that the peer is unavailable.
+ *
+ * <p><strong>Deadlines</strong> specify the maximum time to spend on a job,
+ * composed of one or more operations. Use deadlines to set an upper bound on
+ * the time invested on a job. For example, a battery-conscious app may limit
+ * how much time it spends preloading content.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class Timeout {
+  /**
+   * An empty timeout that neither tracks nor detects timeouts. Use this when
+   * timeouts aren't necessary, such as in implementations whose operations
+   * do not block.
+   */
+  public static final Timeout NONE = new Timeout() {
+    @Override public Timeout timeout(long timeout, TimeUnit unit) {
+      return this;
+    }
+
+    @Override public Timeout deadlineNanoTime(long deadlineNanoTime) {
+      return this;
+    }
+
+    @Override public void throwIfReached() throws IOException {
+    }
+  };
+
+  /**
+   * True if {@code deadlineNanoTime} is defined. There is no equivalent to null
+   * or 0 for {@link System#nanoTime}.
+   */
+  private boolean hasDeadline;
+  private long deadlineNanoTime;
+  private long timeoutNanos;
+
+  public Timeout() {
+  }
+
+  /**
+   * Wait at most {@code timeout} time before aborting an operation. Using a
+   * per-operation timeout means that as long as forward progress is being made,
+   * no sequence of operations will fail.
+   *
+   * <p>If {@code timeout == 0}, operations will run indefinitely. (Operating
+   * system timeouts may still apply.)
+   */
+  public Timeout timeout(long timeout, TimeUnit unit) {
+    if (timeout < 0) throw new IllegalArgumentException("timeout < 0: " + timeout);
+    if (unit == null) throw new IllegalArgumentException("unit == null");
+    this.timeoutNanos = unit.toNanos(timeout);
+    return this;
+  }
+
+  /** Returns the timeout in nanoseconds, or {@code 0} for no timeout. */
+  public long timeoutNanos() {
+    return timeoutNanos;
+  }
+
+  /** Returns true if a deadline is enabled. */
+  public boolean hasDeadline() {
+    return hasDeadline;
+  }
+
+  /**
+   * Returns the {@linkplain System#nanoTime() nano time} when the deadline will
+   * be reached.
+   *
+   * @throws IllegalStateException if no deadline is set.
+   */
+  public long deadlineNanoTime() {
+    if (!hasDeadline) throw new IllegalStateException("No deadline");
+    return deadlineNanoTime;
+  }
+
+  /**
+   * Sets the {@linkplain System#nanoTime() nano time} when the deadline will be
+   * reached. All operations must complete before this time. Use a deadline to
+   * set a maximum bound on the time spent on a sequence of operations.
+   */
+  public Timeout deadlineNanoTime(long deadlineNanoTime) {
+    this.hasDeadline = true;
+    this.deadlineNanoTime = deadlineNanoTime;
+    return this;
+  }
+
+  /** Set a deadline of now plus {@code duration} time. */
+  public final Timeout deadline(long duration, TimeUnit unit) {
+    if (duration <= 0) throw new IllegalArgumentException("duration <= 0: " + duration);
+    if (unit == null) throw new IllegalArgumentException("unit == null");
+    return deadlineNanoTime(System.nanoTime() + unit.toNanos(duration));
+  }
+
+  /** Clears the timeout. Operating system timeouts may still apply. */
+  public Timeout clearTimeout() {
+    this.timeoutNanos = 0;
+    return this;
+  }
+
+  /** Clears the deadline. */
+  public Timeout clearDeadline() {
+    this.hasDeadline = false;
+    return this;
+  }
+
+  /**
+   * Throws an {@link InterruptedIOException} if the deadline has been reached or if the current
+   * thread has been interrupted. This method doesn't detect timeouts; that should be implemented to
+   * asynchronously abort an in-progress operation.
+   */
+  public void throwIfReached() throws IOException {
+    if (Thread.interrupted()) {
+      throw new InterruptedIOException("thread interrupted");
+    }
+
+    if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
+      throw new InterruptedIOException("deadline reached");
+    }
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Util.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Util.java
new file mode 100644
index 0000000..11ecf96
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/Util.java
@@ -0,0 +1,81 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.android.okhttp.okio;
+
+import java.nio.charset.Charset;
+
+final class Util {
+  /** A cheap and type-safe constant for the UTF-8 Charset. */
+  public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  private Util() {
+  }
+
+  public static void checkOffsetAndCount(long size, long offset, long byteCount) {
+    if ((offset | byteCount) < 0 || offset > size || size - offset < byteCount) {
+      throw new ArrayIndexOutOfBoundsException(
+          String.format("size=%s offset=%s byteCount=%s", size, offset, byteCount));
+    }
+  }
+
+  public static short reverseBytesShort(short s) {
+    int i = s & 0xffff;
+    int reversed = (i & 0xff00) >>> 8
+        |          (i & 0x00ff)  << 8;
+    return (short) reversed;
+  }
+
+  public static int reverseBytesInt(int i) {
+    return (i & 0xff000000) >>> 24
+        |  (i & 0x00ff0000) >>>  8
+        |  (i & 0x0000ff00)  <<  8
+        |  (i & 0x000000ff)  << 24;
+  }
+
+  public static long reverseBytesLong(long v) {
+    return (v & 0xff00000000000000L) >>> 56
+        |  (v & 0x00ff000000000000L) >>> 40
+        |  (v & 0x0000ff0000000000L) >>> 24
+        |  (v & 0x000000ff00000000L) >>>  8
+        |  (v & 0x00000000ff000000L)  <<  8
+        |  (v & 0x0000000000ff0000L)  << 24
+        |  (v & 0x000000000000ff00L)  << 40
+        |  (v & 0x00000000000000ffL)  << 56;
+  }
+
+  /**
+   * Throws {@code t}, even if the declared throws clause doesn't permit it.
+   * This is a terrible – but terribly convenient – hack that makes it easy to
+   * catch and rethrow exceptions after cleanup. See Java Puzzlers #43.
+   */
+  public static void sneakyRethrow(Throwable t) {
+    Util.<Error>sneakyThrow2(t);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <T extends Throwable> void sneakyThrow2(Throwable t) throws T {
+    throw (T) t;
+  }
+
+  public static boolean arrayRangeEquals(
+      byte[] a, int aOffset, byte[] b, int bOffset, int byteCount) {
+    for (int i = 0; i < byteCount; i++) {
+      if (a[i + aOffset] != b[i + bOffset]) return false;
+    }
+    return true;
+  }
+}
diff --git a/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/package-info.java b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/package-info.java
new file mode 100644
index 0000000..f4f659e
--- /dev/null
+++ b/repackaged/okio/okio/src/main/java/com/android/okhttp/okio/package-info.java
@@ -0,0 +1,6 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/**
+ * Okio complements {@link java.io} and {@link java.nio} to make it much easier to access, store,
+ * and process your data.
+ */
+package com.android.okhttp.okio;
diff --git a/srcgen/generate_android_src.sh b/srcgen/generate_android_src.sh
new file mode 100755
index 0000000..b50c90d
--- /dev/null
+++ b/srcgen/generate_android_src.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+    echo "Missing environment variables. Did you run build/envsetup.sh and lunch?" 1>&2
+    exit 1
+fi
+
+CLASSPATH=${ANDROID_HOST_OUT}/framework/currysrc.jar
+PROJECT_DIR=${ANDROID_BUILD_TOP}/external/okhttp
+
+cd ${ANDROID_BUILD_TOP}
+make -j15 currysrc
+
+function do_transform() {
+  local SRC_IN_DIR=$1
+  local SRC_OUT_DIR=$2
+
+  if [ ! -d $SRC_OUT_DIR ]; then
+    echo ${SRC_OUT_DIR} does not exist >&2
+    exit 1
+  fi
+  rm -rf ${SRC_OUT_DIR}
+  mkdir -p ${SRC_OUT_DIR}
+
+  java -cp ${CLASSPATH} com.google.currysrc.aosp.RepackagingTransform \
+       --source-dir ${SRC_IN_DIR} \
+       --target-dir ${SRC_OUT_DIR} \
+       --package-transformation "com.squareup:com.android" \
+       --package-transformation "okio:com.android.okhttp.okio" \
+       --tab-size 2 \
+
+}
+
+REPACKAGED_DIR=${PROJECT_DIR}/repackaged
+for i in android okhttp okhttp-urlconnection okhttp-android-support okio/okio
+do
+  for s in src/main/java
+  do
+    IN=${PROJECT_DIR}/$i/$s
+    if [ -d $IN ]; then
+      OUT=${REPACKAGED_DIR}/$i/$s
+      do_transform ${IN} ${OUT}
+    fi
+  done
+done
+
+# Remove an unused source file:
+rm ${REPACKAGED_DIR}/okhttp/src/main/java/com/android/okhttp/internal/Platform.java