Merge remote branch 'goog/dalvik-dev' into dalvik-dev-to-master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
deleted file mode 100644
index e5321b7..0000000
--- a/AndroidManifest.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests">
-
-    <uses-permission android:name="android.permission.INTERNET" />
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
-                     android:targetPackage="android.core.tests"
-                     android:label="cts core tests"/>
-
-    <instrumentation android:name="android.test.InstrumentationCoreTestRunner"
-                     android:targetPackage="android.core.tests"
-                     android:label="cts core tests"/>
-
-</manifest>
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index 08459fa..da75104 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -105,7 +105,6 @@
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := bouncycastle core core-junit
 LOCAL_STATIC_JAVA_LIBRARIES := sqlite-jdbc
-LOCAL_DX_FLAGS := --core-library
 LOCAL_JAVACFLAGS := $(local_javac_flags)
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE := core-tests
@@ -172,7 +171,6 @@
     LOCAL_NO_STANDARD_LIBRARIES := true
     LOCAL_JAVA_LIBRARIES := bouncycastle-hostdex core-hostdex core-junit-hostdex
     LOCAL_STATIC_JAVA_LIBRARIES := sqlite-jdbc-host
-    LOCAL_DX_FLAGS := --core-library
     LOCAL_JAVACFLAGS := $(local_javac_flags)
     LOCAL_MODULE_TAGS := tests
     LOCAL_MODULE := core-tests-hostdex
diff --git a/dalvik/src/main/java/dalvik/system/BlockGuard.java b/dalvik/src/main/java/dalvik/system/BlockGuard.java
index d3c5088..13cc32d 100644
--- a/dalvik/src/main/java/dalvik/system/BlockGuard.java
+++ b/dalvik/src/main/java/dalvik/system/BlockGuard.java
@@ -201,17 +201,14 @@
             return mNetwork.writeDirect(fd, address, offset, count);
         }
 
-        public boolean connectNonBlocking(FileDescriptor fd, InetAddress inetAddress, int port)
-                throws IOException {
+        public boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws IOException {
             BlockGuard.getThreadPolicy().onNetwork();
-            return mNetwork.connectNonBlocking(fd, inetAddress, port);
+            return mNetwork.connect(fd, inetAddress, port);
         }
 
         public boolean isConnected(FileDescriptor fd, int timeout) throws IOException {
             if (timeout != 0) {
-                // Less than 0 is blocking forever.
-                // Greater than 0 is a timeout.
-                // Zero is okay.
+                // Greater than 0 is a timeout, but zero means "poll and return immediately".
                 BlockGuard.getThreadPolicy().onNetwork();
             }
             return mNetwork.isConnected(fd, timeout);
@@ -251,12 +248,6 @@
             mNetwork.sendUrgentData(fd, value);
         }
 
-        public void connect(FileDescriptor aFD, InetAddress inetAddress, int port,
-                int timeout) throws SocketException {
-            BlockGuard.getThreadPolicy().onNetwork();
-            mNetwork.connect(aFD, inetAddress, port, timeout);
-        }
-
         public boolean select(FileDescriptor[] readFDs, FileDescriptor[] writeFDs,
                 int numReadable, int numWritable, long timeout, int[] flags)
                 throws SocketException {
@@ -264,11 +255,6 @@
             return mNetwork.select(readFDs, writeFDs, numReadable, numWritable, timeout, flags);
         }
 
-        public void setSocketOption(FileDescriptor aFD, int opt, Object optVal)
-                throws SocketException {
-            mNetwork.setSocketOption(aFD, opt, optVal);
-        }
-
         public void close(FileDescriptor aFD) throws IOException {
             // We exclude sockets without SO_LINGER so that apps can close their network connections
             // in methods like onDestroy, which will run on the UI thread, without jumping through
diff --git a/include/ScopedBytes.h b/include/ScopedBytes.h
new file mode 100644
index 0000000..cb2614b
--- /dev/null
+++ b/include/ScopedBytes.h
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#ifndef SCOPED_BYTES_H_included
+#define SCOPED_BYTES_H_included
+
+#include "JNIHelp.h"
+
+/**
+ * ScopedBytesRO and ScopedBytesRW attempt to paper over the differences between byte[]s and
+ * ByteBuffers. This in turn helps paper over the differences between non-direct ByteBuffers backed
+ * by byte[]s, direct ByteBuffers backed by bytes[]s, and direct ByteBuffers not backed by byte[]s.
+ * (On Android, this last group only contains MappedByteBuffers.)
+ */
+template<bool readOnly>
+class ScopedBytes {
+public:
+    ScopedBytes(JNIEnv* env, jobject object)
+    : mEnv(env), mObject(object), mByteArray(NULL), mPtr(NULL)
+    {
+        if (mObject == NULL) {
+            jniThrowNullPointerException(mEnv, NULL);
+        } else if (mEnv->IsInstanceOf(mObject, JniConstants::byteArrayClass)) {
+            mByteArray = reinterpret_cast<jbyteArray>(mObject);
+            mPtr = mEnv->GetByteArrayElements(mByteArray, NULL);
+        } else {
+            mPtr = reinterpret_cast<jbyte*>(mEnv->GetDirectBufferAddress(mObject));
+        }
+    }
+
+    ~ScopedBytes() {
+        if (mByteArray != NULL) {
+            mEnv->ReleaseByteArrayElements(mByteArray, mPtr, readOnly ? JNI_ABORT : 0);
+        }
+    }
+
+private:
+    JNIEnv* mEnv;
+    jobject mObject;
+    jbyteArray mByteArray;
+
+protected:
+    jbyte* mPtr;
+
+private:
+    // Disallow copy and assignment.
+    ScopedBytes(const ScopedBytes&);
+    void operator=(const ScopedBytes&);
+};
+
+class ScopedBytesRO : public ScopedBytes<true> {
+public:
+    ScopedBytesRO(JNIEnv* env, jobject object) : ScopedBytes<true>(env, object) {}
+    const jbyte* get() const {
+        return mPtr;
+    }
+};
+
+class ScopedBytesRW : public ScopedBytes<false> {
+public:
+    ScopedBytesRW(JNIEnv* env, jobject object) : ScopedBytes<false>(env, object) {}
+    jbyte* get() {
+        return mPtr;
+    }
+};
+
+#endif  // SCOPED_BYTES_H_included
diff --git a/luni/src/main/java/java/io/Closeable.java b/luni/src/main/java/java/io/Closeable.java
index 56c2b3c..3929e7c 100644
--- a/luni/src/main/java/java/io/Closeable.java
+++ b/luni/src/main/java/java/io/Closeable.java
@@ -17,28 +17,17 @@
 package java.io;
 
 /**
- * Defines an interface for classes that can (or need to) be closed once they
- * are not used any longer. This usually includes all sorts of
- * {@link InputStream}s and {@link OutputStream}s. Calling the {@code close}
- * method releases resources that the object holds.
- * <p>
- * A common pattern for using a {@code Closeable} resource: <pre>   {@code
- *   Closable foo = new Foo();
- *   try {
- *      ...;
- *   } finally {
- *      foo.close();
- *   }
- * }</pre>
+ * An {@code AutoCloseable} whose close method may throw an {@link IOException}.
  */
-public interface Closeable {
+public interface Closeable extends AutoCloseable {
 
     /**
-     * Closes the object and release any system resources it holds. If the
-     * object has already been closed, then invoking this method has no effect.
+     * Closes the object and release any system resources it holds.
      *
-     * @throws IOException
-     *             if any error occurs when closing the object.
+     * <p>Although only the first call has any effect, it is safe to call close
+     * multiple times on the same object. This is more lenient than the
+     * overridden {@code AutoCloseable.close()}, which may be called at most
+     * once.
      */
-    public void close() throws IOException;
+    void close() throws IOException;
 }
diff --git a/luni/src/main/java/java/io/FilePermission.java b/luni/src/main/java/java/io/FilePermission.java
index 94263f1..3daeda1 100644
--- a/luni/src/main/java/java/io/FilePermission.java
+++ b/luni/src/main/java/java/io/FilePermission.java
@@ -24,26 +24,7 @@
 import libcore.util.Objects;
 
 /**
- * A permission for accessing a file or directory. The FilePermission is made up
- * of a pathname and a set of actions which are valid for the pathname.
- * <p>
- * The {@code File.separatorChar} must be used in all pathnames when
- * constructing a FilePermission. The following descriptions will assume the
- * char is {@code /}. A pathname that ends in {@code /*} includes all the files
- * and directories contained in that directory. If the pathname
- * ends in {@code /-}, it includes all the files and directories in that
- * directory <i>recursively</i>. The following pathnames have a special meaning:
- * <ul>
- *   <li>
- *     "*": all files in the current directory;
- *   </li>
- *   <li>
- *     "-": recursively all files and directories in the current directory;
- *   </li>
- *   <li>
- *     "&lt;&lt;ALL FILES&gt;&gt;": any file and directory in the file system.
- *   </li>
- * </ul>
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class FilePermission extends Permission implements Serializable {
 
@@ -52,9 +33,7 @@
     // canonical path of this permission
     private transient String canonPath;
 
-    // list of actions permitted for socket permission in order
-    private static final String[] actionList = { "read", "write", "execute",
-            "delete" };
+    private static final String[] actionList = { "read", "write", "execute", "delete" };
 
     // "canonicalized" action list
     private String actions;
diff --git a/luni/src/main/java/java/io/ObjectInput.java b/luni/src/main/java/java/io/ObjectInput.java
index a10ddfe..e892071 100644
--- a/luni/src/main/java/java/io/ObjectInput.java
+++ b/luni/src/main/java/java/io/ObjectInput.java
@@ -23,7 +23,7 @@
  * @see ObjectInputStream
  * @see ObjectOutput
  */
-public interface ObjectInput extends DataInput {
+public interface ObjectInput extends DataInput, AutoCloseable {
     /**
      * Indicates the number of bytes of primitive data that can be read without
      * blocking.
diff --git a/luni/src/main/java/java/io/ObjectOutput.java b/luni/src/main/java/java/io/ObjectOutput.java
index e7d92c5..2e454ec 100644
--- a/luni/src/main/java/java/io/ObjectOutput.java
+++ b/luni/src/main/java/java/io/ObjectOutput.java
@@ -23,7 +23,7 @@
  * @see ObjectOutputStream
  * @see ObjectInput
  */
-public interface ObjectOutput extends DataOutput {
+public interface ObjectOutput extends DataOutput, AutoCloseable {
     /**
      * Closes the target stream. Implementations of this method should free any
      * resources used by the stream.
diff --git a/luni/src/main/java/java/io/SerializablePermission.java b/luni/src/main/java/java/io/SerializablePermission.java
index 57d4421..a1465fe 100644
--- a/luni/src/main/java/java/io/SerializablePermission.java
+++ b/luni/src/main/java/java/io/SerializablePermission.java
@@ -20,21 +20,7 @@
 import java.security.BasicPermission;
 
 /**
- * Is used to enable access to potentially unsafe serialization operations. It
- * does have a name but no action list. The following table lists valid
- * permission names:
- * <table>
- * <tr>
- * <td>enableSubclassImplementation</td>
- * <td>Subclasses can override serialization behavior.</td>
- * </tr>
- * <tr>
- * <td>enableSubstitution</td>
- * <td>Object substitution is allowed.</td>
- * </tr>
- * </table>
- *
- * @see ObjectStreamConstants
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class SerializablePermission extends BasicPermission {
     private static final long serialVersionUID = 8537212141160296410L;
diff --git a/luni/src/main/java/java/lang/AutoCloseable.java b/luni/src/main/java/java/lang/AutoCloseable.java
new file mode 100644
index 0000000..b3133a9
--- /dev/null
+++ b/luni/src/main/java/java/lang/AutoCloseable.java
@@ -0,0 +1,45 @@
+/*
+ * 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 java.lang;
+
+/**
+ * Defines an interface for classes that can (or need to) be closed once they
+ * are not used any longer. Calling the {@code close} method releases resources
+ * that the object holds.
+ *
+ * <p>A common pattern for using an {@code AutoCloseable} resource: <pre>   {@code
+ *   Closable foo = new Foo();
+ *   try {
+ *      ...;
+ *   } finally {
+ *      foo.close();
+ *   }
+ * }</pre>
+ *
+ * @since 1.7
+ * @hide 1.7
+ */
+public interface AutoCloseable {
+
+    /**
+     * Closes the object and release any system resources it holds.
+     *
+     * <p>Unless the implementing class specifies otherwise, it is an error to
+     * call {@link #close} more than once.
+     */
+    void close() throws Exception;
+}
diff --git a/luni/src/main/java/java/lang/FinalizerThread.java b/luni/src/main/java/java/lang/FinalizerThread.java
index 8b6f337..5f478df 100644
--- a/luni/src/main/java/java/lang/FinalizerThread.java
+++ b/luni/src/main/java/java/lang/FinalizerThread.java
@@ -74,7 +74,8 @@
         try {
             obj.finalize();
         } catch (Throwable ex) {
-            // TODO: print a warning
+            // The RI silently swallows these, but Android has always logged.
+            System.logE("Uncaught exception thrown by finalizer", ex);
         }
     }
 
diff --git a/luni/src/main/java/java/lang/Package.java b/luni/src/main/java/java/lang/Package.java
index e3095ea..2e587dd 100644
--- a/luni/src/main/java/java/lang/Package.java
+++ b/luni/src/main/java/java/lang/Package.java
@@ -49,9 +49,15 @@
  * @see java.lang.ClassLoader
  */
 public class Package implements AnnotatedElement {
+    private static final Annotation[] NO_ANNOTATIONS = new Annotation[0];
 
-    private final String name, specTitle, specVersion, specVendor, implTitle,
-            implVersion, implVendor;
+    private final String name;
+    private final String specTitle;
+    private final String specVersion;
+    private final String specVendor;
+    private final String implTitle;
+    private final String implVersion;
+    private final String implVendor;
     private final URL sealBase;
 
     Package(String name, String specTitle, String specVersion, String specVendor,
@@ -86,37 +92,19 @@
     }
 
     /**
-     * Gets all annotations associated with this package, if any.
-     *
-     * @return an array of {@link Annotation} instances, which may be empty.
-     * @see java.lang.reflect.AnnotatedElement#getAnnotations()
+     * Returns an empty array. Package annotations are not supported on Android.
      */
     public Annotation[] getAnnotations() {
-        return getDeclaredAnnotations(this, true);
+        return NO_ANNOTATIONS;
     }
 
     /**
-     * Gets all annotations directly declared on this package, if any.
-     *
-     * @return an array of {@link Annotation} instances, which may be empty.
-     * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotations()
+     * Returns an empty array. Package annotations are not supported on Android.
      */
     public Annotation[] getDeclaredAnnotations() {
-        return getDeclaredAnnotations(this, false);
+        return NO_ANNOTATIONS;
     }
 
-    /*
-     * Returns the list of declared annotations of the given package.
-     * If no annotations exist, an empty array is returned.
-     *
-     * @param pkg the package of interest
-     * @param publicOnly reflects whether we want only public annotation or all
-     * of them.
-     * @return the list of annotations
-     */
-    // TODO(Google) Provide proper (native) implementation.
-    private static native Annotation[] getDeclaredAnnotations(Package pkg, boolean publicOnly);
-
     /**
      * Indicates whether the specified annotation is present.
      *
diff --git a/luni/src/main/java/java/lang/ProcessManager.java b/luni/src/main/java/java/lang/ProcessManager.java
index ce3474d..cdcbb8a 100644
--- a/luni/src/main/java/java/lang/ProcessManager.java
+++ b/luni/src/main/java/java/lang/ProcessManager.java
@@ -28,7 +28,10 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import libcore.io.ErrnoException;
 import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import static libcore.io.OsConstants.*;
 
 /**
  * Manages child processes.
@@ -54,14 +57,6 @@
     private static final int WAIT_STATUS_STRANGE_ERRNO = -3;
 
     /**
-     * Initializes native static state.
-     */
-    static native void staticInitialize();
-    static {
-        staticInitialize();
-    }
-
-    /**
      * Map from pid to Process. We keep weak references to the Process objects
      * and clean up the entries when no more external references are left. The
      * process objects themselves don't require much memory, but file
@@ -77,9 +72,8 @@
     private ProcessManager() {
         // Spawn a thread to listen for signals from child processes.
         Thread processThread = new Thread(ProcessManager.class.getName()) {
-            @Override
-            public void run() {
-                watchChildren();
+            @Override public void run() {
+                watchChildren(ProcessManager.this);
             }
         };
         processThread.setDaemon(true);
@@ -87,17 +81,10 @@
     }
 
     /**
-     * Kills the process with the given ID.
-     *
-     * @parm pid ID of process to kill
-     */
-    private static native void kill(int pid) throws IOException;
-
-    /**
      * Cleans up after garbage collected processes. Requires the lock on the
      * map.
      */
-    void cleanUp() {
+    private void cleanUp() {
         ProcessReference reference;
         while ((reference = referenceQueue.poll()) != null) {
             synchronized (processReferences) {
@@ -110,7 +97,7 @@
      * Listens for signals from processes and calls back to
      * {@link #onExit(int,int)}.
      */
-    native void watchChildren();
+    private static native void watchChildren(ProcessManager manager);
 
     /**
      * Called by {@link #watchChildren()} when a child process exits.
@@ -118,7 +105,7 @@
      * @param pid ID of process that exited
      * @param exitValue value the process returned upon exit
      */
-    void onExit(int pid, int exitValue) {
+    private void onExit(int pid, int exitValue) {
         ProcessReference processReference = null;
 
         synchronized (processReferences) {
@@ -164,14 +151,14 @@
      * Executes a native process. Fills in in, out, and err and returns the
      * new process ID upon success.
      */
-    static native int exec(String[] command, String[] environment,
+    private static native int exec(String[] command, String[] environment,
             String workingDirectory, FileDescriptor in, FileDescriptor out,
             FileDescriptor err, boolean redirectErrorStream) throws IOException;
 
     /**
      * Executes a process and returns an object representing it.
      */
-    Process exec(String[] taintedCommand, String[] taintedEnvironment, File workingDirectory,
+    public Process exec(String[] taintedCommand, String[] taintedEnvironment, File workingDirectory,
             boolean redirectErrorStream) throws IOException {
         // Make sure we throw the same exceptions as the RI.
         if (taintedCommand == null) {
@@ -223,8 +210,7 @@
                 throw wrapper;
             }
             ProcessImpl process = new ProcessImpl(pid, in, out, err);
-            ProcessReference processReference
-                    = new ProcessReference(process, referenceQueue);
+            ProcessReference processReference = new ProcessReference(process, referenceQueue);
             processReferences.put(pid, processReference);
 
             /*
@@ -238,25 +224,22 @@
     }
 
     static class ProcessImpl extends Process {
+        private final int pid;
 
-        /** Process ID. */
-        final int id;
-
-        final InputStream errorStream;
+        private final InputStream errorStream;
 
         /** Reads output from process. */
-        final InputStream inputStream;
+        private final InputStream inputStream;
 
         /** Sends output to process. */
-        final OutputStream outputStream;
+        private final OutputStream outputStream;
 
         /** The process's exit value. */
-        Integer exitValue = null;
-        final Object exitValueMutex = new Object();
+        private Integer exitValue = null;
+        private final Object exitValueMutex = new Object();
 
-        ProcessImpl(int id, FileDescriptor in, FileDescriptor out,
-                FileDescriptor err) {
-            this.id = id;
+        ProcessImpl(int pid, FileDescriptor in, FileDescriptor out, FileDescriptor err) {
+            this.pid = pid;
 
             this.errorStream = new ProcessInputStream(err);
             this.inputStream = new ProcessInputStream(in);
@@ -265,9 +248,9 @@
 
         public void destroy() {
             try {
-                kill(this.id);
-            } catch (IOException e) {
-                System.logI("Failed to destroy process " + id, e);
+                Libcore.os.kill(pid, SIGKILL);
+            } catch (ErrnoException e) {
+                System.logI("Failed to destroy process " + pid, e);
             }
             IoUtils.closeQuietly(inputStream);
             IoUtils.closeQuietly(errorStream);
@@ -315,7 +298,7 @@
 
         @Override
         public String toString() {
-            return "Process[id=" + id + "]";
+            return "Process[pid=" + pid + "]";
         }
     }
 
@@ -323,10 +306,9 @@
 
         final int processId;
 
-        public ProcessReference(ProcessImpl referent,
-                ProcessReferenceQueue referenceQueue) {
+        public ProcessReference(ProcessImpl referent, ProcessReferenceQueue referenceQueue) {
             super(referent, referenceQueue);
-            this.processId = referent.id;
+            this.processId = referent.pid;
         }
     }
 
@@ -340,10 +322,10 @@
         }
     }
 
-    static final ProcessManager instance = new ProcessManager();
+    private static final ProcessManager instance = new ProcessManager();
 
     /** Gets the process manager. */
-    static ProcessManager getInstance() {
+    public static ProcessManager getInstance() {
         return instance;
     }
 
diff --git a/luni/src/main/java/java/lang/System.java b/luni/src/main/java/java/lang/System.java
index 14b5918..203e5e3 100644
--- a/luni/src/main/java/java/lang/System.java
+++ b/luni/src/main/java/java/lang/System.java
@@ -108,7 +108,7 @@
      *            the user defined output stream to set as the standard output
      *            stream.
      */
-    public static void setOut(java.io.PrintStream newOut) {
+    public static void setOut(PrintStream newOut) {
         setFieldImpl("out", "Ljava/io/PrintStream;", newOut);
     }
 
@@ -120,7 +120,7 @@
      *            the user defined output stream to set as the standard error
      *            output stream.
      */
-    public static void setErr(java.io.PrintStream newErr) {
+    public static void setErr(PrintStream newErr) {
         setFieldImpl("err", "Ljava/io/PrintStream;", newErr);
     }
 
@@ -281,8 +281,6 @@
         p.put("java.io.tmpdir", "/tmp");
         p.put("java.library.path", getenv("LD_LIBRARY_PATH"));
 
-        p.put("java.net.preferIPv6Addresses", "true");
-
         p.put("java.specification.name", "Dalvik Core Library");
         p.put("java.specification.vendor", projectName);
         p.put("java.specification.version", "0.9");
diff --git a/luni/src/main/java/java/lang/Throwable.java b/luni/src/main/java/java/lang/Throwable.java
index 05cb1d1..b561832 100644
--- a/luni/src/main/java/java/lang/Throwable.java
+++ b/luni/src/main/java/java/lang/Throwable.java
@@ -18,9 +18,13 @@
 package java.lang;
 
 import java.io.IOException;
+import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.PrintStream;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import libcore.util.EmptyArray;
 
 /**
  * The superclass of all classes which can be thrown by the VM. The
@@ -56,6 +60,12 @@
     private Throwable cause = this;
 
     /**
+     * Throwables suppressed by this throwable. Null when suppressed exceptions
+     * are disabled.
+     */
+    private List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
+
+    /**
      * An intermediate representation of the stack trace.  This field may
      * be accessed by the VM; do not rename.
      */
@@ -114,6 +124,22 @@
     }
 
     /**
+     * Constructs a new {@code Throwable} with the current stack trace, the
+     * specified detail message and the specified cause.
+     *
+     * @param enableSuppression if false, throwables passed to {@link
+     *     #addSuppressed(Throwable)} will be silently discarded.
+     * @since 1.7
+     * @hide 1.7
+     */
+    protected Throwable(String detailMessage, Throwable throwable, boolean enableSuppression) {
+        this(detailMessage, throwable);
+        if (!enableSuppression) {
+            this.suppressedExceptions = null;
+        }
+    }
+
+    /**
      * Records the stack trace from the point where this method has been called
      * to this {@code Throwable}. This method is invoked by the {@code Throwable} constructors.
      *
@@ -257,31 +283,11 @@
      *            the stream to write the stack trace on.
      */
     public void printStackTrace(PrintStream err) {
-        err.println(toString());
-        // Don't use getStackTrace() as it calls clone()
-        // Get stackTrace, in case stackTrace is reassigned
-        StackTraceElement[] stack = getInternalStackTrace();
-        if (stack != null) {
-            for (StackTraceElement element : stack) {
-                err.println("\tat " + element);
-            }
-        }
-
-        StackTraceElement[] parentStack = stack;
-        Throwable throwable = getCause();
-        while (throwable != null) {
-            err.print("Caused by: ");
-            err.println(throwable);
-            StackTraceElement[] currentStack = throwable.getInternalStackTrace();
-            int duplicates = countDuplicates(currentStack, parentStack);
-            for (int i = 0; i < currentStack.length - duplicates; i++) {
-                err.println("\tat " + currentStack[i]);
-            }
-            if (duplicates > 0) {
-                err.println("\t... " + duplicates + " more");
-            }
-            parentStack = currentStack;
-            throwable = throwable.getCause();
+        try {
+            printStackTrace(err, "", null);
+        } catch (IOException e) {
+            // Appendable.append throws IOException but PrintStream.append doesn't.
+            throw new AssertionError();
         }
     }
 
@@ -295,31 +301,57 @@
      *            the writer to write the stack trace on.
      */
     public void printStackTrace(PrintWriter err) {
-        err.println(toString());
-        // Don't use getStackTrace() as it calls clone()
-        // Get stackTrace, in case stackTrace is reassigned
+        try {
+            printStackTrace(err, "", null);
+        } catch (IOException e) {
+            // Appendable.append throws IOException, but PrintWriter.append doesn't.
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * @param indent additional indentation on each line of the stack trace.
+     *     This is the empty string for all but suppressed throwables.
+     * @param parentStack the parent stack trace to suppress duplicates from, or
+     *     null if this stack trace has no parent.
+     */
+    private void printStackTrace(Appendable err, String indent, StackTraceElement[] parentStack)
+            throws IOException {
+        err.append(toString());
+        err.append("\n");
+
         StackTraceElement[] stack = getInternalStackTrace();
         if (stack != null) {
-            for (StackTraceElement element : stack) {
-                err.println("\tat " + element);
+            int duplicates = parentStack != null ? countDuplicates(stack, parentStack) : 0;
+            for (int i = 0; i < stack.length - duplicates; i++) {
+                err.append(indent);
+                err.append("\tat ");
+                err.append(stack[i].toString());
+                err.append("\n");
+            }
+
+            if (duplicates > 0) {
+                err.append(indent);
+                err.append("\t... ");
+                err.append(Integer.toString(duplicates));
+                err.append(" more\n");
             }
         }
 
-        StackTraceElement[] parentStack = stack;
-        Throwable throwable = getCause();
-        while (throwable != null) {
-            err.print("Caused by: ");
-            err.println(throwable);
-            StackTraceElement[] currentStack = throwable.getInternalStackTrace();
-            int duplicates = countDuplicates(currentStack, parentStack);
-            for (int i = 0; i < currentStack.length - duplicates; i++) {
-                err.println("\tat " + currentStack[i]);
+        // Print suppressed exceptions indented one level deeper.
+        if (suppressedExceptions != null) {
+            for (Throwable throwable : suppressedExceptions) {
+                err.append(indent);
+                err.append("\tSuppressed: ");
+                throwable.printStackTrace(err, indent + "\t", stack);
             }
-            if (duplicates > 0) {
-                err.println("\t... " + duplicates + " more");
-            }
-            parentStack = currentStack;
-            throwable = throwable.getCause();
+        }
+
+        Throwable cause = getCause();
+        if (cause != null) {
+            err.append(indent);
+            err.append("Caused by: ");
+            cause.printStackTrace(err, indent, stack);
         }
     }
 
@@ -346,14 +378,14 @@
      *             if the cause has already been initialized.
      */
     public Throwable initCause(Throwable throwable) {
-        if (cause == this) {
-            if (throwable != this) {
-                cause = throwable;
-                return this;
-            }
-            throw new IllegalArgumentException("Cause cannot be the receiver");
+        if (cause != this) {
+            throw new IllegalStateException("Cause already initialized");
         }
-        throw new IllegalStateException("Cause already initialized");
+        if (throwable == this) {
+            throw new IllegalArgumentException("throwable == this");
+        }
+        cause = throwable;
+        return this;
     }
 
     /**
@@ -369,10 +401,52 @@
         return cause;
     }
 
-    private void writeObject(ObjectOutputStream s) throws IOException {
+    /**
+     * Adds {@code throwable} to the list of throwables suppressed by this. The
+     * throwable will included when this exception's stack trace is printed.
+     *
+     * @throws IllegalArgumentException if {@code throwable == this}.
+     * @throws NullPointerException if {@code throwable == null}.
+     * @since 1.7
+     * @hide 1.7
+     */
+    public final void addSuppressed(Throwable throwable) {
+        if (throwable == this) {
+            throw new IllegalArgumentException("suppressed == this");
+        }
+        if (throwable == null) {
+            throw new NullPointerException("suppressed == null");
+        }
+        if (suppressedExceptions != null) {
+            suppressedExceptions.add(throwable);
+        }
+    }
+
+    /**
+     * Returns the throwables suppressed by this.
+     *
+     * @since 1.7
+     * @hide 1.7
+     */
+    public final Throwable[] getSuppressed() {
+        return (suppressedExceptions != null)
+                ? suppressedExceptions.toArray(new Throwable[suppressedExceptions.size()])
+                : EmptyArray.THROWABLE;
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
         // ensure the stackTrace field is initialized
         getInternalStackTrace();
-        s.defaultWriteObject();
+        out.defaultWriteObject();
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+
+        if (suppressedExceptions != null) {
+            // the deserialized list may be unmodifiable, so just create a mutable copy
+            suppressedExceptions = new ArrayList<Throwable>(suppressedExceptions);
+        }
     }
 
     /*
diff --git a/luni/src/main/java/java/lang/reflect/ReflectPermission.java b/luni/src/main/java/java/lang/reflect/ReflectPermission.java
index ce69dec..afc30a2 100644
--- a/luni/src/main/java/java/lang/reflect/ReflectPermission.java
+++ b/luni/src/main/java/java/lang/reflect/ReflectPermission.java
@@ -20,8 +20,7 @@
 import java.security.BasicPermission;
 
 /**
- * A {@code ReflectPermission} object represents a permission to access
- * operations in the reflection layer.
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class ReflectPermission extends BasicPermission {
 
diff --git a/luni/src/main/java/java/net/BindException.java b/luni/src/main/java/java/net/BindException.java
index 3c5151a..498ead6 100644
--- a/luni/src/main/java/java/net/BindException.java
+++ b/luni/src/main/java/java/net/BindException.java
@@ -26,16 +26,13 @@
     private static final long serialVersionUID = -5945005768251722951L;
 
     /**
-     * Constructs a new instance with its walkback filled in.
+     * Constructs a new instance with the current stack trace.
      */
     public BindException() {
     }
 
     /**
-     * Constructs a new instance with its walkback and message filled in.
-     *
-     * @param detailMessage
-     *            detail message of the exception.
+     * Constructs a new instance with the current stack trace and given detail message.
      */
     public BindException(String detailMessage) {
         super(detailMessage);
diff --git a/luni/src/main/java/java/net/CookieManager.java b/luni/src/main/java/java/net/CookieManager.java
index 3b2669e..b1bd832 100644
--- a/luni/src/main/java/java/net/CookieManager.java
+++ b/luni/src/main/java/java/net/CookieManager.java
@@ -48,7 +48,7 @@
  * The CookiePolicy and CookieStore used are customized. Third, use the
  * customized CookiePolicy and the CookieStore.
  *
- * This implementation conforms to RFC 2965, section 3.3.
+ * This implementation conforms to <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a> section 3.3.
  *
  * @since 1.6
  */
@@ -248,4 +248,4 @@
     public CookieStore getCookieStore() {
         return store;
     }
-}
\ No newline at end of file
+}
diff --git a/luni/src/main/java/java/net/CookiePolicy.java b/luni/src/main/java/java/net/CookiePolicy.java
index 03ea76e..44c8669 100644
--- a/luni/src/main/java/java/net/CookiePolicy.java
+++ b/luni/src/main/java/java/net/CookiePolicy.java
@@ -21,7 +21,7 @@
  * and ACCEPT_ORIGINAL_SERVER respectively. They are used to decide which
  * cookies should be accepted and which should not be.
  *
- * See RFC 2965 sec. 3.3 & 7 for more detail.
+ * See <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a> sections 3.3 and 7 for more detail.
  *
  * @since 1.6
  */
@@ -65,4 +65,4 @@
      * @return true if this cookie should be accepted; false otherwise
      */
     boolean shouldAccept(URI uri, HttpCookie cookie);
-}
\ No newline at end of file
+}
diff --git a/luni/src/main/java/java/net/DatagramSocket.java b/luni/src/main/java/java/net/DatagramSocket.java
index 9c38cf8..95f1003 100644
--- a/luni/src/main/java/java/net/DatagramSocket.java
+++ b/luni/src/main/java/java/net/DatagramSocket.java
@@ -19,6 +19,9 @@
 
 import java.io.IOException;
 import java.nio.channels.DatagramChannel;
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+import static libcore.io.OsConstants.*;
 
 /**
  * This class implements a UDP socket for sending and receiving {@code
@@ -377,17 +380,19 @@
      * via this socket are transmitted via the specified interface.  Any
      * packets received by this socket will come from the specified
      * interface.  Broadcast datagrams received on this interface will
-     * be processed by this socket. {@see SocketOptions#SO_BINDTODEVICE}
+     * be processed by this socket. This corresponds to Linux's SO_BINDTODEVICE.
      *
-     * @hide
+     * @hide used by GoogleTV for DHCP
      */
     public void setNetworkInterface(NetworkInterface netInterface) throws SocketException {
         if (netInterface == null) {
             throw new NullPointerException("networkInterface == null");
         }
-
-        impl.setOption(SocketOptions.SO_BINDTODEVICE,
-            Integer.valueOf(netInterface.getIndex()));
+        try {
+            Libcore.os.setsockoptIfreq(impl.fd, SOL_SOCKET, SO_BINDTODEVICE, netInterface.getName());
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsSocketException();
+        }
     }
 
     /**
diff --git a/luni/src/main/java/java/net/HttpCookie.java b/luni/src/main/java/java/net/HttpCookie.java
index 88fcccb..057afef 100644
--- a/luni/src/main/java/java/net/HttpCookie.java
+++ b/luni/src/main/java/java/net/HttpCookie.java
@@ -16,9 +16,6 @@
 
 package java.net;
 
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
@@ -26,6 +23,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
+import libcore.net.http.HttpDate;
 import libcore.util.Objects;
 
 /**
@@ -64,40 +62,6 @@
  */
 public final class HttpCookie implements Cloneable {
 
-    /**
-     * 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() {
-            return new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); // RFC 1123
-        }
-    };
-
-    /**
-     * 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_FORMATS = new String[] {
-            /* This list comes from  {@code org.apache.http.impl.cookie.BrowserCompatSpec}. */
-            "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 1036
-            "EEE MMM d HH:mm:ss yyyy", // ANSI C asctime()
-            "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 Set<String> RESERVED_NAMES = new HashSet<String>();
 
     static {
@@ -223,7 +187,8 @@
 
     /**
      * Constructs a cookie from a string. The string should comply with
-     * set-cookie or set-cookie2 header format as specified in RFC 2965. Since
+     * set-cookie or set-cookie2 header format as specified in
+     * <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a>. Since
      * set-cookies2 syntax allows more than one cookie definitions in one
      * header, the returned object is a list.
      *
@@ -359,7 +324,7 @@
             } else if (name.equals("expires")) {
                 hasExpires = true;
                 if (cookie.maxAge == -1L) {
-                    Date date = parseHttpDate(value);
+                    Date date = HttpDate.parse(value);
                     if (date != null) {
                         cookie.setExpires(date);
                     } else {
@@ -380,20 +345,6 @@
             }
         }
 
-        private Date parseHttpDate(String value) {
-            try {
-                return STANDARD_DATE_FORMAT.get().parse(value);
-            } catch (ParseException ignore) {
-            }
-            for (String formatString : BROWSER_COMPATIBLE_DATE_FORMATS) {
-                try {
-                    return new SimpleDateFormat(formatString, Locale.US).parse(value);
-                } catch (ParseException ignore) {
-                }
-            }
-            return null;
-        }
-
         /**
          * Returns the next attribute name, or null if the input has been
          * exhausted. Returns wth the cursor on the delimiter that follows.
diff --git a/luni/src/main/java/java/net/HttpURLConnection.java b/luni/src/main/java/java/net/HttpURLConnection.java
index a717ae2..8376220 100644
--- a/luni/src/main/java/java/net/HttpURLConnection.java
+++ b/luni/src/main/java/java/net/HttpURLConnection.java
@@ -20,7 +20,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
-import libcore.net.http.HttpURLConnectionImpl;
+import libcore.net.http.HttpEngine;
 
 /**
  * An {@link URLConnection} for HTTP (<a
@@ -65,12 +65,18 @@
  * }</pre>
  *
  * <h3>Secure Communication with HTTPS</h3>
- * Calling {@link URL#openConnection()} on a URL with the "https" scheme will
- * return an {@link javax.net.ssl.HttpsURLConnection HttpsURLConnection}.
- *
- * <p>This class attempts to create secure connections using common TLS
- * extensions and SSL deflate compression. Should that fail, the connection
- * will be retried with SSL3 only.
+
+ * Calling {@link URL#openConnection()} on a URL with the "https"
+ * scheme will return an {@code HttpsURLConnection}, which allows for
+ * overriding the default {@link javax.net.ssl.HostnameVerifier
+ * HostnameVerifier} and {@link javax.net.ssl.SSLSocketFactory
+ * SSLSocketFactory}. An application-supplied {@code SSLSocketFactory}
+ * created from an {@link javax.net.ssl.SSLContext SSLContext} can
+ * provide a custom {@link javax.net.ssl.X509TrustManager
+ * X509TrustManager} for verifying certificate chains and a custom
+ * {@link javax.net.ssl.X509KeyManager X509KeyManager} for supplying
+ * client certificates. See {@link javax.net.ssl.HttpsURLConnection
+ * HttpsURLConnection} for more details.
  *
  * <h3>Response Handling</h3>
  * {@code HttpURLConnection} will follow up to five HTTP redirects. It will
@@ -244,10 +250,25 @@
 public abstract class HttpURLConnection extends URLConnection {
 
     /**
+     * The subset of HTTP methods that the user may select via {@link
+     * libcore.net.http.HttpURLConnectionImpl#setRequestMethod(String)}.
+     */
+    private static final String[] PERMITTED_USER_METHODS = {
+            HttpEngine.OPTIONS,
+            HttpEngine.GET,
+            HttpEngine.HEAD,
+            HttpEngine.POST,
+            HttpEngine.PUT,
+            HttpEngine.DELETE,
+            HttpEngine.TRACE
+            // Note: we don't allow users to specify "CONNECT"
+    };
+
+    /**
      * The HTTP request method of this {@code HttpURLConnection}. The default
      * value is {@code "GET"}.
      */
-    protected String method = HttpURLConnectionImpl.GET;
+    protected String method = HttpEngine.GET;
 
     /**
      * The status code of the response obtained from the HTTP request. The
@@ -628,7 +649,7 @@
         if (connected) {
             throw new ProtocolException("Connection already established");
         }
-        for (String permittedUserMethod : HttpURLConnectionImpl.PERMITTED_USER_METHODS) {
+        for (String permittedUserMethod : PERMITTED_USER_METHODS) {
             if (permittedUserMethod.equals(method)) {
                 // if there is a supported method that matches the desired
                 // method, then set the current method and return
@@ -638,7 +659,7 @@
         }
         // if none matches, then throw ProtocolException
         throw new ProtocolException("Unknown method '" + method + "'; must be one of " +
-                Arrays.toString(HttpURLConnectionImpl.PERMITTED_USER_METHODS));
+                Arrays.toString(PERMITTED_USER_METHODS));
     }
 
     /**
@@ -708,7 +729,7 @@
      * @param contentLength
      *            the fixed length of the HTTP request body.
      * @throws IllegalStateException
-     *             if already connected or an other mode already set.
+     *             if already connected or another mode already set.
      * @throws IllegalArgumentException
      *             if {@code contentLength} is less than zero.
      */
@@ -750,7 +771,7 @@
             throw new IllegalStateException("Already in fixed-length mode");
         }
         if (chunkLength <= 0) {
-            this.chunkLength = HttpURLConnectionImpl.DEFAULT_CHUNK_LENGTH;
+            this.chunkLength = HttpEngine.DEFAULT_CHUNK_LENGTH;
         } else {
             this.chunkLength = chunkLength;
         }
diff --git a/luni/src/main/java/java/net/IDN.java b/luni/src/main/java/java/net/IDN.java
index 1ee1ca6..4e60209 100644
--- a/luni/src/main/java/java/net/IDN.java
+++ b/luni/src/main/java/java/net/IDN.java
@@ -33,7 +33,9 @@
     public static final int ALLOW_UNASSIGNED = 1;
 
     /**
-     * When set, ASCII strings are checked against RFC 1122 & RFC 1123.
+     * When set, ASCII strings are checked against
+     * <a href="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122</a> and
+     * <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</a>.
      */
     public static final int USE_STD3_ASCII_RULES = 2;
 
@@ -42,7 +44,7 @@
 
     /**
      * Transform a Unicode String to ASCII Compatible Encoding String according
-     * to the algorithm defined in RFC 3490.
+     * to the algorithm defined in <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>.
      *
      * <p>If the transformation fails (because the input is not a valid IDN), an
      * exception will be thrown.
@@ -56,7 +58,7 @@
      * @param flags 0, {@code ALLOW_UNASSIGNED}, {@code USE_STD3_ASCII_RULES},
      *         or {@code ALLOW_UNASSIGNED | USE_STD3_ASCII_RULES}
      * @return the ACE name
-     * @throws IllegalArgumentException if {@code input} does not conform to RFC 3490
+     * @throws IllegalArgumentException if {@code input} does not conform to <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>
      */
     public static String toASCII(String input, int flags) {
         return NativeIDN.toASCII(input, flags);
@@ -67,7 +69,7 @@
      *
      * @param input the Unicode name
      * @return the ACE name
-     * @throws IllegalArgumentException if {@code input} does not conform to RFC 3490
+     * @throws IllegalArgumentException if {@code input} does not conform to <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>
      */
     public static String toASCII(String input) {
         return toASCII(input, 0);
@@ -75,7 +77,7 @@
 
     /**
      * Translates a string from ASCII Compatible Encoding (ACE) to Unicode
-     * according to the algorithm defined in RFC 3490.
+     * according to the algorithm defined in <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>.
      *
      * <p>Unlike {@code toASCII}, this transformation cannot fail.
      *
diff --git a/luni/src/main/java/java/net/Inet4Address.java b/luni/src/main/java/java/net/Inet4Address.java
index 727328e..a12eacd 100644
--- a/luni/src/main/java/java/net/Inet4Address.java
+++ b/luni/src/main/java/java/net/Inet4Address.java
@@ -49,10 +49,8 @@
     public static final InetAddress LOOPBACK =
             new Inet4Address(new byte[] { 127, 0, 0, 1 }, "localhost");
 
-    Inet4Address(byte[] address, String name) {
-        family = AF_INET;
-        ipaddress = address;
-        hostName = name;
+    Inet4Address(byte[] ipaddress, String hostName) {
+        super(AF_INET, ipaddress, hostName);
     }
 
     /**
@@ -98,8 +96,8 @@
 
     /**
      * Returns whether this address has a link-local scope or not.
-     * <p>
-     * RFC 3484 <br>
+     *
+     * <p><a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a>
      * Default Address Selection for Internet Protocol Version 6 (IPv6) states
      * IPv4 auto-configuration addresses, prefix 169.254/16, IPv4 loopback
      * addresses, prefix 127/8, are assigned link-local scope.
@@ -116,8 +114,8 @@
 
     /**
      * Returns whether this address has a site-local scope or not.
-     * <p>
-     * RFC 3484 <br>
+     *
+     * <p><a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a>
      * Default Address Selection for Internet Protocol Version 6 (IPv6) states
      * IPv4 private addresses, prefixes 10/8, 172.16/12, and 192.168/16, are
      * assigned site-local scope.
diff --git a/luni/src/main/java/java/net/Inet6Address.java b/luni/src/main/java/java/net/Inet6Address.java
index 0880f3f..c253a50 100644
--- a/luni/src/main/java/java/net/Inet6Address.java
+++ b/luni/src/main/java/java/net/Inet6Address.java
@@ -33,25 +33,25 @@
 
     private static final int AF_INET6 = 10;
 
-    static final InetAddress ANY =
+    /**
+     * @hide
+     */
+    public static final InetAddress ANY =
             new Inet6Address(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, null, 0);
-    static final InetAddress LOOPBACK =
+
+    /**
+     * @hide
+     */
+    public static final InetAddress LOOPBACK =
             new Inet6Address(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
                     "localhost", 0);
 
+    boolean scope_id_set;
     int scope_id;
 
-    boolean scope_id_set;
-
     boolean scope_ifname_set;
-
     String ifname;
 
-    /*
-     * scoped interface.
-     */
-    transient NetworkInterface scopedIf;
-
     /**
      * Constructs an {@code InetAddress} representing the {@code address} and
      * {@code name} and {@code scope_id}.
@@ -63,10 +63,8 @@
      * @param scope_id
      *            the scope id for link- or site-local addresses.
      */
-    Inet6Address(byte[] address, String name, int scope_id) {
-        this.family = AF_INET6;
-        this.hostName = name;
-        this.ipaddress = address;
+    Inet6Address(byte[] ipaddress, String hostName, int scope_id) {
+        super(AF_INET6, ipaddress, hostName);
         this.scope_id = scope_id;
         this.scope_id_set = (scope_id != 0);
     }
@@ -126,7 +124,7 @@
         }
 
         // find the first address which matches the type addr,
-        // then set the scope_id, ifname and scopedIf.
+        // then set the scope_id and ifname.
         Enumeration<InetAddress> addressList = nif.getInetAddresses();
         while (addressList.hasMoreElements()) {
             InetAddress ia = addressList.nextElement();
@@ -138,7 +136,6 @@
                     address.scope_id = v6ia.scope_id;
                     address.scope_ifname_set = true;
                     address.ifname = nif.getName();
-                    address.scopedIf = nif;
                     break;
                 }
             }
@@ -174,84 +171,40 @@
     }
 
     /**
-     * Returns whether this address is an IP multicast address or not. Valid
-     * IPv6 multicast addresses are binary prefixed with 11111111 or FF (hex).
-     *
-     * @return {@code true} if this address is in the multicast group, {@code
-     *         false} otherwise.
+     * Returns whether this address is an IP multicast address or not.
      */
-    @Override
-    public boolean isMulticastAddress() {
+    @Override public boolean isMulticastAddress() {
         // Multicast addresses are prefixed with 11111111 (255)
         return ipaddress[0] == -1;
     }
 
     /**
-     * Returns whether this address is a unspecified wildcard address "::" or
-     * not.
-     *
-     * @return {@code true} if this instance represents a wildcard address,
-     *         {@code false} otherwise.
+     * Returns true if this address is the unspecified wildcard address "::".
      */
-    @Override
-    public boolean isAnyLocalAddress() {
-        for (int i = 0; i < ipaddress.length; i++) {
-            if (ipaddress[i] != 0) {
-                return false;
-            }
-        }
-        return true;
+    @Override public boolean isAnyLocalAddress() {
+        return Arrays.equals(ipaddress, Inet6Address.ANY.ipaddress);
     }
 
     /**
      * Returns whether this address is the loopback address or not. The only
      * valid IPv6 loopback address is "::1".
-     *
-     * @return {@code true} if this instance represents the loopback address,
-     *         {@code false} otherwise.
      */
-    @Override
-    public boolean isLoopbackAddress() {
-
-        // The last word must be 1
-        if (ipaddress[15] != 1) {
-            return false;
-        }
-
-        // All other words must be 0
-        for (int i = 0; i < 15; i++) {
-            if (ipaddress[i] != 0) {
-                return false;
-            }
-        }
-
-        return true;
+    @Override public boolean isLoopbackAddress() {
+        return Arrays.equals(ipaddress, Inet6Address.LOOPBACK.ipaddress);
     }
 
     /**
-     * Returns whether this address is a link-local address or not. A valid IPv6
-     * link-local address is prefixed with 1111111010.
-     *
-     * @return {@code true} if this instance represents a link-local address,
-     *         {@code false} otherwise.
+     * Returns whether this address is a link-local address or not.
      */
-    @Override
-    public boolean isLinkLocalAddress() {
-
+    @Override public boolean isLinkLocalAddress() {
         // the first 10 bits need to be 1111111010 (1018)
         return (ipaddress[0] == -2) && ((ipaddress[1] & 255) >>> 6) == 2;
     }
 
     /**
-     * Returns whether this address is a site-local address or not. A valid IPv6
-     * site-local address is prefixed with 1111111011.
-     *
-     * @return {@code true} if this instance represents a site-local address,
-     *         {@code false} otherwise.
+     * Returns whether this address is a site-local address or not.
      */
-    @Override
-    public boolean isSiteLocalAddress() {
-
+    @Override public boolean isSiteLocalAddress() {
         // the first 10 bits need to be 1111111011 (1019)
         return (ipaddress[0] == -2) && ((ipaddress[1] & 255) >>> 6) == 3;
     }
@@ -259,57 +212,35 @@
     /**
      * Returns whether this address is a global multicast address or not. A
      * valid IPv6 global multicast address is 11111111xxxx1110 or FF0E hex.
-     *
-     * @return {@code true} if this instance represents a global multicast
-     *         address, {@code false} otherwise.
      */
-    @Override
-    public boolean isMCGlobal() {
+    @Override public boolean isMCGlobal() {
         // the first byte should be 0xFF and the lower 4 bits
         // of the second byte should be 0xE
         return (ipaddress[0] == -1) && (ipaddress[1] & 15) == 14;
     }
 
     /**
-     * Returns whether this address is a node-local multicast address or not. A
-     * valid IPv6 node-local multicast address is prefixed with
-     * 11111111xxxx0001.
-     *
-     * @return {@code true} if this instance represents a node-local multicast
-     *         address, {@code false} otherwise.
+     * Returns whether this address is a node-local multicast address or not.
      */
-    @Override
-    public boolean isMCNodeLocal() {
+    @Override public boolean isMCNodeLocal() {
         // the first byte should be 0xFF and the lower 4 bits
         // of the second byte should be 0x1
         return (ipaddress[0] == -1) && (ipaddress[1] & 15) == 1;
     }
 
     /**
-     * Returns whether this address is a link-local multicast address or not. A
-     * valid IPv6 link-local multicast address is prefixed with
-     * 11111111xxxx0010.
-     *
-     * @return {@code true} if this instance represents a link-local multicast
-     *         address, {@code false} otherwise.
+     * Returns whether this address is a link-local multicast address or not.
      */
-    @Override
-    public boolean isMCLinkLocal() {
+    @Override public boolean isMCLinkLocal() {
         // the first byte should be 0xFF and the lower 4 bits
         // of the second byte should be 0x2
         return (ipaddress[0] == -1) && (ipaddress[1] & 15) == 2;
     }
 
     /**
-     * Returns whether this address is a site-local multicast address or not. A
-     * valid IPv6 site-local multicast address is prefixed with
-     * 11111111xxxx0101.
-     *
-     * @return {@code true} if this instance represents a site-local multicast
-     *         address, {@code false} otherwise.
+     * Returns whether this address is a site-local multicast address or not.
      */
-    @Override
-    public boolean isMCSiteLocal() {
+    @Override public boolean isMCSiteLocal() {
         // the first byte should be 0xFF and the lower 4 bits
         // of the second byte should be 0x5
         return (ipaddress[0] == -1) && (ipaddress[1] & 15) == 5;
@@ -317,53 +248,35 @@
 
     /**
      * Returns whether this address is a organization-local multicast address or
-     * not. A valid IPv6 org-local multicast address is prefixed with
-     * 11111111xxxx1000.
-     *
-     * @return {@code true} if this instance represents a org-local multicast
-     *         address, {@code false} otherwise.
+     * not.
      */
-    @Override
-    public boolean isMCOrgLocal() {
+    @Override public boolean isMCOrgLocal() {
         // the first byte should be 0xFF and the lower 4 bits
         // of the second byte should be 0x8
         return (ipaddress[0] == -1) && (ipaddress[1] & 15) == 8;
     }
 
     /**
-     * Gets the scope id as a number if this address is linked to an interface.
-     * Otherwise returns {@code 0}.
-     *
-     * @return the scope_id of this address or 0 when not linked with an
-     *         interface.
+     * Returns the scope id if this address is scoped to an interface, 0 otherwise.
      */
     public int getScopeId() {
-        if (scope_id_set) {
-            return scope_id;
-        }
-        return 0;
+        return scope_id_set ? scope_id : 0;
     }
 
     /**
-     * Gets the network interface if this address is instanced with a scoped
-     * network interface. Otherwise returns {@code null}.
-     *
-     * @return the scoped network interface of this address.
+     * Returns the network interface if this address is instanced with a scoped
+     * network interface, null otherwise.
      */
     public NetworkInterface getScopedInterface() {
-        if (scope_ifname_set) {
-            return scopedIf;
+        try {
+            return scope_ifname_set ? NetworkInterface.getByName(ifname) : null;
+        } catch (SocketException ex) {
+            return null;
         }
-        return null;
     }
 
     /**
-     * Returns whether this address is IPv4 compatible or not. An IPv4
-     * compatible address is prefixed with 96 bits of 0's. The last 32-bits are
-     * varied corresponding with the 32-bit IPv4 address space.
-     *
-     * @return {@code true} if this instance represents an IPv4 compatible
-     *         address, {@code false} otherwise.
+     * Returns whether this address is an IPv4-compatible address or not.
      */
     public boolean isIPv4CompatibleAddress() {
         for (int i = 0; i < 12; i++) {
@@ -397,27 +310,16 @@
         stream.writeFields();
     }
 
-    private void readObject(ObjectInputStream stream) throws IOException,
-            ClassNotFoundException {
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
         ObjectInputStream.GetField fields = stream.readFields();
         ipaddress = (byte[]) fields.get("ipaddress", null);
         scope_id = fields.get("scope_id", 0);
         scope_id_set = fields.get("scope_id_set", false);
         ifname = (String) fields.get("ifname", null);
         scope_ifname_set = fields.get("scope_ifname_set", false);
-        if (scope_ifname_set && ifname != null) {
-            scopedIf = NetworkInterface.getByName(ifname);
-        }
     }
 
-    /**
-     * Returns a string containing a concise, human-readable description of this
-     * IP address.
-     *
-     * @return the description, as host/address.
-     */
-    @Override
-    public String toString() {
+    @Override public String toString() {
         if (ifname != null) {
             return super.toString() + "%" + ifname;
         }
diff --git a/luni/src/main/java/java/net/InetAddress.java b/luni/src/main/java/java/net/InetAddress.java
index f77a901..463bff4 100644
--- a/luni/src/main/java/java/net/InetAddress.java
+++ b/luni/src/main/java/java/net/InetAddress.java
@@ -31,10 +31,13 @@
 import java.util.Comparator;
 import java.util.Enumeration;
 import java.util.List;
+import libcore.io.GaiException;
 import libcore.io.Libcore;
 import libcore.io.IoUtils;
 import libcore.io.Memory;
+import libcore.io.StructAddrinfo;
 import org.apache.harmony.luni.platform.Platform;
+import static libcore.io.OsConstants.*;
 
 /**
  * An Internet Protocol (IP) address. This can be either an IPv4 address or an IPv6 address, and
@@ -144,21 +147,18 @@
 
     private static final long serialVersionUID = 3286316764910316507L;
 
-    String hostName;
-
-    private static class WaitReachable {
-    }
-
-    private transient Object waitReachable = new WaitReachable();
+    private transient Object waitReachable = new Object();
 
     private boolean reached;
 
     private int addrCount;
 
-    int family = 0;
+    private int family;
 
     byte[] ipaddress;
 
+    String hostName;
+
     /**
      * Constructs an {@code InetAddress}.
      *
@@ -171,7 +171,11 @@
      * InetAddresses (e.g., getByAddress). That is why the API does not have
      * public constructors for any of these classes.
      */
-    InetAddress() {}
+    InetAddress(int family, byte[] ipaddress, String hostName) {
+        this.family = family;
+        this.ipaddress = ipaddress;
+        this.hostName = hostName;
+    }
 
     /**
      * Compares this {@code InetAddress} instance against the specified address
@@ -201,30 +205,16 @@
         return ipaddress.clone();
     }
 
-    static final Comparator<byte[]> SHORTEST_FIRST = new Comparator<byte[]>() {
-        public int compare(byte[] a1, byte[] a2) {
-            return a1.length - a2.length;
-        }
-    };
-
     /**
      * Converts an array of byte arrays representing raw IP addresses of a host
-     * to an array of InetAddress objects, sorting to respect the value of the
-     * system property {@code "java.net.preferIPv6Addresses"}.
+     * to an array of InetAddress objects.
      *
      * @param rawAddresses the raw addresses to convert.
      * @param hostName the hostname corresponding to the IP address.
      * @return the corresponding InetAddresses, appropriately sorted.
      */
-    static InetAddress[] bytesToInetAddresses(byte[][] rawAddresses, String hostName)
+    private static InetAddress[] bytesToInetAddresses(byte[][] rawAddresses, String hostName)
             throws UnknownHostException {
-        // If we prefer IPv4, ignore the RFC3484 ordering we get from getaddrinfo(3)
-        // and always put IPv4 addresses first. Arrays.sort() is stable, so the
-        // internal ordering will not be changed.
-        if (!preferIPv6Addresses()) {
-            Arrays.sort(rawAddresses, SHORTEST_FIRST);
-        }
-
         // Convert the byte arrays to InetAddresses.
         InetAddress[] returnedAddresses = new InetAddress[rawAddresses.length];
         for (int i = 0; i < rawAddresses.length; i++) {
@@ -253,23 +243,18 @@
      * Returns the InetAddresses for {@code host}. The returned array is shared
      * and must be cloned before it is returned to application code.
      */
-    static InetAddress[] getAllByNameImpl(String host) throws UnknownHostException {
+    private static InetAddress[] getAllByNameImpl(String host) throws UnknownHostException {
         if (host == null || host.isEmpty()) {
             return loopbackAddresses();
         }
 
-        // Special-case "0" for legacy IPv4 applications.
-        if (host.equals("0")) {
-            return new InetAddress[] { Inet4Address.ANY };
-        }
-
         // Is it a numeric address?
-        byte[] bytes = ipStringToByteArray(host);
-        if (bytes != null) {
-            return new InetAddress[] { makeInetAddress(bytes, null) };
+        InetAddress result = parseNumericAddressNoThrow(host);
+        if (result != null) {
+            return new InetAddress[] { result };
         }
 
-        return lookupHostByName(host);
+        return lookupHostByName(host).clone();
     }
 
     private static InetAddress makeInetAddress(byte[] bytes, String hostName) throws UnknownHostException {
@@ -282,13 +267,26 @@
         }
     }
 
-    private static native String byteArrayToIpString(byte[] address);
-
-    static native byte[] ipStringToByteArray(String address);
-
-    static boolean preferIPv6Addresses() {
-        String propertyValue = System.getProperty("java.net.preferIPv6Addresses");
-        return Boolean.parseBoolean(propertyValue);
+    private static InetAddress parseNumericAddressNoThrow(String address) {
+        // Accept IPv6 addresses (only) in square brackets for compatibility.
+        if (address.startsWith("[") && address.endsWith("]") && address.indexOf(':') != -1) {
+            address = address.substring(1, address.length() - 1);
+        }
+        StructAddrinfo hints = new StructAddrinfo();
+        hints.ai_flags = AI_NUMERICHOST;
+        InetAddress[] addresses = null;
+        try {
+            addresses = Libcore.os.getaddrinfo(address, hints);
+        } catch (GaiException ignored) {
+        }
+        if (addresses == null) {
+            // For backwards compatibility, deal with address formats that
+            // getaddrinfo does not support. For example, 1.2.3, 1.3, and even 3 are
+            // valid IPv4 addresses according to the Java API. If getaddrinfo fails,
+            // try to use inet_aton.
+            return Libcore.os.inet_aton(address);
+        }
+        return addresses[0];
     }
 
     /**
@@ -309,62 +307,37 @@
     }
 
     /**
-     * Gets the textual representation of this IP address.
-     *
-     * @return the textual representation of host's IP address.
+     * Returns the numeric representation of this IP address (such as "127.0.0.1").
      */
     public String getHostAddress() {
-        return byteArrayToIpString(ipaddress);
+        return Libcore.os.getnameinfo(this, NI_NUMERICHOST); // Can't throw.
     }
 
     /**
-     * Gets the host name of this IP address. If the IP address could not be
-     * resolved, the textual representation in a dotted-quad-notation is
-     * returned.
-     *
-     * @return the corresponding string name of this IP address.
+     * Returns the host name corresponding to this IP address. This may or may not be a
+     * fully-qualified name. If the IP address could not be resolved, the numeric representation
+     * is returned instead (see {@link #getHostAddress}).
      */
     public String getHostName() {
-        try {
-            if (hostName == null) {
-                int address = 0;
-                if (ipaddress.length == 4) {
-                    address = Memory.peekInt(ipaddress, 0, ByteOrder.BIG_ENDIAN);
-                    if (address == 0) {
-                        return hostName = byteArrayToIpString(ipaddress);
-                    }
-                }
-                hostName = getHostByAddrImpl(ipaddress).hostName;
-                if (hostName.equals("localhost") && ipaddress.length == 4
-                        && address != 0x7f000001) {
-                    return hostName = byteArrayToIpString(ipaddress);
-                }
+        if (hostName == null) {
+            try {
+                hostName = getHostByAddrImpl(this).hostName;
+            } catch (UnknownHostException ex) {
+                hostName = getHostAddress();
             }
-        } catch (UnknownHostException e) {
-            return hostName = byteArrayToIpString(ipaddress);
         }
         return hostName;
     }
 
     /**
-     * Returns the fully qualified domain name for the host associated with this IP
-     * address.
+     * Returns the fully qualified hostname corresponding to this IP address.
      */
     public String getCanonicalHostName() {
-        String canonicalName;
         try {
-            int address = 0;
-            if (ipaddress.length == 4) {
-                address = Memory.peekInt(ipaddress, 0, ByteOrder.BIG_ENDIAN);
-                if (address == 0) {
-                    return byteArrayToIpString(ipaddress);
-                }
-            }
-            canonicalName = getHostByAddrImpl(ipaddress).hostName;
-        } catch (UnknownHostException e) {
-            return byteArrayToIpString(ipaddress);
+            return getHostByAddrImpl(this).hostName;
+        } catch (UnknownHostException ex) {
+            return getHostAddress();
         }
-        return canonicalName;
     }
 
     /**
@@ -450,16 +423,31 @@
             }
         }
         try {
-            InetAddress[] addresses = bytesToInetAddresses(getaddrinfo(host), host);
+            StructAddrinfo hints = new StructAddrinfo();
+            hints.ai_flags = AI_ADDRCONFIG;
+            hints.ai_family = AF_UNSPEC;
+            // If we don't specify a socket type, every address will appear twice, once
+            // for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
+            // anyway, just pick one.
+            hints.ai_socktype = SOCK_STREAM;
+            InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints);
+            // TODO: should getaddrinfo set the hostname of the InetAddresses it returns?
+            for (InetAddress address : addresses) {
+                address.hostName = host;
+            }
             addressCache.put(host, addresses);
             return addresses;
-        } catch (UnknownHostException e) {
-            String detailMessage = e.getMessage();
+        } catch (GaiException gaiException) {
+            // TODO: bionic currently returns EAI_NODATA, which is indistinguishable from a real
+            // failure. We need to fix bionic before we can report a more useful error.
+            // if (gaiException.error == EAI_SYSTEM) {
+            //    throw new SecurityException("Permission denied (missing INTERNET permission?)");
+            // }
+            String detailMessage = "Unable to resolve host \"" + host + "\": " + Libcore.os.gai_strerror(gaiException.error);
             addressCache.putUnknownHost(host, detailMessage);
-            throw new UnknownHostException(detailMessage);
+            throw gaiException.rethrowAsUnknownHostException(detailMessage);
         }
     }
-    private static native byte[][] getaddrinfo(String name) throws UnknownHostException;
 
     /**
      * Removes all entries from the VM's DNS cache. This does not affect the C library's DNS
@@ -470,32 +458,14 @@
         addressCache.clear();
     }
 
-    /**
-     * Query the IP stack for the host address. The host is in address form.
-     *
-     * @param addr
-     *            the host address to lookup.
-     * @throws UnknownHostException
-     *             if an error occurs during lookup.
-     */
-    static InetAddress getHostByAddrImpl(byte[] addr) throws UnknownHostException {
+    private static InetAddress getHostByAddrImpl(InetAddress address) throws UnknownHostException {
         BlockGuard.getThreadPolicy().onNetwork();
-        return makeInetAddress(addr, getnameinfo(addr));
-    }
-
-    /**
-     * Resolves an IP address to a hostname. Thread safe.
-     */
-    private static native String getnameinfo(byte[] addr);
-
-    static String getHostNameInternal(String host) throws UnknownHostException {
-        if (host == null || host.isEmpty()) {
-            return Inet4Address.LOOPBACK.getHostAddress();
+        try {
+            String hostname = Libcore.os.getnameinfo(address, NI_NAMEREQD);
+            return makeInetAddress(address.ipaddress.clone(), hostname);
+        } catch (GaiException gaiException) {
+            throw gaiException.rethrowAsUnknownHostException();
         }
-        if (!isNumeric(host)) {
-            return lookupHostByName(host)[0].getHostAddress();
-        }
-        return host;
     }
 
     /**
@@ -517,7 +487,7 @@
      * @hide used by frameworks/base to ensure that a getAllByName won't cause a DNS lookup.
      */
     public static boolean isNumeric(String address) {
-        return ipStringToByteArray(address) != null;
+        return parseNumericAddressNoThrow(address) != null;
     }
 
     /**
@@ -530,26 +500,17 @@
      */
     public static InetAddress parseNumericAddress(String numericAddress) {
         if (numericAddress == null || numericAddress.isEmpty()) {
-            return loopbackAddresses()[0];
+            return Inet6Address.LOOPBACK;
         }
-        byte[] bytes = ipStringToByteArray(numericAddress);
-        if (bytes == null) {
+        InetAddress result = parseNumericAddressNoThrow(numericAddress);
+        if (result == null) {
             throw new IllegalArgumentException("Not a numeric address: " + numericAddress);
         }
-        try {
-            return makeInetAddress(bytes, null);
-        } catch (UnknownHostException ex) {
-            // UnknownHostException can't be thrown if you pass null to makeInetAddress.
-            throw new AssertionError(ex);
-        }
+        return result;
     }
 
     private static InetAddress[] loopbackAddresses() {
-        if (preferIPv6Addresses()) {
-            return new InetAddress[] { Inet6Address.LOOPBACK, Inet4Address.LOOPBACK };
-        } else {
-            return new InetAddress[] { Inet4Address.LOOPBACK, Inet6Address.LOOPBACK };
-        }
+        return new InetAddress[] { Inet6Address.LOOPBACK, Inet4Address.LOOPBACK };
     }
 
     /**
@@ -558,7 +519,7 @@
      * @hide 1.7
      */
     public static InetAddress getLoopbackAddress() {
-        return loopbackAddresses()[0];
+        return Inet6Address.LOOPBACK;
     }
 
     /**
@@ -855,7 +816,7 @@
             if (source != null) {
                 Platform.NETWORK.bind(fd, source, 0);
             }
-            Platform.NETWORK.connect(fd, destination, 7, timeout);
+            IoUtils.connect(fd, destination, 7, timeout);
             reached = true;
         } catch (IOException e) {
             if (ERRMSG_CONNECTION_REFUSED.equals(e.getMessage())) {
@@ -881,17 +842,6 @@
     }
 
     /**
-     * Equivalent to {@code getByAddress(null, ipAddress, scopeId)}. Handy for IPv6 addresses
-     * with no associated hostname.
-     *
-     * <p>(Note that numeric addresses such as {@code "127.0.0.1"} are names for the
-     * purposes of this API. Most callers probably want {@link #getAllByName} instead.)
-     */
-    static InetAddress getByAddress(byte[] ipAddress, int scopeId) throws UnknownHostException {
-        return getByAddressInternal(null, ipAddress, scopeId);
-    }
-
-    /**
      * Equivalent to {@code getByAddress(hostName, ipAddress, 0)}. Handy for IPv4 addresses
      * with an associated hostname.
      *
@@ -918,7 +868,7 @@
      *
      * @throws UnknownHostException if {@code ipAddress} is null or the wrong length.
      */
-    static InetAddress getByAddressInternal(String hostName, byte[] ipAddress, int scopeId)
+    private static InetAddress getByAddressInternal(String hostName, byte[] ipAddress, int scopeId)
             throws UnknownHostException {
         if (ipAddress == null) {
             throw new UnknownHostException("ipAddress == null");
@@ -999,7 +949,7 @@
 
     /*
      * The spec requires that if we encounter a generic InetAddress in
-     * serialized form then we should interpret it as an Inet4 address.
+     * serialized form then we should interpret it as an Inet4Address.
      */
     private Object readResolve() throws ObjectStreamException {
         return new Inet4Address(ipaddress, hostName);
diff --git a/luni/src/main/java/java/net/InterfaceAddress.java b/luni/src/main/java/java/net/InterfaceAddress.java
index 2f8029e..612eeb4 100644
--- a/luni/src/main/java/java/net/InterfaceAddress.java
+++ b/luni/src/main/java/java/net/InterfaceAddress.java
@@ -25,21 +25,9 @@
  */
 public class InterfaceAddress {
     /**
-     * The kernel's interface index for the network interface this address
-     * is currently assigned to. Values start at 1, because 0 means "unknown"
-     * or "any", depending on context.
-     */
-    final int index;
-
-    /**
-     * The network interface's name. "lo" or "eth0", for example.
-     */
-    final String name;
-
-    /**
      * An IPv4 or IPv6 address.
      */
-    final InetAddress address;
+    private final InetAddress address;
 
     /**
      * The IPv4 broadcast address, or null for IPv6.
@@ -48,31 +36,25 @@
 
     private final short prefixLength;
 
-    InterfaceAddress(int index, String name, InetAddress address, InetAddress mask) {
-        assert ((address instanceof Inet4Address) == (mask instanceof Inet4Address));
-        this.index = index;
-        this.name = name;
+    /**
+     * For IPv4.
+     */
+    InterfaceAddress(Inet4Address address, Inet4Address broadcastAddress, Inet4Address mask) {
         this.address = address;
-        this.broadcastAddress = makeBroadcastAddress(address, mask);
+        this.broadcastAddress = broadcastAddress;
         this.prefixLength = countPrefixLength(mask);
     }
 
-    private static InetAddress makeBroadcastAddress(InetAddress address, InetAddress mask) {
-        if (!(address instanceof Inet4Address)) {
-            return null;
-        }
-        byte[] broadcast = new byte[4];
-        byte[] maskBytes = mask.ipaddress;
-        byte[] addrBytes = address.ipaddress;
-        if (maskBytes[0] != 0) {
-            for (int i = 0; i < broadcast.length; ++i) {
-                broadcast[i] = (byte) (addrBytes[i] | ~maskBytes[i]);
-            }
-        }
-        return new Inet4Address(broadcast, null);
+    /**
+     * For IPv6.
+     */
+    InterfaceAddress(Inet6Address address, short prefixLength) {
+        this.address = address;
+        this.broadcastAddress = null;
+        this.prefixLength = prefixLength;
     }
 
-    private static short countPrefixLength(InetAddress mask) {
+    private static short countPrefixLength(Inet4Address mask) {
         short count = 0;
         for (byte b : mask.ipaddress) {
             for (int i = 0; i < 8; ++i) {
diff --git a/luni/src/main/java/java/net/MalformedURLException.java b/luni/src/main/java/java/net/MalformedURLException.java
index 66bbb54..77965e8 100644
--- a/luni/src/main/java/java/net/MalformedURLException.java
+++ b/luni/src/main/java/java/net/MalformedURLException.java
@@ -30,17 +30,13 @@
     private static final long serialVersionUID = -182787522200415866L;
 
     /**
-     * Constructs a new instance of this class with its walkback filled in.
+     * Constructs a new instance with the current stack trace.
      */
     public MalformedURLException() {
     }
 
     /**
-     * Constructs a new instance of this class with its walkback and message
-     * filled in.
-     *
-     * @param detailMessage
-     *            the detail message for this exception instance.
+     * Constructs a new instance with the current stack trace and given detail message.
      */
     public MalformedURLException(String detailMessage) {
         super(detailMessage);
diff --git a/luni/src/main/java/java/net/MulticastGroupRequest.java b/luni/src/main/java/java/net/MulticastGroupRequest.java
deleted file mode 100644
index 91c68f5..0000000
--- a/luni/src/main/java/java/net/MulticastGroupRequest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2009 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 java.net;
-
-/**
- * Used to pass an interface index and multicast address to native code.
- *
- * @hide PlainDatagramSocketImpl use only
- */
-public final class MulticastGroupRequest {
-    private final int gr_interface;
-    private final InetAddress gr_group;
-
-    public MulticastGroupRequest(InetAddress groupAddress, NetworkInterface networkInterface) {
-        gr_group = groupAddress;
-        gr_interface = (networkInterface != null) ? networkInterface.getIndex() : 0;
-    }
-}
diff --git a/luni/src/main/java/java/net/MulticastSocket.java b/luni/src/main/java/java/net/MulticastSocket.java
index a04960f..1c422f2 100644
--- a/luni/src/main/java/java/net/MulticastSocket.java
+++ b/luni/src/main/java/java/net/MulticastSocket.java
@@ -19,6 +19,7 @@
 
 import java.io.IOException;
 import java.util.Enumeration;
+import libcore.io.IoUtils;
 
 /**
  * This class implements a multicast socket for sending and receiving IP
@@ -27,133 +28,95 @@
  * @see DatagramSocket
  */
 public class MulticastSocket extends DatagramSocket {
-    private static final int NO_INTERFACE_INDEX = 0;
-    private static final int UNSET_INTERFACE_INDEX = -1;
-
-    private InetAddress interfaceSet;
+    /**
+     * Stores the address supplied to setInterface so we can return it from getInterface. The
+     * translation to an interface index is lossy because an interface can have multiple addresses.
+     */
+    private InetAddress setAddress;
 
     /**
      * Constructs a multicast socket, bound to any available port on the
-     * localhost.
+     * local host.
      *
-     * @throws IOException
-     *             if an error occurs creating or binding the socket.
+     * @throws IOException if an error occurs.
      */
     public MulticastSocket() throws IOException {
         setReuseAddress(true);
     }
 
     /**
-     * Constructs a multicast socket, bound to the specified port on the
-     * localhost.
+     * Constructs a multicast socket, bound to the specified {@code port} on the
+     * local host.
      *
-     * @param aPort
-     *            the port to bind on the localhost.
-     * @throws IOException
-     *             if an error occurs creating or binding the socket.
+     * @throws IOException if an error occurs.
      */
-    public MulticastSocket(int aPort) throws IOException {
-        super(aPort);
+    public MulticastSocket(int port) throws IOException {
+        super(port);
         setReuseAddress(true);
     }
 
     /**
-     * Gets the network address used by this socket. This is useful on
-     * multihomed machines.
+     * Constructs a {@code MulticastSocket} bound to the address and port specified by
+     * {@code localAddress}, or an unbound {@code MulticastSocket} if {@code localAddress == null}.
      *
-     * @return the address of the network interface through which the datagram
-     *         packets are sent or received.
-     * @throws SocketException
-     *                if an error occurs while getting the interface address.
+     * @throws IllegalArgumentException if {@code localAddress} is not supported (because it's not
+     * an {@code InetSocketAddress}, say).
+     * @throws IOException if an error occurs.
+     */
+    public MulticastSocket(SocketAddress localAddress) throws IOException {
+        super(localAddress);
+        setReuseAddress(true);
+    }
+
+    /**
+     * Returns an address of the outgoing network interface used by this socket. To avoid
+     * inherent unpredictability, new code should use {@link #getNetworkInterface} instead.
+     *
+     * @throws SocketException if an error occurs.
      */
     public InetAddress getInterface() throws SocketException {
         checkClosedAndBind(false);
-        if (interfaceSet == null) {
-            InetAddress ipvXaddress = (InetAddress) impl
-                    .getOption(SocketOptions.IP_MULTICAST_IF);
-            if (ipvXaddress.isAnyLocalAddress()) {
-                // the address was not set at the IPv4 level so check the IPv6
-                // level
-                NetworkInterface theInterface = getNetworkInterface();
-                if (theInterface != null) {
-                    Enumeration<InetAddress> addresses = theInterface
-                            .getInetAddresses();
-                    if (addresses != null) {
-                        while (addresses.hasMoreElements()) {
-                            InetAddress nextAddress = addresses.nextElement();
-                            if (nextAddress instanceof Inet6Address) {
-                                return nextAddress;
-                            }
+        if (setAddress != null) {
+            return setAddress;
+        }
+        InetAddress ipvXaddress = (InetAddress) impl.getOption(SocketOptions.IP_MULTICAST_IF);
+        if (ipvXaddress.isAnyLocalAddress()) {
+            // the address was not set at the IPv4 level so check the IPv6
+            // level
+            NetworkInterface theInterface = getNetworkInterface();
+            if (theInterface != null) {
+                Enumeration<InetAddress> addresses = theInterface.getInetAddresses();
+                if (addresses != null) {
+                    while (addresses.hasMoreElements()) {
+                        InetAddress nextAddress = addresses.nextElement();
+                        if (nextAddress instanceof Inet6Address) {
+                            return nextAddress;
                         }
                     }
                 }
             }
-            return ipvXaddress;
         }
-        return interfaceSet;
+        return ipvXaddress;
     }
 
     /**
-     * Gets the network interface used by this socket. This is useful on
-     * multihomed machines.
+     * Returns the outgoing network interface used by this socket.
      *
-     * @return the network interface used by this socket or {@code null} if no
-     *         interface is set.
-     * @throws SocketException
-     *                if an error occurs while getting the interface.
-     * @since 1.4
+     * @throws SocketException if an error occurs.
      */
     public NetworkInterface getNetworkInterface() throws SocketException {
         checkClosedAndBind(false);
-
-        // check if it is set at the IPv6 level. If so then use that. Otherwise
-        // do it at the IPv4 level
-        Integer theIndex = Integer.valueOf(0);
-        try {
-            theIndex = (Integer) impl.getOption(SocketOptions.IP_MULTICAST_IF2);
-        } catch (SocketException e) {
-            // we may get an exception if IPv6 is not enabled.
+        int index = (Integer) impl.getOption(SocketOptions.IP_MULTICAST_IF2);
+        if (index != 0) {
+            return NetworkInterface.getByIndex(index);
         }
-
-        if (theIndex.intValue() != 0) {
-            Enumeration<NetworkInterface> theInterfaces = NetworkInterface.getNetworkInterfaces();
-            while (theInterfaces.hasMoreElements()) {
-                NetworkInterface nextInterface = theInterfaces.nextElement();
-                if (nextInterface.getIndex() == theIndex.intValue()) {
-                    return nextInterface;
-                }
-            }
-        }
-
-        // ok it was not set at the IPv6 level so try at the IPv4 level
-        InetAddress theAddress = (InetAddress) impl.getOption(SocketOptions.IP_MULTICAST_IF);
-        if (theAddress != null) {
-            if (!theAddress.isAnyLocalAddress()) {
-                return NetworkInterface.getByInetAddress(theAddress);
-            }
-
-            // not set as we got the any address so return a dummy network
-            // interface with only the any address. We do this to be
-            // compatible
-            InetAddress theAddresses[] = new InetAddress[1];
-            if (InetAddress.preferIPv6Addresses()) {
-                theAddresses[0] = Inet6Address.ANY;
-            } else {
-                theAddresses[0] = Inet4Address.ANY;
-            }
-            return new NetworkInterface(null, null, theAddresses, UNSET_INTERFACE_INDEX);
-        }
-
-        // ok not set at all so return null
-        return null;
+        return NetworkInterface.forUnboundMulticastSocket();
     }
 
     /**
-     * Gets the time-to-live (TTL) for multicast packets sent on this socket.
+     * Returns the time-to-live (TTL) for multicast packets sent on this socket.
      *
-     * @return the default value for the time-to-life field.
-     * @throws IOException
-     *                if an error occurs reading the default value.
+     * @throws IOException if an error occurs.
      */
     public int getTimeToLive() throws IOException {
         checkClosedAndBind(false);
@@ -161,13 +124,10 @@
     }
 
     /**
-     * Gets the time-to-live (TTL) for multicast packets sent on this socket.
+     * Returns the time-to-live (TTL) for multicast packets sent on this socket.
      *
-     * @return the default value for the time-to-life field.
-     * @throws IOException
-     *                if an error occurs reading the default value.
+     * @throws IOException if an error occurs.
      * @deprecated Replaced by {@link #getTimeToLive}
-     * @see #getTimeToLive()
      */
     @Deprecated
     public byte getTTL() throws IOException {
@@ -182,8 +142,7 @@
      *
      * @param groupAddr
      *            the multicast group to be joined.
-     * @throws IOException
-     *                if an error occurs while joining a group.
+     * @throws IOException if an error occurs.
      */
     public void joinGroup(InetAddress groupAddr) throws IOException {
         checkJoinOrLeave(groupAddr);
@@ -204,7 +163,6 @@
      *                if the specified address is not a multicast address.
      * @throws IllegalArgumentException
      *                if no multicast group is specified.
-     * @since 1.4
      */
     public void joinGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException {
         checkJoinOrLeave(groupAddress, netInterface);
@@ -238,7 +196,6 @@
      *                if the specified group address is not a multicast address.
      * @throws IllegalArgumentException
      *                if {@code groupAddress} is {@code null}.
-     * @since 1.4
      */
     public void leaveGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException {
         checkJoinOrLeave(groupAddress, netInterface);
@@ -251,7 +208,7 @@
             throw new IllegalArgumentException("groupAddress == null");
         }
 
-        if ((netInterface != null) && (netInterface.getFirstAddress() == null)) {
+        if (netInterface != null && !netInterface.getInetAddresses().hasMoreElements()) {
             throw new SocketException("No address associated with interface: " + netInterface);
         }
 
@@ -278,182 +235,71 @@
     }
 
     /**
-     * Send the packet on this socket. The packet must satisfy the security
-     * policy before it may be sent.
+     * Sends the given {@code packet} on this socket, using the given {@code ttl}. This method is
+     * deprecated because it modifies the TTL socket option for this socket twice on each call.
      *
-     * @param pack
-     *            the {@code DatagramPacket} to send
-     * @param ttl
-     *            the TTL setting for this transmission, overriding the socket
-     *            default
-     * @throws IOException
-     *                if an error occurs while sending data or setting options.
+     * @throws IOException if an error occurs.
      * @deprecated use {@link #setTimeToLive}.
      */
     @Deprecated
-    public void send(DatagramPacket pack, byte ttl) throws IOException {
+    public void send(DatagramPacket packet, byte ttl) throws IOException {
         checkClosedAndBind(false);
-        InetAddress packAddr = pack.getAddress();
+        InetAddress packAddr = packet.getAddress();
         int currTTL = getTimeToLive();
         if (packAddr.isMulticastAddress() && (byte) currTTL != ttl) {
             try {
                 setTimeToLive(ttl & 0xff);
-                impl.send(pack);
+                impl.send(packet);
             } finally {
                 setTimeToLive(currTTL);
             }
         } else {
-            impl.send(pack);
+            impl.send(packet);
         }
     }
 
     /**
-     * Sets the interface address used by this socket. This allows to send
-     * multicast packets on a different interface than the default interface of
-     * the local system. This is useful on multihomed machines.
+     * Sets the outgoing network interface used by this socket. The interface used is the first
+     * interface found to have the given {@code address}. To avoid inherent unpredictability,
+     * new code should use {@link #getNetworkInterface} instead.
      *
-     * @param addr
-     *            the multicast interface network address to set.
-     * @throws SocketException
-     *                if an error occurs while setting the network interface
-     *                address option.
+     * @throws SocketException if an error occurs.
      */
-    public void setInterface(InetAddress addr) throws SocketException {
+    public void setInterface(InetAddress address) throws SocketException {
         checkClosedAndBind(false);
-        if (addr == null) {
-            throw new NullPointerException();
-        }
-        if (addr.isAnyLocalAddress()) {
-            impl.setOption(SocketOptions.IP_MULTICAST_IF, Inet4Address.ANY);
-        } else if (addr instanceof Inet4Address) {
-            impl.setOption(SocketOptions.IP_MULTICAST_IF, addr);
-            // keep the address used to do the set as we must return the same
-            // value and for IPv6 we may not be able to get it back uniquely
-            interfaceSet = addr;
+        if (address == null) {
+            throw new NullPointerException("address == null");
         }
 
-        /*
-         * now we should also make sure this works for IPv6 get the network
-         * interface for the address and set the interface using its index
-         * however if IPv6 is not enabled then we may get an exception. if IPv6
-         * is not enabled
-         */
-        NetworkInterface theInterface = NetworkInterface.getByInetAddress(addr);
-        if ((theInterface != null) && (theInterface.getIndex() != 0)) {
-            try {
-                impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer
-                        .valueOf(theInterface.getIndex()));
-            } catch (SocketException e) {
-                // Ignored
-            }
-        } else if (addr.isAnyLocalAddress()) {
-            try {
-                impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer
-                        .valueOf(0));
-            } catch (SocketException e) {
-                // Ignored
-            }
-        } else if (addr instanceof Inet6Address) {
-            throw new SocketException("Address not associated with an interface: " + addr);
+        NetworkInterface networkInterface = NetworkInterface.getByInetAddress(address);
+        if (networkInterface == null) {
+            throw new SocketException("Address not associated with an interface: " + address);
         }
+        impl.setOption(SocketOptions.IP_MULTICAST_IF2, networkInterface.getIndex());
+        this.setAddress = address;
     }
 
     /**
-     * Sets the network interface used by this socket. This is useful for
-     * multihomed machines.
+     * Sets the outgoing network interface used by this socket to the given
+     * {@code networkInterface}.
      *
-     * @param netInterface
-     *            the multicast network interface to set.
-     * @throws SocketException
-     *                if an error occurs while setting the network interface
-     *                option.
-     * @since 1.4
+     * @throws SocketException if an error occurs.
      */
-    public void setNetworkInterface(NetworkInterface netInterface) throws SocketException {
+    public void setNetworkInterface(NetworkInterface networkInterface) throws SocketException {
         checkClosedAndBind(false);
-
-        if (netInterface == null) {
-            // throw a socket exception indicating that we do not support this
-            throw new SocketException("netInterface == null");
+        if (networkInterface == null) {
+            throw new SocketException("networkInterface == null");
         }
 
-        InetAddress firstAddress = netInterface.getFirstAddress();
-        if (firstAddress == null) {
-            throw new SocketException("No address associated with interface: " + netInterface);
-        }
-
-        if (netInterface.getIndex() == UNSET_INTERFACE_INDEX) {
-            // set the address using IP_MULTICAST_IF to make sure this
-            // works for both IPv4 and IPv6
-            impl.setOption(SocketOptions.IP_MULTICAST_IF, Inet4Address.ANY);
-
-            try {
-                // we have the index so now we pass set the interface
-                // using IP_MULTICAST_IF2. This is what is used to set
-                // the interface on systems which support IPv6
-                impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer.valueOf(NO_INTERFACE_INDEX));
-            } catch (SocketException e) {
-                // for now just do this, -- could be narrowed?
-            }
-        }
-
-        /*
-         * Now try to set using IPv4 way. However, if interface passed in has no
-         * IP addresses associated with it then we cannot do it. first we have
-         * to make sure there is an IPv4 address that we can use to call set
-         * interface otherwise we will not set it
-         */
-        Enumeration<InetAddress> theAddresses = netInterface.getInetAddresses();
-        boolean found = false;
-        firstAddress = null;
-        while ((theAddresses.hasMoreElements()) && (found != true)) {
-            InetAddress theAddress = theAddresses.nextElement();
-            if (theAddress instanceof Inet4Address) {
-                firstAddress = theAddress;
-                found = true;
-            }
-        }
-        if (netInterface.getIndex() == NO_INTERFACE_INDEX) {
-            // the system does not support IPv6 and does not provide
-            // indexes for the network interfaces. Just pass in the
-            // first address for the network interface
-            if (firstAddress != null) {
-                impl.setOption(SocketOptions.IP_MULTICAST_IF, firstAddress);
-            } else {
-                /*
-                 * we should never get here as there should not be any network
-                 * interfaces which have no IPv4 address and which does not have
-                 * the network interface index not set correctly
-                 */
-                throw new SocketException("No address associated with interface: " + netInterface);
-            }
-        } else {
-            // set the address using IP_MULTICAST_IF to make sure this
-            // works for both IPv4 and IPv6
-            if (firstAddress != null) {
-                impl.setOption(SocketOptions.IP_MULTICAST_IF, firstAddress);
-            }
-
-            try {
-                // we have the index so now we pass set the interface
-                // using IP_MULTICAST_IF2. This is what is used to set
-                // the interface on systems which support IPv6
-                impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer
-                        .valueOf(netInterface.getIndex()));
-            } catch (SocketException e) {
-                // for now just do this -- could be narrowed?
-            }
-        }
-
-        interfaceSet = null;
+        impl.setOption(SocketOptions.IP_MULTICAST_IF2, networkInterface.getIndex());
+        this.setAddress = null;
     }
 
     /**
      * Sets the time-to-live (TTL) for multicast packets sent on this socket.
      * Valid TTL values are between 0 and 255 inclusive.
      *
-     * @throws IOException
-     *                if an error occurs while setting the TTL option value.
+     * @throws IOException if an error occurs.
      */
     public void setTimeToLive(int ttl) throws IOException {
         checkClosedAndBind(false);
@@ -467,10 +313,8 @@
      * Sets the time-to-live (TTL) for multicast packets sent on this socket.
      * Valid TTL values are between 0 and 255 inclusive.
      *
-     * @throws IOException
-     *                if an error occurs while setting the TTL option value.
+     * @throws IOException if an error occurs.
      * @deprecated Replaced by {@link #setTimeToLive}
-     * @see #setTimeToLive(int)
      */
     @Deprecated
     public void setTTL(byte ttl) throws IOException {
@@ -480,8 +324,7 @@
 
     @Override
     synchronized void createSocket(int aPort, InetAddress addr) throws SocketException {
-        impl = factory != null ? factory.createDatagramSocketImpl()
-                : new PlainDatagramSocketImpl();
+        impl = factory != null ? factory.createDatagramSocketImpl() : new PlainDatagramSocketImpl();
         impl.create();
         try {
             impl.setOption(SocketOptions.SO_REUSEADDR, Boolean.TRUE);
@@ -494,46 +337,23 @@
     }
 
     /**
-     * Constructs a {@code MulticastSocket} bound to the host/port specified by
-     * the {@code SocketAddress}, or an unbound {@code DatagramSocket} if the
-     * {@code SocketAddress} is {@code null}.
+     * Returns true if multicast loopback is <i>disabled</i>.
+     * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the
+     * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}.
      *
-     * @param localAddr
-     *            the local machine address and port to bind to.
-     * @throws IllegalArgumentException
-     *             if the {@code SocketAddress} is not supported.
-     * @throws IOException
-     *             if an error occurs creating or binding the socket.
-     * @since 1.4
-     */
-    public MulticastSocket(SocketAddress localAddr) throws IOException {
-        super(localAddr);
-        setReuseAddress(true);
-    }
-
-    /**
-     * Gets the state of the {@code SocketOptions.IP_MULTICAST_LOOP}.
-     *
-     * @return {@code true} if the IP multicast loop is enabled, {@code false}
-     *         otherwise.
-     * @throws SocketException
-     *             if the socket is closed or the option is invalid.
-     * @since 1.4
+     * @throws SocketException if an error occurs.
      */
     public boolean getLoopbackMode() throws SocketException {
         checkClosedAndBind(false);
-        return !((Boolean) impl.getOption(SocketOptions.IP_MULTICAST_LOOP))
-                .booleanValue();
+        return !((Boolean) impl.getOption(SocketOptions.IP_MULTICAST_LOOP)).booleanValue();
     }
 
     /**
-     * Sets the {@link SocketOptions#IP_MULTICAST_LOOP}.
+     * Disables multicast loopback if {@code disable == true}.
+     * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the
+     * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}.
      *
-     * @param disable
-     *            true to <i>disable</i> loopback
-     * @throws SocketException
-     *             if the socket is closed or the option is invalid.
-     * @since 1.4
+     * @throws SocketException if an error occurs.
      */
     public void setLoopbackMode(boolean disable) throws SocketException {
         checkClosedAndBind(false);
diff --git a/luni/src/main/java/java/net/NetPermission.java b/luni/src/main/java/java/net/NetPermission.java
index e12a3bf..d9f57fe 100644
--- a/luni/src/main/java/java/net/NetPermission.java
+++ b/luni/src/main/java/java/net/NetPermission.java
@@ -18,21 +18,7 @@
 package java.net;
 
 /**
- * This class represents permissions to configure the access to network
- * resources.
- * <p>
- * There are three valid target names:
- * <dl>
- * <dt>setDefaultAuthenticator</dt>
- * <dd>Allows the default authenticator to be set.</dd>
- * <dt>requestPasswordAuthentication</dt>
- * <dd>Allows the default authenticator to be retrieved.</dd>
- * <dt>specifyStreamHandler</dt>
- * <dd>Allows a stream (protocol) handler to be set when constructing an URL
- * object</dd>
- * </dl>
- *
- * @see java.security.BasicPermission
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class NetPermission extends java.security.BasicPermission {
 
diff --git a/luni/src/main/java/java/net/NetworkInterface.java b/luni/src/main/java/java/net/NetworkInterface.java
index 6ef248f..9aa20a0 100644
--- a/luni/src/main/java/java/net/NetworkInterface.java
+++ b/luni/src/main/java/java/net/NetworkInterface.java
@@ -17,14 +17,22 @@
 
 package java.net;
 
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileReader;
+import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
-import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import libcore.util.EmptyArray;
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import static libcore.io.OsConstants.*;
 
 /**
  * This class is used to represent a network interface of the local device. An
@@ -33,159 +41,184 @@
  * system or to identify the local interface of a joined multicast group.
  */
 public final class NetworkInterface extends Object {
-
-    private static final int CHECK_CONNECT_NO_PORT = -1;
-
     private final String name;
-    private final String displayName;
-    private final List<InterfaceAddress> interfaceAddresses = new LinkedList<InterfaceAddress>();
-
     private final int interfaceIndex;
-    private final List<InetAddress> addresses = new LinkedList<InetAddress>();
+    private final List<InterfaceAddress> interfaceAddresses;
+    private final List<InetAddress> addresses;
 
     private final List<NetworkInterface> children = new LinkedList<NetworkInterface>();
 
     private NetworkInterface parent = null;
 
-    private static NetworkInterface[] getNetworkInterfacesImpl() throws SocketException {
-        Map<String, NetworkInterface> networkInterfaces = new LinkedHashMap<String, NetworkInterface>();
-        for (InterfaceAddress ia : getAllInterfaceAddressesImpl()) {
-            if (ia != null) { // The array may contain harmless null elements.
-                String name = ia.name;
-                NetworkInterface ni = networkInterfaces.get(name);
-                if (ni == null) {
-                    ni = new NetworkInterface(name, name, new InetAddress[] { ia.address }, ia.index);
-                    ni.interfaceAddresses.add(ia);
-                    networkInterfaces.put(name, ni);
-                } else {
-                    ni.addresses.add(ia.address);
-                    ni.interfaceAddresses.add(ia);
-                }
-            }
-        }
-        return networkInterfaces.values().toArray(new NetworkInterface[networkInterfaces.size()]);
-    }
-    private static native InterfaceAddress[] getAllInterfaceAddressesImpl() throws SocketException;
-
-    /**
-     * This constructor is used by the native method in order to construct the
-     * NetworkInterface objects in the array that it returns.
-     *
-     * @param name
-     *            internal name associated with the interface.
-     * @param displayName
-     *            a user interpretable name for the interface.
-     * @param addresses
-     *            the Internet addresses associated with the interface.
-     * @param interfaceIndex
-     *            an index for the interface. Only set for platforms that
-     *            support IPv6.
-     */
-    NetworkInterface(String name, String displayName, InetAddress[] addresses,
-            int interfaceIndex) {
+    private NetworkInterface(String name, int interfaceIndex,
+            List<InetAddress> addresses, List<InterfaceAddress> interfaceAddresses) {
         this.name = name;
-        this.displayName = displayName;
         this.interfaceIndex = interfaceIndex;
-        if (addresses != null) {
-            for (InetAddress address : addresses) {
-                this.addresses.add(address);
-            }
-        }
+        this.addresses = addresses;
+        this.interfaceAddresses = interfaceAddresses;
+    }
+
+    static NetworkInterface forUnboundMulticastSocket() {
+        // This is what the RI returns for a MulticastSocket that hasn't been constrained
+        // to a specific interface.
+        return new NetworkInterface(null, -1,
+                Arrays.asList(Inet6Address.ANY), Collections.<InterfaceAddress>emptyList());
     }
 
     /**
-     * Returns the index for the network interface. Unless the system supports
-     * IPv6 this will be 0.
+     * Returns the index for the network interface, or -1 if unknown.
      *
-     * @return the index
+     * @hide 1.7
      */
-    int getIndex() {
+    public int getIndex() {
         return interfaceIndex;
     }
 
     /**
-     * Returns the first address for the network interface. This is used in the
-     * natives when we need one of the addresses for the interface and any one
-     * will do
-     *
-     * @return the first address if one exists, otherwise null.
-     */
-    InetAddress getFirstAddress() {
-        if (addresses.size() >= 1) {
-            return addresses.get(0);
-        }
-        return null;
-    }
-
-    /**
-     * Gets the name associated with this network interface.
-     *
-     * @return the name of this {@code NetworkInterface} instance.
+     * Returns the name of this network interface (such as "eth0" or "lo").
      */
     public String getName() {
         return name;
     }
 
     /**
-     * Gets a list of addresses bound to this network interface.
-     *
-     * @return the address list of the represented network interface.
+     * Returns an enumeration of the addresses bound to this network interface.
      */
     public Enumeration<InetAddress> getInetAddresses() {
         return Collections.enumeration(addresses);
     }
 
     /**
-     * Gets the human-readable name associated with this network interface.
-     *
-     * @return the display name of this network interface or the name if the
-     *         display name is not available.
+     * Returns a human-readable name for this network interface. On Android, this is the same
+     * string as returned by {@link #getName}.
      */
     public String getDisplayName() {
-        /*
-         * we should return the display name unless it is blank in this case
-         * return the name so that something is displayed.
-         */
-        return displayName.isEmpty() ? name : displayName;
+        return name;
     }
 
     /**
-     * Gets the specific network interface according to a given name.
+     * Returns the {@code NetworkInterface} corresponding to the named network interface, or null
+     * if no interface has this name.
      *
-     * @param interfaceName
-     *            the name to identify the searched network interface.
-     * @return the network interface with the specified name if one exists or
-     *         {@code null} otherwise.
-     * @throws SocketException
-     *             if an error occurs while getting the network interface
-     *             information.
-     * @throws NullPointerException
-     *             if the given interface's name is {@code null}.
+     * @throws SocketException if an error occurs.
+     * @throws NullPointerException if {@code interfaceName == null}.
      */
     public static NetworkInterface getByName(String interfaceName) throws SocketException {
         if (interfaceName == null) {
-            throw new NullPointerException();
+            throw new NullPointerException("interfaceName == null");
         }
-        for (NetworkInterface networkInterface : getNetworkInterfacesList()) {
-            if (networkInterface.name.equals(interfaceName)) {
-                return networkInterface;
+        if (!isValidInterfaceName(interfaceName)) {
+            return null;
+        }
+
+        int interfaceIndex = readIntFile("/sys/class/net/" + interfaceName + "/ifindex");
+        List<InetAddress> addresses = new ArrayList<InetAddress>();
+        List<InterfaceAddress> interfaceAddresses = new ArrayList<InterfaceAddress>();
+        collectIpv6Addresses(interfaceName, interfaceIndex, addresses, interfaceAddresses);
+        collectIpv4Address(interfaceName, addresses, interfaceAddresses);
+
+        return new NetworkInterface(interfaceName, interfaceIndex, addresses, interfaceAddresses);
+    }
+
+    private static void collectIpv6Addresses(String interfaceName, int interfaceIndex,
+            List<InetAddress> addresses, List<InterfaceAddress> interfaceAddresses) throws SocketException {
+        // Format of /proc/net/if_inet6 (all numeric fields are implicit hex).
+        // 1. IPv6 address
+        // 2. interface index
+        // 3. prefix length
+        // 4. scope
+        // 5. flags
+        // 6. interface name
+        // "00000000000000000000000000000001 01 80 10 80       lo"
+        BufferedReader in = null;
+        try {
+            in = new BufferedReader(new FileReader("/proc/net/if_inet6"));
+            String suffix = " " + interfaceName;
+            String line;
+            while ((line = in.readLine()) != null) {
+                if (!line.endsWith(suffix)) {
+                    continue;
+                }
+                byte[] addressBytes = new byte[16];
+                for (int i = 0; i < addressBytes.length; ++i) {
+                    addressBytes[i] = (byte) Integer.parseInt(line.substring(2*i, 2*i + 2), 16);
+                }
+                short prefixLength = Short.parseShort(line.substring(36, 38), 16);
+                Inet6Address inet6Address = new Inet6Address(addressBytes, null, interfaceIndex);
+
+                addresses.add(inet6Address);
+                interfaceAddresses.add(new InterfaceAddress(inet6Address, prefixLength));
+            }
+        } catch (Exception ex) {
+            throw rethrowAsSocketException(ex);
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+    }
+
+    private static void collectIpv4Address(String interfaceName, List<InetAddress> addresses,
+            List<InterfaceAddress> interfaceAddresses) throws SocketException {
+        FileDescriptor fd = null;
+        try {
+            fd = Libcore.os.socket(AF_INET, SOCK_DGRAM, 0);
+            InetAddress address = Libcore.os.ioctlInetAddress(fd, SIOCGIFADDR, interfaceName);
+            InetAddress broadcast = Libcore.os.ioctlInetAddress(fd, SIOCGIFBRDADDR, interfaceName);
+            InetAddress netmask = Libcore.os.ioctlInetAddress(fd, SIOCGIFNETMASK, interfaceName);
+            if (broadcast.equals(Inet4Address.ANY)) {
+                broadcast = null;
+            }
+
+            addresses.add(address);
+            interfaceAddresses.add(new InterfaceAddress((Inet4Address) address,
+                    (Inet4Address) broadcast, (Inet4Address) netmask));
+        } catch (ErrnoException errnoException) {
+            if (errnoException.errno != EADDRNOTAVAIL) {
+                // EADDRNOTAVAIL just means no IPv4 address for this interface.
+                // Anything else is a real error.
+                throw rethrowAsSocketException(errnoException);
+            }
+        } catch (Exception ex) {
+            throw rethrowAsSocketException(ex);
+        } finally {
+            IoUtils.closeQuietly(fd);
+        }
+    }
+
+    private static boolean isValidInterfaceName(String interfaceName) {
+        // Don't just stat because a crafty user might have / or .. in the supposed interface name.
+        for (String validName : new File("/sys/class/net").list()) {
+            if (interfaceName.equals(validName)) {
+                return true;
             }
         }
-        return null;
+        return false;
+    }
+
+    private static int readIntFile(String path) throws SocketException {
+        try {
+            String s = IoUtils.readFileAsString(path).trim();
+            if (s.startsWith("0x")) {
+                return Integer.parseInt(s.substring(2), 16);
+            } else {
+                return Integer.parseInt(s);
+            }
+        } catch (Exception ex) {
+            throw rethrowAsSocketException(ex);
+        }
+    }
+
+    private static SocketException rethrowAsSocketException(Exception ex) throws SocketException {
+        SocketException result = new SocketException();
+        result.initCause(ex);
+        throw result;
     }
 
     /**
-     * Gets the specific network interface according to the given address.
+     * Returns the {@code NetworkInterface} corresponding to the given address, or null if no
+     * interface has this address.
      *
-     * @param address
-     *            the address to identify the searched network interface.
-     * @return the network interface with the specified address if one exists or
-     *         {@code null} otherwise.
-     * @throws SocketException
-     *             if an error occurs while getting the network interface
-     *             information.
-     * @throws NullPointerException
-     *             if the given interface address is invalid.
+     * @throws SocketException if an error occurs.
+     * @throws NullPointerException if {@code address == null}.
      */
     public static NetworkInterface getByInetAddress(InetAddress address) throws SocketException {
         if (address == null) {
@@ -200,6 +233,21 @@
     }
 
     /**
+     * Returns the NetworkInterface corresponding to the given interface index, or null if no
+     * interface has this index.
+     *
+     * @throws SocketException if an error occurs.
+     * @hide 1.7
+     */
+    public static NetworkInterface getByIndex(int index) throws SocketException {
+        String name = Libcore.os.if_indextoname(index);
+        if (name == null) {
+            return null;
+        }
+        return NetworkInterface.getByName(name);
+    }
+
+    /**
      * Gets a list of all network interfaces available on the local system or
      * {@code null} if no interface is available.
      *
@@ -214,20 +262,10 @@
     }
 
     private static List<NetworkInterface> getNetworkInterfacesList() throws SocketException {
-        NetworkInterface[] interfaces = getNetworkInterfacesImpl();
-
-        for (NetworkInterface netif : interfaces) {
-            // Ensure that current NetworkInterface is bound to at least
-            // one InetAddress before processing
-            for (InetAddress addr : netif.addresses) {
-                if (addr.ipaddress.length == 16) {
-                    if (addr.isLinkLocalAddress() || addr.isSiteLocalAddress()) {
-                        ((Inet6Address) addr).scopedIf = netif;
-                        ((Inet6Address) addr).ifname = netif.name;
-                        ((Inet6Address) addr).scope_ifname_set = true;
-                    }
-                }
-            }
+        String[] interfaceNames = new File("/sys/class/net").list();
+        NetworkInterface[] interfaces = new NetworkInterface[interfaceNames.length];
+        for (int i = 0; i < interfaceNames.length; ++i) {
+            interfaces[i] = NetworkInterface.getByName(interfaceNames[i]);
         }
 
         List<NetworkInterface> result = new ArrayList<NetworkInterface>();
@@ -261,8 +299,8 @@
     /**
      * Compares the specified object to this {@code NetworkInterface} and
      * returns whether they are equal or not. The object must be an instance of
-     * {@code NetworkInterface} with the same name, {@code displayName} and list
-     * of network interfaces to be equal.
+     * {@code NetworkInterface} with the same name, display name, and list
+     * of interface addresses.
      *
      * @param obj
      *            the object to compare with this instance.
@@ -281,51 +319,32 @@
         NetworkInterface rhs = (NetworkInterface) obj;
         // TODO: should the order of the addresses matter (we use List.equals)?
         return interfaceIndex == rhs.interfaceIndex &&
-                name.equals(rhs.name) && displayName.equals(rhs.displayName) &&
+                name.equals(rhs.name) &&
                 addresses.equals(rhs.addresses);
     }
 
     /**
      * Returns the hash code for this {@code NetworkInterface}. Since the
      * name should be unique for each network interface the hash code is
-     * generated using this name.
+     * generated using the name.
      */
-    @Override
-    public int hashCode() {
+    @Override public int hashCode() {
         return name.hashCode();
     }
 
-    /**
-     * Gets a string containing a concise, human-readable description of this
-     * network interface.
-     *
-     * @return the textual representation for this network interface.
-     */
-    @Override
-    public String toString() {
-        StringBuilder string = new StringBuilder(25);
-        string.append("[");
-        string.append(name);
-        string.append("][");
-        string.append(displayName);
-        string.append("][");
-        string.append(interfaceIndex);
-        string.append("]");
-
-        /*
-         * get the addresses through this call to make sure we only reveal those
-         * that we should
-         */
-        Enumeration<InetAddress> theAddresses = getInetAddresses();
-        if (theAddresses != null) {
-            while (theAddresses.hasMoreElements()) {
-                InetAddress nextAddress = theAddresses.nextElement();
-                string.append("[");
-                string.append(nextAddress.toString());
-                string.append("]");
-            }
+    @Override public String toString() {
+        StringBuilder sb = new StringBuilder(25);
+        sb.append("[");
+        sb.append(name);
+        sb.append("][");
+        sb.append(interfaceIndex);
+        sb.append("]");
+        for (InetAddress address : addresses) {
+            sb.append("[");
+            sb.append(address.toString());
+            sb.append("]");
         }
-        return string.toString();
+        return sb.toString();
     }
 
     /**
@@ -337,10 +356,10 @@
     }
 
     /**
-     * Returns an {@code Enumeration} of all the sub-interfaces of this network interface.
+     * Returns an enumeration of all the sub-interfaces of this network interface.
      * Sub-interfaces are also known as virtual interfaces.
-     * <p>
-     * For example, {@code eth0:1} would be a sub-interface of {@code eth0}.
+     *
+     * <p>For example, {@code eth0:1} would be a sub-interface of {@code eth0}.
      *
      * @return an Enumeration of all the sub-interfaces of this network interface
      * @since 1.6
@@ -368,12 +387,8 @@
      * @since 1.6
      */
     public boolean isUp() throws SocketException {
-        if (addresses.isEmpty()) {
-            return false;
-        }
-        return isUpImpl(name);
+        return hasFlag(IFF_UP);
     }
-    private static native boolean isUpImpl(String n) throws SocketException;
 
     /**
      * Returns true if this network interface is a loopback interface.
@@ -383,12 +398,8 @@
      * @since 1.6
      */
     public boolean isLoopback() throws SocketException {
-        if (addresses.isEmpty()) {
-            return false;
-        }
-        return isLoopbackImpl(name);
+        return hasFlag(IFF_LOOPBACK);
     }
-    private static native boolean isLoopbackImpl(String n) throws SocketException;
 
     /**
      * Returns true if this network interface is a point-to-point interface.
@@ -399,12 +410,8 @@
      * @since 1.6
      */
     public boolean isPointToPoint() throws SocketException {
-        if (addresses.isEmpty()) {
-            return false;
-        }
-        return isPointToPointImpl(name);
+        return hasFlag(IFF_POINTOPOINT);
     }
-    private static native boolean isPointToPointImpl(String n) throws SocketException;
 
     /**
      * Returns true if this network interface supports multicast.
@@ -413,29 +420,39 @@
      * @since 1.6
      */
     public boolean supportsMulticast() throws SocketException {
-        if (addresses.isEmpty()) {
-            return false;
-        }
-        return supportsMulticastImpl(name);
+        return hasFlag(IFF_MULTICAST);
     }
-    private static native boolean supportsMulticastImpl(String n) throws SocketException;
+
+    private boolean hasFlag(int mask) throws SocketException {
+        int flags = readIntFile("/sys/class/net/" + name + "/flags");
+        return (flags & mask) != 0;
+    }
 
     /**
-     * Returns the hardware address of the interface, if it has one, and the
-     * user has the necessary privileges to access the address.
+     * Returns the hardware address of the interface, if it has one, or null otherwise.
      *
-     * @return a byte array containing the address or null if the address
-     *         doesn't exist or is not accessible.
      * @throws SocketException if an I/O error occurs.
      * @since 1.6
      */
     public byte[] getHardwareAddress() throws SocketException {
-        if (addresses.isEmpty()) {
-            return EmptyArray.BYTE;
+        try {
+            // Parse colon-separated bytes with a trailing newline: "aa:bb:cc:dd:ee:ff\n".
+            String s = IoUtils.readFileAsString("/sys/class/net/" + name + "/address");
+            byte[] result = new byte[s.length()/3];
+            for (int i = 0; i < result.length; ++i) {
+                result[i] = (byte) Integer.parseInt(s.substring(3*i, 3*i + 2), 16);
+            }
+            // We only want to return non-zero hardware addresses.
+            for (int i = 0; i < result.length; ++i) {
+                if (result[i] != 0) {
+                    return result;
+                }
+            }
+            return null;
+        } catch (Exception ex) {
+            throw rethrowAsSocketException(ex);
         }
-        return getHardwareAddressImpl(name);
     }
-    private static native byte[] getHardwareAddressImpl(String n) throws SocketException;
 
     /**
      * Returns the Maximum Transmission Unit (MTU) of this interface.
@@ -445,12 +462,8 @@
      * @since 1.6
      */
     public int getMTU() throws SocketException {
-        if (addresses.isEmpty()) {
-            return 0;
-        }
-        return getMTUImpl(name);
+        return readIntFile("/sys/class/net/" + name + "/mtu");
     }
-    private static native int getMTUImpl(String n) throws SocketException;
 
     /**
      * Returns true if this interface is a virtual interface (also called
diff --git a/luni/src/main/java/java/net/NoRouteToHostException.java b/luni/src/main/java/java/net/NoRouteToHostException.java
index 64b20b1..3d46821 100644
--- a/luni/src/main/java/java/net/NoRouteToHostException.java
+++ b/luni/src/main/java/java/net/NoRouteToHostException.java
@@ -27,17 +27,13 @@
     private static final long serialVersionUID = -1897550894873493790L;
 
     /**
-     * Constructs a new instance of this exception with its walkback filled in.
+     * Constructs a new instance with the current stack trace.
      */
     public NoRouteToHostException() {
     }
 
     /**
-     * Constructs a new instance of this exception with its walkback and message
-     * filled in.
-     *
-     * @param detailMessage
-     *            the detail message for this exception.
+     * Constructs a new instance with the current stack trace and given detail message.
      */
     public NoRouteToHostException(String detailMessage) {
         super(detailMessage);
diff --git a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
index e3ef565..0eb413f 100644
--- a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
@@ -24,12 +24,12 @@
 import java.net.DatagramSocketImpl;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
-import java.net.MulticastGroupRequest;
 import java.net.NetworkInterface;
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.UnknownHostException;
 import libcore.io.IoUtils;
+import libcore.io.StructGroupReq;
 import libcore.util.EmptyArray;
 import org.apache.harmony.luni.platform.Platform;
 
@@ -108,7 +108,7 @@
 
     @Override
     public int getTimeToLive() throws IOException {
-        return (Integer) getOption(IoUtils.IP_MULTICAST_TTL);
+        return (Integer) getOption(IoUtils.JAVA_IP_MULTICAST_TTL);
     }
 
     @Override
@@ -116,29 +116,34 @@
         return (byte) getTimeToLive();
     }
 
+    private static StructGroupReq makeGroupReq(InetAddress gr_group, NetworkInterface networkInterface) {
+        int gr_interface = (networkInterface != null) ? networkInterface.getIndex() : 0;
+        return new StructGroupReq(gr_interface, gr_group);
+    }
+
     @Override
     public void join(InetAddress addr) throws IOException {
-        setOption(IoUtils.MCAST_JOIN_GROUP, new MulticastGroupRequest(addr, null));
+        setOption(IoUtils.JAVA_MCAST_JOIN_GROUP, makeGroupReq(addr, null));
     }
 
     @Override
     public void joinGroup(SocketAddress addr, NetworkInterface netInterface) throws IOException {
         if (addr instanceof InetSocketAddress) {
             InetAddress groupAddr = ((InetSocketAddress) addr).getAddress();
-            setOption(IoUtils.MCAST_JOIN_GROUP, new MulticastGroupRequest(groupAddr, netInterface));
+            setOption(IoUtils.JAVA_MCAST_JOIN_GROUP, makeGroupReq(groupAddr, netInterface));
         }
     }
 
     @Override
     public void leave(InetAddress addr) throws IOException {
-        setOption(IoUtils.MCAST_LEAVE_GROUP, new MulticastGroupRequest(addr, null));
+        setOption(IoUtils.JAVA_MCAST_LEAVE_GROUP, makeGroupReq(addr, null));
     }
 
     @Override
     public void leaveGroup(SocketAddress addr, NetworkInterface netInterface) throws IOException {
         if (addr instanceof InetSocketAddress) {
             InetAddress groupAddr = ((InetSocketAddress) addr).getAddress();
-            setOption(IoUtils.MCAST_LEAVE_GROUP, new MulticastGroupRequest(groupAddr, netInterface));
+            setOption(IoUtils.JAVA_MCAST_LEAVE_GROUP, makeGroupReq(groupAddr, netInterface));
         }
     }
 
@@ -179,13 +184,13 @@
                               port, address);
     }
 
-    public void setOption(int optID, Object val) throws SocketException {
-        Platform.NETWORK.setSocketOption(fd, optID, val);
+    public void setOption(int option, Object value) throws SocketException {
+        IoUtils.setSocketOption(fd, option, value);
     }
 
     @Override
     public void setTimeToLive(int ttl) throws IOException {
-        setOption(IoUtils.IP_MULTICAST_TTL, Integer.valueOf(ttl));
+        setOption(IoUtils.JAVA_IP_MULTICAST_TTL, Integer.valueOf(ttl));
     }
 
     @Override
@@ -195,9 +200,7 @@
 
     @Override
     public void connect(InetAddress inetAddr, int port) throws SocketException {
-        Platform.NETWORK.connect(fd, inetAddr, port, 0);
-
-        // if we get here then we are connected at the native level
+        IoUtils.connect(fd, inetAddr, port); // Throws on failure.
         try {
             connectedAddress = InetAddress.getByAddress(inetAddr.getAddress());
         } catch (UnknownHostException e) {
diff --git a/luni/src/main/java/java/net/PlainSocketImpl.java b/luni/src/main/java/java/net/PlainSocketImpl.java
index 937193e..ccdbb5b 100644
--- a/luni/src/main/java/java/net/PlainSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainSocketImpl.java
@@ -171,10 +171,10 @@
             if (streaming && usingSocks()) {
                 socksConnect(anAddr, aPort, 0);
             } else {
-                Platform.NETWORK.connect(fd, normalAddr, aPort, timeout);
+                IoUtils.connect(fd, normalAddr, aPort, timeout);
             }
         } catch (ConnectException e) {
-            throw new ConnectException(anAddr + ":" + aPort + " - " + e.getMessage());
+            throw new ConnectException(anAddr + " (port " + aPort + "): " + e.getMessage());
         }
         super.address = normalAddr;
         super.port = aPort;
@@ -270,8 +270,8 @@
     }
 
     @Override
-    public void setOption(int optID, Object val) throws SocketException {
-        Platform.NETWORK.setSocketOption(fd, optID, val);
+    public void setOption(int option, Object value) throws SocketException {
+        IoUtils.setSocketOption(fd, option, value);
     }
 
     /**
@@ -304,12 +304,11 @@
     /**
      * Connect using a SOCKS server.
      */
-    private void socksConnect(InetAddress applicationServerAddress,
-            int applicationServerPort, int timeout) throws IOException {
+    private void socksConnect(InetAddress applicationServerAddress, int applicationServerPort, int timeout) throws IOException {
         try {
-            Platform.NETWORK.connect(fd, socksGetServerAddress(), socksGetServerPort(), timeout);
+            IoUtils.connect(fd, socksGetServerAddress(), socksGetServerPort(), timeout);
         } catch (Exception e) {
-            throw new SocketException("SOCKS connection failed: " + e);
+            throw new SocketException("SOCKS connection failed", e);
         }
 
         socksRequestConnection(applicationServerAddress, applicationServerPort);
@@ -372,13 +371,12 @@
      */
     private void socksBind() throws IOException {
         try {
-            Platform.NETWORK.connect(fd, socksGetServerAddress(), socksGetServerPort(), 0);
+            IoUtils.connect(fd, socksGetServerAddress(), socksGetServerPort());
         } catch (Exception e) {
-            throw new IOException("Unable to connect to SOCKS server: " + e);
+            throw new IOException("Unable to connect to SOCKS server", e);
         }
 
-        // There must be a connection to an application host for the bind to
-        // work.
+        // There must be a connection to an application host for the bind to work.
         if (lastConnectedAddress == null) {
             throw new SocketException("Invalid SOCKS client");
         }
diff --git a/luni/src/main/java/java/net/PortUnreachableException.java b/luni/src/main/java/java/net/PortUnreachableException.java
index c59706e..b8e8fa9 100644
--- a/luni/src/main/java/java/net/PortUnreachableException.java
+++ b/luni/src/main/java/java/net/PortUnreachableException.java
@@ -26,17 +26,13 @@
     private static final long serialVersionUID = 8462541992376507323L;
 
     /**
-     * Constructs a new instance of this class with its walkback filled in.
+     * Constructs a new instance with the current stack trace.
      */
     public PortUnreachableException() {
     }
 
     /**
-     * Constructs a new instance of this class with its walkback and message
-     * filled in.
-     *
-     * @param detailMessage
-     *            the detail message for this exception.
+     * Constructs a new instance with the current stack trace and given detail message.
      */
     public PortUnreachableException(String detailMessage) {
         super(detailMessage);
diff --git a/luni/src/main/java/java/net/ProtocolException.java b/luni/src/main/java/java/net/ProtocolException.java
index 4d7dd35..979f7d9 100644
--- a/luni/src/main/java/java/net/ProtocolException.java
+++ b/luni/src/main/java/java/net/ProtocolException.java
@@ -27,17 +27,13 @@
     private static final long serialVersionUID = -6098449442062388080L;
 
     /**
-     * Constructs a new instance of this class with its walkback filled in.
+     * Constructs a new instance with the current stack trace.
      */
     public ProtocolException() {
     }
 
     /**
-     * Constructs a new instance of this class with its walkback and message
-     * filled in.
-     *
-     * @param detailMessage
-     *            the detail message for this exception.
+     * Constructs a new instance with the current stack trace and given detail message.
      */
     public ProtocolException(String detailMessage) {
         super(detailMessage);
diff --git a/luni/src/main/java/java/net/Socket.java b/luni/src/main/java/java/net/Socket.java
index e3733b2..06e1ab3 100644
--- a/luni/src/main/java/java/net/Socket.java
+++ b/luni/src/main/java/java/net/Socket.java
@@ -131,10 +131,10 @@
      * Creates a new streaming socket connected to the target host specified by
      * the parameters {@code dstName} and {@code dstPort}. The socket is bound
      * to any available port on the local host.
-     * <p><strong>Implementation note:</strong> this implementation tries each
-     * IP address for the given hostname until it either connects successfully
-     * or it exhausts the set. It will try both IPv4 and IPv6 addresses in the
-     * order specified by the system property {@code "java.net.preferIPv6Addresses"}.
+     *
+     * <p>This implementation tries each IP address for the given hostname (in
+     * <a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a> order)
+     * until it either connects successfully or it exhausts the set.
      *
      * @param dstName
      *            the target host name or IP address to connect to.
@@ -153,13 +153,11 @@
      * Creates a new streaming socket connected to the target host specified by
      * the parameters {@code dstName} and {@code dstPort}. On the local endpoint
      * the socket is bound to the given address {@code localAddress} on port
-     * {@code localPort}.
+     * {@code localPort}. If {@code host} is {@code null} a loopback address is used to connect to.
      *
-     * If {@code host} is {@code null} a loopback address is used to connect to.
-     * <p><strong>Implementation note:</strong> this implementation tries each
-     * IP address for the given hostname until it either connects successfully
-     * or it exhausts the set. It will try both IPv4 and IPv6 addresses in the
-     * order specified by the system property {@code "java.net.preferIPv6Addresses"}.
+     * <p>This implementation tries each IP address for the given hostname (in
+     * <a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a> order)
+     * until it either connects successfully or it exhausts the set.
      *
      * @param dstName
      *            the target host name or IP address to connect to.
@@ -183,10 +181,10 @@
      * Creates a new streaming or datagram socket connected to the target host
      * specified by the parameters {@code hostName} and {@code port}. The socket
      * is bound to any available port on the local host.
-     * <p><strong>Implementation note:</strong> this implementation tries each
-     * IP address for the given hostname until it either connects successfully
-     * or it exhausts the set. It will try both IPv4 and IPv6 addresses in the
-     * order specified by the system property {@code "java.net.preferIPv6Addresses"}.
+     *
+     * <p>This implementation tries each IP address for the given hostname (in
+     * <a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a> order)
+     * until it either connects successfully or it exhausts the set.
      *
      * @param hostName
      *            the target host name or IP address to connect to.
diff --git a/luni/src/main/java/java/net/SocketException.java b/luni/src/main/java/java/net/SocketException.java
index 0f930e0..b7366b3 100644
--- a/luni/src/main/java/java/net/SocketException.java
+++ b/luni/src/main/java/java/net/SocketException.java
@@ -28,20 +28,30 @@
     private static final long serialVersionUID = -5935874303556886934L;
 
     /**
-     * Constructs a new {@code SocketException} instance with its walkback
-     * filled in.
+     * Constructs a new instance with the current stack trace.
      */
     public SocketException() {
     }
 
     /**
-     * Constructs a new {@code SocketException} instance with its walkback and
-     * message filled in.
-     *
-     * @param detailMessage
-     *            the detail message of this exception.
+     * Constructs a new instance with the current stack trace and given detail message.
      */
     public SocketException(String detailMessage) {
         super(detailMessage);
     }
+    /**
+     * Constructs a new instance with the current stack trace and given cause.
+     * @hide internal use only
+     */
+    public SocketException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new instance with the current stack trace, given detail message and given cause.
+     * @hide internal use only
+     */
+    public SocketException(String detailMessage, Throwable cause) {
+        super(detailMessage, cause);
+    }
 }
diff --git a/luni/src/main/java/java/net/SocketOptions.java b/luni/src/main/java/java/net/SocketOptions.java
index ea4a2d7..e23fc97 100644
--- a/luni/src/main/java/java/net/SocketOptions.java
+++ b/luni/src/main/java/java/net/SocketOptions.java
@@ -45,7 +45,7 @@
     public static final int SO_LINGER = 128;
 
     /**
-     * Timeout in milliseconds for blocking accept or read/receive operations (but not
+     * Integer timeout in milliseconds for blocking accept or read/receive operations (but not
      * write/send operations). A timeout of 0 means no timeout. Negative
      * timeouts are not allowed.
      *
@@ -54,39 +54,31 @@
     public static final int SO_TIMEOUT = 4102;
 
     /**
-     * Whether data is sent immediately on this socket.
+     * This boolean option specifies whether data is sent immediately on this socket.
      * As a side-effect this could lead to low packet efficiency. The
      * socket implementation uses the Nagle's algorithm to try to reach a higher
      * packet efficiency if this option is disabled.
      */
     public static final int TCP_NODELAY = 1;
 
-    // For 5 and 6 see MulticastSocket
-
-    // For 7 see PlainDatagramSocketImpl
-
     /**
-     * The interface used to send multicast packets.
-     * This option is only available on a {@link MulticastSocket}.
+     * This is an IPv4-only socket option whose functionality is subsumed by
+     * {@link #IP_MULTICAST_IF} and not implemented on Android.
      */
     public static final int IP_MULTICAST_IF = 16;
 
     /**
-     * This option can be used to set one specific interface on a multihomed
-     * host on which incoming connections are accepted. It's only available on
-     * server-side sockets.
+     * This option does not correspond to any Unix socket option and is not implemented on Android.
      */
     public static final int SO_BINDADDR = 15;
 
     /**
-     * This option specifies whether a reuse of a local address is allowed even
-     * if an other socket is not yet removed by the operating system. It's only
+     * This boolean option specifies whether a reuse of a local address is allowed even
+     * if another socket is not yet removed by the operating system. It's only
      * available on a {@code MulticastSocket}.
      */
     public static final int SO_REUSEADDR = 4;
 
-    // 10 not currently used
-
     /**
      * The size in bytes of a socket's send buffer. This must be an integer greater than 0.
      * This is a hint to the kernel; the kernel may use a larger buffer.
@@ -105,27 +97,13 @@
     public static final int SO_RCVBUF = 4098;
 
     /**
-     * This option can be used to bind a datagram socket to a
-     * particular network interface.  When this is done, only packets
-     * received on the specified interface will be processed by the
-     * socket.  Packets sent via this socket will be transmitted by
-     * the specified interface.  The argument to this operation is the
-     * network-interface index.
-     * @hide
-     */
-    public static final int SO_BINDTODEVICE = 8192;
-
-    // For 13, see DatagramSocket
-
-    /**
-     * This option specifies whether socket implementations can send keepalive
-     * messages if no data has been sent for a longer time.
+     * This boolean option specifies whether the kernel sends keepalive messages.
      */
     public static final int SO_KEEPALIVE = 8;
 
     /**
-     * This option specifies the value for the type-of-service field of the IPv4 header, or the
-     * traffic class field of the IPv6 header. These correspond to the IP_TOS and IPV6_TCLASS
+     * This integer option specifies the value for the type-of-service field of the IPv4 header,
+     * or the traffic class field of the IPv6 header. These correspond to the IP_TOS and IPV6_TCLASS
      * socket options. These may be ignored by the underlying OS. Values must be between 0 and 255
      * inclusive.
      *
@@ -135,14 +113,16 @@
     public static final int IP_TOS = 3;
 
     /**
-     * This option specifies whether the local loopback of multicast packets is
+     * This boolean option specifies whether the local loopback of multicast packets is
      * enabled or disabled. This option is enabled by default on multicast
-     * sockets.
+     * sockets. Note that the sense of this option in Java is the
+     * <i>opposite</i> of the underlying Unix {@code IP_MULTICAST_LOOP}.
+     * See {@link MulticastSocket#setLoopbackMode}.
      */
     public static final int IP_MULTICAST_LOOP = 18;
 
     /**
-     * This option can be used to enable broadcasting on datagram sockets.
+     * This boolean option can be used to enable broadcasting on datagram sockets.
      */
     public static final int SO_BROADCAST = 32;
 
@@ -153,10 +133,8 @@
     public static final int SO_OOBINLINE = 4099;
 
     /**
-     * This option can be used to set one specific interface on a multihomed
-     * host on which incoming connections are accepted. It's only available on
-     * server-side sockets. This option supports setting outgoing interfaces
-     * with either IPv4 or IPv6 addresses.
+     * This integer option sets the outgoing interface for multicast packets
+     * using an interface index.
      */
     public static final int IP_MULTICAST_IF2 = 31;
 
diff --git a/luni/src/main/java/java/net/SocketPermission.java b/luni/src/main/java/java/net/SocketPermission.java
index c63872e..92168c5 100644
--- a/luni/src/main/java/java/net/SocketPermission.java
+++ b/luni/src/main/java/java/net/SocketPermission.java
@@ -26,39 +26,7 @@
 import java.util.Locale;
 
 /**
- * Regulates the access to network operations available through sockets through
- * permissions. A permission consists of a target (a host), and an associated
- * action list. The target should identify the host by either indicating the
- * (possibly wildcarded (eg. {@code .company.com})) DNS style name of the host
- * or its IP address in standard {@code nn.nn.nn.nn} ("dot") notation. The
- * action list can be made up of one or more of the following actions separated
- * by a comma:
- * <dl>
- * <dt>connect</dt>
- * <dd>requests permission to connect to the host</dd>
- * <dt>listen</dt>
- * <dd>requests permission to listen for connections from the host</dd>
- * <dt>accept</dt>
- * <dd>requests permission to accept connections from the host</dd>
- * <dt>resolve</dt>
- * <dd>requests permission to resolve the hostname</dd>
- * </dl>
- * Note that {@code resolve} is implied when any (or none) of the others are
- * present.
- * <p>
- * Access to a particular port can be requested by appending a colon and a
- * single digit to the name (eg. {@code .company.com:7000}). A range of port
- * numbers can also be specified, by appending a pattern of the form
- * <i>LOW-HIGH</i> where <i>LOW</i> and <i>HIGH</i> are valid port numbers. If
- * either <i>LOW</i> or <i>HIGH</i> is omitted it is equivalent to entering the
- * lowest or highest possible value respectively. For example:
- *
- * <pre>
- * {@code SocketPermission("www.company.com:7000-", "connect,accept")}
- * </pre>
- *
- * represents the permission to connect to and accept connections from {@code
- * www.company.com} on ports in the range {@code 7000} to {@code 65535}.
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class SocketPermission extends Permission implements Serializable {
 
@@ -376,13 +344,12 @@
     private String getIPString(boolean isCheck) {
         if (!resolved) {
             try {
-                ipString = InetAddress.getHostNameInternal(hostName);
-            } catch (UnknownHostException e) {
-                // ignore
+                return InetAddress.getAllByName(hostName)[0].getHostAddress();
+            } catch (UnknownHostException ignored) {
             }
             resolved = true;
         }
-        return ipString;
+        return null;
     }
 
     /**
diff --git a/luni/src/main/java/java/net/SocketPermissionCollection.java b/luni/src/main/java/java/net/SocketPermissionCollection.java
index 545782b..1611308 100644
--- a/luni/src/main/java/java/net/SocketPermissionCollection.java
+++ b/luni/src/main/java/java/net/SocketPermissionCollection.java
@@ -23,9 +23,7 @@
 import java.util.Vector;
 
 /**
- * This class represents a list of {@code SocketPermission} objects and provides
- * a method to check whether or not a specific permission is implied by this
- * {@code SocketPermissionCollection}.
+ * Legacy security code; this class exists for compatibility only.
  */
 final class SocketPermissionCollection extends PermissionCollection {
 
diff --git a/luni/src/main/java/java/net/SocketTimeoutException.java b/luni/src/main/java/java/net/SocketTimeoutException.java
index 8ce324a9..62e8731 100644
--- a/luni/src/main/java/java/net/SocketTimeoutException.java
+++ b/luni/src/main/java/java/net/SocketTimeoutException.java
@@ -28,18 +28,13 @@
     private static final long serialVersionUID = -8846654841826352300L;
 
     /**
-     * Creates a new {@code SocketTimeoutException} instance with its walkback
-     * filled in.
+     * Constructs a new instance with the current stack trace.
      */
     public SocketTimeoutException() {
     }
 
     /**
-     * Creates a new {@code SocketTimeoutException} instance with its walkback
-     * and message filled in.
-     *
-     * @param detailMessage
-     *            the detail message of this exception.
+     * Constructs a new instance with the current stack trace and given detail message.
      */
     public SocketTimeoutException(String detailMessage) {
         super(detailMessage);
diff --git a/luni/src/main/java/java/net/URI.java b/luni/src/main/java/java/net/URI.java
index 0001236..3fcfb21 100644
--- a/luni/src/main/java/java/net/URI.java
+++ b/luni/src/main/java/java/net/URI.java
@@ -25,7 +25,8 @@
 import libcore.net.UriCodec;
 
 /**
- * This class represents an instance of a URI as defined by RFC 2396.
+ * This class represents an instance of a URI as defined by
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>.
  */
 public final class URI implements Comparable<URI>, Serializable {
 
@@ -55,7 +56,7 @@
      * Encodes the unescaped characters of {@code s} that are not permitted.
      * Permitted characters are:
      * <ul>
-     *   <li>Unreserved characters in RFC 2396.
+     *   <li>Unreserved characters in <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>.
      *   <li>{@code extraOkayChars},
      *   <li>non-ASCII, non-control, non-whitespace characters
      * </ul>
@@ -99,8 +100,9 @@
      * @param uri
      *            the textual URI representation to be parsed into a URI object.
      * @throws URISyntaxException
-     *             if the given string {@code uri} doesn't fit to the
-     *             specification RFC2396 or could not be parsed correctly.
+     *         if the given {@code uri} isn't an
+     *         <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a> URI
+     *         or could not be parsed correctly.
      */
     public URI(String uri) throws URISyntaxException {
         parseURI(uri, false);
@@ -120,11 +122,11 @@
      * @param frag
      *            the fragment part of the URI.
      * @throws URISyntaxException
-     *             if the temporary created string doesn't fit to the
-     *             specification RFC2396 or could not be parsed correctly.
+     *         if the resulting URI isn't an
+     *         <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a> URI
+     *         or could not be parsed correctly.
      */
-    public URI(String scheme, String ssp, String frag)
-            throws URISyntaxException {
+    public URI(String scheme, String ssp, String frag) throws URISyntaxException {
         StringBuilder uri = new StringBuilder();
         if (scheme != null) {
             uri.append(scheme);
@@ -165,8 +167,9 @@
      * @param fragment
      *            the fragment part of the URI.
      * @throws URISyntaxException
-     *             if the temporary created string doesn't fit to the
-     *             specification RFC2396 or could not be parsed correctly.
+     *         if the resulting URI isn't an
+     *         <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a> URI
+     *         or could not be parsed correctly.
      */
     public URI(String scheme, String userInfo, String host, int port,
             String path, String query, String fragment)
@@ -246,11 +249,11 @@
      * @param fragment
      *            the fragment part of the URI.
      * @throws URISyntaxException
-     *             if the temporary created string doesn't fit to the
-     *             specification RFC2396 or could not be parsed correctly.
+     *         if the resulting URI isn't an
+     *         <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a> URI
+     *         or could not be parsed correctly.
      */
-    public URI(String scheme, String host, String path, String fragment)
-            throws URISyntaxException {
+    public URI(String scheme, String host, String path, String fragment) throws URISyntaxException {
         this(scheme, null, host, -1, path, null, fragment);
     }
 
@@ -273,8 +276,9 @@
      * @param fragment
      *            the fragment part of the URI.
      * @throws URISyntaxException
-     *             if the temporary created string doesn't fit to the
-     *             specification RFC2396 or could not be parsed correctly.
+     *         if the resulting URI isn't an
+     *         <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a> URI
+     *         or could not be parsed correctly.
      */
     public URI(String scheme, String authority, String path, String query,
             String fragment) throws URISyntaxException {
@@ -569,13 +573,9 @@
                 throw new URISyntaxException(host,
                         "Expected a closing square bracket for IPv6 address", 0);
             }
-            byte[] bytes = InetAddress.ipStringToByteArray(host);
-            /*
-             * The native IP parser may return 4 bytes for addresses like
-             * "[::FFFF:127.0.0.1]". This is allowed, but we must not accept
-             * IPv4-formatted addresses in square braces like "[127.0.0.1]".
-             */
-            if (bytes != null && (bytes.length == 16 || bytes.length == 4 && host.contains(":"))) {
+            if (InetAddress.isNumeric(host)) {
+                // If it's numeric, the presence of square brackets guarantees
+                // that it's a numeric IPv6 address.
                 return true;
             }
             throw new URISyntaxException(host, "Malformed IPv6 address");
@@ -600,10 +600,13 @@
             return false;
         }
 
-        // IPv4 address
-        byte[] bytes = InetAddress.ipStringToByteArray(host);
-        if (bytes != null && bytes.length == 4) {
-            return true;
+        // IPv4 address?
+        try {
+            InetAddress ia = InetAddress.parseNumericAddress(host);
+            if (ia instanceof Inet4Address) {
+                return true;
+            }
+        } catch (IllegalArgumentException ex) {
         }
 
         if (forceServer) {
diff --git a/luni/src/main/java/java/net/URL.java b/luni/src/main/java/java/net/URL.java
index 8db1bdd..8779cca 100644
--- a/luni/src/main/java/java/net/URL.java
+++ b/luni/src/main/java/java/net/URL.java
@@ -30,7 +30,8 @@
 
 /**
  * A URL instance specifies the location of a resource on the internet as
- * specified by RFC 1738. Such a resource can be a simple file or a service
+ * specified by <a href="http://www.ietf.org/rfc/rfc1738.txt">RFC 1738</a>.
+ * Such a resource can be a simple file or a service
  * which generates the output dynamically. A URL is divided in its parts
  * protocol, host name, port, path, file, user-info, query, reference and
  * authority. However, not each of this parts has to be defined.
diff --git a/luni/src/main/java/java/net/URLConnection.java b/luni/src/main/java/java/net/URLConnection.java
index 61253f3..26b805f 100644
--- a/luni/src/main/java/java/net/URLConnection.java
+++ b/luni/src/main/java/java/net/URLConnection.java
@@ -58,8 +58,8 @@
  *      Resources from the local file system can be loaded using {@code file:}
  *      URIs. File connections can only be used for input.
  *   <li><strong>FTP</strong><br>
- *      File Transfer Protocol (<a href="http://www.ietf.org/rfc/rfc959.txt">RFC
- *      959</a>) is supported, but with no public subclass. FTP connections can
+ *      File Transfer Protocol (<a href="http://www.ietf.org/rfc/rfc959.txt">RFC 959</a>)
+ *      is supported, but with no public subclass. FTP connections can
  *      be used for input or output but not both.
  *      <p>By default, FTP connections will be made using {@code anonymous} as
  *      the username and the empty string as the password. Specify alternate
@@ -444,7 +444,7 @@
     }
 
     /**
-     * Returns an unchangeable map of the response-header fields and values. The
+     * Returns an unmodifiable map of the response-header fields and values. The
      * response-header field names are the key values of the map. The map values
      * are lists of header field values associated with a particular key name.
      *
@@ -460,7 +460,7 @@
     }
 
     /**
-     * Returns an unchangeable map of general request properties used by this
+     * Returns an unmodifiable map of general request properties used by this
      * connection. The request property names are the key values of the map. The
      * map values are lists of property values of the corresponding key name.
      *
@@ -535,7 +535,7 @@
             return defaultValue;
         }
         try {
-            return Date.parse(date);
+            return Date.parse(date); // TODO: use HttpDate.parse()
         } catch (Exception e) {
             return defaultValue;
         }
diff --git a/luni/src/main/java/java/net/UnknownServiceException.java b/luni/src/main/java/java/net/UnknownServiceException.java
index 5daec4b..a8afa41 100644
--- a/luni/src/main/java/java/net/UnknownServiceException.java
+++ b/luni/src/main/java/java/net/UnknownServiceException.java
@@ -30,18 +30,13 @@
     private static final long serialVersionUID = -4169033248853639508L;
 
     /**
-     * Constructs a new {@code UnknownServiceException} instance with its
-     * walkback filled in.
+     * Constructs a new instance with the current stack trace.
      */
     public UnknownServiceException() {
     }
 
     /**
-     * Constructs a new {@code UnknownServiceException} instance with its
-     * walkback and message filled in.
-     *
-     * @param detailMessage
-     *            the detail message for this exception.
+     * Constructs a new instance with the current stack trace and given detail message.
      */
     public UnknownServiceException(String detailMessage) {
         super(detailMessage);
diff --git a/luni/src/main/java/java/nio/ByteBuffer.java b/luni/src/main/java/java/nio/ByteBuffer.java
index c539f76..ef725c1 100644
--- a/luni/src/main/java/java/nio/ByteBuffer.java
+++ b/luni/src/main/java/java/nio/ByteBuffer.java
@@ -18,6 +18,7 @@
 package java.nio;
 
 import java.util.Arrays;
+import libcore.io.Memory;
 
 /**
  * A buffer for bytes.
@@ -761,14 +762,30 @@
      */
     public ByteBuffer put(ByteBuffer src) {
         if (src == this) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("src == this");
         }
-        if (src.remaining() > remaining()) {
+        int srcByteCount = src.remaining();
+        if (srcByteCount > remaining()) {
             throw new BufferOverflowException();
         }
-        byte[] contents = new byte[src.remaining()];
-        src.get(contents);
-        put(contents);
+
+        Object srcObject = src.isDirect() ? src : NioUtils.unsafeArray(src);
+        int srcOffset = src.position();
+        if (!src.isDirect()) {
+            srcOffset += NioUtils.unsafeArrayOffset(src);
+        }
+
+        ByteBuffer dst = this;
+        Object dstObject = dst.isDirect() ? dst : NioUtils.unsafeArray(dst);
+        int dstOffset = dst.position();
+        if (!dst.isDirect()) {
+            dstOffset += NioUtils.unsafeArrayOffset(dst);
+        }
+
+        Memory.memmove(dstObject, dstOffset, srcObject, srcOffset, srcByteCount);
+        src.position(src.limit());
+        dst.position(dst.position() + srcByteCount);
+
         return this;
     }
 
diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java
index 9887394..970c116 100644
--- a/luni/src/main/java/java/nio/DatagramChannelImpl.java
+++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java
@@ -124,11 +124,9 @@
 
         // check the address
         InetSocketAddress inetSocketAddress = SocketChannelImpl.validateAddress(address);
-
         try {
             begin();
-            Platform.NETWORK.connect(fd,
-                    inetSocketAddress.getAddress(), inetSocketAddress.getPort(), 0);
+            IoUtils.connect(fd, inetSocketAddress.getAddress(), inetSocketAddress.getPort());
         } catch (ConnectException e) {
             // ConnectException means connect fail, not exception
         } finally {
diff --git a/luni/src/main/java/java/nio/HeapByteBuffer.java b/luni/src/main/java/java/nio/HeapByteBuffer.java
index 8ba9980..6b65ec1 100644
--- a/luni/src/main/java/java/nio/HeapByteBuffer.java
+++ b/luni/src/main/java/java/nio/HeapByteBuffer.java
@@ -35,7 +35,7 @@
 abstract class HeapByteBuffer extends BaseByteBuffer {
 
     /**
-     * These fields are non-private for NioUtils.unsafeByteArray.
+     * These fields are non-private for NioUtils.unsafeArray.
      */
     final byte[] backingArray;
     final int offset;
diff --git a/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java b/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java
index a746bd1..5edce30 100644
--- a/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java
+++ b/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java
@@ -62,8 +62,7 @@
 
     @Override
     public ByteBuffer compact() {
-        int addr = effectiveDirectAddress;
-        Memory.memmove(addr, addr + position, remaining());
+        Memory.memmove(this, 0, this, position, remaining());
         position = limit - position;
         limit = capacity;
         mark = UNSET_MARK;
diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java
index 35f5490..51cda16 100644
--- a/luni/src/main/java/java/nio/SocketChannelImpl.java
+++ b/luni/src/main/java/java/nio/SocketChannelImpl.java
@@ -164,27 +164,17 @@
         InetAddress normalAddr = inetSocketAddress.getAddress();
         int port = inetSocketAddress.getPort();
 
-        // When connecting, map ANY address to Localhost
+        // When connecting, map ANY address to localhost
         if (normalAddr.isAnyLocalAddress()) {
             normalAddr = InetAddress.getLocalHost();
         }
 
-        // connect result
-        int result = EOF;
         boolean finished = false;
-
         try {
             if (isBlocking()) {
                 begin();
-                Platform.NETWORK.connect(fd, normalAddr, port, 0);
-                finished = true; // Or we'd have thrown an exception.
-            } else {
-                finished = Platform.NETWORK.connectNonBlocking(fd, normalAddr, port);
-                // set back to nonblocking to work around with a bug in portlib
-                if (!isBlocking()) {
-                    IoUtils.setBlocking(fd, false);
-                }
             }
+            finished = IoUtils.connect(fd, normalAddr, port);
             isBound = finished;
         } catch (IOException e) {
             if (e instanceof ConnectException && !isBlocking()) {
@@ -231,7 +221,6 @@
 
     @Override
     public boolean finishConnect() throws IOException {
-        // status check
         synchronized (this) {
             if (!isOpen()) {
                 throw new ClosedChannelException();
@@ -247,11 +236,8 @@
         boolean finished = false;
         try {
             begin();
-            final int WAIT_FOREVER = -1;
-            final int POLL = 0;
-            finished = Platform.NETWORK.isConnected(fd, isBlocking() ? WAIT_FOREVER : POLL);
+            finished = Platform.NETWORK.isConnected(fd, 0);
             isBound = finished;
-            initLocalAddressAndPort();
         } catch (ConnectException e) {
             if (isOpen()) {
                 close();
@@ -265,8 +251,6 @@
         synchronized (this) {
             status = (finished ? SOCKET_STATUS_CONNECTED : status);
             isBound = finished;
-            // TPE: Workaround for bug that turns socket back to blocking
-            if (!isBlocking()) implConfigureBlocking(false);
         }
         return finished;
     }
diff --git a/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java b/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
index 8104f22..8e98a02 100644
--- a/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
+++ b/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
@@ -88,8 +88,7 @@
      */
     @Override
     synchronized public final SelectionKey keyFor(Selector selector) {
-        for (int i = 0; i < keyList.size(); i++) {
-            SelectionKey key = keyList.get(i);
+        for (SelectionKey key : keyList) {
             if (key != null && key.selector() == selector) {
                 return key;
             }
@@ -173,8 +172,7 @@
     @Override
     synchronized protected final void implCloseChannel() throws IOException {
         implCloseSelectableChannel();
-        for (int i = 0; i < keyList.size(); i++) {
-            SelectionKey key = keyList.get(i);
+        for (SelectionKey key : keyList) {
             if (key != null) {
                 key.cancel();
             }
@@ -235,20 +233,20 @@
      */
     @Override
     public final SelectableChannel configureBlocking(boolean blockingMode) throws IOException {
-        if (isOpen()) {
-            synchronized (blockingLock) {
-                if (isBlocking == blockingMode) {
-                    return this;
-                }
-                if (blockingMode && containsValidKeys()) {
-                    throw new IllegalBlockingModeException();
-                }
-                implConfigureBlocking(blockingMode);
-                isBlocking = blockingMode;
-            }
-            return this;
+        if (!isOpen()) {
+            throw new ClosedChannelException();
         }
-        throw new ClosedChannelException();
+        synchronized (blockingLock) {
+            if (isBlocking == blockingMode) {
+                return this;
+            }
+            if (blockingMode && containsValidKeys()) {
+                throw new IllegalBlockingModeException();
+            }
+            implConfigureBlocking(blockingMode);
+            isBlocking = blockingMode;
+        }
+        return this;
     }
 
     /**
@@ -260,8 +258,7 @@
      * @throws IOException
      *             if an I/O error occurs.
      */
-    protected abstract void implConfigureBlocking(boolean blockingMode)
-            throws IOException;
+    protected abstract void implConfigureBlocking(boolean blockingMode) throws IOException;
 
     /*
      * package private for deregister method in AbstractSelector.
@@ -277,8 +274,7 @@
      * otherwise.
      */
     private synchronized boolean containsValidKeys() {
-        for (int i = 0; i < keyList.size(); i++) {
-            SelectionKey key = keyList.get(i);
+        for (SelectionKey key : keyList) {
             if (key != null && key.isValid()) {
                 return true;
             }
diff --git a/luni/src/main/java/java/security/AllPermission.java b/luni/src/main/java/java/security/AllPermission.java
index 1d2841d..c05a230 100644
--- a/luni/src/main/java/java/security/AllPermission.java
+++ b/luni/src/main/java/java/security/AllPermission.java
@@ -19,9 +19,7 @@
 
 
 /**
- * {@code AllPermission} represents the permission to perform any operation.
- * Since its {@link #implies(Permission)} method always returns {@code true},
- * granting this permission is equivalent to disabling security.
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class AllPermission extends Permission {
 
diff --git a/luni/src/main/java/java/security/SecurityPermission.java b/luni/src/main/java/java/security/SecurityPermission.java
index 599ec6f..c2dfc56 100644
--- a/luni/src/main/java/java/security/SecurityPermission.java
+++ b/luni/src/main/java/java/security/SecurityPermission.java
@@ -18,8 +18,7 @@
 package java.security;
 
 /**
- * {@code SecurityPermission} objects guard access to the mechanisms which
- * implement security. Security permissions have names, but not actions.
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class SecurityPermission extends BasicPermission {
 
diff --git a/luni/src/main/java/java/security/UnresolvedPermission.java b/luni/src/main/java/java/security/UnresolvedPermission.java
index 0865a96..2884421 100644
--- a/luni/src/main/java/java/security/UnresolvedPermission.java
+++ b/luni/src/main/java/java/security/UnresolvedPermission.java
@@ -30,11 +30,7 @@
 import org.apache.harmony.security.fortress.PolicyUtils;
 
 /**
- * An {@code UnresolvedPermission} represents a {@code Permission} whose type
- * should be resolved lazy and not during initialization time of the {@code
- * Policy}. {@code UnresolvedPermission}s contain all information to be replaced
- * by a concrete typed {@code Permission} right before the access checks are
- * performed.
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class UnresolvedPermission extends Permission
     implements Serializable {
diff --git a/luni/src/main/java/java/security/acl/Permission.java b/luni/src/main/java/java/security/acl/Permission.java
index 1b452d9..12391a9 100644
--- a/luni/src/main/java/java/security/acl/Permission.java
+++ b/luni/src/main/java/java/security/acl/Permission.java
@@ -18,10 +18,7 @@
 package java.security.acl;
 
 /**
- * The interface that represents a permission.
- * <p>
- * It can be granted or denied to a {@link java.security.Principal Principal}
- * using an {@link Acl}.
+ * Legacy security code; this class exists for compatibility only.
  */
 public interface Permission {
 
diff --git a/luni/src/main/java/java/sql/Connection.java b/luni/src/main/java/java/sql/Connection.java
index ff534fc..38b4b49 100644
--- a/luni/src/main/java/java/sql/Connection.java
+++ b/luni/src/main/java/java/sql/Connection.java
@@ -40,7 +40,7 @@
  * SQL {@code WHERE} clause</br></li>
  * </ul>
  */
-public interface Connection extends Wrapper {
+public interface Connection extends Wrapper, AutoCloseable {
 
     /**
      * A constant indicating that transactions are not supported.
diff --git a/luni/src/main/java/java/sql/ResultSet.java b/luni/src/main/java/java/sql/ResultSet.java
index 3b7c76e..64ecb59 100644
--- a/luni/src/main/java/java/sql/ResultSet.java
+++ b/luni/src/main/java/java/sql/ResultSet.java
@@ -81,7 +81,7 @@
  * statement is executed again, or the same statement's next {@code ResultSet}
  * is retrieved (if the statement returned of multiple results).
  */
-public interface ResultSet extends Wrapper {
+public interface ResultSet extends Wrapper, AutoCloseable {
 
     /**
      * A constant used to indicate that a {@code ResultSet} object must be
diff --git a/luni/src/main/java/java/sql/SQLPermission.java b/luni/src/main/java/java/sql/SQLPermission.java
index c254929..0418648 100644
--- a/luni/src/main/java/java/sql/SQLPermission.java
+++ b/luni/src/main/java/java/sql/SQLPermission.java
@@ -22,17 +22,7 @@
 import java.security.Guard;
 
 /**
- * A Permission relating to security access control in the {@code java.sql}
- * package.
- * <p>
- * Currently, the only permission supported has the name " {@code setLog}". The
- * {@code setLog} permission controls whether a Java application or applet can
- * open a logging stream using the {@code DriverManager.setLogWriter} method or
- * the {@code DriverManager.setLogStream} method. This is a potentially
- * dangerous operation since the logging stream can contain sensitive
- * information such as usernames and passwords.
- *
- * @see DriverManager
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class SQLPermission extends BasicPermission implements Guard,
         Serializable {
diff --git a/luni/src/main/java/java/sql/Statement.java b/luni/src/main/java/java/sql/Statement.java
index 71ad154..1f7e297 100644
--- a/luni/src/main/java/java/sql/Statement.java
+++ b/luni/src/main/java/java/sql/Statement.java
@@ -34,7 +34,7 @@
  * @see ResultSet
  * @see Connection#createStatement
  */
-public interface Statement extends Wrapper {
+public interface Statement extends Wrapper, AutoCloseable {
 
     /**
      * Passing this constant to {@link #getMoreResults} implies that all {@code
diff --git a/luni/src/main/java/java/text/BreakIterator.java b/luni/src/main/java/java/text/BreakIterator.java
index 13f7443..5bc7f0c 100644
--- a/luni/src/main/java/java/text/BreakIterator.java
+++ b/luni/src/main/java/java/text/BreakIterator.java
@@ -477,10 +477,7 @@
     public abstract void setText(CharacterIterator newText);
 
     /**
-     * Creates a copy of this iterator, all status information including the
-     * current position are kept the same.
-     *
-     * @return a copy of this iterator.
+     * Returns a copy of this iterator.
      */
     @Override
     public Object clone() {
diff --git a/luni/src/main/java/java/text/StringCharacterIterator.java b/luni/src/main/java/java/text/StringCharacterIterator.java
index 13c3543..c8b11f5 100644
--- a/luni/src/main/java/java/text/StringCharacterIterator.java
+++ b/luni/src/main/java/java/text/StringCharacterIterator.java
@@ -81,8 +81,7 @@
      *            start}, {@code location > end} or if {@code end} is greater
      *            than the length of {@code value}.
      */
-    public StringCharacterIterator(String value, int start, int end,
-            int location) {
+    public StringCharacterIterator(String value, int start, int end, int location) {
         string = value;
         if (start < 0 || end > string.length() || start > end
                 || location < start || location > end) {
diff --git a/luni/src/main/java/java/util/PropertyPermission.java b/luni/src/main/java/java/util/PropertyPermission.java
index a5a4738..b33a1a7 100644
--- a/luni/src/main/java/java/util/PropertyPermission.java
+++ b/luni/src/main/java/java/util/PropertyPermission.java
@@ -26,17 +26,7 @@
 import java.security.PermissionCollection;
 
 /**
- * {@code PropertyPermission} objects represent a permission to access system
- * properties.
- * <p>
- * A permission is one of the possible permission strings like "user.name" or
- * "java.version". It's also possible to use a wildcard to define the permission
- * to several properties at once. For example "user.*" will define the
- * permission for "user.home", "user.name", "user.dir", ... "*" defines the
- * permission for all available properties.
- * <p>
- * There are two possible permission action types: read and write. Possible
- * actions are "read", "write", or "read,write"/"write,read".
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class PropertyPermission extends BasicPermission {
     private static final long serialVersionUID = 885438825399942851L;
diff --git a/luni/src/main/java/java/util/regex/PatternSyntaxException.java b/luni/src/main/java/java/util/regex/PatternSyntaxException.java
index a8dec47..0c1de4b 100644
--- a/luni/src/main/java/java/util/regex/PatternSyntaxException.java
+++ b/luni/src/main/java/java/util/regex/PatternSyntaxException.java
@@ -88,31 +88,34 @@
      */
     @Override
     public String getMessage() {
-        StringBuilder builder = new StringBuilder("Syntax error");
-
+        StringBuilder sb = new StringBuilder();
         if (desc != null) {
-            builder.append(' ');
-            builder.append(desc);
+            sb.append(desc);
         }
 
         if (index >= 0) {
-            builder.append(" near index " + index + ":");
+            if (desc != null) {
+                sb.append(' ');
+            }
+            sb.append("near index ");
+            sb.append(index);
+            sb.append(':');
         }
 
         if (pattern != null) {
-            builder.append('\n');
-            builder.append(pattern);
+            sb.append('\n');
+            sb.append(pattern);
 
             if (index >= 0) {
                 char[] spaces = new char[index];
                 Arrays.fill(spaces, ' ');
-                builder.append('\n');
-                builder.append(spaces);
-                builder.append('^');
+                sb.append('\n');
+                sb.append(spaces);
+                sb.append('^');
             }
         }
 
-        return builder.toString();
+        return sb.toString();
     }
 
     /**
diff --git a/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java b/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java
index 8c49690..0f22f38 100644
--- a/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java
+++ b/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java
@@ -24,8 +24,82 @@
 import java.security.cert.X509Certificate;
 
 /**
- * This abstract subclass of {@code HttpURLConnection} defines methods for
- * managing HTTPS connections according to the description given by RFC 2818.
+ * An {@link HttpURLConnection} for HTTPS (<a
+ * href="http://tools.ietf.org/html/rfc2818">RFC 2818</a>). A
+ * connected {@code HttpsURLConnection} allows access to the
+ * negotiated cipher suite, the server certificate chain, and the
+ * client certificate chain if any.
+ *
+ * <h3>Providing an application specific X509TrustManager</h3>
+ *
+ * If an application wants to trust Certificate Authority (CA)
+ * certificates that are not part of the system, it should specify its
+ * own {@code X509TrustManager} via a {@code SSLSocketFactory} set on
+ * the {@code HttpsURLConnection}. The {@code X509TrustManager} can be
+ * created based on a {@code KeyStore} using a {@code
+ * TrustManagerFactory} to supply trusted CA certificates. Note that
+ * self-signed certificates are effectively their own CA and can be
+ * trusted by including them in a {@code KeyStore}.
+ *
+ * <p>For example, to trust a set of certificates specified by a {@code KeyStore}:
+ * <pre>   {@code
+ *   KeyStore keyStore = ...;
+ *   TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+ *   tmf.init(keyStore);
+ *
+ *   SSLContext context = SSLContext.getInstance("TLS");
+ *   context.init(null, tmf.getTrustManagers(), null);
+ *
+ *   URL url = new URL("https://www.example.com/");
+ *   HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
+ *   urlConnection.setSSLSocketFactory(context.getSocketFactory());
+ *   InputStream in = urlConnection.getInputStream();
+ * }</pre>
+ *
+ * <p>It is possible to implement {@code X509TrustManager} directly
+ * instead of using one created by a {@code
+ * TrustManagerFactory}. While this is straightforward in the insecure
+ * case of allowing all certificate chains to pass verification,
+ * writing a proper implementation will usually want to take advantage
+ * of {@link java.security.cert.CertPathValidator
+ * CertPathValidator}. In general, it might be better to write a
+ * custom {@code KeyStore} implementation to pass to the {@code
+ * TrustManagerFactory} than to try and write a custom {@code
+ * X509TrustManager}.
+ *
+ * <h3>Providing an application specific X509KeyManager</h3>
+ *
+ * A custom {@code X509KeyManager} can be used to supply a client
+ * certificate and its associated private key to authenticate a
+ * connection to the server. The {@code X509KeyManager} can be created
+ * based on a {@code KeyStore} using a {@code KeyManagerFactory}.
+ *
+ * <p>For example, to supply client certificates from a {@code KeyStore}:
+ * <pre>   {@code
+ *   KeyStore keyStore = ...;
+ *   KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
+ *   kmf.init(keyStore);
+ *
+ *   SSLContext context = SSLContext.getInstance("TLS");
+ *   context.init(kmf.getKeyManagers(), null, null);
+ *
+ *   URL url = new URL("https://www.example.com/");
+ *   HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
+ *   urlConnection.setSSLSocketFactory(context.getSocketFactory());
+ *   InputStream in = urlConnection.getInputStream();
+ * }</pre>
+ *
+ * <p>A {@code X509KeyManager} can also be implemented directly. This
+ * can allow an application to return a certificate and private key
+ * from a non-{@code KeyStore} source or to specify its own logic for
+ * selecting a specific credential to use when many may be present in
+ * a single {@code KeyStore}.
+ *
+ * <h3>TLS Intolerance Support</h3>
+ *
+ * This class attempts to create secure connections using common TLS
+ * extensions and SSL deflate compression. Should that fail, the
+ * connection will be retried with SSLv3 only.
  */
 public abstract class HttpsURLConnection extends HttpURLConnection {
 
diff --git a/luni/src/main/java/javax/net/ssl/SSLPermission.java b/luni/src/main/java/javax/net/ssl/SSLPermission.java
index 5b5c76f..e881cc4 100644
--- a/luni/src/main/java/javax/net/ssl/SSLPermission.java
+++ b/luni/src/main/java/javax/net/ssl/SSLPermission.java
@@ -20,15 +20,7 @@
 import java.security.BasicPermission;
 
 /**
- * The class representing a network permission.
- * <p>
- * The following permissions are defined, allowing the specified action:
- * <dl>
- * <dt> {@code "setHostnameVerifier"} </dt>
- * <dd> setting a callback object for additional verification of a hostname mismatch.</dd>
- * <dt> {@code "getSSLSessionContext"} </dt>
- * <dd> getting the {@code SSLSessionContext} of an {@code SSLSession}.</dd>
- * </dl>
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class SSLPermission extends BasicPermission {
 
diff --git a/luni/src/main/java/javax/security/auth/AuthPermission.java b/luni/src/main/java/javax/security/auth/AuthPermission.java
index 196979d..173f679 100644
--- a/luni/src/main/java/javax/security/auth/AuthPermission.java
+++ b/luni/src/main/java/javax/security/auth/AuthPermission.java
@@ -20,35 +20,7 @@
 import java.security.BasicPermission;
 
 /**
- * Governs the use of methods in this package and also its subpackages. A
- * <i>target name</i> of the permission specifies which methods are allowed
- * without specifying the concrete action lists. Possible target names and
- * associated authentication permissions are:
- *
- * <pre>
- *    doAs                      invoke Subject.doAs methods.
- *    doAsPrivileged            invoke the Subject.doAsPrivileged methods.
- *    getSubject                invoke Subject.getSubject().
- *    getSubjectFromDomainCombiner    invoke SubjectDomainCombiner.getSubject().
- *    setReadOnly               invoke Subject.setReadonly().
- *    modifyPrincipals          modify the set of principals
- *                              associated with a Subject.
- *    modifyPublicCredentials   modify the set of public credentials
- *                              associated with a Subject.
- *    modifyPrivateCredentials  modify the set of private credentials
- *                              associated with a Subject.
- *    refreshCredential         invoke the refresh method on a credential of a
- *                              refreshable credential class.
- *    destroyCredential         invoke the destroy method on a credential of a
- *                              destroyable credential class.
- *    createLoginContext.<i>name</i>   instantiate a LoginContext with the
- *                              specified name. The wildcard name ('*')
- *                              allows to a LoginContext of any name.
- *    getLoginConfiguration     invoke the getConfiguration method of
- *                              javax.security.auth.login.Configuration.
- *    refreshLoginConfiguration Invoke the refresh method of
- *                              javax.security.auth.login.Configuration.
- * </pre>
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class AuthPermission extends BasicPermission {
 
diff --git a/luni/src/main/java/javax/security/auth/PrivateCredentialPermission.java b/luni/src/main/java/javax/security/auth/PrivateCredentialPermission.java
index f86b2fa..7a9903d 100644
--- a/luni/src/main/java/javax/security/auth/PrivateCredentialPermission.java
+++ b/luni/src/main/java/javax/security/auth/PrivateCredentialPermission.java
@@ -26,26 +26,7 @@
 import java.util.Set;
 
 /**
- * Protects private credential objects belonging to a {@code Subject}. It has
- * only one action which is "read". The target name of this permission has a
- * special syntax:
- *
- * <pre>
- * targetName = CredentialClass {PrincipalClass &quot;PrincipalName&quot;}*
- * </pre>
- *
- * First it states a credential class and is followed then by a list of one or
- * more principals identifying the subject.
- * <p>
- * The principals on their part are specified as the name of the {@code
- * Principal} class followed by the principal name in quotes. For example, the
- * following file may define permission to read the private credentials of a
- * principal named "Bob": "com.sun.PrivateCredential com.sun.Principal \"Bob\""
- * <p>
- * The syntax also allows the use of the wildcard "*" in place of {@code
- * CredentialClass} or {@code PrincipalClass} and/or {@code PrincipalName}.
- *
- * @see Principal
+ * Legacy security code; this class exists for compatibility only.
  */
 public final class PrivateCredentialPermission extends Permission {
 
diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java
index 352a41e..9e2b2c9 100644
--- a/luni/src/main/java/libcore/io/ForwardingOs.java
+++ b/luni/src/main/java/libcore/io/ForwardingOs.java
@@ -45,15 +45,22 @@
     public StructStatFs fstatfs(FileDescriptor fd) throws ErrnoException { return os.fstatfs(fd); }
     public void fsync(FileDescriptor fd) throws ErrnoException { os.fsync(fd); }
     public void ftruncate(FileDescriptor fd, long length) throws ErrnoException { os.ftruncate(fd, length); }
+    public String gai_strerror(int error) { return os.gai_strerror(error); }
+    public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException { return os.getaddrinfo(node, hints); }
     public String getenv(String name) { return os.getenv(name); }
+    public String getnameinfo(InetAddress address, int flags) throws GaiException { return os.getnameinfo(address, flags); }
     public SocketAddress getsockname(FileDescriptor fd) throws ErrnoException { return os.getsockname(fd); }
     public int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptByte(fd, level, option); }
     public InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptInAddr(fd, level, option); }
     public int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptInt(fd, level, option); }
     public StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptLinger(fd, level, option); }
     public StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptTimeval(fd, level, option); }
+    public String if_indextoname(int index) { return os.if_indextoname(index); }
+    public InetAddress inet_aton(String address) { return os.inet_aton(address); }
+    public InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException { return os.ioctlInetAddress(fd, cmd, interfaceName); }
     public int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException { return os.ioctlInt(fd, cmd, arg); }
     public boolean isatty(FileDescriptor fd) { return os.isatty(fd); }
+    public void kill(int pid, int signal) throws ErrnoException { os.kill(pid, signal); }
     public void listen(FileDescriptor fd, int backlog) throws ErrnoException { os.listen(fd, backlog); }
     public long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return os.lseek(fd, offset, whence); }
     public StructStat lstat(String path) throws ErrnoException { return os.lstat(path); }
@@ -72,7 +79,13 @@
     public void remove(String path) throws ErrnoException { os.remove(path); }
     public void rename(String oldPath, String newPath) throws ErrnoException { os.rename(oldPath, newPath); }
     public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { return os.sendfile(outFd, inFd, inOffset, byteCount); }
+    public void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptByte(fd, level, option, value); }
+    public void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException { os.setsockoptIfreq(fd, level, option, value); }
     public void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptInt(fd, level, option, value); }
+    public void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptIpMreqn(fd, level, option, value); }
+    public void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { os.setsockoptGroupReq(fd, level, option, value); }
+    public void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { os.setsockoptLinger(fd, level, option, value); }
+    public void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { os.setsockoptTimeval(fd, level, option, value); }
     public void shutdown(FileDescriptor fd, int how) throws ErrnoException { os.shutdown(fd, how); }
     public FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException { return os.socket(domain, type, protocol); }
     public StructStat stat(String path) throws ErrnoException { return os.stat(path); }
diff --git a/luni/src/main/java/libcore/io/GaiException.java b/luni/src/main/java/libcore/io/GaiException.java
new file mode 100644
index 0000000..08143dc
--- /dev/null
+++ b/luni/src/main/java/libcore/io/GaiException.java
@@ -0,0 +1,66 @@
+/*
+ * 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 libcore.io;
+
+import java.net.UnknownHostException;
+import libcore.io.OsConstants;
+
+/**
+ * An unchecked exception thrown when the {@link Os} {@code getaddrinfo} or {@code getnameinfo}
+ * methods fail. This exception contains the native error value, for comparison against the
+ * {@code GAI_} constants in {@link OsConstants}, should sophisticated
+ * callers need to adjust their behavior based on the exact failure.
+ */
+public final class GaiException extends RuntimeException {
+    private final String functionName;
+    public final int error;
+
+    public GaiException(String functionName, int error) {
+        this.functionName = functionName;
+        this.error = error;
+    }
+
+    public GaiException(String functionName, int error, Throwable cause) {
+        super(cause);
+        this.functionName = functionName;
+        this.error = error;
+    }
+
+    /**
+     * Converts the stashed function name and error value to a human-readable string.
+     * We do this here rather than in the constructor so that callers only pay for
+     * this if they need it.
+     */
+    @Override public String getMessage() {
+        String gaiName = OsConstants.gaiName(error);
+        if (gaiName == null) {
+            gaiName = "GAI_ error " + error;
+        }
+        String description = Libcore.os.gai_strerror(error);
+        return functionName + " failed: " + gaiName + " (" + description + ")";
+    }
+
+    public UnknownHostException rethrowAsUnknownHostException(String detailMessage) throws UnknownHostException {
+        UnknownHostException newException = new UnknownHostException(detailMessage);
+        newException.initCause(this);
+        throw newException;
+    }
+
+    public UnknownHostException rethrowAsUnknownHostException() throws UnknownHostException {
+        throw rethrowAsUnknownHostException(getMessage());
+    }
+}
diff --git a/luni/src/main/java/libcore/io/IoUtils.java b/luni/src/main/java/libcore/io/IoUtils.java
index 0a4de9f..eeb8d99 100644
--- a/luni/src/main/java/libcore/io/IoUtils.java
+++ b/luni/src/main/java/libcore/io/IoUtils.java
@@ -28,12 +28,17 @@
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.SocketOptions;
+import java.net.SocketTimeoutException;
+import java.nio.charset.Charsets;
 import java.util.Arrays;
 import libcore.io.ErrnoException;
 import libcore.io.Libcore;
 import libcore.util.MutableInt;
 import static libcore.io.OsConstants.*;
 
+// TODO: kill this!
+import org.apache.harmony.luni.platform.Platform;
+
 public final class IoUtils {
     private IoUtils() {
     }
@@ -191,6 +196,67 @@
     }
 
     /**
+     * Connects socket 'fd' to 'inetAddress' on 'port', with no timeout. The lack of a timeout
+     * means this method won't throw SocketTimeoutException.
+     */
+    public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException {
+        try {
+            return IoUtils.connect(fd, inetAddress, port, 0);
+        } catch (SocketTimeoutException ex) {
+            throw new AssertionError(ex); // Can't happen for a connect without a timeout.
+        }
+    }
+
+    /**
+     * Connects socket 'fd' to 'inetAddress' on 'port', with a the given 'timeoutMs'.
+     * Use timeoutMs == 0 for a blocking connect with no timeout.
+     */
+    public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws SocketException, SocketTimeoutException {
+        try {
+            return connectErrno(fd, inetAddress, port, timeoutMs);
+        } catch (SocketException ex) {
+            throw ex; // We don't want to doubly wrap these.
+        } catch (SocketTimeoutException ex) {
+            throw ex; // We don't want to doubly wrap these.
+        } catch (IOException ex) {
+            throw new SocketException(ex);
+        }
+    }
+
+    // TODO: this is the wrong name now, but when this gets rewritten without Platform.NETWORK...
+    private static boolean connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws IOException {
+        // With no timeout, just call connect(2) directly.
+        if (timeoutMs == 0) {
+            return Platform.NETWORK.connect(fd, inetAddress, port);
+        }
+
+        // With a timeout, we set the socket to non-blocking, connect(2), and then loop
+        // using select(2) to decide whether we're connected, whether we should keep waiting,
+        // or whether we've seen a permanent failure and should give up.
+        long finishTimeMs = System.currentTimeMillis() + timeoutMs;
+        IoUtils.setBlocking(fd, false);
+        try {
+            if (Platform.NETWORK.connect(fd, inetAddress, port)) {
+                return true;
+            }
+            int remainingTimeoutMs;
+            do {
+                remainingTimeoutMs = (int) (finishTimeMs - System.currentTimeMillis());
+                if (remainingTimeoutMs <= 0) {
+                    String detail = "failed to connect to " + inetAddress + " (port " + port + ")";
+                    if (timeoutMs > 0) {
+                        detail += " after " + timeoutMs + "ms";
+                    }
+                    throw new SocketTimeoutException(detail);
+                }
+            } while (!Platform.NETWORK.isConnected(fd, remainingTimeoutMs));
+            return true; // Or we'd have thrown.
+        } finally {
+            IoUtils.setBlocking(fd, true);
+        }
+    }
+
+    /**
      * Sets 'fd' to be blocking or non-blocking, according to the state of 'blocking'.
      */
     public static void setBlocking(FileDescriptor fd, boolean blocking) throws IOException {
@@ -229,6 +295,11 @@
         }
     }
 
+    // Socket options used by java.net but not exposed in SocketOptions.
+    public static final int JAVA_MCAST_JOIN_GROUP = 19;
+    public static final int JAVA_MCAST_LEAVE_GROUP = 20;
+    public static final int JAVA_IP_MULTICAST_TTL = 17;
+
     /**
      * java.net has its own socket options similar to the underlying Unix ones. We paper over the
      * differences here.
@@ -241,45 +312,26 @@
         }
     }
 
-    // Socket options used by java.net but not exposed in SocketOptions.
-    public static final int MCAST_JOIN_GROUP = 19;
-    public static final int MCAST_LEAVE_GROUP = 20;
-    public static final int IP_MULTICAST_TTL = 17;
-
     private static Object getSocketOptionErrno(FileDescriptor fd, int option) throws SocketException {
         switch (option) {
-        case SocketOptions.IP_MULTICAST_IF2:
-            if (boundIPv4(fd)) {
-                // The caller's asking for an interface index, but that's not how IPv4 works.
-                // Our Java should never get here, because we'll try IP_MULTICAST_IF first and
-                // that will satisfy us.
-                throw new SocketException("no interface index for IPv4");
-            } else {
-                return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF);
-            }
         case SocketOptions.IP_MULTICAST_IF:
+            // This is IPv4-only.
             return Libcore.os.getsockoptInAddr(fd, IPPROTO_IP, IP_MULTICAST_IF);
+        case SocketOptions.IP_MULTICAST_IF2:
+            // This is IPv6-only.
+            return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF);
         case SocketOptions.IP_MULTICAST_LOOP:
-            if (boundIPv4(fd)) {
-                // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int,
-                // IPv4 multicast TTL uses a byte.
-                return booleanFromInt(Libcore.os.getsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_LOOP));
-            } else {
-                return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP));
-            }
-        case IoUtils.IP_MULTICAST_TTL:
-            if (boundIPv4(fd)) {
-                // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte.
-                return Libcore.os.getsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_TTL);
-            } else {
-                return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS);
-            }
+            // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
+            // it doesn't matter which we return.
+            return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP));
+        case IoUtils.JAVA_IP_MULTICAST_TTL:
+            // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
+            // it doesn't matter which we return.
+            return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS);
         case SocketOptions.IP_TOS:
-            if (boundIPv4(fd)) {
-                return Libcore.os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
-            } else {
-                return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS);
-            }
+            // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
+            // it doesn't matter which we return.
+            return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS);
         case SocketOptions.SO_BROADCAST:
             return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_BROADCAST));
         case SocketOptions.SO_KEEPALIVE:
@@ -303,23 +355,102 @@
         case SocketOptions.TCP_NODELAY:
             return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY));
         default:
-            throw new SocketException("unknown socket option " + option);
+            throw new SocketException("Unknown socket option: " + option);
         }
     }
 
-    private static boolean boundIPv4(FileDescriptor fd) {
-        SocketAddress sa = Libcore.os.getsockname(fd);
-        if (!(sa instanceof InetSocketAddress)) {
-            return false;
-        }
-        InetSocketAddress isa = (InetSocketAddress) sa;
-        return (isa.getAddress() instanceof Inet4Address);
-    }
-
     private static boolean booleanFromInt(int i) {
         return (i != 0);
     }
 
+    private static int booleanToInt(boolean b) {
+        return b ? 1 : 0;
+    }
+
+    /**
+     * java.net has its own socket options similar to the underlying Unix ones. We paper over the
+     * differences here.
+     */
+    public static void setSocketOption(FileDescriptor fd, int option, Object value) throws SocketException {
+        try {
+            setSocketOptionErrno(fd, option, value);
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsSocketException();
+        }
+    }
+
+    private static void setSocketOptionErrno(FileDescriptor fd, int option, Object value) throws SocketException {
+        switch (option) {
+        case SocketOptions.IP_MULTICAST_IF:
+            throw new UnsupportedOperationException("Use IP_MULTICAST_IF2 on Android");
+        case SocketOptions.IP_MULTICAST_IF2:
+            // Although IPv6 was cleaned up to use int, IPv4 uses an ip_mreqn containing an int.
+            Libcore.os.setsockoptIpMreqn(fd, IPPROTO_IP, IP_MULTICAST_IF, (Integer) value);
+            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (Integer) value);
+            return;
+        case SocketOptions.IP_MULTICAST_LOOP:
+            // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte.
+            Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_LOOP, booleanToInt((Boolean) value));
+            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, booleanToInt((Boolean) value));
+            return;
+        case IoUtils.JAVA_IP_MULTICAST_TTL:
+            // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int,
+            // IPv4 multicast TTL uses a byte.
+            Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_TTL, (Integer) value);
+            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (Integer) value);
+            return;
+        case SocketOptions.IP_TOS:
+            Libcore.os.setsockoptInt(fd, IPPROTO_IP, IP_TOS, (Integer) value);
+            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS, (Integer) value);
+            return;
+        case SocketOptions.SO_BROADCAST:
+            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_BROADCAST, booleanToInt((Boolean) value));
+            return;
+        case SocketOptions.SO_KEEPALIVE:
+            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE, booleanToInt((Boolean) value));
+            return;
+        case SocketOptions.SO_LINGER:
+            boolean on = false;
+            int seconds = 0;
+            if (value instanceof Integer) {
+                on = true;
+                seconds = Math.min((Integer) value, 65535);
+            }
+            StructLinger linger = new StructLinger(booleanToInt(on), seconds);
+            Libcore.os.setsockoptLinger(fd, SOL_SOCKET, SO_LINGER, linger);
+            return;
+        case SocketOptions.SO_OOBINLINE:
+            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE, booleanToInt((Boolean) value));
+            return;
+        case SocketOptions.SO_RCVBUF:
+            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, (Integer) value);
+            return;
+        case SocketOptions.SO_REUSEADDR:
+            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR, booleanToInt((Boolean) value));
+            return;
+        case SocketOptions.SO_SNDBUF:
+            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_SNDBUF, (Integer) value);
+            return;
+        case SocketOptions.SO_TIMEOUT:
+            int millis = (Integer) value;
+            StructTimeval tv = StructTimeval.fromMillis(millis);
+            Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, tv);
+            return;
+        case SocketOptions.TCP_NODELAY:
+            Libcore.os.setsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY, booleanToInt((Boolean) value));
+            return;
+        case IoUtils.JAVA_MCAST_JOIN_GROUP:
+        case IoUtils.JAVA_MCAST_LEAVE_GROUP:
+            StructGroupReq groupReq = (StructGroupReq) value;
+            int level = (groupReq.gr_group instanceof Inet4Address) ? IPPROTO_IP : IPPROTO_IPV6;
+            int op = (option == JAVA_MCAST_JOIN_GROUP) ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP;
+            Libcore.os.setsockoptGroupReq(fd, level, op, groupReq);
+            return;
+        default:
+            throw new SocketException("Unknown socket option: " + option);
+        }
+    }
+
     public static InetAddress getSocketLocalAddress(FileDescriptor fd) {
         SocketAddress sa = Libcore.os.getsockname(fd);
         InetSocketAddress isa = (InetSocketAddress) sa;
@@ -336,12 +467,29 @@
      * Returns the contents of 'path' as a byte array.
      */
     public static byte[] readFileAsByteArray(String path) throws IOException {
+        return readFileAsBytes(path).toByteArray();
+    }
+
+    /**
+     * Returns the contents of 'path' as a string. The contents are assumed to be UTF-8.
+     */
+    public static String readFileAsString(String path) throws IOException {
+        return readFileAsBytes(path).toString(Charsets.UTF_8);
+    }
+
+    private static UnsafeByteSequence readFileAsBytes(String path) throws IOException {
         RandomAccessFile f = null;
         try {
             f = new RandomAccessFile(path, "r");
-            byte[] buf = new byte[(int) f.length()];
-            f.readFully(buf);
-            return buf;
+            UnsafeByteSequence bytes = new UnsafeByteSequence((int) f.length());
+            byte[] buffer = new byte[8192];
+            while (true) {
+                int byteCount = f.read(buffer);
+                if (byteCount == -1) {
+                    return bytes;
+                }
+                bytes.write(buffer, 0, byteCount);
+            }
         } finally {
             IoUtils.closeQuietly(f);
         }
diff --git a/luni/src/main/java/libcore/io/Memory.java b/luni/src/main/java/libcore/io/Memory.java
index ddf0d52..9bf1acb 100644
--- a/luni/src/main/java/libcore/io/Memory.java
+++ b/luni/src/main/java/libcore/io/Memory.java
@@ -19,6 +19,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
 /**
@@ -137,7 +138,17 @@
         }
     }
 
-    public static native void memmove(int destAddress, int srcAddress, long byteCount);
+    /**
+     * Copies 'byteCount' bytes from the source to the destination. The objects are either
+     * instances of DirectByteBuffer or byte[]. The offsets in the byte[] case must include
+     * the Buffer.arrayOffset if the array came from a Buffer.array call. We could make this
+     * private and provide the four type-safe variants, but then ByteBuffer.put(ByteBuffer)
+     * would need to work out which to call based on whether the source and destination buffers
+     * are direct or not.
+     *
+     * @hide make type-safe before making public?
+     */
+    public static native void memmove(Object dstObject, int dstOffset, Object srcObject, int srcOffset, long byteCount);
 
     public static native byte peekByte(int address);
     public static native int peekInt(int address, boolean swap);
diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java
index 0f10584..7fceedc 100644
--- a/luni/src/main/java/libcore/io/Os.java
+++ b/luni/src/main/java/libcore/io/Os.java
@@ -36,15 +36,23 @@
     public StructStatFs fstatfs(FileDescriptor fd) throws ErrnoException;
     public void fsync(FileDescriptor fd) throws ErrnoException;
     public void ftruncate(FileDescriptor fd, long length) throws ErrnoException;
+    public String gai_strerror(int error);
+    public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException;
     public String getenv(String name);
+    /* TODO: break into getnameinfoHost and getnameinfoService? */
+    public String getnameinfo(InetAddress address, int flags) throws GaiException;
     public SocketAddress getsockname(FileDescriptor fd) throws ErrnoException;
     public int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException;
     public InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException;
     public int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException;
     public StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException;
     public StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException;
+    public String if_indextoname(int index);
+    public InetAddress inet_aton(String address);
+    public InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException;
     public int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException;
     public boolean isatty(FileDescriptor fd);
+    public void kill(int pid, int signal) throws ErrnoException;
     public void listen(FileDescriptor fd, int backlog) throws ErrnoException;
     public long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException;
     public void mincore(long address, long byteCount, byte[] vector) throws ErrnoException;
@@ -63,7 +71,13 @@
     public void remove(String path) throws ErrnoException;
     public void rename(String oldPath, String newPath) throws ErrnoException;
     public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException;
+    public void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+    public void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException;
     public void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+    public void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+    public void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException;
+    public void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException;
+    public void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException;
     public void shutdown(FileDescriptor fd, int how) throws ErrnoException;
     public FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException;
     public StructStat stat(String path) throws ErrnoException;
diff --git a/luni/src/main/java/libcore/io/OsConstants.java b/luni/src/main/java/libcore/io/OsConstants.java
index 616e60a..34a931c 100644
--- a/luni/src/main/java/libcore/io/OsConstants.java
+++ b/luni/src/main/java/libcore/io/OsConstants.java
@@ -39,12 +39,30 @@
     public static final int AF_INET6 = placeholder();
     public static final int AF_UNIX = placeholder();
     public static final int AF_UNSPEC = placeholder();
+    public static final int AI_ADDRCONFIG = placeholder();
+    public static final int AI_ALL = placeholder();
+    public static final int AI_CANONNAME = placeholder();
+    public static final int AI_NUMERICHOST = placeholder();
+    public static final int AI_NUMERICSERV = placeholder();
+    public static final int AI_PASSIVE = placeholder();
+    public static final int AI_V4MAPPED = placeholder();
     public static final int E2BIG = placeholder();
     public static final int EACCES = placeholder();
     public static final int EADDRINUSE = placeholder();
     public static final int EADDRNOTAVAIL = placeholder();
     public static final int EAFNOSUPPORT = placeholder();
     public static final int EAGAIN = placeholder();
+    public static final int EAI_AGAIN = placeholder();
+    public static final int EAI_BADFLAGS = placeholder();
+    public static final int EAI_FAIL = placeholder();
+    public static final int EAI_FAMILY = placeholder();
+    public static final int EAI_MEMORY = placeholder();
+    public static final int EAI_NODATA = placeholder();
+    public static final int EAI_NONAME = placeholder();
+    public static final int EAI_OVERFLOW = placeholder();
+    public static final int EAI_SERVICE = placeholder();
+    public static final int EAI_SOCKTYPE = placeholder();
+    public static final int EAI_SYSTEM = placeholder();
     public static final int EALREADY = placeholder();
     public static final int EBADF = placeholder();
     public static final int EBADMSG = placeholder();
@@ -139,6 +157,22 @@
     public static final int F_SETOWN = placeholder();
     public static final int F_UNLCK = placeholder();
     public static final int F_WRLCK = placeholder();
+    public static final int IFF_ALLMULTI = placeholder();
+    public static final int IFF_AUTOMEDIA = placeholder();
+    public static final int IFF_BROADCAST = placeholder();
+    public static final int IFF_DEBUG = placeholder();
+    public static final int IFF_DYNAMIC = placeholder();
+    public static final int IFF_LOOPBACK = placeholder();
+    public static final int IFF_MASTER = placeholder();
+    public static final int IFF_MULTICAST = placeholder();
+    public static final int IFF_NOARP = placeholder();
+    public static final int IFF_NOTRAILERS = placeholder();
+    public static final int IFF_POINTOPOINT = placeholder();
+    public static final int IFF_PORTSEL = placeholder();
+    public static final int IFF_PROMISC = placeholder();
+    public static final int IFF_RUNNING = placeholder();
+    public static final int IFF_SLAVE = placeholder();
+    public static final int IFF_UP = placeholder();
     public static final int IPPROTO_ICMP = placeholder();
     public static final int IPPROTO_IP = placeholder();
     public static final int IPPROTO_IPV6 = placeholder();
@@ -180,6 +214,11 @@
     public static final int MS_ASYNC = placeholder();
     public static final int MS_INVALIDATE = placeholder();
     public static final int MS_SYNC = placeholder();
+    public static final int NI_DGRAM = placeholder();
+    public static final int NI_NAMEREQD = placeholder();
+    public static final int NI_NOFQDN = placeholder();
+    public static final int NI_NUMERICHOST = placeholder();
+    public static final int NI_NUMERICSERV = placeholder();
     public static final int O_ACCMODE = placeholder();
     public static final int O_APPEND = placeholder();
     public static final int O_CREAT = placeholder();
@@ -202,11 +241,49 @@
     public static final int SHUT_RD = placeholder();
     public static final int SHUT_RDWR = placeholder();
     public static final int SHUT_WR = placeholder();
+    public static final int SIGABRT = placeholder();
+    public static final int SIGALRM = placeholder();
+    public static final int SIGBUS = placeholder();
+    public static final int SIGCHLD = placeholder();
+    public static final int SIGCONT = placeholder();
+    public static final int SIGFPE = placeholder();
+    public static final int SIGHUP = placeholder();
+    public static final int SIGILL = placeholder();
+    public static final int SIGINT = placeholder();
+    public static final int SIGIO = placeholder();
+    public static final int SIGKILL = placeholder();
+    public static final int SIGPIPE = placeholder();
+    public static final int SIGPROF = placeholder();
+    public static final int SIGPWR = placeholder();
+    public static final int SIGQUIT = placeholder();
+    public static final int SIGRTMAX = placeholder();
+    public static final int SIGRTMIN = placeholder();
+    public static final int SIGSEGV = placeholder();
+    public static final int SIGSTKFLT = placeholder();
+    public static final int SIGSTOP = placeholder();
+    public static final int SIGSYS = placeholder();
+    public static final int SIGTERM = placeholder();
+    public static final int SIGTRAP = placeholder();
+    public static final int SIGTSTP = placeholder();
+    public static final int SIGTTIN = placeholder();
+    public static final int SIGTTOU = placeholder();
+    public static final int SIGURG = placeholder();
+    public static final int SIGUSR1 = placeholder();
+    public static final int SIGUSR2 = placeholder();
+    public static final int SIGVTALRM = placeholder();
+    public static final int SIGWINCH = placeholder();
+    public static final int SIGXCPU = placeholder();
+    public static final int SIGXFSZ = placeholder();
+    public static final int SIOCGIFADDR = placeholder();
+    public static final int SIOCGIFBRDADDR = placeholder();
+    public static final int SIOCGIFDSTADDR = placeholder();
+    public static final int SIOCGIFNETMASK = placeholder();
     public static final int SOCK_DGRAM = placeholder();
     public static final int SOCK_RAW = placeholder();
     public static final int SOCK_SEQPACKET = placeholder();
     public static final int SOCK_STREAM = placeholder();
     public static final int SOL_SOCKET = placeholder();
+    public static final int SO_BINDTODEVICE = placeholder();
     public static final int SO_BROADCAST = placeholder();
     public static final int SO_DEBUG = placeholder();
     public static final int SO_DONTROUTE = placeholder();
@@ -348,6 +425,43 @@
     public static final int _SC_XOPEN_VERSION = placeholder();
     public static final int _SC_XOPEN_XCU_VERSION = placeholder();
 
+    public static String gaiName(int error) {
+        if (error == EAI_AGAIN) {
+            return "EAI_AGAIN";
+        }
+        if (error == EAI_BADFLAGS) {
+            return "EAI_BADFLAGS";
+        }
+        if (error == EAI_FAIL) {
+            return "EAI_FAIL";
+        }
+        if (error == EAI_FAMILY) {
+            return "EAI_FAMILY";
+        }
+        if (error == EAI_MEMORY) {
+            return "EAI_MEMORY";
+        }
+        if (error == EAI_NODATA) {
+            return "EAI_NODATA";
+        }
+        if (error == EAI_NONAME) {
+            return "EAI_NONAME";
+        }
+        if (error == EAI_OVERFLOW) {
+            return "EAI_OVERFLOW";
+        }
+        if (error == EAI_SERVICE) {
+            return "EAI_SERVICE";
+        }
+        if (error == EAI_SOCKTYPE) {
+            return "EAI_SOCKTYPE";
+        }
+        if (error == EAI_SYSTEM) {
+            return "EAI_SYSTEM";
+        }
+        return null;
+    }
+
     public static String errnoName(int errno) {
         if (errno == E2BIG) {
             return "E2BIG";
diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java
index 6c49e51..4e23dc0 100644
--- a/luni/src/main/java/libcore/io/Posix.java
+++ b/luni/src/main/java/libcore/io/Posix.java
@@ -39,15 +39,22 @@
     public native StructStatFs fstatfs(FileDescriptor fd) throws ErrnoException;
     public native void fsync(FileDescriptor fd) throws ErrnoException;
     public native void ftruncate(FileDescriptor fd, long length) throws ErrnoException;
+    public native String gai_strerror(int error);
+    public native InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException;
     public native String getenv(String name);
+    public native String getnameinfo(InetAddress address, int flags) throws GaiException;
     public native SocketAddress getsockname(FileDescriptor fd) throws ErrnoException;
     public native int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException;
     public native InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException;
     public native int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException;
     public native StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException;
     public native StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException;
+    public native InetAddress inet_aton(String address);
+    public native String if_indextoname(int index);
+    public native InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException;
     public native int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException;
     public native boolean isatty(FileDescriptor fd);
+    public native void kill(int pid, int signal) throws ErrnoException;
     public native void listen(FileDescriptor fd, int backlog) throws ErrnoException;
     public native long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException;
     public native void mincore(long address, long byteCount, byte[] vector) throws ErrnoException;
@@ -62,17 +69,27 @@
     public native StructStat lstat(String path) throws ErrnoException;
     public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
         if (buffer.isDirect()) {
-            return readDirectBuffer(fd, buffer, buffer.position(), buffer.remaining());
+            return readBytes(fd, buffer, buffer.position(), buffer.remaining());
+        } else {
+            return readBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
         }
-        return read(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
     }
-    private native int readDirectBuffer(FileDescriptor fd, ByteBuffer buffer, int position, int remaining) throws ErrnoException;
-    public native int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
+    public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+        // This indirection isn't strictly necessary, but ensures that our public interface is type safe.
+        return readBytes(fd, bytes, byteOffset, byteCount);
+    }
+    private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException;
     public native int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
     public native void remove(String path) throws ErrnoException;
     public native void rename(String oldPath, String newPath) throws ErrnoException;
     public native long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException;
+    public native void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+    public native void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException;
     public native void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+    public native void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+    public native void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException;
+    public native void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException;
+    public native void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException;
     public native void shutdown(FileDescriptor fd, int how) throws ErrnoException;
     public native FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException;
     public native StructStat stat(String path) throws ErrnoException;
@@ -83,11 +100,15 @@
     public native StructUtsname uname() throws ErrnoException;
     public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
         if (buffer.isDirect()) {
-            return writeDirectBuffer(fd, buffer, buffer.position(), buffer.remaining());
+            return writeBytes(fd, buffer, buffer.position(), buffer.remaining());
+        } else {
+            return writeBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
         }
-        return write(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
     }
-    private native int writeDirectBuffer(FileDescriptor fd, ByteBuffer buffer, int position, int remaining) throws ErrnoException;
-    public native int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
+    public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+        // This indirection isn't strictly necessary, but ensures that our public interface is type safe.
+        return writeBytes(fd, bytes, byteOffset, byteCount);
+    }
+    private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException;
     public native int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
 }
diff --git a/luni/src/main/java/libcore/io/StructAddrinfo.java b/luni/src/main/java/libcore/io/StructAddrinfo.java
new file mode 100644
index 0000000..8c8181d
--- /dev/null
+++ b/luni/src/main/java/libcore/io/StructAddrinfo.java
@@ -0,0 +1,51 @@
+/*
+ * 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 libcore.io;
+
+import java.net.InetAddress;
+
+/**
+ * Information returned/taken by getaddrinfo(3). Corresponds to C's {@code struct addrinfo} from
+ * <a href="http://pubs.opengroup.org/onlinepubs/009695399/basedefs/netdb.h.html">&lt;netdb.h&gt;</a>
+ *
+ * TODO: we currently only _take_ a StructAddrinfo; getaddrinfo returns an InetAddress[].
+ */
+public final class StructAddrinfo {
+    /** Flags describing the kind of lookup to be done. (Such as AI_ADDRCONFIG.) */
+    public int ai_flags;
+
+    /** Desired address family for results. (Such as AF_INET6 for IPv6. AF_UNSPEC means "any".) */
+    public int ai_family;
+
+    /** Socket type. (Such as SOCK_DGRAM. 0 means "any".) */
+    public int ai_socktype;
+
+    /** Protocol. (Such as IPPROTO_IPV6 IPv6. 0 means "any".) */
+    public int ai_protocol;
+
+    /** Address length. (Not useful in Java.) */
+    // public int ai_addrlen;
+
+    /** Address. */
+    public InetAddress ai_addr;
+
+    /** Canonical name of service location (if AI_CANONNAME provided in ai_flags). */
+    // public String ai_canonname;
+
+    /** Next element in linked list. */
+    public StructAddrinfo ai_next;
+}
diff --git a/luni/src/main/java/libcore/io/StructFlock.java b/luni/src/main/java/libcore/io/StructFlock.java
index 6d63064..11c29df 100644
--- a/luni/src/main/java/libcore/io/StructFlock.java
+++ b/luni/src/main/java/libcore/io/StructFlock.java
@@ -16,8 +16,6 @@
 
 package libcore.io;
 
-import static libcore.io.OsConstants.*;
-
 /**
  * Information returned/taken by fcntl(2) F_GETFL and F_SETFL. Corresponds to C's
  * {@code struct flock} from
diff --git a/luni/src/main/java/libcore/io/StructGroupReq.java b/luni/src/main/java/libcore/io/StructGroupReq.java
new file mode 100644
index 0000000..0bdf783
--- /dev/null
+++ b/luni/src/main/java/libcore/io/StructGroupReq.java
@@ -0,0 +1,36 @@
+/*
+ * 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 libcore.io;
+
+import java.net.InetAddress;
+
+/**
+ * Corresponds to C's {@code struct group_req}.
+ */
+public final class StructGroupReq {
+    public final int gr_interface;
+    public final InetAddress gr_group;
+
+    public StructGroupReq(int gr_interface, InetAddress gr_group) {
+        this.gr_interface = gr_interface;
+        this.gr_group = gr_group;
+    }
+
+    @Override public String toString() {
+        return "StructGroupReq[gr_interface=" + gr_interface + ",gr_group=" + gr_group + "]";
+    }
+}
diff --git a/luni/src/main/java/libcore/io/StructStat.java b/luni/src/main/java/libcore/io/StructStat.java
index 9d1a877..05ecca7 100644
--- a/luni/src/main/java/libcore/io/StructStat.java
+++ b/luni/src/main/java/libcore/io/StructStat.java
@@ -16,8 +16,6 @@
 
 package libcore.io;
 
-import static libcore.io.OsConstants.*;
-
 /**
  * File information returned by fstat(2), lstat(2), and stat(2). Corresponds to C's
  * {@code struct stat} from
diff --git a/luni/src/main/java/libcore/io/StructStatFs.java b/luni/src/main/java/libcore/io/StructStatFs.java
index f853c9a..603dc86 100644
--- a/luni/src/main/java/libcore/io/StructStatFs.java
+++ b/luni/src/main/java/libcore/io/StructStatFs.java
@@ -16,8 +16,6 @@
 
 package libcore.io;
 
-import static libcore.io.OsConstants.*;
-
 /**
  * File information returned by fstatfs(2) and statfs(2).
  *
diff --git a/luni/src/main/java/libcore/io/StructUtsname.java b/luni/src/main/java/libcore/io/StructUtsname.java
index 4bbde16..e6a8e42 100644
--- a/luni/src/main/java/libcore/io/StructUtsname.java
+++ b/luni/src/main/java/libcore/io/StructUtsname.java
@@ -16,11 +16,9 @@
 
 package libcore.io;
 
-import static libcore.io.OsConstants.*;
-
 /**
  * Information returned by uname(2). Corresponds to C's
- * {@code struct utname} from
+ * {@code struct utsname} from
  * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_utsname.h.html">&lt;sys/utsname.h&gt;</a>
  */
 public final class StructUtsname {
diff --git a/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java b/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
index 40bc554..755bc96 100644
--- a/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
+++ b/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
@@ -34,15 +34,15 @@
  */
 abstract class AbstractHttpInputStream extends InputStream {
     protected final InputStream in;
-    protected final HttpURLConnectionImpl httpURLConnection;
+    protected final HttpEngine httpEngine;
     private final CacheRequest cacheRequest;
     private final OutputStream cacheBody;
     protected boolean closed;
 
-    AbstractHttpInputStream(InputStream in, HttpURLConnectionImpl httpURLConnection,
+    AbstractHttpInputStream(InputStream in, HttpEngine httpEngine,
             CacheRequest cacheRequest) throws IOException {
         this.in = in;
-        this.httpURLConnection = httpURLConnection;
+        this.httpEngine = httpEngine;
 
         OutputStream cacheBody = cacheRequest != null ? cacheRequest.getBody() : null;
 
@@ -83,7 +83,7 @@
         if (cacheRequest != null) {
             cacheBody.close();
         }
-        httpURLConnection.releaseSocket(reuseSocket);
+        httpEngine.releaseSocket(reuseSocket);
     }
 
     /**
@@ -102,6 +102,6 @@
         if (cacheRequest != null) {
             cacheRequest.abort();
         }
-        httpURLConnection.releaseSocket(false);
+        httpEngine.releaseSocket(false);
     }
 }
diff --git a/luni/src/main/java/libcore/net/http/ChunkedInputStream.java b/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
index 7a53728..274f03c 100644
--- a/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
+++ b/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
@@ -31,8 +31,8 @@
     private boolean hasMoreChunks = true;
 
     ChunkedInputStream(InputStream is, CacheRequest cacheRequest,
-            HttpURLConnectionImpl httpURLConnection) throws IOException {
-        super(is, httpURLConnection, cacheRequest);
+            HttpEngine httpEngine) throws IOException {
+        super(is, httpEngine, cacheRequest);
     }
 
     @Override public int read(byte[] buffer, int offset, int count) throws IOException {
@@ -73,9 +73,9 @@
     private void readChunkSize() throws IOException {
         // read the suffix of the previous chunk
         if (bytesRemainingInChunk != NO_CHUNK_YET) {
-            HttpURLConnectionImpl.readLine(in);
+            HttpEngine.readLine(in);
         }
-        String chunkSizeString = HttpURLConnectionImpl.readLine(in);
+        String chunkSizeString = HttpEngine.readLine(in);
         int index = chunkSizeString.indexOf(";");
         if (index != -1) {
             chunkSizeString = chunkSizeString.substring(0, index);
@@ -87,7 +87,7 @@
         }
         if (bytesRemainingInChunk == 0) {
             hasMoreChunks = false;
-            httpURLConnection.readHeaders(); // actually trailers!
+            httpEngine.readTrailers();
             endOfInput(true);
         }
     }
diff --git a/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java b/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java
index 3596cd1..1091af7 100644
--- a/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java
+++ b/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java
@@ -28,8 +28,8 @@
     private int bytesRemaining;
 
     public FixedLengthInputStream(InputStream is, CacheRequest cacheRequest,
-            HttpURLConnectionImpl httpURLConnection, int length) throws IOException {
-        super(is, httpURLConnection, cacheRequest);
+            HttpEngine httpEngine, int length) throws IOException {
+        super(is, httpEngine, cacheRequest);
         bytesRemaining = length;
         if (bytesRemaining == 0) {
             endOfInput(true);
diff --git a/luni/src/main/java/libcore/net/http/HeaderParser.java b/luni/src/main/java/libcore/net/http/HeaderParser.java
new file mode 100644
index 0000000..31e09f9
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/HeaderParser.java
@@ -0,0 +1,111 @@
+/*
+ * 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 libcore.net.http;
+
+final class HeaderParser {
+
+    public interface CacheControlHandler {
+        void handle(String directive, String parameter);
+    }
+
+    /**
+     * Parse a comma-separated list of cache control header values.
+     */
+    public static void parseCacheControl(String value, CacheControlHandler handler) {
+        int pos = 0;
+        while (pos < value.length()) {
+            int tokenStart = pos;
+            pos = skipUntil(value, pos, "=,");
+            String directive = value.substring(tokenStart, pos).trim();
+
+            if (pos == value.length() || value.charAt(pos) == ',') {
+                pos++; // consume ',' (if necessary)
+                handler.handle(directive, null);
+                continue;
+            }
+
+            pos++; // consume '='
+            pos = skipWhitespace(value, pos);
+
+            String parameter;
+
+            // quoted string
+            if (pos < value.length() && value.charAt(pos) == '\"') {
+                pos++; // consume '"' open quote
+                int parameterStart = pos;
+                pos = skipUntil(value, pos, "\"");
+                parameter = value.substring(parameterStart, pos);
+                pos++; // consume '"' close quote (if necessary)
+
+            // unquoted string
+            } else {
+                int parameterStart = pos;
+                pos = skipUntil(value, pos, ",");
+                parameter = value.substring(parameterStart, pos).trim();
+            }
+
+            handler.handle(directive, parameter);
+        }
+    }
+
+    /**
+     * 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.
+     */
+    private 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.
+     */
+    private 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
+     * -1 if it cannot be parsed.
+     */
+    public static int parseSeconds(String value) {
+        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 -1;
+        }
+    }
+}
diff --git a/luni/src/main/java/libcore/net/http/HttpConnection.java b/luni/src/main/java/libcore/net/http/HttpConnection.java
index 13e0971..b5441e6 100644
--- a/luni/src/main/java/libcore/net/http/HttpConnection.java
+++ b/luni/src/main/java/libcore/net/http/HttpConnection.java
@@ -24,11 +24,13 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Proxy;
+import java.net.ProxySelector;
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.SocketTimeoutException;
 import java.net.URI;
+import java.util.List;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
@@ -45,7 +47,7 @@
  * <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
  * which isn't so much a connection as a single request/response pair.
  */
-public final class HttpConnection {
+final class HttpConnection {
     private final Address address;
 
     private final Socket socket;
@@ -85,6 +87,47 @@
         this.socket = socketCandidate;
     }
 
+    public static HttpConnection connect(URI uri, Proxy proxy, boolean requiresTunnel,
+            int connectTimeout) throws IOException {
+        /*
+         * Try an explicitly-specified proxy.
+         */
+        if (proxy != null) {
+            Address address = (proxy.type() == Proxy.Type.DIRECT)
+                    ? new Address(uri)
+                    : new Address(uri, proxy, requiresTunnel);
+            return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
+        }
+
+        /*
+         * Try connecting to each of the proxies provided by the ProxySelector
+         * until a connection succeeds.
+         */
+        ProxySelector selector = ProxySelector.getDefault();
+        List<Proxy> proxyList = selector.select(uri);
+        if (proxyList != null) {
+            for (Proxy selectedProxy : proxyList) {
+                if (selectedProxy.type() == Proxy.Type.DIRECT) {
+                    // the same as NO_PROXY
+                    // TODO: if the selector recommends a direct connection, attempt that?
+                    continue;
+                }
+                try {
+                    Address address = new Address(uri, selectedProxy, requiresTunnel);
+                    return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
+                } catch (IOException e) {
+                    // failed to connect, tell it to the selector
+                    selector.connectFailed(uri, selectedProxy.address(), e);
+                }
+            }
+        }
+
+        /*
+         * Try a direct connection. If this fails, this method will throw.
+         */
+        return HttpConnectionPool.INSTANCE.get(new Address(uri), connectTimeout);
+    }
+
     public void closeSocketAndStreams() {
         IoUtils.closeQuietly(sslOutputStream);
         IoUtils.closeQuietly(sslInputStream);
@@ -280,6 +323,10 @@
             this.socketPort = proxySocketAddress.getPort();
         }
 
+        public Proxy getProxy() {
+            return proxy;
+        }
+
         @Override public boolean equals(Object other) {
             if (other instanceof Address) {
                 Address that = (Address) other;
diff --git a/luni/src/main/java/libcore/net/http/HttpConnectionPool.java b/luni/src/main/java/libcore/net/http/HttpConnectionPool.java
index 0fb5261..7b7f74c 100644
--- a/luni/src/main/java/libcore/net/http/HttpConnectionPool.java
+++ b/luni/src/main/java/libcore/net/http/HttpConnectionPool.java
@@ -36,7 +36,7 @@
  * are changed. This assumes that the applications that set these parameters do
  * so before making HTTP connections, and that this class is initialized lazily.
  */
-public final class HttpConnectionPool {
+final class HttpConnectionPool {
 
     public static final HttpConnectionPool INSTANCE = new HttpConnectionPool();
 
diff --git a/luni/src/main/java/libcore/net/http/HttpDate.java b/luni/src/main/java/libcore/net/http/HttpDate.java
new file mode 100644
index 0000000..a41cf81
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/HttpDate.java
@@ -0,0 +1,91 @@
+/*
+ * 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 libcore.net.http;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Best-effort parser for HTTP dates.
+ */
+public final class HttpDate {
+
+    /**
+     * 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() {
+            DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+            rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
+            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_FORMATS = new String[] {
+            /* This list comes from  {@code org.apache.http.impl.cookie.BrowserCompatSpec}. */
+            "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 1036
+            "EEE MMM d HH:mm:ss yyyy", // ANSI C asctime()
+            "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",
+    };
+
+    /**
+     * Returns the date for {@code value}. Returns null if the value couldn't be
+     * parsed.
+     */
+    public static Date parse(String value) {
+        try {
+            return STANDARD_DATE_FORMAT.get().parse(value);
+        } catch (ParseException ignore) {
+        }
+        for (String formatString : BROWSER_COMPATIBLE_DATE_FORMATS) {
+            try {
+                return new SimpleDateFormat(formatString, Locale.US).parse(value);
+            } catch (ParseException ignore) {
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the string for {@code value}.
+     */
+    public static String format(Date value) {
+        return STANDARD_DATE_FORMAT.get().format(value);
+    }
+}
diff --git a/luni/src/main/java/libcore/net/http/HttpEngine.java b/luni/src/main/java/libcore/net/http/HttpEngine.java
new file mode 100644
index 0000000..d023cbc
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/HttpEngine.java
@@ -0,0 +1,852 @@
+/*
+ *  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 libcore.net.http;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.CookieHandler;
+import java.net.HttpURLConnection;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.net.ResponseCache;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.Charsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+import libcore.util.EmptyArray;
+
+/**
+ * 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.
+ *
+ * <p>This class may hold a socket connection that needs to be released or
+ * recycled. By default, this socket connection is released to the pool when the
+ * last byte of the response is consumed. To prevent the connection from being
+ * released to the pool, use {@link #dontReleaseSocketToPool()}.
+ */
+public class HttpEngine {
+    private static final CacheResponse BAD_GATEWAY_RESPONSE = new CacheResponse() {
+        @Override public Map<String, List<String>> getHeaders() throws IOException {
+            Map<String, List<String>> result = new HashMap<String, List<String>>();
+            result.put(null, Collections.singletonList("HTTP/1.1 502 Bad Gateway"));
+            // TODO: other required fields?
+            return result;
+        }
+        @Override public InputStream getBody() throws IOException {
+            return new ByteArrayInputStream(EmptyArray.BYTE);
+        }
+    };
+
+    /**
+     * The maximum number of bytes to buffer when sending headers and a request
+     * body. When the headers and body can be sent in a single write, the
+     * request completes sooner. In one WiFi benchmark, using a large enough
+     * buffer sped up some uploads by half.
+     */
+    private static final int MAX_REQUEST_BUFFER_LENGTH = 32768;
+
+    public static final int DEFAULT_CHUNK_LENGTH = 1024;
+
+    public static final String OPTIONS = "OPTIONS";
+    public static final String GET = "GET";
+    public static final String HEAD = "HEAD";
+    public static final String POST = "POST";
+    public static final String PUT = "PUT";
+    public static final String DELETE = "DELETE";
+    public static final String TRACE = "TRACE";
+    public static final String CONNECT = "CONNECT";
+
+    public static final int HTTP_CONTINUE = 100;
+
+    /**
+     * HTTP 1.1 doesn't specify how many redirects to follow, but HTTP/1.0
+     * recommended 5. http://www.w3.org/Protocols/HTTP/1.0/spec.html#Code3xx
+     */
+    public static final int MAX_REDIRECTS = 5;
+
+    protected final HttpURLConnectionImpl policy;
+
+    protected final String method;
+
+    private ResponseSource responseSource;
+
+    protected HttpConnection connection;
+    private InputStream socketIn;
+    private OutputStream socketOut;
+
+    /**
+     * This stream buffers the request headers and the request body when their
+     * combined size is less than MAX_REQUEST_BUFFER_LENGTH. By combining them
+     * we can save socket writes, which in turn saves a packet transmission.
+     * This is socketOut if the request size is large or unknown.
+     */
+    private OutputStream requestOut;
+    private AbstractHttpOutputStream requestBodyOut;
+
+    private InputStream responseBodyIn;
+
+    private final ResponseCache responseCache = ResponseCache.getDefault();
+    private CacheResponse cacheResponse;
+    private CacheRequest cacheRequest;
+
+    /** The time when the request headers were written, or -1 if they haven't been written yet. */
+    private 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;
+
+    boolean sendChunked;
+
+    /**
+     * The version this client will use. Either 0 for HTTP/1.0, or 1 for
+     * HTTP/1.1. Upon receiving a non-HTTP/1.1 response, this client
+     * automatically sets its version to HTTP/1.0.
+     */
+    // TODO: is HTTP minor version tracked across HttpEngines?
+    private int httpMinorVersion = 1; // Assume HTTP/1.1
+
+    private final URI uri;
+
+    private final RawHeaders rawRequestHeaders;
+
+    /** Null until a response is received from the network or the cache */
+    private RawHeaders rawResponseHeaders;
+
+    /*
+     * The cache response currently being validated on a conditional get. Null
+     * if the cached response doesn't exist or doesn't need validation. If the
+     * conditional get succeeds, these will be used for the response headers and
+     * body. If it fails, these be closed and set to null.
+     */
+    private ResponseHeaders responseHeadersToValidate;
+    private InputStream responseBodyToValidate;
+
+    /**
+     * True if the socket connection should be released to the connection pool
+     * when the response has been fully read.
+     */
+    private boolean dontReleaseSocketToPool;
+
+    /**
+     * @param connection the connection used for an intermediate response
+     *     immediately prior to this request/response pair, such as a same-host
+     *     redirect. This engine assumes ownership of the connection and must
+     *     release it when it is unneeded.
+     */
+    public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
+            HttpConnection connection, RetryableOutputStream requestBodyOut) throws IOException {
+        if (policy.getDoOutput()) {
+            if (method == GET) {
+                // they are requesting a stream to write to. This implies a POST method
+                method = POST;
+            } else if (method != PUT && method != POST) {
+                // If the request method is neither PUT or POST, then you're not writing
+                throw new ProtocolException(method + " does not support writing");
+            }
+        }
+
+        this.policy = policy;
+        this.method = method;
+        this.rawRequestHeaders = new RawHeaders(requestHeaders);
+        this.connection = connection;
+        this.requestBodyOut = requestBodyOut;
+
+        try {
+            uri = policy.getURL().toURILenient();
+        } catch (URISyntaxException e) {
+            throw new IOException(e);
+        }
+    }
+
+    public final void dontReleaseSocketToPool() {
+        this.dontReleaseSocketToPool = true;
+    }
+
+    /**
+     * 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.
+     */
+    public final void sendRequest() throws IOException {
+        if (responseSource != null) {
+            return;
+        }
+
+        prepareRawRequestHeaders();
+        RequestHeaders cacheRequestHeaders = new RequestHeaders(uri, rawRequestHeaders);
+        initResponseSource(cacheRequestHeaders);
+
+        /*
+         * The raw response source may require the network, but the request
+         * headers may forbid network use. In that case, dispose of the network
+         * response and use a BAD_GATEWAY response instead.
+         */
+        if (cacheRequestHeaders.onlyIfCached && responseSource.requiresConnection()) {
+            if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
+                this.responseHeadersToValidate = null;
+                IoUtils.closeQuietly(responseBodyToValidate);
+                this.responseBodyToValidate = null;
+            }
+            this.responseSource = ResponseSource.CACHE;
+            this.cacheResponse = BAD_GATEWAY_RESPONSE;
+            setResponse(RawHeaders.fromMultimap(cacheResponse.getHeaders()),
+                    cacheResponse.getBody());
+        }
+
+        if (responseSource.requiresConnection()) {
+            sendSocketRequest();
+        } else {
+            // TODO: release 'connection' if it is non-null
+        }
+    }
+
+    /**
+     * Initialize the source for this response. It may be corrected later if the
+     * request headers forbids network use.
+     */
+    private void initResponseSource(RequestHeaders cacheRequestHeaders) throws IOException {
+        responseSource = ResponseSource.NETWORK;
+        if (!policy.getUseCaches() || responseCache == null) {
+            return;
+        }
+
+        CacheResponse candidate = responseCache.get(uri, method, rawRequestHeaders.toMultimap());
+        if (candidate == null || !acceptCacheResponseType(candidate)) {
+            return;
+        }
+        Map<String, List<String>> responseHeaders = candidate.getHeaders();
+        if (responseHeaders == null) {
+            return;
+        }
+        InputStream cacheBodyIn = candidate.getBody(); // must be closed
+        if (cacheBodyIn == null) {
+            return;
+        }
+
+        RawHeaders headers = RawHeaders.fromMultimap(responseHeaders);
+        ResponseHeaders cacheResponseHeaders = new ResponseHeaders(uri, headers);
+        long now = System.currentTimeMillis();
+        this.responseSource = cacheResponseHeaders.chooseResponseSource(now, cacheRequestHeaders);
+        if (responseSource == ResponseSource.CACHE) {
+            this.cacheResponse = candidate;
+            setResponse(headers, cacheBodyIn);
+        } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
+            this.cacheResponse = candidate;
+            this.responseHeadersToValidate = cacheResponseHeaders;
+            this.responseBodyToValidate = cacheBodyIn;
+        } else if (responseSource == ResponseSource.NETWORK) {
+            IoUtils.closeQuietly(cacheBodyIn);
+        } else {
+            throw new AssertionError();
+        }
+    }
+
+    private void sendSocketRequest() throws IOException {
+        if (connection == null) {
+            connect();
+        }
+
+        if (socketOut != null || requestOut != null || socketIn != null) {
+            throw new IllegalStateException();
+        }
+
+        socketOut = connection.getOutputStream();
+        requestOut = socketOut;
+        socketIn = connection.getInputStream();
+
+        if (policy.getDoOutput() && method != CONNECT) {
+            initRequestBodyOut();
+        }
+    }
+
+    /**
+     * Connect to the origin server either directly or via a proxy.
+     */
+    protected void connect() throws IOException {
+        if (connection == null) {
+            connection = openSocketConnection();
+        }
+    }
+
+    protected final HttpConnection openSocketConnection() throws IOException {
+        HttpConnection result = HttpConnection.connect(
+                uri, policy.getProxy(), requiresTunnel(), policy.getConnectTimeout());
+        Proxy proxy = result.getAddress().getProxy();
+        if (proxy != null) {
+            policy.setProxy(proxy);
+        }
+        result.setSoTimeout(policy.getReadTimeout());
+        return result;
+    }
+
+    protected void initRequestBodyOut() throws IOException {
+        int contentLength = -1;
+        String contentLengthString = rawRequestHeaders.get("Content-Length");
+        if (contentLengthString != null) {
+            contentLength = Integer.parseInt(contentLengthString);
+        }
+
+        String encoding = rawRequestHeaders.get("Transfer-Encoding");
+        int chunkLength = policy.getChunkLength();
+        if (chunkLength > 0 || "chunked".equalsIgnoreCase(encoding)) {
+            sendChunked = true;
+            contentLength = -1;
+            if (chunkLength == -1) {
+                chunkLength = DEFAULT_CHUNK_LENGTH;
+            }
+        }
+
+        if (socketOut == null) {
+            throw new IllegalStateException("No socket to write to; was a POST cached?");
+        }
+
+        if (httpMinorVersion == 0) {
+            sendChunked = false;
+        }
+
+        int fixedContentLength = policy.getFixedContentLength();
+        if (requestBodyOut != null) {
+            // request body was already initialized by the predecessor HTTP engine
+        } else if (fixedContentLength != -1) {
+            writeRequestHeaders(fixedContentLength);
+            requestBodyOut = new FixedLengthOutputStream(requestOut, fixedContentLength);
+        } else if (sendChunked) {
+            writeRequestHeaders(-1);
+            requestBodyOut = new ChunkedOutputStream(requestOut, chunkLength);
+        } else if (contentLength != -1) {
+            writeRequestHeaders(contentLength);
+            requestBodyOut = new RetryableOutputStream(contentLength);
+        } else {
+            requestBodyOut = new RetryableOutputStream();
+        }
+    }
+
+    /**
+     * @param body the response body, or null if it doesn't exist or isn't
+     *     available.
+     */
+    private void setResponse(RawHeaders headers, InputStream body) throws IOException {
+        if (this.responseBodyIn != null) {
+            throw new IllegalStateException();
+        }
+        this.rawResponseHeaders = headers;
+        this.httpMinorVersion = rawResponseHeaders.getHttpMinorVersion();
+        if (body != null) {
+            initContentStream(body);
+        }
+    }
+
+    /**
+     * Returns the request body or null if this request doesn't have a body.
+     */
+    public final OutputStream getRequestBody() {
+        if (responseSource == null) {
+            throw new IllegalStateException();
+        }
+        return requestBodyOut;
+    }
+
+    public final boolean hasResponse() {
+        return rawResponseHeaders != null;
+    }
+
+    public final RawHeaders getRequestHeaders() {
+        return rawRequestHeaders;
+    }
+
+    public final RawHeaders getResponseHeaders() {
+        if (rawResponseHeaders == null) {
+            throw new IllegalStateException();
+        }
+        return rawResponseHeaders;
+    }
+
+    public final InputStream getResponseBody() {
+        if (rawResponseHeaders == null) {
+            throw new IllegalStateException();
+        }
+        return responseBodyIn;
+    }
+
+    public final CacheResponse getCacheResponse() {
+        if (rawResponseHeaders == null) {
+            throw new IllegalStateException();
+        }
+        return cacheResponse;
+    }
+
+    public final HttpConnection getConnection() {
+        return connection;
+    }
+
+    /**
+     * Returns true if {@code cacheResponse} is of the right type. This
+     * condition is necessary but not sufficient for the cached response to
+     * be used.
+     */
+    protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
+        return true;
+    }
+
+    private void maybeCache() throws IOException {
+        // Are we caching at all?
+        if (!policy.getUseCaches() || responseCache == null) {
+            return;
+        }
+
+        // Should we cache this response for this request?
+        RequestHeaders requestCacheHeaders = new RequestHeaders(uri, rawRequestHeaders);
+        ResponseHeaders responseCacheHeaders = new ResponseHeaders(uri, rawResponseHeaders);
+        if (!responseCacheHeaders.isCacheable(requestCacheHeaders)) {
+            return;
+        }
+
+        // Offer this request to the cache.
+        cacheRequest = responseCache.put(uri, getHttpConnectionToCache());
+    }
+
+    protected HttpURLConnection getHttpConnectionToCache() {
+        return policy;
+    }
+
+    /**
+     * Releases this connection so that it may be either reused or closed.
+     */
+    public final void releaseSocket(boolean reuseSocket) {
+        // we cannot recycle sockets that have incomplete output.
+        if (requestBodyOut != null && !requestBodyOut.closed) {
+            reuseSocket = false;
+        }
+
+        // if the headers specify that the connection shouldn't be reused, don't reuse it
+        if (hasConnectionCloseHeaders()) {
+            reuseSocket = false;
+        }
+
+        /*
+         * Don't return the socket to the connection pool if this is an
+         * intermediate response; we're going to use it again right away.
+         */
+        if (dontReleaseSocketToPool && reuseSocket) {
+            return;
+        }
+
+        if (connection != null) {
+            if (reuseSocket) {
+                HttpConnectionPool.INSTANCE.recycle(connection);
+            } else {
+                connection.closeSocketAndStreams();
+            }
+            connection = null;
+        }
+
+        /*
+         * Ensure that no further I/O attempts from this instance make their way
+         * to the underlying connection (which may get recycled).
+         */
+        socketOut = null;
+        socketIn = null;
+        requestOut = null;
+    }
+
+    /**
+     * Consume the response body so the socket connection can be used for new
+     * message pairs.
+     */
+    public final void discardResponseBody() throws IOException {
+        if (responseBodyIn != null) {
+            if (!(responseBodyIn instanceof UnknownLengthHttpInputStream)) {
+                // skip the response so that the connection may be reused for the retry
+                Streams.skipAll(responseBodyIn);
+            }
+            responseBodyIn.close();
+        }
+    }
+
+    private void initContentStream(InputStream transferStream) throws IOException {
+        if (transparentGzip
+                && "gzip".equalsIgnoreCase(rawResponseHeaders.get("Content-Encoding"))) {
+            /*
+             * If the response was transparently gzipped, remove the gzip header field
+             * so clients don't double decompress. http://b/3009828
+             */
+            rawResponseHeaders.removeAll("Content-Encoding");
+            responseBodyIn = new GZIPInputStream(transferStream);
+        } else {
+            responseBodyIn = transferStream;
+        }
+    }
+
+    private InputStream getTransferStream() throws IOException {
+        if (!hasResponseBody()) {
+            return new FixedLengthInputStream(socketIn, cacheRequest, this, 0);
+        }
+
+        if ("chunked".equalsIgnoreCase(rawResponseHeaders.get("Transfer-Encoding"))) {
+            return new ChunkedInputStream(socketIn, cacheRequest, this);
+        }
+
+        String contentLength = rawResponseHeaders.get("Content-Length");
+        if (contentLength != null) {
+            try {
+                int length = Integer.parseInt(contentLength);
+                return new FixedLengthInputStream(socketIn, cacheRequest, this, length);
+            } catch (NumberFormatException ignored) {
+            }
+        }
+
+        /*
+         * Wrap the input stream from the HttpConnection (rather than
+         * just returning "socketIn" directly here), so that we can control
+         * its use after the reference escapes.
+         */
+        return new UnknownLengthHttpInputStream(socketIn, cacheRequest, this);
+    }
+
+    /**
+     * Returns the characters up to but not including the next "\r\n", "\n", or
+     * the end of the stream, consuming the end of line delimiter.
+     */
+    static String readLine(InputStream is) throws IOException {
+        StringBuilder result = new StringBuilder(80);
+        while (true) {
+            int c = is.read();
+            if (c == -1 || c == '\n') {
+                break;
+            }
+
+            result.append((char) c);
+        }
+        int length = result.length();
+        if (length > 0 && result.charAt(length - 1) == '\r') {
+            result.setLength(length - 1);
+        }
+        return result.toString();
+    }
+
+    private void readResponseHeaders() throws IOException {
+        RawHeaders headers;
+        do {
+            headers = new RawHeaders();
+            headers.setStatusLine(readLine(socketIn).trim());
+            readHeaders(headers);
+            setResponse(headers, null);
+        } while (headers.getResponseCode() == HTTP_CONTINUE);
+    }
+
+    /**
+     * Returns true if the response must have a (possibly 0-length) body.
+     * See RFC 2616 section 4.3.
+     */
+    public final boolean hasResponseBody() {
+        int responseCode = rawResponseHeaders.getResponseCode();
+        if (method != HEAD
+                && method != CONNECT
+                && (responseCode < HTTP_CONTINUE || responseCode >= 200)
+                && responseCode != HttpURLConnectionImpl.HTTP_NO_CONTENT
+                && responseCode != HttpURLConnectionImpl.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.
+         */
+        String contentLength = rawResponseHeaders.get("Content-Length");
+        if (contentLength != null && Integer.parseInt(contentLength) > 0) {
+            return true;
+        }
+        if ("chunked".equalsIgnoreCase(rawResponseHeaders.get("Transfer-Encoding"))) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Trailers are headers included after the last chunk of a response encoded
+     * with chunked encoding.
+     */
+    final void readTrailers() throws IOException {
+        readHeaders(rawResponseHeaders);
+    }
+
+    private void readHeaders(RawHeaders headers) throws IOException {
+        // parse the result headers until the first blank line
+        String line;
+        while ((line = readLine(socketIn)).length() > 1) {
+            // Header parsing
+            int index = line.indexOf(":");
+            if (index == -1) {
+                headers.add("", line);
+            } else {
+                headers.add(line.substring(0, index), line.substring(index + 1));
+            }
+        }
+
+        CookieHandler cookieHandler = CookieHandler.getDefault();
+        if (cookieHandler != null) {
+            cookieHandler.put(uri, headers.toMultimap());
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * @param contentLength the number of bytes in the request body, or -1 if
+     *      the request body length is unknown.
+     */
+    private void writeRequestHeaders(int contentLength) throws IOException {
+        if (sentRequestMillis != -1) {
+            throw new IllegalStateException();
+        }
+
+        RawHeaders headersToSend = getNetworkRequestHeaders();
+        byte[] bytes = headersToSend.toHeaderString().getBytes(Charsets.ISO_8859_1);
+
+        if (contentLength != -1 && bytes.length + contentLength <= MAX_REQUEST_BUFFER_LENGTH) {
+            requestOut = new BufferedOutputStream(socketOut, bytes.length + contentLength);
+        }
+
+        sentRequestMillis = System.currentTimeMillis();
+        requestOut.write(bytes);
+    }
+
+    /**
+     * Returns the headers to send on a network request.
+     *
+     * <p>This adds the content length and content-type headers, which are
+     * neither needed nor known when querying the response cache.
+     *
+     * <p>It updates the status line, which may need to be fully qualified if
+     * the connection is using a proxy.
+     */
+    protected RawHeaders getNetworkRequestHeaders() throws IOException {
+        rawRequestHeaders.setStatusLine(getRequestLine());
+
+        int fixedContentLength = policy.getFixedContentLength();
+        if (fixedContentLength != -1) {
+            rawRequestHeaders.addIfAbsent("Content-Length", Integer.toString(fixedContentLength));
+        } else if (sendChunked) {
+            rawRequestHeaders.addIfAbsent("Transfer-Encoding", "chunked");
+        } else if (requestBodyOut instanceof RetryableOutputStream) {
+            int size = ((RetryableOutputStream) requestBodyOut).contentLength();
+            rawRequestHeaders.addIfAbsent("Content-Length", Integer.toString(size));
+        }
+
+        if (requestBodyOut != null) {
+            rawRequestHeaders.addIfAbsent("Content-Type", "application/x-www-form-urlencoded");
+        }
+
+        return rawRequestHeaders;
+    }
+
+    /**
+     * Populates requestHeaders 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 void prepareRawRequestHeaders() throws IOException {
+        rawRequestHeaders.setStatusLine(getRequestLine());
+
+        if (rawRequestHeaders.get("User-Agent") == null) {
+            rawRequestHeaders.add("User-Agent", getDefaultUserAgent());
+        }
+
+        if (rawRequestHeaders.get("Host") == null) {
+            rawRequestHeaders.add("Host", getOriginAddress(policy.getURL()));
+        }
+
+        if (httpMinorVersion > 0) {
+            rawRequestHeaders.addIfAbsent("Connection", "Keep-Alive");
+        }
+
+        if (rawRequestHeaders.get("Accept-Encoding") == null) {
+            transparentGzip = true;
+            rawRequestHeaders.set("Accept-Encoding", "gzip");
+        }
+
+        CookieHandler cookieHandler = CookieHandler.getDefault();
+        if (cookieHandler != null) {
+            Map<String, List<String>> allCookieHeaders
+                    = cookieHandler.get(uri, rawRequestHeaders.toMultimap());
+            for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
+                String key = entry.getKey();
+                if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) {
+                    rawRequestHeaders.addAll(key, entry.getValue());
+                }
+            }
+        }
+    }
+
+    private String getRequestLine() {
+        String protocol = (httpMinorVersion == 0) ? "HTTP/1.0" : "HTTP/1.1";
+        return method + " " + requestString() + " " + protocol;
+    }
+
+    private String requestString() {
+        URL url = policy.getURL();
+        if (includeAuthorityInRequestLine()) {
+            return url.toString();
+        } else {
+            String fileOnly = url.getFile();
+            if (fileOnly == null || fileOnly.isEmpty()) {
+                fileOnly = "/";
+            }
+            return fileOnly;
+        }
+    }
+
+    /**
+     * 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").
+     *
+     * <p>This is non-final because for HTTPS it's never necessary to supply the
+     * full URL, even if a proxy is in use.
+     */
+    protected boolean includeAuthorityInRequestLine() {
+        return policy.usingProxy();
+    }
+
+    protected final String getDefaultUserAgent() {
+        String agent = System.getProperty("http.agent");
+        return agent != null ? agent : ("Java" + System.getProperty("java.version"));
+    }
+
+    public final boolean hasConnectionCloseHeaders() {
+        return (rawResponseHeaders != null
+                && "close".equalsIgnoreCase(rawResponseHeaders.get("Connection")))
+                || ("close".equalsIgnoreCase(rawRequestHeaders.get("Connection")));
+    }
+
+    protected final String getOriginAddress(URL url) {
+        int port = url.getPort();
+        String result = url.getHost();
+        if (port > 0 && port != policy.getDefaultPort()) {
+            result = result + ":" + port;
+        }
+        return result;
+    }
+
+    protected boolean requiresTunnel() {
+        return false;
+    }
+
+    /**
+     * Flushes the remaining request header and body, parses the HTTP response
+     * headers and starts reading the HTTP response body if it exists.
+     */
+    public final void readResponse() throws IOException {
+        if (hasResponse()) {
+            return;
+        }
+
+        if (responseSource == null) {
+            throw new IllegalStateException("readResponse() without sendRequest()");
+        }
+
+        if (!responseSource.requiresConnection()) {
+            return;
+        }
+
+        if (sentRequestMillis == -1) {
+            int contentLength = requestBodyOut instanceof RetryableOutputStream
+                    ? ((RetryableOutputStream) requestBodyOut).contentLength()
+                    : -1;
+            writeRequestHeaders(contentLength);
+        }
+
+        if (requestBodyOut != null) {
+            requestBodyOut.close();
+            if (requestBodyOut instanceof RetryableOutputStream) {
+                ((RetryableOutputStream) requestBodyOut).writeToSocket(requestOut);
+            }
+        }
+
+        requestOut.flush();
+        requestOut = socketOut;
+
+        readResponseHeaders();
+        rawResponseHeaders.add(ResponseHeaders.SENT_MILLIS, Long.toString(sentRequestMillis));
+        rawResponseHeaders.add(ResponseHeaders.RECEIVED_MILLIS,
+                Long.toString(System.currentTimeMillis()));
+
+        if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
+            if (responseHeadersToValidate.validate(new ResponseHeaders(uri, rawResponseHeaders))) {
+                // discard the network response
+                discardResponseBody();
+
+                // use the cache response
+                setResponse(responseHeadersToValidate.headers, responseBodyToValidate);
+                responseBodyToValidate = null;
+                return;
+            } else {
+                IoUtils.closeQuietly(responseBodyToValidate);
+                responseBodyToValidate = null;
+                responseHeadersToValidate = null;
+            }
+        }
+
+        if (hasResponseBody()) {
+            maybeCache(); // reentrant. this calls into user code which may call back into this!
+        }
+
+        initContentStream(getTransferStream());
+    }
+}
diff --git a/luni/src/main/java/libcore/net/http/HttpHandler.java b/luni/src/main/java/libcore/net/http/HttpHandler.java
index 976d49c..e168f42 100644
--- a/luni/src/main/java/libcore/net/http/HttpHandler.java
+++ b/luni/src/main/java/libcore/net/http/HttpHandler.java
@@ -23,60 +23,20 @@
 import java.net.URLConnection;
 import java.net.URLStreamHandler;
 
-/**
- * This is the handler that manages all transactions between the client and a
- * HTTP remote server.
- */
-public class HttpHandler extends URLStreamHandler {
+public final class HttpHandler extends URLStreamHandler {
 
-    /**
-     * Returns a connection to the HTTP server specified by this
-     * <code>URL</code>.
-     *
-     * @param u
-     *            the URL to which the connection is pointing to
-     * @return a connection to the resource pointed by this url.
-     *
-     * @throws IOException
-     *             if this handler fails to establish a connection
-     */
-    @Override
-    protected URLConnection openConnection(URL u) throws IOException {
+    @Override protected URLConnection openConnection(URL u) throws IOException {
         return new HttpURLConnectionImpl(u, getDefaultPort());
     }
 
-    /**
-     * Returns a connection, which is established via the <code>proxy</code>,
-     * to the HTTP server specified by this <code>URL</code>. If the
-     * <code>proxy</code> is DIRECT type, the connection is made in normal
-     * way.
-     *
-     * @param url
-     *            the URL which the connection is pointing to
-     * @param proxy
-     *            the proxy which is used to make the connection
-     * @return a connection to the resource pointed by this url.
-     *
-     * @throws IOException
-     *             if this handler fails to establish a connection.
-     * @throws IllegalArgumentException
-     *             if any argument is null or the type of proxy is wrong.
-     * @throws UnsupportedOperationException
-     *             if the protocol handler doesn't support this method.
-     */
-    @Override
-    protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
+    @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
         if (url == null || proxy == null) {
             throw new IllegalArgumentException("url == null || proxy == null");
         }
         return new HttpURLConnectionImpl(url, getDefaultPort(), proxy);
     }
 
-    /**
-     * Return the default port.
-     */
-    @Override
-    protected int getDefaultPort() {
+    @Override protected int getDefaultPort() {
         return 80;
     }
 }
diff --git a/luni/src/main/java/libcore/net/http/HttpHeaders.java b/luni/src/main/java/libcore/net/http/HttpHeaders.java
deleted file mode 100644
index 8eca3b5..0000000
--- a/luni/src/main/java/libcore/net/http/HttpHeaders.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- *  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 libcore.net.http;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-
-/**
- * The HTTP status and header lines of a single HTTP message. This class
- * maintains the order of the header lines within the HTTP message.
- */
-public final class HttpHeaders implements Cloneable {
-
-    private static final Comparator<String> HEADER_COMPARATOR = new Comparator<String>() {
-        @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);
-            }
-        }
-    };
-
-    private final List<String> alternatingKeysAndValues = new ArrayList<String>(20);
-    private String statusLine;
-
-    public HttpHeaders() {}
-
-    public HttpHeaders(HttpHeaders copyFrom) {
-        statusLine = copyFrom.statusLine;
-        alternatingKeysAndValues.addAll(copyFrom.alternatingKeysAndValues);
-    }
-
-    public void setStatusLine(String statusLine) {
-        this.statusLine = statusLine;
-    }
-
-    public String getStatusLine() {
-        return statusLine;
-    }
-
-    /**
-     * Add a field with the specified value.
-     */
-    public void add(String key, String value) {
-        if (key == null) {
-            throw new IllegalArgumentException("key == null");
-        }
-        if (value == null) {
-            /*
-             * Given null values, the RI sends a malformed header line like
-             * "Accept\r\n". For platform compatibility and HTTP compliance, we
-             * print a warning and ignore null values.
-             */
-            System.logW("Ignoring HTTP header field '" + key + "' because its value is null");
-            return;
-        }
-        alternatingKeysAndValues.add(key);
-        alternatingKeysAndValues.add(value);
-    }
-
-    public void removeAll(String key) {
-        for (int i = 0; i < alternatingKeysAndValues.size(); i += 2) {
-            if (key.equalsIgnoreCase(alternatingKeysAndValues.get(i))) {
-                alternatingKeysAndValues.remove(i); // key
-                alternatingKeysAndValues.remove(i); // value
-            }
-        }
-    }
-
-    public void addAll(String key, List<String> headers) {
-        for (String header : headers) {
-            add(key, header);
-        }
-    }
-
-    public void addIfAbsent(String key, String value) {
-        if (get(key) == null) {
-            add(key, value);
-        }
-    }
-
-    /**
-     * 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 void set(String key, String value) {
-        removeAll(key);
-        add(key, value);
-    }
-
-    /**
-     * Returns the number of header lines.
-     */
-    public int length() {
-        return alternatingKeysAndValues.size() / 2;
-    }
-
-    /**
-     * Returns the key at {@code position} or null if that is out of range.
-     */
-    public String getKey(int index) {
-        int keyIndex = index * 2;
-        if (keyIndex < 0 || keyIndex >= alternatingKeysAndValues.size()) {
-            return null;
-        }
-        return alternatingKeysAndValues.get(keyIndex);
-    }
-
-    /**
-     * Returns the value at {@code index} or null if that is out of range.
-     */
-    public String getValue(int index) {
-        int valueIndex = index * 2 + 1;
-        if (valueIndex < 0 || valueIndex >= alternatingKeysAndValues.size()) {
-            return null;
-        }
-        return alternatingKeysAndValues.get(valueIndex);
-    }
-
-    /**
-     * Returns the last value corresponding to the specified key, or null.
-     */
-    public String get(String key) {
-        for (int i = alternatingKeysAndValues.size() - 2; i >= 0; i -= 2) {
-            if (key.equalsIgnoreCase(alternatingKeysAndValues.get(i))) {
-                return alternatingKeysAndValues.get(i + 1);
-            }
-        }
-        return null;
-    }
-
-    public String toHeaderString() {
-        StringBuilder result = new StringBuilder(256);
-        result.append(statusLine).append("\r\n");
-        for (int i = 0; i < alternatingKeysAndValues.size(); i += 2) {
-            result.append(alternatingKeysAndValues.get(i)).append(": ")
-                    .append(alternatingKeysAndValues.get(i + 1)).append("\r\n");
-        }
-        result.append("\r\n");
-        return result.toString();
-    }
-
-    /**
-     * Returns an immutable map containing each field to its list of values. The
-     * status line is mapped to null.
-     */
-    public Map<String, List<String>> toMultimap() {
-        Map<String, List<String>> result = new TreeMap<String, List<String>>(HEADER_COMPARATOR);
-        for (int i = 0; i < alternatingKeysAndValues.size(); i += 2) {
-            String key = alternatingKeysAndValues.get(i);
-            String value = alternatingKeysAndValues.get(i + 1);
-
-            List<String> allValues = new ArrayList<String>();
-            List<String> otherValues = result.get(key);
-            if (otherValues != null) {
-                allValues.addAll(otherValues);
-            }
-            allValues.add(value);
-            result.put(key, Collections.unmodifiableList(allValues));
-        }
-        if (statusLine != null) {
-            result.put(null, Collections.unmodifiableList(Collections.singletonList(statusLine)));
-        }
-        return Collections.unmodifiableMap(result);
-    }
-
-    /**
-     * Creates a header from the given map of fields to values. If present, the
-     * null key's last element will be used to set the status line.
-     */
-    public static HttpHeaders fromMultimap(Map<String, List<String>> map) {
-        HttpHeaders result = new HttpHeaders();
-        for (Entry<String, List<String>> entry : map.entrySet()) {
-            String key = entry.getKey();
-            List<String> values = entry.getValue();
-            if (key != null) {
-                result.addAll(key, values);
-            } else if (!values.isEmpty()) {
-                result.setStatusLine(values.get(values.size() - 1));
-            }
-        }
-        return result;
-    }
-}
diff --git a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
index 3bf2638..95b37fd 100644
--- a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
+++ b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
@@ -17,15 +17,11 @@
 
 package libcore.net.http;
 
-import java.io.BufferedOutputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Authenticator;
-import java.net.CacheRequest;
-import java.net.CacheResponse;
-import java.net.CookieHandler;
 import java.net.HttpRetryException;
 import java.net.HttpURLConnection;
 import java.net.InetAddress;
@@ -33,486 +29,150 @@
 import java.net.PasswordAuthentication;
 import java.net.ProtocolException;
 import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.ResponseCache;
 import java.net.SocketPermission;
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.net.URL;
 import java.nio.charset.Charsets;
 import java.security.Permission;
-import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
-import java.util.TimeZone;
-import java.util.zip.GZIPInputStream;
-import libcore.io.Streams;
 import org.apache.harmony.luni.util.Base64;
 
 /**
- * This subclass extends <code>HttpURLConnection</code> which in turns extends
- * <code>URLConnection</code> This is the actual class that "does the work",
- * such as connecting, sending request and getting the content from the remote
- * server.
+ * 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 not 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
- * headers, request method, etc.) are immutable. Test the {@code connection}
- * field on this class for null/non-null to determine of an instance is
- * currently connected to a server.
+ * header fields, request method, etc.) are immutable. Test the {@code
+ * connection} field on this class for null/non-null to determine of an instance
+ * is currently connected to a server.
  */
 public class HttpURLConnectionImpl extends HttpURLConnection {
-    public static final String OPTIONS = "OPTIONS";
-    public static final String GET = "GET";
-    public static final String HEAD = "HEAD";
-    public static final String POST = "POST";
-    public static final String PUT = "PUT";
-    public static final String DELETE = "DELETE";
-    public static final String TRACE = "TRACE";
-    public static final String CONNECT = "CONNECT";
-
-    public static final int HTTP_CONTINUE = 100;
-
-    /**
-     * HTTP 1.1 doesn't specify how many redirects to follow, but HTTP/1.0
-     * recommended 5. http://www.w3.org/Protocols/HTTP/1.0/spec.html#Code3xx
-     */
-    public static final int MAX_REDIRECTS = 5;
-
-    /**
-     * The subset of HTTP methods that the user may select via {@link #setRequestMethod}.
-     */
-    public static String PERMITTED_USER_METHODS[] = {
-            OPTIONS,
-            GET,
-            HEAD,
-            POST,
-            PUT,
-            DELETE,
-            TRACE
-            // Note: we don't allow users to specify "CONNECT"
-    };
-
-    public static final int DEFAULT_CHUNK_LENGTH = 1024;
-
-    /**
-     * The maximum number of bytes to buffer when sending a header and a request
-     * body. When the header and body can be sent in a single write, the request
-     * completes sooner. In one WiFi benchmark, using a large enough buffer sped
-     * up some uploads by half.
-     */
-    private static final int MAX_REQUEST_BUFFER_LENGTH = 32768;
 
     private final int defaultPort;
 
-    /**
-     * The version this client will use. Either 0 for HTTP/1.0, or 1 for
-     * HTTP/1.1. Upon receiving a non-HTTP/1.1 response, this client
-     * automatically sets its version to HTTP/1.0.
-     */
-    private int httpVersion = 1; // Assume HTTP/1.1
-
-    protected HttpConnection connection;
-    private InputStream socketIn;
-    private OutputStream socketOut;
-
-    /**
-     * This stream buffers the request header and the request body when their
-     * combined size is less than MAX_REQUEST_BUFFER_LENGTH. By combining them
-     * we can save socket writes, which in turn saves a packet transmission.
-     * This is socketOut if the request size is large or unknown.
-     */
-    private OutputStream requestOut;
-
-    private AbstractHttpOutputStream requestBodyOut;
-
-    protected InputStream responseBodyIn;
-
-    private ResponseCache responseCache;
-
-    protected CacheResponse cacheResponse;
-
-    private CacheRequest cacheRequest;
-
-    private boolean hasTriedCache;
-
-    private boolean sentRequestHeaders;
-
-    /**
-     * True if this client added an "Accept-Encoding: gzip" header and is
-     * therefore responsible for also decompressing the transfer stream.
-     */
-    private boolean transparentGzip = false;
-
-    boolean sendChunked;
-
-    // proxy which is used to make the connection.
     private Proxy proxy;
 
-    // the destination URI
-    private URI uri;
+    // TODO: should these be set by URLConnection.setDefaultRequestProperty ?
+    private static RawHeaders defaultRequestHeaders = new RawHeaders();
 
-    private static HttpHeaders defaultRequestHeader = new HttpHeaders();
-
-    private final HttpHeaders requestHeader;
-
-    /** Null until a response is received from the network or the cache */
-    private HttpHeaders responseHeader;
+    protected RawHeaders rawRequestHeaders = new RawHeaders(defaultRequestHeaders);
 
     private int redirectionCount;
 
-    /**
-     * Intermediate responses are always followed by another request for the
-     * same content, possibly from a different URL or with different headers.
-     */
-    protected boolean intermediateResponse = false;
+    protected IOException httpEngineFailure;
+    protected HttpEngine httpEngine;
 
-    /**
-     * Creates an instance of the <code>HttpURLConnection</code>
-     *
-     * @param url
-     *            URL The URL this connection is connecting
-     * @param port
-     *            int The default connection port
-     */
     protected HttpURLConnectionImpl(URL url, int port) {
         super(url);
         defaultPort = port;
-        requestHeader = new HttpHeaders(defaultRequestHeader);
-        responseCache = ResponseCache.getDefault();
     }
 
-    /**
-     * Creates an instance of the <code>HttpURLConnection</code>
-     *
-     * @param url
-     *            URL The URL this connection is connecting
-     * @param port
-     *            int The default connection port
-     * @param proxy
-     *            Proxy The proxy which is used to make the connection
-     */
     protected HttpURLConnectionImpl(URL url, int port, Proxy proxy) {
         this(url, port);
         this.proxy = proxy;
     }
 
     @Override public final void connect() throws IOException {
-        if (connected) {
-            return;
-        }
-        makeConnection();
-    }
-
-    /**
-     * Internal method to open a connection to the server. Unlike connect(),
-     * this method may be called multiple times for a single response. This may
-     * be necessary when following redirects.
-     *
-     * <p>Request parameters may not be changed after this method has been
-     * called.
-     */
-    protected void makeConnection() throws IOException {
-        connected = true;
-
-        if (connection != null || responseBodyIn != null) {
-            return;
-        }
-
+        initHttpEngine();
         try {
-            uri = url.toURILenient();
-        } catch (URISyntaxException e) {
-            throw new IOException(e);
+            httpEngine.sendRequest();
+        } catch (IOException e) {
+            httpEngineFailure = e;
+            throw e;
         }
-
-        if (getFromCache()) {
-            return;
-        }
-
-        // try to determine: to use the proxy or not
-        if (proxy != null) {
-            // try to make the connection to the proxy
-            // specified in constructor.
-            // IOException will be thrown in the case of failure
-            connection = getHttpConnection(proxy);
-        } else {
-            // Use system-wide ProxySelect to select proxy list,
-            // then try to connect via elements in the proxy list.
-            ProxySelector selector = ProxySelector.getDefault();
-            List<Proxy> proxyList = selector.select(uri);
-            if (proxyList != null) {
-                for (Proxy selectedProxy : proxyList) {
-                    if (selectedProxy.type() == Proxy.Type.DIRECT) {
-                        // the same as NO_PROXY
-                        continue;
-                    }
-                    try {
-                        connection = getHttpConnection(selectedProxy);
-                        proxy = selectedProxy;
-                        break; // connected
-                    } catch (IOException e) {
-                        // failed to connect, tell it to the selector
-                        selector.connectFailed(uri, selectedProxy.address(), e);
-                    }
-                }
-            }
-            if (connection == null) {
-                // make direct connection
-                connection = getHttpConnection(null);
-            }
-        }
-        connection.setSoTimeout(getReadTimeout());
-        setUpTransportIO(connection);
-    }
-
-    /**
-     * Returns connected socket to be used for this HTTP connection.
-     */
-    private HttpConnection getHttpConnection(Proxy proxy) throws IOException {
-        HttpConnection.Address address;
-        if (proxy == null || proxy.type() == Proxy.Type.DIRECT) {
-            this.proxy = null; // not using proxy
-            address = new HttpConnection.Address(uri);
-        } else {
-            address = new HttpConnection.Address(uri, proxy, requiresTunnel());
-        }
-        return HttpConnectionPool.INSTANCE.get(address, getConnectTimeout());
-    }
-
-    /**
-     * Sets up the data streams used to send requests and read responses.
-     */
-    protected void setUpTransportIO(HttpConnection connection) throws IOException {
-        socketOut = connection.getOutputStream();
-        requestOut = socketOut;
-        socketIn = connection.getInputStream();
-    }
-
-    /**
-     * Attempts to load the response headers and body from the response cache.
-     * Returns true if the request was satisfied by the cache.
-     */
-    private boolean getFromCache() throws IOException {
-        if (!useCaches || responseCache == null || hasTriedCache) {
-            return false;
-        }
-
-        hasTriedCache = true;
-        CacheResponse candidate = responseCache.get(uri, method, requestHeader.toMultimap());
-        if (!acceptCacheResponse(candidate)) {
-            return false;
-        }
-        Map<String, List<String>> headersMap = candidate.getHeaders();
-        if (headersMap == null) {
-            return false;
-        }
-        InputStream cacheBodyIn = candidate.getBody();
-        if (cacheBodyIn == null) {
-            return false;
-        }
-
-        cacheResponse = candidate;
-        responseHeader = HttpHeaders.fromMultimap(headersMap);
-        parseStatusLine();
-        responseBodyIn = cacheBodyIn;
-        return true;
-    }
-
-    /**
-     * Returns true if {@code cacheResponse} is sufficient to forgo a network
-     * request. HTTPS connections require secure cache responses.
-     */
-    protected boolean acceptCacheResponse(CacheResponse cacheResponse) {
-        return cacheResponse != null;
-    }
-
-    private void maybeCache() throws IOException {
-        // Are we caching at all?
-        if (!useCaches || responseCache == null) {
-            return;
-        }
-        // Should we cache this particular response code?
-        // TODO: cache response code 300 HTTP_MULT_CHOICE ?
-        if (responseCode != HTTP_OK && responseCode != HTTP_NOT_AUTHORITATIVE &&
-                responseCode != HTTP_PARTIAL && responseCode != HTTP_MOVED_PERM &&
-                responseCode != HTTP_GONE) {
-            return;
-        }
-        // Offer this request to the cache.
-        cacheRequest = responseCache.put(uri, getConnectionForCaching());
     }
 
     /**
      * Close the socket connection to the remote origin server or proxy.
      */
     @Override public final void disconnect() {
-        releaseSocket(false);
-    }
-
-    /**
-     * Releases this connection so that it may be either reused or closed.
-     */
-    protected final void releaseSocket(boolean reuseSocket) {
-        // we cannot recycle sockets that have incomplete output.
-        if (requestBodyOut != null && !requestBodyOut.closed) {
-            reuseSocket = false;
-        }
-
-        // if the headers specify that the connection shouldn't be reused, don't reuse it
-        if (hasConnectionCloseHeader()) {
-            reuseSocket = false;
-        }
-
-        /*
-         * Don't return the socket to the connection pool if this is an
-         * intermediate response; we're going to use it again right away.
-         */
-        if (intermediateResponse && reuseSocket) {
-            return;
-        }
-
-        if (connection != null) {
-            if (reuseSocket) {
-                HttpConnectionPool.INSTANCE.recycle(connection);
-            } else {
-                connection.closeSocketAndStreams();
-            }
-            connection = null;
-        }
-
-        /*
-         * Ensure that no further I/O attempts from this instance make their way
-         * to the underlying connection (which may get recycled).
-         */
-        socketOut = null;
-        socketIn = null;
-        requestOut = null;
-    }
-
-    /**
-     * Discard all state initialized from the HTTP response including response
-     * code, message, headers and body.
-     */
-    protected final void discardIntermediateResponse() throws IOException {
-        boolean oldIntermediateResponse = intermediateResponse;
-        intermediateResponse = true;
-        try {
-            if (responseBodyIn != null) {
-                if (!(responseBodyIn instanceof UnknownLengthHttpInputStream)) {
-                    // skip the response so that the connection may be reused for the retry
-                    Streams.skipAll(responseBodyIn);
-                }
-                responseBodyIn.close();
-                responseBodyIn = null;
-            }
-            sentRequestHeaders = false;
-            responseHeader = null;
-            responseCode = -1;
-            responseMessage = null;
-            cacheRequest = null;
-            uri = null;
-            cacheResponse = null;
-            hasTriedCache = false;
-        } finally {
-            intermediateResponse = oldIntermediateResponse;
+        // TODO: what happens if they call disconnect() before connect?
+        if (httpEngine != null) {
+            httpEngine.releaseSocket(false);
         }
     }
 
     /**
      * 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.
-     * <p>
-     * If the content type is not what stated above,
-     * <code>FileNotFoundException</code> is thrown.
-     *
-     * @return InputStream the error input stream returned by the server.
      */
-    @Override
-    public final InputStream getErrorStream() {
-        if (connected && method != HEAD && responseCode >= HTTP_BAD_REQUEST) {
-            return responseBodyIn;
+    @Override public final InputStream getErrorStream() {
+        try {
+            HttpEngine response = getResponse();
+            if (response.hasResponseBody()
+                    && response.getResponseHeaders().getResponseCode() >= HTTP_BAD_REQUEST) {
+                return response.getResponseBody();
+            }
+            return null;
+        } catch (IOException e) {
+            return null;
         }
-        return null;
     }
 
     /**
      * 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) {
+    @Override public final String getHeaderField(int position) {
         try {
-            retrieveResponse();
-        } catch (IOException ignored) {
+            return getResponse().getResponseHeaders().getValue(position);
+        } catch (IOException e) {
+            return null;
         }
-        return responseHeader != null ? responseHeader.getValue(position) : null;
     }
 
     /**
-     * Returns the value of the field corresponding to the <code>key</code>
-     * Returns <code>null</code> if there is no such field.
-     *
-     * If there are multiple fields with that key, the last field value is
-     * returned.
-     *
-     * @return java.lang.String The value of the header field
-     * @param key
-     *            java.lang.String the name of the header field
-     *
-     * @see #getHeaderField(int)
-     * @see #getHeaderFieldKey
+     * 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 key) {
+    @Override public final String getHeaderField(String fieldName) {
         try {
-            retrieveResponse();
-        } catch (IOException ignored) {
-        }
-        if (responseHeader == null) {
+            RawHeaders responseHeaders = getResponse().getResponseHeaders();
+            return fieldName == null
+                    ? responseHeaders.getStatusLine()
+                    : responseHeaders.get(fieldName);
+        } catch (IOException e) {
             return null;
         }
-        return key == null ? responseHeader.getStatusLine() : responseHeader.get(key);
     }
 
-    @Override
-    public final String getHeaderFieldKey(int position) {
+    @Override public final String getHeaderFieldKey(int position) {
         try {
-            retrieveResponse();
-        } catch (IOException ignored) {
+            return getResponse().getResponseHeaders().getFieldName(position);
+        } catch (IOException e) {
+            return null;
         }
-        return responseHeader != null ? responseHeader.getKey(position) : null;
     }
 
-    @Override
-    public final Map<String, List<String>> getHeaderFields() {
+    @Override public final Map<String, List<String>> getHeaderFields() {
         try {
-            retrieveResponse();
-        } catch (IOException ignored) {
+            return getResponse().getResponseHeaders().toMultimap();
+        } catch (IOException e) {
+            return null;
         }
-        return responseHeader != null ? responseHeader.toMultimap() : null;
     }
 
-    @Override
-    public final Map<String, List<String>> getRequestProperties() {
+    @Override public final Map<String, List<String>> getRequestProperties() {
         if (connected) {
             throw new IllegalStateException(
                     "Cannot access request header fields after connection is set");
         }
-        return requestHeader.toMultimap();
+        return rawRequestHeaders.toMultimap();
     }
 
-    @Override
-    public final InputStream getInputStream() throws IOException {
+    @Override public final InputStream getInputStream() throws IOException {
         if (!doInput) {
             throw new ProtocolException("This protocol does not support input");
         }
 
-        retrieveResponse();
+        HttpEngine response = getResponse();
 
         /*
          * if the requested file does not exist, throw an exception formerly the
@@ -520,495 +180,70 @@
          * text/html this has changed to return FileNotFoundException for all
          * file types
          */
-        if (responseCode >= HTTP_BAD_REQUEST) {
+        if (getResponseCode() >= HTTP_BAD_REQUEST) {
             throw new FileNotFoundException(url.toString());
         }
 
-        if (responseBodyIn == null) {
-            throw new IOException("No response body exists; responseCode=" + responseCode);
-        }
-
-        return responseBodyIn;
-    }
-
-    private void initContentStream() throws IOException {
-        InputStream transferStream = getTransferStream();
-        if (transparentGzip && "gzip".equalsIgnoreCase(responseHeader.get("Content-Encoding"))) {
-            /*
-             * If the response was transparently gzipped, remove the gzip header
-             * so clients don't double decompress. http://b/3009828
-             */
-            responseHeader.removeAll("Content-Encoding");
-            responseBodyIn = new GZIPInputStream(transferStream);
-        } else {
-            responseBodyIn = transferStream;
-        }
-    }
-
-    private InputStream getTransferStream() throws IOException {
-        if (!hasResponseBody()) {
-            return new FixedLengthInputStream(socketIn, cacheRequest, this, 0);
-        }
-
-        if ("chunked".equalsIgnoreCase(responseHeader.get("Transfer-Encoding"))) {
-            return new ChunkedInputStream(socketIn, cacheRequest, this);
-        }
-
-        String contentLength = responseHeader.get("Content-Length");
-        if (contentLength != null) {
-            try {
-                int length = Integer.parseInt(contentLength);
-                return new FixedLengthInputStream(socketIn, cacheRequest, this, length);
-            } catch (NumberFormatException ignored) {
-            }
-        }
-
-        /*
-         * Wrap the input stream from the HttpConnection (rather than
-         * just returning "socketIn" directly here), so that we can control
-         * its use after the reference escapes.
-         */
-        return new UnknownLengthHttpInputStream(socketIn, cacheRequest, this);
-    }
-
-    @Override
-    public final OutputStream getOutputStream() throws IOException {
-        if (!doOutput) {
-            throw new ProtocolException("Does not support output");
-        }
-
-        // you can't write after you read
-        if (sentRequestHeaders) {
-            // TODO: just return 'requestBodyOut' if that's non-null?
-            throw new ProtocolException(
-                    "OutputStream unavailable because request headers have already been sent!");
-        }
-
-        if (requestBodyOut != null) {
-            return requestBodyOut;
-        }
-
-        // they are requesting a stream to write to. This implies a POST method
-        if (method == GET) {
-            method = POST;
-        }
-
-        // If the request method is neither PUT or POST, then you're not writing
-        if (method != PUT && method != POST) {
-            throw new ProtocolException(method + " does not support writing");
-        }
-
-        int contentLength = -1;
-        String contentLengthString = requestHeader.get("Content-Length");
-        if (contentLengthString != null) {
-            contentLength = Integer.parseInt(contentLengthString);
-        }
-
-        String encoding = requestHeader.get("Transfer-Encoding");
-        if (chunkLength > 0 || "chunked".equalsIgnoreCase(encoding)) {
-            sendChunked = true;
-            contentLength = -1;
-            if (chunkLength == -1) {
-                chunkLength = DEFAULT_CHUNK_LENGTH;
-            }
-        }
-
-        connect();
-
-        if (socketOut == null) {
-            // TODO: what should we do if a cached response exists?
-            throw new IOException("No socket to write to; was a POST cached?");
-        }
-
-        if (httpVersion == 0) {
-            sendChunked = false;
-        }
-
-        if (fixedContentLength != -1) {
-            writeRequestHeaders(fixedContentLength);
-            requestBodyOut = new FixedLengthOutputStream(requestOut, fixedContentLength);
-        } else if (sendChunked) {
-            writeRequestHeaders(-1);
-            requestBodyOut = new ChunkedOutputStream(requestOut, chunkLength);
-        } else if (contentLength != -1) {
-            writeRequestHeaders(contentLength);
-            requestBodyOut = new RetryableOutputStream(contentLength);
-        } else {
-            requestBodyOut = new RetryableOutputStream();
-        }
-        return requestBodyOut;
-    }
-
-    @Override
-    public final Permission getPermission() throws IOException {
-        String connectToAddress = getConnectToHost() + ":" + getConnectToPort();
-        return new SocketPermission(connectToAddress, "connect, resolve");
-    }
-
-    @Override
-    public final String getRequestProperty(String field) {
-        if (field == null) {
-            return null;
-        }
-        return requestHeader.get(field);
-    }
-
-    /**
-     * Returns the characters up to but not including the next "\r\n", "\n", or
-     * the end of the stream, consuming the end of line delimiter.
-     */
-    static String readLine(InputStream is) throws IOException {
-        StringBuilder result = new StringBuilder(80);
-        while (true) {
-            int c = is.read();
-            if (c == -1 || c == '\n') {
-                break;
-            }
-
-            result.append((char) c);
-        }
-        int length = result.length();
-        if (length > 0 && result.charAt(length - 1) == '\r') {
-            result.setLength(length - 1);
-        }
-        return result.toString();
-    }
-
-    protected String requestString() {
-        if (usingProxy()) {
-            return url.toString();
-        }
-        String file = url.getFile();
-        if (file == null || file.length() == 0) {
-            file = "/";
-        }
-        return file;
-    }
-
-    private void readResponseHeaders() throws IOException {
-        do {
-            responseHeader = new HttpHeaders();
-            responseHeader.setStatusLine(readLine(socketIn).trim());
-            readHeaders();
-            parseStatusLine();
-        } while (responseCode == HTTP_CONTINUE);
-    }
-
-    /**
-     * Returns true if the response must have a (possibly 0-length) body.
-     * See RFC 2616 section 4.3.
-     */
-    private boolean hasResponseBody() {
-        if (method != HEAD
-                && method != CONNECT
-                && (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.
-         */
-        String contentLength = responseHeader.get("Content-Length");
-        if (contentLength != null && Integer.parseInt(contentLength) > 0) {
-            return true;
-        }
-        if ("chunked".equalsIgnoreCase(responseHeader.get("Transfer-Encoding"))) {
-            return true;
-        }
-
-        return false;
-    }
-
-    @Override
-    public final int getResponseCode() throws IOException {
-        retrieveResponse();
-        return responseCode;
-    }
-
-    private void parseStatusLine() {
-        httpVersion = 1;
-        responseCode = -1;
-        responseMessage = null;
-
-        // Status line sample: "HTTP/1.0 200 OK"
-        String response = responseHeader.getStatusLine();
-        if (response == null || !response.startsWith("HTTP/")) {
-            return;
-        }
-        response = response.trim();
-        int mark = response.indexOf(" ") + 1;
-        if (mark == 0) {
-            return;
-        }
-        if (response.charAt(mark - 2) != '1') {
-            httpVersion = 0;
-        }
-        int last = mark + 3;
-        if (last > response.length()) {
-            last = response.length();
-        }
-        responseCode = Integer.parseInt(response.substring(mark, last));
-        if (last + 1 <= response.length()) {
-            responseMessage = response.substring(last + 1);
-        }
-    }
-
-    void readHeaders() throws IOException {
-        // parse the result headers until the first blank line
-        String line;
-        while ((line = readLine(socketIn)).length() > 1) {
-            // Header parsing
-            int index = line.indexOf(":");
-            if (index == -1) {
-                responseHeader.add("", line.trim());
-            } else {
-                responseHeader.add(line.substring(0, index), line.substring(index + 1).trim());
-            }
-        }
-
-        CookieHandler cookieHandler = CookieHandler.getDefault();
-        if (cookieHandler != null) {
-            cookieHandler.put(uri, responseHeader.toMultimap());
-        }
-    }
-
-    /**
-     * 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 receives the proper
-     * value.
-     *
-     * @param contentLength the number of bytes in the request body, or -1 if
-     *      the request body length is unknown.
-     */
-    private void writeRequestHeaders(int contentLength) throws IOException {
-        byte[] headerBytes = prepareRequestHeaders().toHeaderString().getBytes(Charsets.ISO_8859_1);
-
-        if (contentLength != -1
-                && headerBytes.length + contentLength <= MAX_REQUEST_BUFFER_LENGTH) {
-            requestOut = new BufferedOutputStream(socketOut, headerBytes.length + contentLength);
-        }
-
-        requestOut.write(headerBytes);
-        sentRequestHeaders = true;
-    }
-
-    /**
-     * Populates requestHeader with the HTTP headers to be sent. Header values are
-     * derived from the request itself and the cookie manager.
-     *
-     * <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 HttpHeaders prepareRequestHeaders() throws IOException {
-        /*
-         * If we're establishing an HTTPS tunnel with CONNECT (RFC 2817 5.2),
-         * send only the minimum set of headers. This avoids sending potentially
-         * sensitive data like HTTP cookies to the proxy unencrypted.
-         */
-        if (method == CONNECT) {
-            HttpHeaders proxyHeader = new HttpHeaders();
-            proxyHeader.setStatusLine(getStatusLine());
-
-            // always set Host and User-Agent
-            String host = requestHeader.get("Host");
-            if (host == null) {
-                host = getOriginAddress(url);
-            }
-            proxyHeader.set("Host", host);
-
-            String userAgent = requestHeader.get("User-Agent");
-            if (userAgent == null) {
-                userAgent = getDefaultUserAgent();
-            }
-            proxyHeader.set("User-Agent", userAgent);
-
-            // copy over the Proxy-Authorization header if it exists
-            String proxyAuthorization = requestHeader.get("Proxy-Authorization");
-            if (proxyAuthorization != null) {
-                proxyHeader.set("Proxy-Authorization", proxyAuthorization);
-            }
-
-            // Always set the Proxy-Connection to Keep-Alive for the benefit of
-            // HTTP/1.0 proxies like Squid.
-            proxyHeader.set("Proxy-Connection", "Keep-Alive");
-            return proxyHeader;
-        }
-
-        requestHeader.setStatusLine(getStatusLine());
-
-        if (requestHeader.get("User-Agent") == null) {
-            requestHeader.add("User-Agent", getDefaultUserAgent());
-        }
-
-        if (requestHeader.get("Host") == null) {
-            requestHeader.add("Host", getOriginAddress(url));
-        }
-
-        if (httpVersion > 0) {
-            requestHeader.addIfAbsent("Connection", "Keep-Alive");
-        }
-
-        if (fixedContentLength != -1) {
-            requestHeader.addIfAbsent("Content-Length", Integer.toString(fixedContentLength));
-        } else if (sendChunked) {
-            requestHeader.addIfAbsent("Transfer-Encoding", "chunked");
-        } else if (requestBodyOut instanceof RetryableOutputStream) {
-            int size = ((RetryableOutputStream) requestBodyOut).contentLength();
-            requestHeader.addIfAbsent("Content-Length", Integer.toString(size));
-        }
-
-        if (requestBodyOut != null) {
-            requestHeader.addIfAbsent("Content-Type", "application/x-www-form-urlencoded");
-        }
-
-        if (requestHeader.get("Accept-Encoding") == null) {
-            transparentGzip = true;
-            requestHeader.set("Accept-Encoding", "gzip");
-        }
-
-        CookieHandler cookieHandler = CookieHandler.getDefault();
-        if (cookieHandler != null) {
-            Map<String, List<String>> allCookieHeaders
-                    = cookieHandler.get(uri, requestHeader.toMultimap());
-            for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
-                String key = entry.getKey();
-                if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) {
-                    requestHeader.addAll(key, entry.getValue());
-                }
-            }
-        }
-
-        return requestHeader;
-    }
-
-    private String getStatusLine() {
-        String protocol = (httpVersion == 0) ? "HTTP/1.0" : "HTTP/1.1";
-        return method + " " + requestString() + " " + protocol;
-    }
-
-    private String getDefaultUserAgent() {
-        String agent = System.getProperty("http.agent");
-        return agent != null ? agent : ("Java" + System.getProperty("java.version"));
-    }
-
-    private boolean hasConnectionCloseHeader() {
-        return (responseHeader != null
-                && "close".equalsIgnoreCase(responseHeader.get("Connection")))
-                || (requestHeader != null
-                && "close".equalsIgnoreCase(requestHeader.get("Connection")));
-    }
-
-    private String getOriginAddress(URL url) {
-        int port = url.getPort();
-        String result = url.getHost();
-        if (port > 0 && port != defaultPort) {
-            result = result + ":" + port;
+        InputStream result = response.getResponseBody();
+        if (result == null) {
+            throw new IOException("No response body exists; responseCode=" + getResponseCode());
         }
         return result;
     }
 
-    /**
-     * A slightly different implementation from this parent's
-     * <code>setIfModifiedSince()</code> Since this HTTP impl supports
-     * IfModifiedSince as one of the header field, the request header is updated
-     * with the new value.
-     *
-     *
-     * @param newValue
-     *            the number of millisecond since epoch
-     *
-     * @throws IllegalStateException
-     *             if already connected.
-     */
-    @Override
-    public final void setIfModifiedSince(long newValue) {
-        super.setIfModifiedSince(newValue);
-        // convert from millisecond since epoch to date string
-        SimpleDateFormat sdf = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
-        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
-        String date = sdf.format(new Date(newValue));
-        requestHeader.add("If-Modified-Since", date);
+    @Override public final OutputStream getOutputStream() throws IOException {
+        connect();
+        return httpEngine.getRequestBody();
     }
 
-    @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();
-        }
-        requestHeader.set(field, newValue);
+    @Override public final Permission getPermission() throws IOException {
+        String connectToAddress = getConnectToHost() + ":" + getConnectToPort();
+        return new SocketPermission(connectToAddress, "connect, resolve");
     }
 
-    @Override
-    public final void addRequestProperty(String field, String value) {
-        if (connected) {
-            throw new IllegalStateException("Cannot set request property after connection is made");
-        }
-        if (field == null) {
-            throw new NullPointerException();
-        }
-        requestHeader.add(field, value);
-    }
-
-    /**
-     * Returns the target port of the socket connection; either a port of the
-     * origin server or an intermediate proxy.
-     */
-    private int getConnectToPort() {
-        int hostPort = usingProxy()
-                ? ((InetSocketAddress) proxy.address()).getPort()
-                : url.getPort();
-        return hostPort < 0 ? defaultPort : hostPort;
-    }
-
-    /**
-     * Returns the target address of the socket connection; either the address
-     * of the origin server or an intermediate proxy.
-     */
-    private InetAddress getConnectToInetAddress() throws IOException {
-        return usingProxy()
-                ? ((InetSocketAddress) proxy.address()).getAddress()
-                : InetAddress.getByName(url.getHost());
-    }
-
-    /**
-     * Returns the target host name of the socket connection; either the host
-     * name of the origin server or an intermediate proxy.
-     */
     private String getConnectToHost() {
         return usingProxy()
                 ? ((InetSocketAddress) proxy.address()).getHostName()
-                : url.getHost();
+                : getURL().getHost();
     }
 
-    @Override public final boolean usingProxy() {
-        return (proxy != null && proxy.type() != Proxy.Type.DIRECT);
+    private int getConnectToPort() {
+        int hostPort = usingProxy()
+                ? ((InetSocketAddress) proxy.address()).getPort()
+                : getURL().getPort();
+        return hostPort < 0 ? getDefaultPort() : hostPort;
     }
 
-    protected boolean requiresTunnel() {
-        return false;
+    @Override public final String getRequestProperty(String field) {
+        if (field == null) {
+            return null;
+        }
+        return rawRequestHeaders.get(field);
+    }
+
+    private void initHttpEngine() throws IOException {
+        if (httpEngineFailure != null) {
+            throw httpEngineFailure;
+        } else if (httpEngine != null) {
+            return;
+        }
+
+        connected = true;
+        try {
+            httpEngine = newHttpEngine(method, rawRequestHeaders, null, null);
+        } catch (IOException e) {
+            httpEngineFailure = e;
+            throw e;
+        }
     }
 
     /**
-     * Returns this connection in a form suitable for use by the response cache.
-     * If this returns an HTTPS connection, only secure cache responses will be
-     * honored.
+     * Create a new HTTP engine. This hook method is non-final so it can be
+     * overridden by HttpsURLConnectionImpl.
      */
-    protected HttpURLConnection getConnectionForCaching() {
-        return this;
+    protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
+            HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
+        return new HttpEngine(this, method, requestHeaders, connection, requestBody);
     }
 
     /**
@@ -1016,72 +251,56 @@
      * many HTTP requests in the process in order to cope with redirects and
      * authentication.
      */
-    protected final void retrieveResponse() throws IOException {
-        if (responseHeader != null) {
-            return;
+    private HttpEngine getResponse() throws IOException {
+        initHttpEngine();
+
+        if (httpEngine.hasResponse()) {
+            return httpEngine;
         }
 
-        redirectionCount = 0;
-        while (true) {
-            makeConnection();
+        try {
+            while (true) {
+                httpEngine.sendRequest();
+                httpEngine.readResponse();
 
-            if (cacheResponse == null) {
-                getFromNetwork();
+                Retry retry = processResponseHeaders();
+                if (retry == Retry.NONE) {
+                    break;
+                }
+
+                /*
+                 * The first request was insufficient. Prepare for another...
+                 */
+                OutputStream requestBody = httpEngine.getRequestBody();
+                if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
+                    throw new HttpRetryException("Cannot retry streamed HTTP body",
+                            httpEngine.getResponseHeaders().getResponseCode());
+                }
+
+                if (retry == Retry.SAME_CONNECTION && httpEngine.hasConnectionCloseHeaders()) {
+                    retry = Retry.NEW_CONNECTION;
+                }
+
+                HttpConnection connection = null;
+                if (retry == Retry.NEW_CONNECTION) {
+                    httpEngine.discardResponseBody();
+                    httpEngine.releaseSocket(true);
+                } else {
+                    httpEngine.dontReleaseSocketToPool();
+                    httpEngine.discardResponseBody();
+                    connection = httpEngine.getConnection();
+                }
+
+                httpEngine = newHttpEngine(method, rawRequestHeaders, connection,
+                        (RetryableOutputStream) requestBody);
             }
-
-            Retry retry = processResponseHeaders();
-
-            if (retry == Retry.NONE) {
-                return;
-            }
-
-            /*
-             * The first request wasn't sufficient. Prepare for another...
-             */
-
-            if (requestBodyOut != null && !(requestBodyOut instanceof RetryableOutputStream)) {
-                throw new HttpRetryException("Cannot retry streamed HTTP body", responseCode);
-            }
-
-            if (retry == Retry.SAME_CONNECTION && hasConnectionCloseHeader()) {
-                retry = Retry.NEW_CONNECTION;
-            }
-
-            discardIntermediateResponse();
-
-            if (retry == Retry.NEW_CONNECTION) {
-                releaseSocket(true);
-            }
+            return httpEngine;
+        } catch (IOException e) {
+            httpEngineFailure = e;
+            throw e;
         }
     }
 
-    private void getFromNetwork() throws IOException {
-        if (!sentRequestHeaders) {
-            int contentLength = requestBodyOut instanceof RetryableOutputStream
-                    ? ((RetryableOutputStream) requestBodyOut).contentLength()
-                    : -1;
-            writeRequestHeaders(contentLength);
-        }
-
-        if (requestBodyOut != null) {
-            requestBodyOut.close();
-            if (requestBodyOut instanceof RetryableOutputStream) {
-                ((RetryableOutputStream) requestBodyOut).writeToSocket(requestOut);
-            }
-        }
-
-        requestOut.flush();
-        requestOut = socketOut;
-
-        readResponseHeaders();
-
-        if (hasResponseBody()) {
-            maybeCache(); // reentrant. this calls into user code which may call back into this!
-        }
-
-        initContentStream();
-    }
-
     enum Retry {
         NONE,
         SAME_CONNECTION,
@@ -1094,72 +313,89 @@
      * prepare for a follow up request.
      */
     private Retry processResponseHeaders() throws IOException {
+        RawHeaders responseHeaders = httpEngine.getResponseHeaders();
+        int responseCode = responseHeaders.getResponseCode();
         switch (responseCode) {
-            case HTTP_PROXY_AUTH: // proxy authorization failed ?
-                if (!usingProxy()) {
-                    throw new IOException(
-                            "Received HTTP_PROXY_AUTH (407) code while not using proxy");
-                }
-                return processAuthHeader("Proxy-Authenticate", "Proxy-Authorization");
+        case HTTP_PROXY_AUTH: // proxy authorization failed ?
+            if (!usingProxy()) {
+                throw new IOException(
+                        "Received HTTP_PROXY_AUTH (407) code while not using proxy");
+            }
+            return processAuthHeader("Proxy-Authenticate", "Proxy-Authorization");
 
-            case HTTP_UNAUTHORIZED: // HTTP authorization failed ?
-                return processAuthHeader("WWW-Authenticate", "Authorization");
+        case HTTP_UNAUTHORIZED: // HTTP authorization failed ?
+            return processAuthHeader("WWW-Authenticate", "Authorization");
 
-            case HTTP_MULT_CHOICE:
-            case HTTP_MOVED_PERM:
-            case HTTP_MOVED_TEMP:
-            case HTTP_SEE_OTHER:
-            case HTTP_USE_PROXY:
-                if (!getInstanceFollowRedirects()) {
-                    return Retry.NONE;
-                }
-                if (requestBodyOut != null) {
-                    // TODO: follow redirects for retryable output streams...
-                    return Retry.NONE;
-                }
-                if (++redirectionCount > MAX_REDIRECTS) {
-                    throw new ProtocolException("Too many redirects");
-                }
-                String location = getHeaderField("Location");
-                if (location == null) {
-                    return Retry.NONE;
-                }
-                if (responseCode == HTTP_USE_PROXY) {
-                    int start = 0;
-                    if (location.startsWith(url.getProtocol() + ':')) {
-                        start = url.getProtocol().length() + 1;
-                    }
-                    if (location.startsWith("//", start)) {
-                        start += 2;
-                    }
-                    setProxy(location.substring(start));
-                    return Retry.NEW_CONNECTION;
-                }
-                URL previousUrl = url;
-                url = new URL(previousUrl, location);
-                if (!previousUrl.getProtocol().equals(url.getProtocol())) {
-                    return Retry.NONE; // the scheme changed; don't retry.
-                }
-                if (previousUrl.getHost().equals(url.getHost())
-                        && previousUrl.getEffectivePort() == url.getEffectivePort()) {
-                    return Retry.SAME_CONNECTION;
-                } else {
-                    // TODO: strip cookies?
-                    requestHeader.removeAll("Host");
-                    return Retry.NEW_CONNECTION;
-                }
-
-            default:
+        case HTTP_MULT_CHOICE:
+        case HTTP_MOVED_PERM:
+        case HTTP_MOVED_TEMP:
+        case HTTP_SEE_OTHER:
+        case HTTP_USE_PROXY:
+            if (!getInstanceFollowRedirects()) {
                 return Retry.NONE;
+            }
+            if (httpEngine.getRequestBody() != null) {
+                // TODO: follow redirects for retryable output streams...
+                return Retry.NONE;
+            }
+            if (++redirectionCount > HttpEngine.MAX_REDIRECTS) {
+                throw new ProtocolException("Too many redirects");
+            }
+            String location = responseHeaders.get("Location");
+            if (location == null) {
+                return Retry.NONE;
+            }
+            if (responseCode == HTTP_USE_PROXY) {
+                int start = 0;
+                if (location.startsWith(url.getProtocol() + ':')) {
+                    start = url.getProtocol().length() + 1;
+                }
+                if (location.startsWith("//", start)) {
+                    start += 2;
+                }
+                setProxy(location.substring(start));
+                return Retry.NEW_CONNECTION;
+            }
+            URL previousUrl = url;
+            url = new URL(previousUrl, location);
+            if (!previousUrl.getProtocol().equals(url.getProtocol())) {
+                return Retry.NONE; // the scheme changed; don't retry.
+            }
+            if (previousUrl.getHost().equals(url.getHost())
+                    && previousUrl.getEffectivePort() == url.getEffectivePort()) {
+                return Retry.SAME_CONNECTION;
+            } else {
+                // TODO: strip cookies?
+                rawRequestHeaders.removeAll("Host");
+                return Retry.NEW_CONNECTION;
+            }
+
+        default:
+            return Retry.NONE;
         }
     }
 
+    private void setProxy(String proxy) {
+        // TODO: convert IllegalArgumentException etc. to ProtocolException?
+        int colon = proxy.indexOf(':');
+        String host;
+        int port;
+        if (colon != -1) {
+            host = proxy.substring(0, colon);
+            port = Integer.parseInt(proxy.substring(colon + 1));
+        } else {
+            host = proxy;
+            port = getDefaultPort();
+        }
+        this.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
+    }
+
     /**
      * React to a failed authorization response by looking up new credentials.
      */
-    private Retry processAuthHeader(String responseHeader, String retryHeader) throws IOException {
+    private Retry processAuthHeader(String fieldName, String value) throws IOException {
         // keep asking for username/password until authorized
-        String challenge = this.responseHeader.get(responseHeader);
+        String challenge = httpEngine.getResponseHeaders().get(fieldName);
         if (challenge == null) {
             throw new IOException("Received authentication challenge is null");
         }
@@ -1168,7 +404,7 @@
             return Retry.NONE; // could not find credentials, end request cycle
         }
         // add authorization credentials, bypassing the already-connected check
-        requestHeader.set(retryHeader, credentials);
+        rawRequestHeaders.set(value, credentials);
         return Retry.SAME_CONNECTION;
     }
 
@@ -1202,18 +438,70 @@
         return scheme + " " + encoded;
     }
 
-    private void setProxy(String proxy) {
-        // TODO: convert IllegalArgumentException etc. to ProtocolException?
-        int colon = proxy.indexOf(':');
-        String host;
-        int port;
-        if (colon != -1) {
-            host = proxy.substring(0, colon);
-            port = Integer.parseInt(proxy.substring(colon + 1));
-        } else {
-            host = proxy;
-            port = defaultPort;
+    private InetAddress getConnectToInetAddress() throws IOException {
+        return usingProxy()
+                ? ((InetSocketAddress) proxy.address()).getAddress()
+                : InetAddress.getByName(getURL().getHost());
+    }
+
+    final int getDefaultPort() {
+        return defaultPort;
+    }
+
+    /** @see HttpURLConnection#setFixedLengthStreamingMode(int) */
+    final int getFixedContentLength() {
+        return fixedContentLength;
+    }
+
+    /** @see HttpURLConnection#setChunkedStreamingMode(int) */
+    final int getChunkLength() {
+        return chunkLength;
+    }
+
+    final Proxy getProxy() {
+        return proxy;
+    }
+
+    final void setProxy(Proxy proxy) {
+        this.proxy = proxy;
+    }
+
+
+    @Override public final boolean usingProxy() {
+        return (proxy != null && proxy.type() != Proxy.Type.DIRECT);
+    }
+
+    @Override public String getResponseMessage() throws IOException {
+        return getResponse().getResponseHeaders().getResponseMessage();
+    }
+
+    @Override public final int getResponseCode() throws IOException {
+        return getResponse().getResponseHeaders().getResponseCode();
+    }
+
+    @Override public final void setIfModifiedSince(long newValue) {
+        // TODO: set this lazily in prepareRequestHeaders()
+        super.setIfModifiedSince(newValue);
+        rawRequestHeaders.add("If-Modified-Since", HttpDate.format(new Date(newValue)));
+    }
+
+    @Override public final void setRequestProperty(String field, String newValue) {
+        if (connected) {
+            throw new IllegalStateException("Cannot set request property after connection is made");
         }
-        this.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
+        if (field == null) {
+            throw new NullPointerException("field == null");
+        }
+        rawRequestHeaders.set(field, newValue);
+    }
+
+    @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");
+        }
+        rawRequestHeaders.add(field, value);
     }
 }
diff --git a/luni/src/main/java/libcore/net/http/HttpsHandler.java b/luni/src/main/java/libcore/net/http/HttpsHandler.java
index 142d95f..ed9ba72 100644
--- a/luni/src/main/java/libcore/net/http/HttpsHandler.java
+++ b/luni/src/main/java/libcore/net/http/HttpsHandler.java
@@ -23,26 +23,20 @@
 import java.net.URLConnection;
 import java.net.URLStreamHandler;
 
-/**
- * Handler for HttpsURLConnection implementation.
- */
-public class HttpsHandler extends URLStreamHandler {
+public final class HttpsHandler extends URLStreamHandler {
 
-    @Override
-    protected URLConnection openConnection(URL url) throws IOException {
+    @Override protected URLConnection openConnection(URL url) throws IOException {
         return new HttpsURLConnectionImpl(url, getDefaultPort());
     }
 
-    @Override
-    protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
+    @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
         if (url == null || proxy == null) {
             throw new IllegalArgumentException("url == null || proxy == null");
         }
         return new HttpsURLConnectionImpl(url, getDefaultPort(), proxy);
     }
 
-    @Override
-    protected int getDefaultPort() {
+    @Override protected int getDefaultPort() {
         return 443;
     }
 }
diff --git a/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java b/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
index 9f7ae90..46f74f4 100644
--- a/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
+++ b/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
@@ -36,383 +36,393 @@
 import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLSocket;
 
-/**
- * HttpsURLConnection implementation.
- */
-public class HttpsURLConnectionImpl extends HttpsURLConnection {
+final class HttpsURLConnectionImpl extends HttpsURLConnection {
 
-    /**
-     * HttpsEngine that allows reuse of HttpURLConnectionImpl
-     */
-    private final HttpsEngine httpsEngine;
-
-    /**
-     * Local stash of HttpsEngine.connection.sslSocket for answering
-     * queries such as getCipherSuite even after
-     * httpsEngine.Connection has been recycled. It's presense is also
-     * used to tell if the HttpsURLConnection is considered connected,
-     * as opposed to the connected field of URLConnection or the a
-     * non-null connect in HttpURLConnectionImpl
-    */
-    private SSLSocket sslSocket;
+    /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl */
+    private final HttpUrlConnectionDelegate delegate;
 
     protected HttpsURLConnectionImpl(URL url, int port) {
         super(url);
-        httpsEngine = new HttpsEngine(url, port);
+        delegate = new HttpUrlConnectionDelegate(url, port);
     }
 
     protected HttpsURLConnectionImpl(URL url, int port, Proxy proxy) {
         super(url);
-        httpsEngine = new HttpsEngine(url, port, proxy);
+        delegate = new HttpUrlConnectionDelegate(url, port, proxy);
     }
 
     private void checkConnected() {
-        if (sslSocket == null) {
+        if (delegate.getSSLSocket() == null) {
             throw new IllegalStateException("Connection has not yet been established");
         }
     }
 
     @Override
     public String getCipherSuite() {
-        SecureCacheResponse cacheResponse = httpsEngine.getCacheResponse();
+        SecureCacheResponse cacheResponse = delegate.getCacheResponse();
         if (cacheResponse != null) {
             return cacheResponse.getCipherSuite();
         }
         checkConnected();
-        return sslSocket.getSession().getCipherSuite();
+        return delegate.getSSLSocket().getSession().getCipherSuite();
     }
 
     @Override
     public Certificate[] getLocalCertificates() {
-        SecureCacheResponse cacheResponse = httpsEngine.getCacheResponse();
+        SecureCacheResponse cacheResponse = delegate.getCacheResponse();
         if (cacheResponse != null) {
             List<Certificate> result = cacheResponse.getLocalCertificateChain();
             return result != null ? result.toArray(new Certificate[result.size()]) : null;
         }
         checkConnected();
-        return sslSocket.getSession().getLocalCertificates();
+        return delegate.getSSLSocket().getSession().getLocalCertificates();
     }
 
     @Override
     public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
-        SecureCacheResponse cacheResponse = httpsEngine.getCacheResponse();
+        SecureCacheResponse cacheResponse = delegate.getCacheResponse();
         if (cacheResponse != null) {
             List<Certificate> result = cacheResponse.getServerCertificateChain();
             return result != null ? result.toArray(new Certificate[result.size()]) : null;
         }
         checkConnected();
-        return sslSocket.getSession().getPeerCertificates();
+        return delegate.getSSLSocket().getSession().getPeerCertificates();
     }
 
     @Override
     public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
-        SecureCacheResponse cacheResponse = httpsEngine.getCacheResponse();
+        SecureCacheResponse cacheResponse = delegate.getCacheResponse();
         if (cacheResponse != null) {
             return cacheResponse.getPeerPrincipal();
         }
         checkConnected();
-        return sslSocket.getSession().getPeerPrincipal();
+        return delegate.getSSLSocket().getSession().getPeerPrincipal();
     }
 
     @Override
     public Principal getLocalPrincipal() {
-        SecureCacheResponse cacheResponse = httpsEngine.getCacheResponse();
+        SecureCacheResponse cacheResponse = delegate.getCacheResponse();
         if (cacheResponse != null) {
             return cacheResponse.getLocalPrincipal();
         }
         checkConnected();
-        return sslSocket.getSession().getLocalPrincipal();
+        return delegate.getSSLSocket().getSession().getLocalPrincipal();
     }
 
     @Override
     public void disconnect() {
-        httpsEngine.disconnect();
+        delegate.disconnect();
     }
 
     @Override
     public InputStream getErrorStream() {
-        return httpsEngine.getErrorStream();
+        return delegate.getErrorStream();
     }
 
     @Override
     public String getRequestMethod() {
-        return httpsEngine.getRequestMethod();
+        return delegate.getRequestMethod();
     }
 
     @Override
     public int getResponseCode() throws IOException {
-        return httpsEngine.getResponseCode();
+        return delegate.getResponseCode();
     }
 
     @Override
     public String getResponseMessage() throws IOException {
-        return httpsEngine.getResponseMessage();
+        return delegate.getResponseMessage();
     }
 
     @Override
     public void setRequestMethod(String method) throws ProtocolException {
-        httpsEngine.setRequestMethod(method);
+        delegate.setRequestMethod(method);
     }
 
     @Override
     public boolean usingProxy() {
-        return httpsEngine.usingProxy();
+        return delegate.usingProxy();
     }
 
     @Override
     public boolean getInstanceFollowRedirects() {
-        return httpsEngine.getInstanceFollowRedirects();
+        return delegate.getInstanceFollowRedirects();
     }
 
     @Override
     public void setInstanceFollowRedirects(boolean followRedirects) {
-        httpsEngine.setInstanceFollowRedirects(followRedirects);
+        delegate.setInstanceFollowRedirects(followRedirects);
     }
 
     @Override
     public void connect() throws IOException {
         connected = true;
-        httpsEngine.connect();
+        delegate.connect();
     }
 
     @Override
     public boolean getAllowUserInteraction() {
-        return httpsEngine.getAllowUserInteraction();
+        return delegate.getAllowUserInteraction();
     }
 
     @Override
     public Object getContent() throws IOException {
-        return httpsEngine.getContent();
+        return delegate.getContent();
     }
 
     @SuppressWarnings("unchecked") // Spec does not generify
     @Override
     public Object getContent(Class[] types) throws IOException {
-        return httpsEngine.getContent(types);
+        return delegate.getContent(types);
     }
 
     @Override
     public String getContentEncoding() {
-        return httpsEngine.getContentEncoding();
+        return delegate.getContentEncoding();
     }
 
     @Override
     public int getContentLength() {
-        return httpsEngine.getContentLength();
+        return delegate.getContentLength();
     }
 
     @Override
     public String getContentType() {
-        return httpsEngine.getContentType();
+        return delegate.getContentType();
     }
 
     @Override
     public long getDate() {
-        return httpsEngine.getDate();
+        return delegate.getDate();
     }
 
     @Override
     public boolean getDefaultUseCaches() {
-        return httpsEngine.getDefaultUseCaches();
+        return delegate.getDefaultUseCaches();
     }
 
     @Override
     public boolean getDoInput() {
-        return httpsEngine.getDoInput();
+        return delegate.getDoInput();
     }
 
     @Override
     public boolean getDoOutput() {
-        return httpsEngine.getDoOutput();
+        return delegate.getDoOutput();
     }
 
     @Override
     public long getExpiration() {
-        return httpsEngine.getExpiration();
+        return delegate.getExpiration();
     }
 
     @Override
     public String getHeaderField(int pos) {
-        return httpsEngine.getHeaderField(pos);
+        return delegate.getHeaderField(pos);
     }
 
     @Override
     public Map<String, List<String>> getHeaderFields() {
-        return httpsEngine.getHeaderFields();
+        return delegate.getHeaderFields();
     }
 
     @Override
     public Map<String, List<String>> getRequestProperties() {
-        return httpsEngine.getRequestProperties();
+        return delegate.getRequestProperties();
     }
 
     @Override
     public void addRequestProperty(String field, String newValue) {
-        httpsEngine.addRequestProperty(field, newValue);
+        delegate.addRequestProperty(field, newValue);
     }
 
     @Override
     public String getHeaderField(String key) {
-        return httpsEngine.getHeaderField(key);
+        return delegate.getHeaderField(key);
     }
 
     @Override
     public long getHeaderFieldDate(String field, long defaultValue) {
-        return httpsEngine.getHeaderFieldDate(field, defaultValue);
+        return delegate.getHeaderFieldDate(field, defaultValue);
     }
 
     @Override
     public int getHeaderFieldInt(String field, int defaultValue) {
-        return httpsEngine.getHeaderFieldInt(field, defaultValue);
+        return delegate.getHeaderFieldInt(field, defaultValue);
     }
 
     @Override
     public String getHeaderFieldKey(int posn) {
-        return httpsEngine.getHeaderFieldKey(posn);
+        return delegate.getHeaderFieldKey(posn);
     }
 
     @Override
     public long getIfModifiedSince() {
-        return httpsEngine.getIfModifiedSince();
+        return delegate.getIfModifiedSince();
     }
 
     @Override
     public InputStream getInputStream() throws IOException {
-        return httpsEngine.getInputStream();
+        return delegate.getInputStream();
     }
 
     @Override
     public long getLastModified() {
-        return httpsEngine.getLastModified();
+        return delegate.getLastModified();
     }
 
     @Override
     public OutputStream getOutputStream() throws IOException {
-        return httpsEngine.getOutputStream();
+        return delegate.getOutputStream();
     }
 
     @Override
     public Permission getPermission() throws IOException {
-        return httpsEngine.getPermission();
+        return delegate.getPermission();
     }
 
     @Override
     public String getRequestProperty(String field) {
-        return httpsEngine.getRequestProperty(field);
+        return delegate.getRequestProperty(field);
     }
 
     @Override
     public URL getURL() {
-        return httpsEngine.getURL();
+        return delegate.getURL();
     }
 
     @Override
     public boolean getUseCaches() {
-        return httpsEngine.getUseCaches();
+        return delegate.getUseCaches();
     }
 
     @Override
     public void setAllowUserInteraction(boolean newValue) {
-        httpsEngine.setAllowUserInteraction(newValue);
+        delegate.setAllowUserInteraction(newValue);
     }
 
     @Override
     public void setDefaultUseCaches(boolean newValue) {
-        httpsEngine.setDefaultUseCaches(newValue);
+        delegate.setDefaultUseCaches(newValue);
     }
 
     @Override
     public void setDoInput(boolean newValue) {
-        httpsEngine.setDoInput(newValue);
+        delegate.setDoInput(newValue);
     }
 
     @Override
     public void setDoOutput(boolean newValue) {
-        httpsEngine.setDoOutput(newValue);
+        delegate.setDoOutput(newValue);
     }
 
     @Override
     public void setIfModifiedSince(long newValue) {
-        httpsEngine.setIfModifiedSince(newValue);
+        delegate.setIfModifiedSince(newValue);
     }
 
     @Override
     public void setRequestProperty(String field, String newValue) {
-        httpsEngine.setRequestProperty(field, newValue);
+        delegate.setRequestProperty(field, newValue);
     }
 
     @Override
     public void setUseCaches(boolean newValue) {
-        httpsEngine.setUseCaches(newValue);
+        delegate.setUseCaches(newValue);
     }
 
     @Override
     public void setConnectTimeout(int timeout) {
-        httpsEngine.setConnectTimeout(timeout);
+        delegate.setConnectTimeout(timeout);
     }
 
     @Override
     public int getConnectTimeout() {
-        return httpsEngine.getConnectTimeout();
+        return delegate.getConnectTimeout();
     }
 
     @Override
     public void setReadTimeout(int timeout) {
-        httpsEngine.setReadTimeout(timeout);
+        delegate.setReadTimeout(timeout);
     }
 
     @Override
     public int getReadTimeout() {
-        return httpsEngine.getReadTimeout();
+        return delegate.getReadTimeout();
     }
 
     @Override
     public String toString() {
-        return httpsEngine.toString();
+        return delegate.toString();
     }
 
     @Override
     public void setFixedLengthStreamingMode(int contentLength) {
-        httpsEngine.setFixedLengthStreamingMode(contentLength);
+        delegate.setFixedLengthStreamingMode(contentLength);
     }
 
     @Override
     public void setChunkedStreamingMode(int chunkLength) {
-        httpsEngine.setChunkedStreamingMode(chunkLength);
+        delegate.setChunkedStreamingMode(chunkLength);
     }
 
-    private final class HttpsEngine extends HttpURLConnectionImpl {
-
-        protected HttpsEngine(URL url, int port) {
+    private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
+        private HttpUrlConnectionDelegate(URL url, int port) {
             super(url, port);
         }
 
-        protected HttpsEngine(URL url, int port, Proxy proxy) {
+        private HttpUrlConnectionDelegate(URL url, int port, Proxy proxy) {
             super(url, port, proxy);
         }
 
-        @Override public void makeConnection() throws IOException {
-            connected = true;
+        @Override protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
+                HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
+            return new HttpsEngine(this, method, requestHeaders, connection, requestBody,
+                    HttpsURLConnectionImpl.this);
+        }
 
-            if (connection != null || responseBodyIn != null) {
-                return;
-            }
+        public SecureCacheResponse getCacheResponse() {
+            return (SecureCacheResponse) httpEngine.getCacheResponse();
+        }
 
-            /*
-             * Short-circuit a reentrant call. The first step in doing SSL with
-             * an HTTP proxy requires calling retrieveResponse() which calls
-             * back into makeConnection(). We can return immediately because the
-             * unencrypted connection is already valid.
-             */
-            if (method == CONNECT) {
-                return;
-            }
+        public SSLSocket getSSLSocket() {
+            HttpsEngine engine = (HttpsEngine) httpEngine;
+            return engine != null ? engine.sslSocket : null;
+        }
+    }
 
-            boolean connectionReused;
+    private static class HttpsEngine extends HttpEngine {
+
+        /**
+         * Local stash of HttpsEngine.connection.sslSocket for answering
+         * queries such as getCipherSuite even after
+         * httpsEngine.Connection has been recycled. It's presence is also
+         * used to tell if the HttpsURLConnection is considered connected,
+         * as opposed to the connected field of URLConnection or the a
+         * non-null connect in HttpURLConnectionImpl
+         */
+        private SSLSocket sslSocket;
+
+        private final HttpsURLConnectionImpl enclosing;
+
+        /**
+         * @param policy the HttpURLConnectionImpl with connection configuration
+         * @param enclosing the HttpsURLConnection with HTTPS features
+         */
+        private HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
+                HttpConnection connection, RetryableOutputStream requestBody,
+                HttpsURLConnectionImpl enclosing) throws IOException {
+            super(policy, method, requestHeaders, connection, requestBody);
+            this.sslSocket = connection != null ? connection.getSecureSocketIfConnected() : null;
+            this.enclosing = enclosing;
+        }
+
+        @Override protected void connect() throws IOException {
             // first try an SSL connection with compression and
             // various TLS extensions enabled, if it fails (and its
             // not unheard of that it will) fallback to a more
             // barebones connections
+            boolean connectionReused;
             try {
                 connectionReused = makeSslConnection(true);
             } catch (IOException e) {
@@ -427,9 +437,8 @@
             }
 
             if (!connectionReused) {
-                sslSocket = connection.verifySecureSocketHostname(getHostnameVerifier());
+                sslSocket = connection.verifySecureSocketHostname(enclosing.getHostnameVerifier());
             }
-            setUpTransportIO(connection);
         }
 
         /**
@@ -441,12 +450,12 @@
          * an SSL3 only fallback mode without compression.
          */
         private boolean makeSslConnection(boolean tlsTolerant) throws IOException {
-
-            super.makeConnection();
-
-            // if we got a response from the cache, we're done
-            if (cacheResponse != null) {
-                return true;
+            // make an SSL Tunnel on the first message pair of each SSL + proxy connection
+            if (connection == null) {
+                connection = openSocketConnection();
+                if (connection.getAddress().getProxy() != null) {
+                    makeTunnel(policy, connection, getRequestHeaders());
+                }
             }
 
             // if super.makeConnection returned a connection from the
@@ -460,63 +469,78 @@
                 return true;
             }
 
-            // make SSL Tunnel
-            if (requiresTunnel()) {
-                String originalMethod = method;
-                method = CONNECT;
-                intermediateResponse = true;
-                try {
-                    retrieveResponse();
-                    discardIntermediateResponse();
-                } finally {
-                    method = originalMethod;
-                    intermediateResponse = false;
-                }
-            }
-
-            connection.setupSecureSocket(getSSLSocketFactory(), tlsTolerant);
+            connection.setupSecureSocket(enclosing.getSSLSocketFactory(), tlsTolerant);
             return false;
         }
 
-        public SecureCacheResponse getCacheResponse() {
-            return (SecureCacheResponse) cacheResponse;
+        private void makeTunnel(HttpURLConnectionImpl policy, HttpConnection connection,
+                RawHeaders requestHeaders) throws IOException {
+            HttpEngine connect = new ProxyConnectEngine(policy, requestHeaders, connection);
+            connect.sendRequest();
+            connect.readResponse();
         }
 
-        @Override protected void setUpTransportIO(HttpConnection connection) throws IOException {
-            if (!requiresTunnel() && sslSocket == null) {
-                return; // don't initialize streams that won't be used
-            }
-            super.setUpTransportIO(connection);
-        }
-
-        @Override protected boolean requiresTunnel() {
-            return usingProxy();
-        }
-
-        @Override protected HttpURLConnection getConnectionForCaching() {
-            return HttpsURLConnectionImpl.this;
-        }
-
-        @Override protected boolean acceptCacheResponse(CacheResponse cacheResponse) {
+        @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
             return cacheResponse instanceof SecureCacheResponse;
         }
 
-        @Override protected String requestString() {
-            if (!usingProxy()) {
-                return super.requestString();
+        @Override protected boolean includeAuthorityInRequestLine() {
+            // Even if there is a proxy, it isn't involved. Always request just the file.
+            return false;
+        }
 
-            } else if (method == CONNECT) {
-                // SSL tunnels require host:port for the origin server
-                return url.getHost() + ":" + url.getEffectivePort();
+        @Override protected HttpURLConnection getHttpConnectionToCache() {
+            return enclosing;
+        }
+    }
 
-            } else {
-                // we has made SSL Tunneling, return /requested.data
-                String file = url.getFile();
-                if (file == null || file.length() == 0) {
-                    file = "/";
-                }
-                return file;
+    private static class ProxyConnectEngine extends HttpEngine {
+        public ProxyConnectEngine(HttpURLConnectionImpl policy, RawHeaders requestHeaders,
+                HttpConnection connection) throws IOException {
+            super(policy, HttpEngine.CONNECT, requestHeaders, null, null);
+            this.connection = connection;
+        }
+
+        /**
+         * If we're establishing an HTTPS tunnel with CONNECT (RFC 2817 5.2), send
+         * only the minimum set of headers. This avoids sending potentially
+         * sensitive data like HTTP cookies to the proxy unencrypted.
+         */
+        @Override protected RawHeaders getNetworkRequestHeaders() throws IOException {
+            RawHeaders privateHeaders = getRequestHeaders();
+            URL url = policy.getURL();
+
+            RawHeaders result = new RawHeaders();
+            result.setStatusLine("CONNECT " + url.getHost() + ":" + url.getEffectivePort()
+                    + " HTTP/1.1");
+
+            // Always set Host and User-Agent.
+            String host = privateHeaders.get("Host");
+            if (host == null) {
+                host = getOriginAddress(url);
             }
+            result.set("Host", host);
+
+            String userAgent = privateHeaders.get("User-Agent");
+            if (userAgent == null) {
+                userAgent = getDefaultUserAgent();
+            }
+            result.set("User-Agent", userAgent);
+
+            // Copy over the Proxy-Authorization header if it exists.
+            String proxyAuthorization = privateHeaders.get("Proxy-Authorization");
+            if (proxyAuthorization != null) {
+                result.set("Proxy-Authorization", proxyAuthorization);
+            }
+
+            // Always set the Proxy-Connection to Keep-Alive for the benefit of
+            // HTTP/1.0 proxies like Squid.
+            result.set("Proxy-Connection", "Keep-Alive");
+            return result;
+        }
+
+        @Override protected boolean requiresTunnel() {
+            return true;
         }
     }
 }
diff --git a/luni/src/main/java/libcore/net/http/RawHeaders.java b/luni/src/main/java/libcore/net/http/RawHeaders.java
new file mode 100644
index 0000000..a6ec3c3
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/RawHeaders.java
@@ -0,0 +1,274 @@
+/*
+ *  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 libcore.net.http;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+/**
+ * The HTTP status and unparsed header fields of a single HTTP message. Values
+ * are represented as uninterpreted strings; use {@link RequestHeaders} and
+ * {@link ResponseHeaders} for interpreted headers. This class maintains the
+ * order of the header fields within the HTTP message.
+ *
+ * <p>This class tracks fields 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.
+ */
+final class RawHeaders implements Cloneable {
+
+    private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() {
+        @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);
+            }
+        }
+    };
+
+    private final List<String> namesAndValues = new ArrayList<String>(20);
+    private String statusLine;
+    private int httpMinorVersion = 1;
+    private int responseCode = -1;
+    private String responseMessage;
+
+    public RawHeaders() {}
+
+    public RawHeaders(RawHeaders copyFrom) {
+        namesAndValues.addAll(copyFrom.namesAndValues);
+        statusLine = copyFrom.statusLine;
+        httpMinorVersion = copyFrom.httpMinorVersion;
+        responseCode = copyFrom.responseCode;
+        responseMessage = copyFrom.responseMessage;
+    }
+
+    /**
+     * Sets the response status line (like "HTTP/1.0 200 OK") or request line
+     * (like "GET / HTTP/1.1").
+     */
+    public void setStatusLine(String statusLine) {
+        this.statusLine = statusLine;
+
+        if (statusLine == null || !statusLine.startsWith("HTTP/")) {
+            return;
+        }
+        statusLine = statusLine.trim();
+        int mark = statusLine.indexOf(" ") + 1;
+        if (mark == 0) {
+            return;
+        }
+        if (statusLine.charAt(mark - 2) != '1') {
+            this.httpMinorVersion = 0;
+        }
+        int last = mark + 3;
+        if (last > statusLine.length()) {
+            last = statusLine.length();
+        }
+        this.responseCode = Integer.parseInt(statusLine.substring(mark, last));
+        if (last + 1 <= statusLine.length()) {
+            this.responseMessage = statusLine.substring(last + 1);
+        }
+    }
+
+    public String getStatusLine() {
+        return statusLine;
+    }
+
+    /**
+     * Returns the status line's HTTP minor version. This returns 0 for HTTP/1.0
+     * and 1 for HTTP/1.1. This returns 1 if the HTTP version is unknown.
+     */
+    public int getHttpMinorVersion() {
+        return httpMinorVersion != -1 ? httpMinorVersion : 1;
+    }
+
+    /**
+     * Returns the HTTP status code or -1 if it is unknown.
+     */
+    public int getResponseCode() {
+        return responseCode;
+    }
+
+    /**
+     * Returns the HTTP status message or null if it is unknown.
+     */
+    public String getResponseMessage() {
+        return responseMessage;
+    }
+
+    /**
+     * Add a field with the specified value.
+     */
+    public void add(String fieldName, String value) {
+        if (fieldName == null) {
+            throw new IllegalArgumentException("fieldName == null");
+        }
+        if (value == null) {
+            /*
+             * Given null values, the RI sends a malformed field line like
+             * "Accept\r\n". For platform compatibility and HTTP compliance, we
+             * print a warning and ignore null values.
+             */
+            System.logW("Ignoring HTTP header field '" + fieldName + "' because its value is null");
+            return;
+        }
+        namesAndValues.add(fieldName);
+        namesAndValues.add(value.trim());
+    }
+
+    public void removeAll(String fieldName) {
+        for (int i = 0; i < namesAndValues.size(); i += 2) {
+            if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) {
+                namesAndValues.remove(i); // field name
+                namesAndValues.remove(i); // value
+            }
+        }
+    }
+
+    public void addAll(String fieldName, List<String> headerFields) {
+        for (String value : headerFields) {
+            add(fieldName, value);
+        }
+    }
+
+    public void addIfAbsent(String fieldName, String value) {
+        if (get(fieldName) == null) {
+            add(fieldName, value);
+        }
+    }
+
+    /**
+     * 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 void set(String fieldName, String value) {
+        removeAll(fieldName);
+        add(fieldName, value);
+    }
+
+    /**
+     * Returns the number of field values.
+     */
+    public int length() {
+        return namesAndValues.size() / 2;
+    }
+
+    /**
+     * Returns the field at {@code position} or null if that is out of range.
+     */
+    public String getFieldName(int index) {
+        int fieldNameIndex = index * 2;
+        if (fieldNameIndex < 0 || fieldNameIndex >= namesAndValues.size()) {
+            return null;
+        }
+        return namesAndValues.get(fieldNameIndex);
+    }
+
+    /**
+     * Returns the value at {@code index} or null if that is out of range.
+     */
+    public String getValue(int index) {
+        int valueIndex = index * 2 + 1;
+        if (valueIndex < 0 || valueIndex >= namesAndValues.size()) {
+            return null;
+        }
+        return namesAndValues.get(valueIndex);
+    }
+
+    /**
+     * Returns the last value corresponding to the specified field, or null.
+     */
+    public String get(String fieldName) {
+        for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {
+            if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) {
+                return namesAndValues.get(i + 1);
+            }
+        }
+        return null;
+    }
+
+    public String toHeaderString() {
+        StringBuilder result = new StringBuilder(256);
+        result.append(statusLine).append("\r\n");
+        for (int i = 0; i < namesAndValues.size(); i += 2) {
+            result.append(namesAndValues.get(i)).append(": ")
+                    .append(namesAndValues.get(i + 1)).append("\r\n");
+        }
+        result.append("\r\n");
+        return result.toString();
+    }
+
+    /**
+     * Returns an immutable map containing each field to its list of values. The
+     * status line is mapped to null.
+     */
+    public Map<String, List<String>> toMultimap() {
+        Map<String, List<String>> result = new TreeMap<String, List<String>>(FIELD_NAME_COMPARATOR);
+        for (int i = 0; i < namesAndValues.size(); i += 2) {
+            String fieldName = namesAndValues.get(i);
+            String value = namesAndValues.get(i + 1);
+
+            List<String> allValues = new ArrayList<String>();
+            List<String> otherValues = result.get(fieldName);
+            if (otherValues != null) {
+                allValues.addAll(otherValues);
+            }
+            allValues.add(value);
+            result.put(fieldName, Collections.unmodifiableList(allValues));
+        }
+        if (statusLine != null) {
+            result.put(null, Collections.unmodifiableList(Collections.singletonList(statusLine)));
+        }
+        return Collections.unmodifiableMap(result);
+    }
+
+    /**
+     * Creates a new instance from the given map of fields to values. If
+     * present, the null field's last element will be used to set the status
+     * line.
+     */
+    public static RawHeaders fromMultimap(Map<String, List<String>> map) {
+        RawHeaders result = new RawHeaders();
+        for (Entry<String, List<String>> entry : map.entrySet()) {
+            String fieldName = entry.getKey();
+            List<String> values = entry.getValue();
+            if (fieldName != null) {
+                result.addAll(fieldName, values);
+            } else if (!values.isEmpty()) {
+                result.setStatusLine(values.get(values.size() - 1));
+            }
+        }
+        return result;
+    }
+}
diff --git a/luni/src/main/java/libcore/net/http/RequestHeaders.java b/luni/src/main/java/libcore/net/http/RequestHeaders.java
new file mode 100644
index 0000000..ce9354b
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/RequestHeaders.java
@@ -0,0 +1,97 @@
+/*
+ * 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 libcore.net.http;
+
+import java.net.URI;
+
+/**
+ * Parsed HTTP request headers.
+ */
+final class RequestHeaders {
+    final URI uri;
+    final RawHeaders headers;
+
+    /** Don't use a cache to satisfy this request. */
+    boolean noCache;
+    int maxAgeSeconds = -1;
+    int maxStaleSeconds = -1;
+    int minFreshSeconds = -1;
+
+    /**
+     * 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.
+     */
+    boolean onlyIfCached;
+    String noCacheField;
+
+    /**
+     * True if the request contains conditions that save the server from sending
+     * a response that the client has locally. When the caller adds conditions,
+     * this cache won't participate in the request.
+     */
+    boolean hasConditions;
+
+    /**
+     * True if the request contains an authorization field. Although this isn't
+     * necessarily a shared cache, it follows the spec's strict requirements for
+     * shared caches.
+     */
+    boolean hasAuthorization;
+
+    public RequestHeaders(URI uri, RawHeaders headers) {
+        this.uri = uri;
+        this.headers = headers;
+
+        HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
+            @Override public void handle(String directive, String parameter) {
+                if (directive.equalsIgnoreCase("no-cache")) {
+                    noCache = true;
+                    noCacheField = parameter;
+                } else if (directive.equalsIgnoreCase("max-age")) {
+                    maxAgeSeconds = HeaderParser.parseSeconds(parameter);
+                } else if (directive.equalsIgnoreCase("max-stale")) {
+                    maxStaleSeconds = HeaderParser.parseSeconds(parameter);
+                } else if (directive.equalsIgnoreCase("min-fresh")) {
+                    minFreshSeconds = HeaderParser.parseSeconds(parameter);
+                } else if (directive.equalsIgnoreCase("only-if-cached")) {
+                    onlyIfCached = true;
+                }
+            }
+        };
+
+        for (int i = 0; i < headers.length(); i++) {
+            String fieldName = headers.getFieldName(i);
+            String value = headers.getValue(i);
+            if ("Cache-Control".equalsIgnoreCase(fieldName)) {
+                HeaderParser.parseCacheControl(value, handler);
+            } else if ("Pragma".equalsIgnoreCase(fieldName)) {
+                if (value.equalsIgnoreCase("no-cache")) {
+                    noCache = true;
+                }
+            } else if ("If-None-Match".equalsIgnoreCase(fieldName)) {
+                hasConditions = true;
+            } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) {
+                hasConditions = true;
+            } else if ("Authorization".equalsIgnoreCase(fieldName)) {
+                hasAuthorization = true;
+            }
+        }
+    }
+}
diff --git a/luni/src/main/java/libcore/net/http/ResponseHeaders.java b/luni/src/main/java/libcore/net/http/ResponseHeaders.java
new file mode 100644
index 0000000..2ee5a58
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/ResponseHeaders.java
@@ -0,0 +1,323 @@
+/*
+ * 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 libcore.net.http;
+
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Parsed HTTP response headers.
+ */
+final class ResponseHeaders {
+
+    /** HTTP header name for the local time when the request was sent. */
+    public static final String SENT_MILLIS = "X-Android-Sent-Millis";
+
+    /** HTTP header name for the local time when the response was received. */
+    public static final String RECEIVED_MILLIS = "X-Android-Received-Millis";
+
+    final URI uri;
+    final RawHeaders headers;
+
+    /** The server's time when this response was served, if known. */
+    Date servedDate;
+
+    /** The last modified date of the response, if known. */
+    Date lastModified;
+
+    /**
+     * The expiration date of the response, if known. If both this field and the
+     * max age are set, the max age is preferred.
+     */
+    Date expires;
+
+    /**
+     * Extension header set by HttpURLConnectionImpl specifying the timestamp
+     * when the HTTP request was first initiated.
+     */
+    long sentRequestMillis;
+
+    /**
+     * Extension header set by HttpURLConnectionImpl specifying the timestamp
+     * when the HTTP response was first received.
+     */
+    long receivedResponseMillis;
+
+    /**
+     * In the 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.
+     */
+    boolean noCache;
+
+    /** If true, this response should not be cached. */
+    boolean noStore;
+
+    /**
+     * The duration past the response's served date that it can be served
+     * without validation.
+     */
+    int maxAgeSeconds = -1;
+
+    /**
+     * 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.
+     */
+    int sMaxAgeSeconds = -1;
+
+    /**
+     * This request header 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.
+     */
+    boolean isPublic;
+    String privateField;
+    boolean mustRevalidate;
+    String etag;
+    int ageSeconds = -1;
+
+    public ResponseHeaders(URI uri, RawHeaders headers) {
+        this.uri = uri;
+        this.headers = headers;
+
+        HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
+            @Override public void handle(String directive, String parameter) {
+                if (directive.equalsIgnoreCase("no-cache")) {
+                    noCache = true;
+                } else if (directive.equalsIgnoreCase("no-store")) {
+                    noStore = true;
+                } else if (directive.equalsIgnoreCase("max-age")) {
+                    maxAgeSeconds = HeaderParser.parseSeconds(parameter);
+                } else if (directive.equalsIgnoreCase("s-maxage")) {
+                    sMaxAgeSeconds = HeaderParser.parseSeconds(parameter);
+                } else if (directive.equalsIgnoreCase("public")) {
+                    isPublic = true;
+                } else if (directive.equalsIgnoreCase("private")) {
+                    privateField = parameter;
+                } else if (directive.equalsIgnoreCase("must-revalidate")) {
+                    mustRevalidate = true;
+                }
+            }
+        };
+
+        for (int i = 0; i < headers.length(); i++) {
+            String fieldName = headers.getFieldName(i);
+            String value = headers.getValue(i);
+            if ("Cache-Control".equalsIgnoreCase(fieldName)) {
+                HeaderParser.parseCacheControl(value, handler);
+            } else if ("Date".equalsIgnoreCase(fieldName)) {
+                servedDate = HttpDate.parse(value);
+            } else if ("Expires".equalsIgnoreCase(fieldName)) {
+                expires = HttpDate.parse(value);
+            } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
+                lastModified = HttpDate.parse(value);
+            } else if ("ETag".equalsIgnoreCase(fieldName)) {
+                etag = value;
+            } else if ("Pragma".equalsIgnoreCase(fieldName)) {
+                if (value.equalsIgnoreCase("no-cache")) {
+                    noCache = true;
+                }
+            } else if ("Age".equalsIgnoreCase(fieldName)) {
+                ageSeconds = HeaderParser.parseSeconds(value);
+            } else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) {
+                sentRequestMillis = Long.parseLong(value);
+            } else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
+                receivedResponseMillis = Long.parseLong(value);
+            }
+        }
+    }
+
+    /**
+     * Returns the current age of the response, in milliseconds. The calculation
+     * is specified by RFC 2616, 13.2.3 Age Calculations.
+     */
+    private long computeAge(long nowMillis) {
+        long apparentReceivedAge = servedDate != null
+                ? Math.max(0, receivedResponseMillis - servedDate.getTime())
+                : 0;
+        long receivedAge = ageSeconds != -1
+                ? Math.max(apparentReceivedAge, TimeUnit.SECONDS.toMillis(ageSeconds))
+                : apparentReceivedAge;
+        long responseDuration = receivedResponseMillis - sentRequestMillis;
+        long residentDuration = nowMillis - receivedResponseMillis;
+        return receivedAge + responseDuration + residentDuration;
+    }
+
+    /**
+     * Returns the number of milliseconds that the response was fresh for,
+     * starting from the served date.
+     */
+    private long computeFreshnessLifetime() {
+        if (maxAgeSeconds != -1) {
+            return TimeUnit.SECONDS.toMillis(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 && uri.getRawQuery() == 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 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 maxAgeSeconds == -1 && expires == null;
+    }
+
+    /**
+     * Returns true if this response can be stored to later serve another
+     * request.
+     */
+    public boolean isCacheable(RequestHeaders request) {
+        /*
+         * Always go to network for uncacheable response codes (RFC 2616, 13.4),
+         * This implementation doesn't support caching partial content.
+         */
+        int responseCode = headers.getResponseCode();
+        if (responseCode != HttpURLConnection.HTTP_OK
+                && responseCode != HttpURLConnection.HTTP_NOT_AUTHORITATIVE
+                && responseCode != HttpURLConnection.HTTP_MULT_CHOICE
+                && responseCode != HttpURLConnection.HTTP_MOVED_PERM
+                && responseCode != HttpURLConnection.HTTP_GONE) {
+            return false;
+        }
+
+        /*
+         * Responses to authorized requests aren't cacheable unless they include
+         * a 'public', 'must-revalidate' or 's-maxage' directive.
+         */
+        if (request.hasAuthorization
+                && !isPublic
+                && !mustRevalidate
+                && sMaxAgeSeconds == -1) {
+            return false;
+        }
+
+        if (noStore) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the source to satisfy {@code request} given this cached response.
+     */
+    public ResponseSource chooseResponseSource(long nowMillis, RequestHeaders request) {
+        /*
+         * 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(request)) {
+            return ResponseSource.NETWORK;
+        }
+
+        if (request.noCache || request.hasConditions) {
+            return ResponseSource.NETWORK;
+        }
+
+        long ageMillis = computeAge(nowMillis);
+        long freshMillis = computeFreshnessLifetime();
+
+        if (request.maxAgeSeconds != -1) {
+            freshMillis = Math.min(freshMillis,
+                    TimeUnit.SECONDS.toMillis(request.maxAgeSeconds));
+        }
+
+        long minFreshMillis = 0;
+        if (request.minFreshSeconds != -1) {
+            minFreshMillis = TimeUnit.SECONDS.toMillis(request.minFreshSeconds);
+        }
+
+        long maxStaleMillis = 0;
+        if (request.maxStaleSeconds != -1) {
+            maxStaleMillis = TimeUnit.SECONDS.toMillis(request.maxStaleSeconds);
+        }
+
+        if (!noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
+            if (ageMillis + minFreshMillis >= freshMillis) {
+                // TODO: this should be RESPONSE headers
+                request.headers.add("Warning", "110 HttpURLConnection \"Response is stale\"");
+            }
+            if (ageMillis > TimeUnit.HOURS.toMillis(24) && isFreshnessLifetimeHeuristic()) {
+                // TODO: this should be RESPONSE headers
+                request.headers.add("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
+            }
+            return ResponseSource.CACHE;
+        }
+
+        if (lastModified != null) {
+            request.headers.add("If-Modified-Since", HttpDate.format(lastModified));
+            request.hasConditions = true;
+        } else if (servedDate != null) {
+            request.headers.add("If-Modified-Since", HttpDate.format(servedDate));
+            request.hasConditions = true;
+        }
+
+        if (etag != null) {
+            request.headers.add("If-None-Match", etag);
+            request.hasConditions = true;
+        }
+
+        return request.hasConditions
+                ? ResponseSource.CONDITIONAL_CACHE
+                : ResponseSource.NETWORK;
+    }
+
+    /**
+     * Returns true if this cached response should be used; false if the
+     * network response should be used.
+     */
+    public boolean validate(ResponseHeaders networkResponse) {
+        if (networkResponse.headers.getResponseCode() == HttpURLConnection.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.
+         */
+        if (lastModified != null
+                && networkResponse.lastModified != null
+                && networkResponse.lastModified.getTime() < lastModified.getTime()) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/luni/src/main/java/libcore/net/http/ResponseSource.java b/luni/src/main/java/libcore/net/http/ResponseSource.java
new file mode 100644
index 0000000..731e37f
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/ResponseSource.java
@@ -0,0 +1,40 @@
+/*
+ * 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 libcore.net.http;
+
+enum ResponseSource {
+
+    /**
+     * Return the response from the cache immediately.
+     */
+    CACHE,
+
+    /**
+     * Make a conditional request to the host, returning the cache response if
+     * the cache is valid and the network response otherwise.
+     */
+    CONDITIONAL_CACHE,
+
+    /**
+     * Return the response from the network.
+     */
+    NETWORK;
+
+    public boolean requiresConnection() {
+        return this == CONDITIONAL_CACHE || this == NETWORK;
+    }
+}
diff --git a/luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java b/luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java
index b1167c9..a8224a3 100644
--- a/luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java
+++ b/luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java
@@ -28,8 +28,8 @@
     private boolean inputExhausted;
 
     UnknownLengthHttpInputStream(InputStream is, CacheRequest cacheRequest,
-            HttpURLConnectionImpl httpURLConnection) throws IOException {
-        super(is, httpURLConnection, cacheRequest);
+            HttpEngine httpEngine) throws IOException {
+        super(is, httpEngine, cacheRequest);
     }
 
     @Override public int read(byte[] buffer, int offset, int count) throws IOException {
diff --git a/luni/src/main/java/libcore/util/EmptyArray.java b/luni/src/main/java/libcore/util/EmptyArray.java
index d2ee4cf..2040ec1 100644
--- a/luni/src/main/java/libcore/util/EmptyArray.java
+++ b/luni/src/main/java/libcore/util/EmptyArray.java
@@ -28,4 +28,5 @@
     public static final Class<?>[] CLASS = new Class[0];
     public static final Object[] OBJECT = new Object[0];
     public static final String[] STRING = new String[0];
+    public static final Throwable[] THROWABLE = new Throwable[0];
 }
diff --git a/luni/src/main/java/org/apache/harmony/luni/platform/INetworkSystem.java b/luni/src/main/java/org/apache/harmony/luni/platform/INetworkSystem.java
index e5a511e..485d722 100644
--- a/luni/src/main/java/org/apache/harmony/luni/platform/INetworkSystem.java
+++ b/luni/src/main/java/org/apache/harmony/luni/platform/INetworkSystem.java
@@ -41,8 +41,7 @@
 
     public int writeDirect(FileDescriptor fd, int address, int offset, int count) throws IOException;
 
-    public boolean connectNonBlocking(FileDescriptor fd, InetAddress inetAddress, int port)
-            throws IOException;
+    public boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws IOException;
     public boolean isConnected(FileDescriptor fd, int timeout) throws IOException;
 
     public int send(FileDescriptor fd, byte[] data, int offset, int length,
@@ -59,9 +58,6 @@
 
     public void sendUrgentData(FileDescriptor fd, byte value);
 
-    public void connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeout)
-            throws SocketException;
-
     /**
      * Select the given file descriptors for read and write operations.
      *
@@ -96,17 +92,6 @@
             int numReadable, int numWritable, long timeout, int[] flags)
             throws SocketException;
 
-    /*
-     * Set the nominated socket option in the IP stack.
-     *
-     * @param fd the socket descriptor @param opt the option selector @param
-     * optVal the nominated option value
-     *
-     * @throws SocketException if the option is invalid or cannot be set
-     */
-    public void setSocketOption(FileDescriptor fd, int opt, Object optVal)
-            throws SocketException;
-
     /**
      * It is an error to close the same file descriptor from multiple threads
      * concurrently.
diff --git a/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java b/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java
index 3fc4ab4..e98b41cd 100644
--- a/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java
+++ b/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java
@@ -43,11 +43,7 @@
 
     public native void bind(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException;
 
-    public native void connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeout)
-            throws SocketException;
-
-    public native boolean connectNonBlocking(FileDescriptor fd, InetAddress inetAddress, int port)
-            throws IOException;
+    public native boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws IOException;
     public native boolean isConnected(FileDescriptor fd, int timeout) throws IOException;
 
     public native void disconnectDatagram(FileDescriptor fd) throws SocketException;
@@ -91,9 +87,6 @@
 
     public native void sendUrgentData(FileDescriptor fd, byte value);
 
-    public native void setSocketOption(FileDescriptor fd, int opt, Object optVal)
-            throws SocketException;
-
     public native void close(FileDescriptor fd) throws IOException;
 
     public native int write(FileDescriptor fd, byte[] data, int offset, int count)
diff --git a/luni/src/main/native/ErrorCode.cpp b/luni/src/main/native/ErrorCode.cpp
deleted file mode 100644
index 9947625..0000000
--- a/luni/src/main/native/ErrorCode.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
-*******************************************************************************
-* Copyright (C) 1996-2005, International Business Machines Corporation and    *
-* others. All Rights Reserved.                                                *
-*******************************************************************************
-*
-*******************************************************************************
-*/
-
-#include "ErrorCode.h"
-#include "JNIHelp.h"
-
-/**
-* Checks if an error has occurred, throwing a suitable exception if so.
-* @param env JNI environment
-* @param errorCode code to determine if it is an error
-* @return 0 if errorCode is not an error, 1 if errorCode is an error, but the
-*         creation of the exception to be thrown fails
- * @exception thrown if errorCode represents an error
-*/
-UBool icu4jni_error(JNIEnv* env, UErrorCode errorCode)
-{
-    const char* message = u_errorName(errorCode);
-    if (errorCode <= U_ZERO_ERROR || errorCode >= U_ERROR_LIMIT) {
-        return 0;
-    }
-
-    switch (errorCode) {
-    case U_ILLEGAL_ARGUMENT_ERROR:
-        return jniThrowException(env, "java/lang/IllegalArgumentException", message);
-    case U_INDEX_OUTOFBOUNDS_ERROR:
-    case U_BUFFER_OVERFLOW_ERROR:
-        return jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", message);
-    case U_UNSUPPORTED_ERROR:
-        return jniThrowException(env, "java/lang/UnsupportedOperationException", message);
-    default:
-        return jniThrowRuntimeException(env, message);
-    }
-}
diff --git a/luni/src/main/native/ErrorCode.h b/luni/src/main/native/ErrorCode.h
deleted file mode 100644
index 1599bc6..0000000
--- a/luni/src/main/native/ErrorCode.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
-*******************************************************************************
-* Copyright (C) 1996-2005, International Business Machines Corporation and    *
-* others. All Rights Reserved.                                                *
-*******************************************************************************
-*******************************************************************************
-*/
-
-#ifndef ERRORCODE_H
-#define ERRORCODE_H
-
-#include <jni.h>
-#include "unicode/utypes.h"
-#include "unicode/putil.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
-* Checks if an error has occured.
-* Throws a generic Java RuntimeException if an error has occured.
-* @param env JNI environment variable
-* @param errorcode code to determine if it is an erro
-* @return 0 if errorcode is not an error, 1 if errorcode is an error, but the
-*         creation of the exception to be thrown fails
-* @exception thrown if errorcode represents an error
-*/
-UBool icu4jni_error(JNIEnv* env, UErrorCode errorcode);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/luni/src/main/native/JniConstants.cpp b/luni/src/main/native/JniConstants.cpp
index 646bd3f..2681b47 100644
--- a/luni/src/main/native/JniConstants.cpp
+++ b/luni/src/main/native/JniConstants.cpp
@@ -32,15 +32,14 @@
 jclass JniConstants::fieldClass;
 jclass JniConstants::fieldPositionIteratorClass;
 jclass JniConstants::fileDescriptorClass;
+jclass JniConstants::gaiExceptionClass;
 jclass JniConstants::inetAddressClass;
 jclass JniConstants::inetSocketAddressClass;
 jclass JniConstants::inflaterClass;
 jclass JniConstants::integerClass;
-jclass JniConstants::interfaceAddressClass;
 jclass JniConstants::localeDataClass;
 jclass JniConstants::longClass;
 jclass JniConstants::methodClass;
-jclass JniConstants::multicastGroupRequestClass;
 jclass JniConstants::mutableIntClass;
 jclass JniConstants::mutableLongClass;
 jclass JniConstants::parsePositionClass;
@@ -50,7 +49,9 @@
 jclass JniConstants::socketImplClass;
 jclass JniConstants::stringArrayClass;
 jclass JniConstants::stringClass;
+jclass JniConstants::structAddrinfoClass;
 jclass JniConstants::structFlockClass;
+jclass JniConstants::structGroupReqClass;
 jclass JniConstants::structLingerClass;
 jclass JniConstants::structStatClass;
 jclass JniConstants::structStatFsClass;
@@ -81,15 +82,14 @@
     fieldClass = findClass(env, "java/lang/reflect/Field");
     fieldPositionIteratorClass = findClass(env, "libcore/icu/NativeDecimalFormat$FieldPositionIterator");
     fileDescriptorClass = findClass(env, "java/io/FileDescriptor");
+    gaiExceptionClass = findClass(env, "libcore/io/GaiException");
     inetAddressClass = findClass(env, "java/net/InetAddress");
     inetSocketAddressClass = findClass(env, "java/net/InetSocketAddress");
     inflaterClass = findClass(env, "java/util/zip/Inflater");
     integerClass = findClass(env, "java/lang/Integer");
-    interfaceAddressClass = findClass(env, "java/net/InterfaceAddress");
     localeDataClass = findClass(env, "libcore/icu/LocaleData");
     longClass = findClass(env, "java/lang/Long");
     methodClass = findClass(env, "java/lang/reflect/Method");
-    multicastGroupRequestClass = findClass(env, "java/net/MulticastGroupRequest");
     mutableIntClass = findClass(env, "libcore/util/MutableInt");
     mutableLongClass = findClass(env, "libcore/util/MutableLong");
     parsePositionClass = findClass(env, "java/text/ParsePosition");
@@ -99,7 +99,9 @@
     socketImplClass = findClass(env, "java/net/SocketImpl");
     stringArrayClass = findClass(env, "[Ljava/lang/String;");
     stringClass = findClass(env, "java/lang/String");
+    structAddrinfoClass = findClass(env, "libcore/io/StructAddrinfo");
     structFlockClass = findClass(env, "libcore/io/StructFlock");
+    structGroupReqClass = findClass(env, "libcore/io/StructGroupReq");
     structLingerClass = findClass(env, "libcore/io/StructLinger");
     structStatClass = findClass(env, "libcore/io/StructStat");
     structStatFsClass = findClass(env, "libcore/io/StructStatFs");
diff --git a/luni/src/main/native/JniConstants.h b/luni/src/main/native/JniConstants.h
index 2cc41a7..af5056b 100644
--- a/luni/src/main/native/JniConstants.h
+++ b/luni/src/main/native/JniConstants.h
@@ -54,15 +54,14 @@
     static jclass fieldClass;
     static jclass fieldPositionIteratorClass;
     static jclass fileDescriptorClass;
+    static jclass gaiExceptionClass;
     static jclass inetAddressClass;
     static jclass inetSocketAddressClass;
     static jclass inflaterClass;
     static jclass integerClass;
-    static jclass interfaceAddressClass;
     static jclass localeDataClass;
     static jclass longClass;
     static jclass methodClass;
-    static jclass multicastGroupRequestClass;
     static jclass mutableIntClass;
     static jclass mutableLongClass;
     static jclass parsePositionClass;
@@ -72,7 +71,9 @@
     static jclass socketImplClass;
     static jclass stringArrayClass;
     static jclass stringClass;
+    static jclass structAddrinfoClass;
     static jclass structFlockClass;
+    static jclass structGroupReqClass;
     static jclass structLingerClass;
     static jclass structStatClass;
     static jclass structStatFsClass;
diff --git a/luni/src/main/native/JniException.cpp b/luni/src/main/native/JniException.cpp
index 427427d..6626448 100644
--- a/luni/src/main/native/JniException.cpp
+++ b/luni/src/main/native/JniException.cpp
@@ -17,31 +17,33 @@
 #include "JniException.h"
 #include "JNIHelp.h"
 
+bool maybeThrowIcuException(JNIEnv* env, UErrorCode error) {
+    if (U_SUCCESS(error)) {
+        return false;
+    }
+    const char* message = u_errorName(error);
+    switch (error) {
+    case U_ILLEGAL_ARGUMENT_ERROR:
+        return jniThrowException(env, "java/lang/IllegalArgumentException", message);
+    case U_INDEX_OUTOFBOUNDS_ERROR:
+    case U_BUFFER_OVERFLOW_ERROR:
+        return jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", message);
+    case U_UNSUPPORTED_ERROR:
+        return jniThrowException(env, "java/lang/UnsupportedOperationException", message);
+    default:
+        return jniThrowRuntimeException(env, message);
+    }
+}
+
 void jniThrowExceptionWithErrno(JNIEnv* env, const char* exceptionClassName, int error) {
     char buf[BUFSIZ];
     jniThrowException(env, exceptionClassName, jniStrError(error, buf, sizeof(buf)));
 }
 
-void jniThrowBindException(JNIEnv* env, int error) {
-    jniThrowExceptionWithErrno(env, "java/net/BindException", error);
-}
-
-void jniThrowConnectException(JNIEnv* env, int error) {
-    jniThrowExceptionWithErrno(env, "java/net/ConnectException", error);
-}
-
 void jniThrowOutOfMemoryError(JNIEnv* env, const char* message) {
     jniThrowException(env, "java/lang/OutOfMemoryError", message);
 }
 
-void jniThrowSecurityException(JNIEnv* env, int error) {
-    jniThrowExceptionWithErrno(env, "java/lang/SecurityException", error);
-}
-
 void jniThrowSocketException(JNIEnv* env, int error) {
     jniThrowExceptionWithErrno(env, "java/net/SocketException", error);
 }
-
-void jniThrowSocketTimeoutException(JNIEnv* env, int error) {
-    jniThrowExceptionWithErrno(env, "java/net/SocketTimeoutException", error);
-}
diff --git a/luni/src/main/native/JniException.h b/luni/src/main/native/JniException.h
index c2e2556..b3447d2 100644
--- a/luni/src/main/native/JniException.h
+++ b/luni/src/main/native/JniException.h
@@ -18,14 +18,13 @@
 #define JNI_EXCEPTION_H_included
 
 #include "jni.h"
+#include "unicode/utypes.h" // For UErrorCode.
 
 void jniThrowExceptionWithErrno(JNIEnv* env, const char* exceptionClassName, int error);
 
-void jniThrowBindException(JNIEnv* env, int error);
-void jniThrowConnectException(JNIEnv* env, int error);
 void jniThrowOutOfMemoryError(JNIEnv* env, const char* message);
-void jniThrowSecurityException(JNIEnv* env, int error);
 void jniThrowSocketException(JNIEnv* env, int error);
-void jniThrowSocketTimeoutException(JNIEnv* env, int error);
+
+bool maybeThrowIcuException(JNIEnv* env, UErrorCode error);
 
 #endif  // JNI_EXCEPTION_H_included
diff --git a/luni/src/main/native/NetworkUtilities.cpp b/luni/src/main/native/NetworkUtilities.cpp
index 2e46935..26df02d 100644
--- a/luni/src/main/native/NetworkUtilities.cpp
+++ b/luni/src/main/native/NetworkUtilities.cpp
@@ -24,8 +24,9 @@
 #include <fcntl.h>
 #include <stdio.h>
 #include <string.h>
+#include <sys/socket.h>
 
-bool byteArrayToSocketAddress(JNIEnv* env, jclass, jbyteArray byteArray, int port, sockaddr_storage* ss) {
+static bool byteArrayToSocketAddress(JNIEnv* env, jbyteArray byteArray, int port, sockaddr_storage* ss) {
     if (byteArray == NULL) {
         jniThrowNullPointerException(env, NULL);
         return false;
@@ -59,7 +60,24 @@
     return true;
 }
 
-jbyteArray socketAddressToByteArray(JNIEnv* env, const sockaddr_storage* ss) {
+static jbyteArray socketAddressToByteArray(JNIEnv* env, const sockaddr_storage* ss) {
+    // Convert IPv4-mapped addresses to IPv4 addresses.
+    // The RI states "Java will never return an IPv4-mapped address".
+    sockaddr_storage tmp;
+    memset(&tmp, 0, sizeof(tmp));
+    const sockaddr_in6* sin6 = reinterpret_cast<const sockaddr_in6*>(ss);
+    if (ss->ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+        // Copy the IPv6 address into the temporary sockaddr_storage.
+        memcpy(&tmp, ss, sizeof(tmp));
+        // Unmap it into an IPv4 address.
+        sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&tmp);
+        sin->sin_family = AF_INET;
+        sin->sin_port = sin6->sin6_port;
+        memcpy(&sin->sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], 4);
+        // Fall through into the regular conversion using the unmapped address.
+        ss = &tmp;
+    }
+
     const void* rawAddress;
     size_t addressLength;
     if (ss->ss_family == AF_INET) {
@@ -87,7 +105,7 @@
     return byteArray;
 }
 
-jobject byteArrayToInetAddress(JNIEnv* env, jbyteArray byteArray) {
+static jobject byteArrayToInetAddress(JNIEnv* env, jbyteArray byteArray) {
     if (byteArray == NULL) {
         return NULL;
     }
@@ -112,7 +130,7 @@
     }
     static jfieldID fid = env->GetFieldID(JniConstants::inetAddressClass, "ipaddress", "[B");
     jbyteArray addressBytes = reinterpret_cast<jbyteArray>(env->GetObjectField(inetAddress, fid));
-    return byteArrayToSocketAddress(env, NULL, addressBytes, port, ss);
+    return byteArrayToSocketAddress(env, addressBytes, port, ss);
 }
 
 bool setBlocking(int fd, bool blocking) {
diff --git a/luni/src/main/native/NetworkUtilities.h b/luni/src/main/native/NetworkUtilities.h
index a3438c8..5e5b19e 100644
--- a/luni/src/main/native/NetworkUtilities.h
+++ b/luni/src/main/native/NetworkUtilities.h
@@ -15,18 +15,8 @@
  */
 
 #include "jni.h"
-
 #include <sys/socket.h>
 
-// Convert from byte[] to InetAddress.
-jobject byteArrayToInetAddress(JNIEnv* env, jbyteArray byteArray);
-
-// Convert from byte[] to sockaddr_storage.
-bool byteArrayToSocketAddress(JNIEnv* env, jclass, jbyteArray byteArray, int port, sockaddr_storage* ss);
-
-// Convert from sockaddr_storage to byte[].
-jbyteArray socketAddressToByteArray(JNIEnv* env, const sockaddr_storage* ss);
-
 // Convert from sockaddr_storage to InetAddress.
 jobject socketAddressToInetAddress(JNIEnv* env, const sockaddr_storage* ss);
 
@@ -34,5 +24,7 @@
 bool inetAddressToSocketAddress(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss);
 
 
+
 // Changes 'fd' to be blocking/non-blocking. Returns false and sets errno on failure.
+// @Deprecated - use IoUtils.setBlocking
 bool setBlocking(int fd, bool blocking);
diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp
index 0b7b1ff..e518944 100644
--- a/luni/src/main/native/Register.cpp
+++ b/luni/src/main/native/Register.cpp
@@ -31,8 +31,6 @@
 extern int register_java_lang_StrictMath(JNIEnv* env);
 extern int register_java_lang_System(JNIEnv* env);
 extern int register_java_math_NativeBN(JNIEnv* env);
-extern int register_java_net_InetAddress(JNIEnv* env);
-extern int register_java_net_NetworkInterface(JNIEnv* env);
 extern int register_java_nio_ByteOrder(JNIEnv* env);
 extern int register_java_nio_charset_Charsets(JNIEnv* env);
 extern int register_java_text_Bidi(JNIEnv* env);
@@ -62,7 +60,7 @@
 extern int register_org_apache_harmony_xnet_provider_jsse_NativeCrypto(JNIEnv* env);
 
 // DalvikVM calls this on startup, so we can statically register all our native methods.
-extern "C" int registerCoreLibrariesJni(JNIEnv* env) {
+int registerCoreLibrariesJni(JNIEnv* env) {
     ScopedLocalFrame localFrame(env);
 
     JniConstants::init(env);
@@ -78,8 +76,6 @@
             register_java_lang_StrictMath(env) != -1 &&
             register_java_lang_System(env) != -1 &&
             register_java_math_NativeBN(env) != -1 &&
-            register_java_net_InetAddress(env) != -1 &&
-            register_java_net_NetworkInterface(env) != -1 &&
             register_java_nio_ByteOrder(env) != -1 &&
             register_java_nio_charset_Charsets(env) != -1 &&
             register_java_text_Bidi(env) != -1 &&
diff --git a/luni/src/main/native/ifaddrs-android.cpp b/luni/src/main/native/ifaddrs-android.cpp
deleted file mode 100644
index 4193bdf..0000000
--- a/luni/src/main/native/ifaddrs-android.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2009 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.
- */
-
-#ifdef HAVE_ANDROID_OS
-
-#include "ifaddrs-android.h"
-
-#include <arpa/inet.h>
-#include <cstring>
-#include <errno.h>
-#include <net/if.h>
-#include <netinet/in.h>
-#include <new>
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <stdio.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-
-#include "ScopedFd.h"
-#include "UniquePtr.h"
-
-ifaddrs::ifaddrs(ifaddrs* next, sockaddr* ifa_addr, sockaddr* ifa_netmask)
-: ifa_next(next), ifa_name(NULL), ifa_flags(0), ifa_addr(ifa_addr), ifa_netmask(ifa_netmask)
-{
-}
-
-ifaddrs::~ifaddrs() {
-    delete ifa_next;
-    delete[] ifa_name;
-    delete ifa_addr;
-    delete ifa_netmask;
-}
-
-static bool sendNetlinkMessage(int fd, const void* data, size_t byteCount) {
-    ssize_t sentByteCount = TEMP_FAILURE_RETRY(send(fd, data, byteCount, 0));
-    return (sentByteCount == static_cast<ssize_t>(byteCount));
-}
-
-static ssize_t recvNetlinkMessage(int fd, char* buf, size_t byteCount) {
-    return TEMP_FAILURE_RETRY(recv(fd, buf, byteCount, 0));
-}
-
-// Returns a pointer to the first byte in the address data (which is
-// stored in network byte order).
-static uint8_t* sockaddrBytes(int family, sockaddr_storage* ss) {
-    if (family == AF_INET) {
-        sockaddr_in* ss4 = reinterpret_cast<sockaddr_in*>(ss);
-        return reinterpret_cast<uint8_t*>(&ss4->sin_addr);
-    } else if (family == AF_INET6) {
-        sockaddr_in6* ss6 = reinterpret_cast<sockaddr_in6*>(ss);
-        return reinterpret_cast<uint8_t*>(&ss6->sin6_addr);
-    }
-    return NULL;
-}
-
-// Netlink gives us the address family in the header, and the
-// sockaddr_in or sockaddr_in6 bytes as the payload. We need to
-// stitch the two bits together into the sockaddr that's part of
-// our portable interface.
-static sockaddr* toSocketAddress(int family, void* data, size_t byteCount) {
-    // Set the address proper...
-    sockaddr_storage* ss = new sockaddr_storage;
-    memset(ss, 0, sizeof(*ss));
-    ss->ss_family = family;
-    uint8_t* dst = sockaddrBytes(family, ss);
-    memcpy(dst, data, byteCount);
-    return reinterpret_cast<sockaddr*>(ss);
-}
-
-// Netlink gives us the prefix length as a bit count. We need to turn
-// that into a BSD-compatible netmask represented by a sockaddr*.
-static sockaddr* toNetmask(int family, size_t prefixLength) {
-    // ...and work out the netmask from the prefix length.
-    sockaddr_storage* ss = new sockaddr_storage;
-    memset(ss, 0, sizeof(*ss));
-    ss->ss_family = family;
-    uint8_t* dst = sockaddrBytes(family, ss);
-    memset(dst, 0xff, prefixLength / 8);
-    if ((prefixLength % 8) != 0) {
-        dst[prefixLength/8] = (0xff << (8 - (prefixLength % 8)));
-    }
-    return reinterpret_cast<sockaddr*>(ss);
-}
-
-// Sadly, we can't keep the interface index for portability with BSD.
-// We'll have to keep the name instead, and re-query the index when
-// we need it later.
-static bool setNameAndFlagsByIndex(ifaddrs* ifa, int interfaceIndex) {
-    // Get the name.
-    char buf[IFNAMSIZ];
-    char* name = if_indextoname(interfaceIndex, buf);
-    if (name == NULL) {
-        return false;
-    }
-    ifa->ifa_name = new char[strlen(name) + 1];
-    strcpy(ifa->ifa_name, name);
-
-    // Get the flags.
-    ScopedFd fd(socket(AF_INET, SOCK_DGRAM, 0));
-    if (fd.get() == -1) {
-        return false;
-    }
-    ifreq ifr;
-    memset(&ifr, 0, sizeof(ifr));
-    strcpy(ifr.ifr_name, name);
-    int rc = ioctl(fd.get(), SIOCGIFFLAGS, &ifr);
-    if (rc == -1) {
-        return false;
-    }
-    ifa->ifa_flags = ifr.ifr_flags;
-    return true;
-}
-
-// Source-compatible with the BSD function.
-int getifaddrs(ifaddrs** result) {
-    // Simplify cleanup for callers.
-    *result = NULL;
-
-    // Create a netlink socket.
-    ScopedFd fd(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE));
-    if (fd.get() < 0) {
-        return -1;
-    }
-
-    // Ask for the address information.
-    struct {
-        nlmsghdr netlinkHeader;
-        ifaddrmsg msg;
-    } request;
-    memset(&request, 0, sizeof(request));
-    request.netlinkHeader.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST | NLM_F_MATCH;
-    request.netlinkHeader.nlmsg_type = RTM_GETADDR;
-    request.netlinkHeader.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(request)));
-    request.msg.ifa_family = AF_UNSPEC; // All families.
-    request.msg.ifa_index = 0; // All interfaces.
-    if (!sendNetlinkMessage(fd.get(), &request, request.netlinkHeader.nlmsg_len)) {
-        return -1;
-    }
-
-    // Read the responses.
-    const size_t bufferSize = 65536;
-    UniquePtr<char[]> buf(new char[bufferSize]);
-    ssize_t bytesRead;
-    while ((bytesRead  = recvNetlinkMessage(fd.get(), &buf[0], bufferSize)) > 0) {
-        nlmsghdr* hdr = reinterpret_cast<nlmsghdr*>(&buf[0]);
-        for (; NLMSG_OK(hdr, (size_t)bytesRead); hdr = NLMSG_NEXT(hdr, bytesRead)) {
-            switch (hdr->nlmsg_type) {
-            case NLMSG_DONE:
-                return 0;
-            case NLMSG_ERROR:
-                return -1;
-            case RTM_NEWADDR:
-                {
-                    // A given RTM_NEWADDR payload may contain multiple addresses. The while loop
-                    // below iterates through them. These locals contain the best address we've
-                    // seen so far.
-                    int ifa_index = -1;
-                    UniquePtr<sockaddr> ifa_addr;
-                    UniquePtr<sockaddr> ifa_netmask;
-
-                    ifaddrmsg* address = reinterpret_cast<ifaddrmsg*>(NLMSG_DATA(hdr));
-                    rtattr* rta = IFA_RTA(address);
-                    size_t ifaPayloadLength = IFA_PAYLOAD(hdr);
-                    while (RTA_OK(rta, ifaPayloadLength)) {
-                        // We can't just use IFA_ADDRESS because it's the destination address for
-                        // a point-to-point interface; we can't just use IFA_LOCAL because we don't
-                        // always have it. That is: we want IFA_LOCAL if we get it, but IFA_ADDRESS
-                        // otherwise. We take advantage of the fact that the kernel returns
-                        // IFA_LOCAL (if available) second.
-                        if (rta->rta_type == IFA_LOCAL || rta->rta_type == IFA_ADDRESS) {
-                            int family = address->ifa_family;
-                            if (family == AF_INET || family == AF_INET6) {
-                                ifa_index = address->ifa_index;
-                                ifa_addr.reset(toSocketAddress(family, RTA_DATA(rta), RTA_PAYLOAD(rta)));
-                                ifa_netmask.reset(toNetmask(family, address->ifa_prefixlen));
-                            }
-                        }
-                        rta = RTA_NEXT(rta, ifaPayloadLength);
-                    }
-
-                    // Did we get a usable address? If so, thread it on our list.
-                    if (ifa_index != -1) {
-                        *result = new ifaddrs(*result, ifa_addr.release(), ifa_netmask.release());
-                        if (!setNameAndFlagsByIndex(*result, ifa_index)) {
-                            return -1;
-                        }
-                    }
-                }
-                break;
-            }
-        }
-    }
-    // We only get here if recv fails before we see a NLMSG_DONE.
-    return -1;
-}
-
-// Source-compatible with the BSD function.
-void freeifaddrs(ifaddrs* addresses) {
-    delete addresses;
-}
-
-#endif
diff --git a/luni/src/main/native/ifaddrs-android.h b/luni/src/main/native/ifaddrs-android.h
deleted file mode 100644
index c89d08a..0000000
--- a/luni/src/main/native/ifaddrs-android.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2009 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.
- */
-
-#ifndef IFADDRS_ANDROID_H_included
-#define IFADDRS_ANDROID_H_included
-
-struct sockaddr;
-
-// Android (bionic) doesn't have getifaddrs(3)/freeifaddrs(3).
-// We fake it here, so java_net_NetworkInterface.cpp can use that API.
-// This code should move into bionic, though.
-
-// Source-compatible subset of the BSD struct.
-struct ifaddrs {
-    // Pointer to next struct in list, or NULL at end.
-    ifaddrs* ifa_next;
-
-    // Interface name.
-    char* ifa_name;
-
-    // Interface flags.
-    unsigned int ifa_flags;
-
-    // Interface network address.
-    sockaddr* ifa_addr;
-
-    // Interface netmask.
-    sockaddr* ifa_netmask;
-
-    ifaddrs(ifaddrs* next, sockaddr* ifa_addr, sockaddr* ifa_netmask);
-
-    ~ifaddrs();
-
-private:
-    // Disallow copy and assignment.
-    ifaddrs(const ifaddrs&);
-    void operator=(const ifaddrs&);
-};
-
-int getifaddrs(ifaddrs** result);
-void freeifaddrs(ifaddrs* addresses);
-
-#endif  // IFADDRS_ANDROID_H_included
diff --git a/luni/src/main/native/java_io_File.cpp b/luni/src/main/native/java_io_File.cpp
index 20cbb7e..db88a11 100644
--- a/luni/src/main/native/java_io_File.cpp
+++ b/luni/src/main/native/java_io_File.cpp
@@ -20,7 +20,6 @@
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
-#include "ScopedFd.h"
 #include "ScopedPrimitiveArray.h"
 #include "ScopedUtfChars.h"
 #include "readlink.h"
diff --git a/luni/src/main/native/java_lang_ProcessManager.cpp b/luni/src/main/native/java_lang_ProcessManager.cpp
index 386b5f3..2a1638f 100644
--- a/luni/src/main/native/java_lang_ProcessManager.cpp
+++ b/luni/src/main/native/java_lang_ProcessManager.cpp
@@ -31,16 +31,6 @@
 #include "JniConstants.h"
 #include "utils/Log.h"
 
-/** Environment variables. */
-extern char **environ;
-
-static jmethodID onExitMethod = NULL;
-
-#ifdef ANDROID
-// Keeps track of the system properties fd so we don't close it.
-static int androidSystemPropertiesFd = -1;
-#endif
-
 /*
  * These are constants shared with the higher level code in
  * ProcessManager.java.
@@ -50,28 +40,17 @@
 #define WAIT_STATUS_STRANGE_ERRNO (-3) // observed an undocumented errno
 
 /**
- * Kills process with the given ID.
- */
-static void ProcessManager_kill(JNIEnv* env, jclass, jint pid) {
-    int result = kill((pid_t) pid, SIGKILL);
-    if (result == -1) {
-        jniThrowIOException(env, errno);
-    }
-}
-
-/**
  * Loops indefinitely and calls ProcessManager.onExit() when children exit.
  */
-static void ProcessManager_watchChildren(JNIEnv* env, jobject o) {
+static void ProcessManager_watchChildren(JNIEnv* env, jclass processManagerClass, jobject processManager) {
+    static jmethodID onExitMethod = env->GetMethodID(processManagerClass, "onExit", "(II)V");
     if (onExitMethod == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException",
-                "staticInitialize() must run first.");
+        return;
     }
 
-    while (1) {
+    while (true) {
+        // Wait for children in our process group.
         int status;
-
-        /* wait for children in our process group */
         pid_t pid = waitpid(0, &status, 0);
 
         if (pid >= 0) {
@@ -116,15 +95,14 @@
                      * wait() other than the two that are handled
                      * immediately above.
                      */
-                    LOGE("Error %d calling wait(): %s", errno,
-                            strerror(errno));
+                    LOGE("Error %d calling wait(): %s", errno, strerror(errno));
                     status = WAIT_STATUS_STRANGE_ERRNO;
                     break;
                 }
             }
         }
 
-        env->CallVoidMethod(o, onExitMethod, pid, status);
+        env->CallVoidMethod(processManager, onExitMethod, pid, status);
         if (env->ExceptionOccurred()) {
             /*
              * The callback threw, so break out of the loop and return,
@@ -136,17 +114,13 @@
 }
 
 /** Close all open fds > 2 (i.e. everything but stdin/out/err), != skipFd. */
-static void closeNonStandardFds(int skipFd) {
+static void closeNonStandardFds(int skipFd1, int skipFd2) {
     // TODO: rather than close all these non-open files, we could look in /proc/self/fd.
     rlimit rlimit;
     getrlimit(RLIMIT_NOFILE, &rlimit);
     const int max_fd = rlimit.rlim_max;
     for (int fd = 3; fd < max_fd; ++fd) {
-        if (fd != skipFd
-#ifdef ANDROID
-                && fd != androidSystemPropertiesFd
-#endif
-                ) {
+        if (fd != skipFd1 && fd != skipFd2) {
             close(fd);
         }
     }
@@ -156,8 +130,7 @@
 
 /** Closes all pipes in the given array. */
 static void closePipes(int pipes[], int skipFd) {
-    int i;
-    for (i = 0; i < PIPE_COUNT * 2; i++) {
+    for (int i = 0; i < PIPE_COUNT * 2; i++) {
         int fd = pipes[i];
         if (fd == -1) {
             return;
@@ -173,11 +146,17 @@
         const char* workingDirectory, jobject inDescriptor,
         jobject outDescriptor, jobject errDescriptor,
         jboolean redirectErrorStream) {
-    int i, result, error;
+
+    // Keep track of the system properties fd so we don't close it.
+    int androidSystemPropertiesFd = -1;
+    char* fdString = getenv("ANDROID_PROPERTY_WORKSPACE");
+    if (fdString) {
+        androidSystemPropertiesFd = atoi(fdString);
+    }
 
     // Create 4 pipes: stdin, stdout, stderr, and an exec() status pipe.
     int pipes[PIPE_COUNT * 2] = { -1, -1, -1, -1, -1, -1, -1, -1 };
-    for (i = 0; i < PIPE_COUNT; i++) {
+    for (int i = 0; i < PIPE_COUNT; i++) {
         if (pipe(pipes + i * 2) == -1) {
             jniThrowIOException(env, errno);
             closePipes(pipes, -1);
@@ -225,8 +204,8 @@
         // Make statusOut automatically close if execvp() succeeds.
         fcntl(statusOut, F_SETFD, FD_CLOEXEC);
 
-        // Close remaining open fds with the exception of statusOut.
-        closeNonStandardFds(statusOut);
+        // Close remaining unwanted open fds.
+        closeNonStandardFds(statusOut, androidSystemPropertiesFd);
 
         // Switch to working directory.
         if (workingDirectory != NULL) {
@@ -237,6 +216,7 @@
 
         // Set up environment.
         if (environment != NULL) {
+            extern char** environ; // Standard, but not in any header file.
             environ = environment;
         }
 
@@ -247,7 +227,7 @@
 
         // If we got here, execvp() failed or the working dir was invalid.
         execFailed:
-            error = errno;
+            int error = errno;
             write(statusOut, &error, sizeof(int));
             close(statusOut);
             exit(error);
@@ -264,6 +244,7 @@
     // Check status pipe for an error code. If execvp() succeeds, the other
     // end of the pipe should automatically close, in which case, we'll read
     // nothing.
+    int result;
     int count = read(statusIn, &result, sizeof(int));
     close(statusIn);
     if (count > 0) {
@@ -321,8 +302,7 @@
 /**
  * Converts Java String[] to char** and delegates to executeProcess().
  */
-static pid_t ProcessManager_exec(
-        JNIEnv* env, jclass, jobjectArray javaCommands,
+static pid_t ProcessManager_exec(JNIEnv* env, jclass, jobjectArray javaCommands,
         jobjectArray javaEnvironment, jstring javaWorkingDirectory,
         jobject inDescriptor, jobject outDescriptor, jobject errDescriptor,
         jboolean redirectErrorStream) {
@@ -339,8 +319,7 @@
     // Convert environment array.
     char** environment = convertStrings(env, javaEnvironment);
 
-    pid_t result = executeProcess(
-            env, commands, environment, workingDirectory,
+    pid_t result = executeProcess(env, commands, environment, workingDirectory,
             inDescriptor, outDescriptor, errDescriptor, redirectErrorStream);
 
     // Temporarily clear exception so we can clean up.
@@ -366,28 +345,8 @@
     return result;
 }
 
-/**
- * Looks up Java members.
- */
-static void ProcessManager_staticInitialize(JNIEnv* env,
-        jclass clazz) {
-#ifdef ANDROID
-    char* fdString = getenv("ANDROID_PROPERTY_WORKSPACE");
-    if (fdString) {
-        androidSystemPropertiesFd = atoi(fdString);
-    }
-#endif
-
-    onExitMethod = env->GetMethodID(clazz, "onExit", "(II)V");
-    if (onExitMethod == NULL) {
-        return;
-    }
-}
-
 static JNINativeMethod methods[] = {
-    NATIVE_METHOD(ProcessManager, kill, "(I)V"),
-    NATIVE_METHOD(ProcessManager, staticInitialize, "()V"),
-    NATIVE_METHOD(ProcessManager, watchChildren, "()V"),
+    NATIVE_METHOD(ProcessManager, watchChildren, "(Ljava/lang/ProcessManager;)V"),
     NATIVE_METHOD(ProcessManager, exec, "([Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Z)I"),
 };
 int register_java_lang_ProcessManager(JNIEnv* env) {
diff --git a/luni/src/main/native/java_net_InetAddress.cpp b/luni/src/main/native/java_net_InetAddress.cpp
deleted file mode 100644
index b85f870..0000000
--- a/luni/src/main/native/java_net_InetAddress.cpp
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2006 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.
- */
-
-#define LOG_TAG "InetAddress"
-
-#define LOG_DNS 0
-
-#include "JNIHelp.h"
-#include "JniConstants.h"
-#include "LocalArray.h"
-#include "NetworkUtilities.h"
-#include "ScopedLocalRef.h"
-#include "ScopedUtfChars.h"
-#include "jni.h"
-#include "utils/Log.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <netdb.h>
-#include <errno.h>
-
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <sys/socket.h>
-
-#if LOG_DNS
-static void logIpString(addrinfo* ai, const char* name)
-{
-    char ipString[INET6_ADDRSTRLEN];
-    int result = getnameinfo(ai->ai_addr, ai->ai_addrlen, ipString,
-                             sizeof(ipString), NULL, 0, NI_NUMERICHOST);
-    if (result == 0) {
-        LOGD("%s: %s (family %d, proto %d)", name, ipString, ai->ai_family,
-             ai->ai_protocol);
-    } else {
-        LOGE("%s: getnameinfo: %s", name, gai_strerror(result));
-    }
-}
-#else
-static inline void logIpString(addrinfo*, const char*)
-{
-}
-#endif
-
-static jobjectArray InetAddress_getaddrinfo(JNIEnv* env, jclass, jstring javaName) {
-    ScopedUtfChars name(env, javaName);
-    if (name.c_str() == NULL) {
-        return NULL;
-    }
-
-    addrinfo hints;
-    memset(&hints, 0, sizeof(hints));
-    hints.ai_family = AF_UNSPEC;
-    hints.ai_flags = AI_ADDRCONFIG;
-    /*
-     * If we don't specify a socket type, every address will appear twice, once
-     * for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
-     * anyway, just pick one.
-     */
-    hints.ai_socktype = SOCK_STREAM;
-
-    addrinfo* addressList = NULL;
-    jobjectArray addressArray = NULL;
-    int result = getaddrinfo(name.c_str(), NULL, &hints, &addressList);
-    if (result == 0 && addressList) {
-        // Count results so we know how to size the output array.
-        int addressCount = 0;
-        for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
-            if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
-                addressCount++;
-            }
-        }
-
-        // Prepare output array.
-        addressArray = env->NewObjectArray(addressCount, JniConstants::byteArrayClass, NULL);
-        if (addressArray == NULL) {
-            // Appropriate exception will be thrown.
-            LOGE("getaddrinfo: could not allocate array of size %i", addressCount);
-            freeaddrinfo(addressList);
-            return NULL;
-        }
-
-        // Examine returned addresses one by one, save them in the output array.
-        int index = 0;
-        for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
-            sockaddr* address = ai->ai_addr;
-            size_t addressLength = 0;
-            void* rawAddress;
-
-            switch (ai->ai_family) {
-                // Find the raw address length and start pointer.
-                case AF_INET6:
-                    addressLength = 16;
-                    rawAddress = &reinterpret_cast<sockaddr_in6*>(address)->sin6_addr.s6_addr;
-                    logIpString(ai, name.c_str());
-                    break;
-                case AF_INET:
-                    addressLength = 4;
-                    rawAddress = &reinterpret_cast<sockaddr_in*>(address)->sin_addr.s_addr;
-                    logIpString(ai, name.c_str());
-                    break;
-                default:
-                    // Unknown address family. Skip this address.
-                    LOGE("getaddrinfo: Unknown address family %d", ai->ai_family);
-                    continue;
-            }
-
-            // Convert each IP address into a Java byte array.
-            ScopedLocalRef<jbyteArray> byteArray(env, env->NewByteArray(addressLength));
-            if (byteArray.get() == NULL) {
-                // Out of memory error will be thrown on return.
-                LOGE("getaddrinfo: Can't allocate %d-byte array", addressLength);
-                addressArray = NULL;
-                break;
-            }
-            env->SetByteArrayRegion(byteArray.get(),
-                    0, addressLength, reinterpret_cast<jbyte*>(rawAddress));
-            env->SetObjectArrayElement(addressArray, index, byteArray.get());
-            index++;
-        }
-    } else if (result == EAI_SYSTEM && errno == EACCES) {
-        /* No permission to use network */
-        jniThrowException(env, "java/lang/SecurityException",
-                "Permission denied (maybe missing INTERNET permission)");
-    } else {
-        jniThrowExceptionFmt(env, "java/net/UnknownHostException",
-                "Unable to resolve host \"%s\": %s", name.c_str(), gai_strerror(result));
-    }
-
-    if (addressList) {
-        freeaddrinfo(addressList);
-    }
-
-    return addressArray;
-}
-
-/**
- * Looks up the name corresponding to an IP address.
- *
- * @param javaAddress: a byte array containing the raw IP address bytes. Must be
- *         4 or 16 bytes long.
- * @return the hostname.
- * @throws UnknownHostException: the IP address has no associated hostname.
- */
-static jstring InetAddress_getnameinfo(JNIEnv* env, jclass,
-                                         jbyteArray javaAddress)
-{
-    if (javaAddress == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return NULL;
-    }
-
-    // Convert the raw address bytes into a socket address structure.
-    sockaddr_storage ss;
-    memset(&ss, 0, sizeof(ss));
-
-    size_t socklen;
-    const size_t addressLength = env->GetArrayLength(javaAddress);
-    if (addressLength == 4) {
-        sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ss);
-        sin->sin_family = AF_INET;
-        socklen = sizeof(sockaddr_in);
-        jbyte* dst = reinterpret_cast<jbyte*>(&sin->sin_addr.s_addr);
-        env->GetByteArrayRegion(javaAddress, 0, 4, dst);
-    } else if (addressLength == 16) {
-        sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(&ss);
-        sin6->sin6_family = AF_INET6;
-        socklen = sizeof(sockaddr_in6);
-        jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr);
-        env->GetByteArrayRegion(javaAddress, 0, 16, dst);
-    } else {
-        // The caller already throws an exception in this case. Don't worry
-        // about it here.
-        return NULL;
-    }
-
-    // Look up the host name from the IP address.
-    char name[NI_MAXHOST];
-    int ret = getnameinfo(reinterpret_cast<sockaddr*>(&ss), socklen,
-                          name, sizeof(name), NULL, 0, NI_NAMEREQD);
-    if (ret != 0) {
-        jniThrowException(env, "java/net/UnknownHostException", gai_strerror(ret));
-        return NULL;
-    }
-
-    return env->NewStringUTF(name);
-}
-
-static jbyteArray InetAddress_ipStringToByteArray(JNIEnv* env, jobject, jstring javaString) {
-    // Convert the String to UTF-8 bytes.
-    ScopedUtfChars chars(env, javaString);
-    if (chars.c_str() == NULL) {
-        return NULL;
-    }
-    size_t byteCount = chars.size();
-    LocalArray<INET6_ADDRSTRLEN> bytes(byteCount + 1);
-    char* ipString = &bytes[0];
-    strcpy(ipString, chars.c_str());
-
-    // Accept IPv6 addresses (only) in square brackets for compatibility.
-    if (ipString[0] == '[' && ipString[byteCount - 1] == ']' && strchr(ipString, ':') != NULL) {
-        memmove(ipString, ipString + 1, byteCount - 2);
-        ipString[byteCount - 2] = '\0';
-    }
-
-    jbyteArray result = NULL;
-    addrinfo hints;
-    memset(&hints, 0, sizeof(hints));
-    hints.ai_flags = AI_NUMERICHOST;
-
-    sockaddr_storage ss;
-    memset(&ss, 0, sizeof(ss));
-
-    addrinfo* res = NULL;
-    int ret = getaddrinfo(ipString, NULL, &hints, &res);
-    if (ret == 0 && res) {
-        // Convert IPv4-mapped addresses to IPv4 addresses.
-        // The RI states "Java will never return an IPv4-mapped address".
-        sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(res->ai_addr);
-        if (res->ai_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
-            sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ss);
-            sin->sin_family = AF_INET;
-            sin->sin_port = sin6->sin6_port;
-            memcpy(&sin->sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], 4);
-            result = socketAddressToByteArray(env, &ss);
-        } else {
-            result = socketAddressToByteArray(env, reinterpret_cast<sockaddr_storage*>(res->ai_addr));
-        }
-    } else {
-        // For backwards compatibility, deal with address formats that
-        // getaddrinfo does not support. For example, 1.2.3, 1.3, and even 3 are
-        // valid IPv4 addresses according to the Java API. If getaddrinfo fails,
-        // try to use inet_aton.
-        sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ss);
-        if (inet_aton(ipString, &sin->sin_addr)) {
-            sin->sin_family = AF_INET;
-            sin->sin_port = 0;
-            result = socketAddressToByteArray(env, &ss);
-        }
-    }
-
-    if (res) {
-        freeaddrinfo(res);
-    }
-
-    if (!result) {
-        env->ExceptionClear();
-    }
-
-    return result;
-}
-
-static jstring InetAddress_byteArrayToIpString(JNIEnv* env, jobject, jbyteArray byteArray) {
-    if (byteArray == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return NULL;
-    }
-    sockaddr_storage ss;
-    if (!byteArrayToSocketAddress(env, NULL, byteArray, 0, &ss)) {
-        return NULL;
-    }
-    // TODO: getnameinfo seems to want its length parameter to be exactly
-    // sizeof(sockaddr_in) for an IPv4 address and sizeof (sockaddr_in6) for an
-    // IPv6 address. Fix getnameinfo so it accepts sizeof(sockaddr_storage), and
-    // then remove this hack.
-    int sa_size;
-    if (ss.ss_family == AF_INET) {
-        sa_size = sizeof(sockaddr_in);
-    } else if (ss.ss_family == AF_INET6) {
-        sa_size = sizeof(sockaddr_in6);
-    } else {
-        // byteArrayToSocketAddress already threw.
-        return NULL;
-    }
-    char ipString[INET6_ADDRSTRLEN];
-    int rc = getnameinfo(reinterpret_cast<sockaddr*>(&ss), sa_size,
-            ipString, sizeof(ipString), NULL, 0, NI_NUMERICHOST);
-    if (rc != 0) {
-        jniThrowException(env, "java/net/UnknownHostException", gai_strerror(rc));
-        return NULL;
-    }
-    return env->NewStringUTF(ipString);
-}
-
-static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(InetAddress, byteArrayToIpString, "([B)Ljava/lang/String;"),
-    NATIVE_METHOD(InetAddress, getaddrinfo, "(Ljava/lang/String;)[[B"),
-    NATIVE_METHOD(InetAddress, getnameinfo, "([B)Ljava/lang/String;"),
-    NATIVE_METHOD(InetAddress, ipStringToByteArray, "(Ljava/lang/String;)[B"),
-};
-int register_java_net_InetAddress(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "java/net/InetAddress", gMethods, NELEM(gMethods));
-}
diff --git a/luni/src/main/native/java_net_NetworkInterface.cpp b/luni/src/main/native/java_net_NetworkInterface.cpp
deleted file mode 100644
index f8f0ba4..0000000
--- a/luni/src/main/native/java_net_NetworkInterface.cpp
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- *  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.
- */
-
-#define LOG_TAG "NetworkInterface"
-
-#include "JNIHelp.h"
-#include "JniConstants.h"
-#include "JniException.h"
-#include "jni.h"
-#include "NetworkUtilities.h"
-#include "ScopedFd.h"
-
-#include <errno.h>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include <net/if.h> // Note: Can't appear before <sys/socket.h> on OS X.
-
-#ifdef HAVE_ANDROID_OS
-#include "ifaddrs-android.h"
-#else
-#include <ifaddrs.h>
-#endif
-
-// Ensures we always call freeifaddrs(3) to clean up after getifaddrs(3).
-class ScopedInterfaceAddresses {
-public:
-    ScopedInterfaceAddresses() : list(NULL) {
-    }
-
-    bool init() {
-        int rc = getifaddrs(&list);
-        return (rc != -1);
-    }
-
-    ~ScopedInterfaceAddresses() {
-        freeifaddrs(list);
-    }
-
-    ifaddrs* list;
-
-private:
-    // Disallow copy and assignment.
-    ScopedInterfaceAddresses(const ScopedInterfaceAddresses&);
-    void operator=(const ScopedInterfaceAddresses&);
-};
-
-static jobject makeInterfaceAddress(JNIEnv* env, jint interfaceIndex, ifaddrs* ifa) {
-    jmethodID constructor = env->GetMethodID(JniConstants::interfaceAddressClass, "<init>",
-            "(ILjava/lang/String;Ljava/net/InetAddress;Ljava/net/InetAddress;)V");
-    if (constructor == NULL) {
-        return NULL;
-    }
-    jobject javaName = env->NewStringUTF(ifa->ifa_name);
-    if (javaName == NULL) {
-        return NULL;
-    }
-    sockaddr_storage* addr = reinterpret_cast<sockaddr_storage*>(ifa->ifa_addr);
-    jobject javaAddress = socketAddressToInetAddress(env, addr);
-    if (javaAddress == NULL) {
-        return NULL;
-    }
-    sockaddr_storage* mask = reinterpret_cast<sockaddr_storage*>(ifa->ifa_netmask);
-    jobject javaMask = socketAddressToInetAddress(env, mask);
-    if (javaMask == NULL) {
-        return NULL;
-    }
-    return env->NewObject(JniConstants::interfaceAddressClass, constructor,
-            interfaceIndex, javaName, javaAddress, javaMask);
-}
-
-static jobjectArray NetworkInterface_getAllInterfaceAddressesImpl(JNIEnv* env, jclass) {
-    // Get the list of interface addresses.
-    ScopedInterfaceAddresses addresses;
-    if (!addresses.init()) {
-        jniThrowSocketException(env, errno);
-        return NULL;
-    }
-
-    // Count how many there are.
-    int interfaceAddressCount = 0;
-    for (ifaddrs* ifa = addresses.list; ifa != NULL; ifa = ifa->ifa_next) {
-        ++interfaceAddressCount;
-    }
-
-    // Build the InterfaceAddress[]...
-    jobjectArray result =
-            env->NewObjectArray(interfaceAddressCount, JniConstants::interfaceAddressClass, NULL);
-    if (result == NULL) {
-        return NULL;
-    }
-
-    // And fill it in...
-    int arrayIndex = 0;
-    for (ifaddrs* ifa = addresses.list; ifa != NULL; ifa = ifa->ifa_next) {
-        // We're only interested in IP addresses.
-        int family = ifa->ifa_addr->sa_family;
-        if (family != AF_INET && family != AF_INET6) {
-            continue;
-        }
-        // Until we implement Java 6's NetworkInterface.isUp,
-        // we only want interfaces that are up.
-        if ((ifa->ifa_flags & IFF_UP) == 0) {
-            continue;
-        }
-        // Find the interface's index, and skip this address if
-        // the interface has gone away.
-        int interfaceIndex = if_nametoindex(ifa->ifa_name);
-        if (interfaceIndex == 0) {
-            continue;
-        }
-        // Make a new InterfaceAddress, and insert it into the array.
-        jobject element = makeInterfaceAddress(env, interfaceIndex, ifa);
-        if (element == NULL) {
-            return NULL;
-        }
-        env->SetObjectArrayElement(result, arrayIndex, element);
-        if (env->ExceptionCheck()) {
-            return NULL;
-        }
-        ++arrayIndex;
-    }
-    return result;
-}
-
-static bool doIoctl(JNIEnv* env, jstring name, int request, ifreq& ifr) {
-    // Copy the name into the ifreq structure, if there's room...
-    jsize nameLength = env->GetStringLength(name);
-    if (nameLength >= IFNAMSIZ) {
-        errno = ENAMETOOLONG;
-        jniThrowSocketException(env, errno);
-        return false;
-    }
-    memset(&ifr, 0, sizeof(ifr));
-    env->GetStringUTFRegion(name, 0, nameLength, ifr.ifr_name);
-
-    // ...and do the ioctl.
-    ScopedFd fd(socket(AF_INET, SOCK_DGRAM, 0));
-    if (fd.get() == -1) {
-        jniThrowSocketException(env, errno);
-        return false;
-    }
-    int rc = ioctl(fd.get(), request, &ifr);
-    if (rc == -1) {
-        jniThrowSocketException(env, errno);
-        return false;
-    }
-    return true;
-}
-
-static jboolean hasFlag(JNIEnv* env, jstring name, int flag) {
-    ifreq ifr;
-    doIoctl(env, name, SIOCGIFFLAGS, ifr); // May throw.
-    return (ifr.ifr_flags & flag) != 0;
-}
-
-static jbyteArray NetworkInterface_getHardwareAddressImpl(JNIEnv* env, jclass, jstring name) {
-    ifreq ifr;
-    if (!doIoctl(env, name, SIOCGIFHWADDR, ifr)) {
-        return NULL;
-    }
-    jbyte bytes[IFHWADDRLEN];
-    bool isEmpty = true;
-    for (int i = 0; i < IFHWADDRLEN; ++i) {
-        bytes[i] = ifr.ifr_hwaddr.sa_data[i];
-        if (bytes[i] != 0) {
-            isEmpty = false;
-        }
-    }
-    if (isEmpty) {
-        return NULL;
-    }
-    jbyteArray result = env->NewByteArray(IFHWADDRLEN);
-    env->SetByteArrayRegion(result, 0, IFHWADDRLEN, bytes);
-    return result;
-}
-
-static jint NetworkInterface_getMTUImpl(JNIEnv* env, jclass, jstring name) {
-    ifreq ifr;
-    doIoctl(env, name, SIOCGIFMTU, ifr); // May throw.
-    return ifr.ifr_mtu;
-}
-
-static jboolean NetworkInterface_isLoopbackImpl(JNIEnv* env, jclass, jstring name) {
-    return hasFlag(env, name, IFF_LOOPBACK);
-}
-
-static jboolean NetworkInterface_isPointToPointImpl(JNIEnv* env, jclass, jstring name) {
-    return hasFlag(env, name, IFF_POINTOPOINT); // Unix API typo!
-}
-
-static jboolean NetworkInterface_isUpImpl(JNIEnv* env, jclass, jstring name) {
-    return hasFlag(env, name, IFF_UP);
-}
-
-static jboolean NetworkInterface_supportsMulticastImpl(JNIEnv* env, jclass, jstring name) {
-    return hasFlag(env, name, IFF_MULTICAST);
-}
-
-static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(NetworkInterface, getAllInterfaceAddressesImpl, "()[Ljava/net/InterfaceAddress;"),
-    NATIVE_METHOD(NetworkInterface, getHardwareAddressImpl, "(Ljava/lang/String;)[B"),
-    NATIVE_METHOD(NetworkInterface, getMTUImpl, "(Ljava/lang/String;)I"),
-    NATIVE_METHOD(NetworkInterface, isLoopbackImpl, "(Ljava/lang/String;)Z"),
-    NATIVE_METHOD(NetworkInterface, isPointToPointImpl, "(Ljava/lang/String;)Z"),
-    NATIVE_METHOD(NetworkInterface, isUpImpl, "(Ljava/lang/String;)Z"),
-    NATIVE_METHOD(NetworkInterface, supportsMulticastImpl, "(Ljava/lang/String;)Z"),
-};
-int register_java_net_NetworkInterface(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "java/net/NetworkInterface", gMethods, NELEM(gMethods));
-}
diff --git a/luni/src/main/native/java_text_Bidi.cpp b/luni/src/main/native/java_text_Bidi.cpp
index f44fe3d..27d3ddb 100644
--- a/luni/src/main/native/java_text_Bidi.cpp
+++ b/luni/src/main/native/java_text_Bidi.cpp
@@ -17,9 +17,9 @@
 
 #define LOG_TAG "Bidi"
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "JniException.h"
 #include "ScopedPrimitiveArray.h"
 #include "UniquePtr.h"
 #include "unicode/ubidi.h"
@@ -88,18 +88,18 @@
     }
     UErrorCode err = U_ZERO_ERROR;
     ubidi_setPara(data->uBiDi(), chars.get(), length, paraLevel, data->embeddingLevels(), &err);
-    icu4jni_error(env, err);
+    maybeThrowIcuException(env, err);
 }
 
 static jlong Bidi_ubidi_setLine(JNIEnv* env, jclass, jlong ptr, jint start, jint limit) {
-    UErrorCode err = U_ZERO_ERROR;
-    UBiDi* sized = ubidi_openSized(limit - start, 0, &err);
-    if (icu4jni_error(env, err) != FALSE) {
+    UErrorCode status = U_ZERO_ERROR;
+    UBiDi* sized = ubidi_openSized(limit - start, 0, &status);
+    if (maybeThrowIcuException(env, status)) {
         return 0;
     }
     UniquePtr<BiDiData> lineData(new BiDiData(sized));
-    ubidi_setLine(uBiDi(ptr), start, limit, lineData->uBiDi(), &err);
-    icu4jni_error(env, err);
+    ubidi_setLine(uBiDi(ptr), start, limit, lineData->uBiDi(), &status);
+    maybeThrowIcuException(env, status);
     return reinterpret_cast<uintptr_t>(lineData.release());
 }
 
@@ -116,9 +116,9 @@
 }
 
 static jbyteArray Bidi_ubidi_getLevels(JNIEnv* env, jclass, jlong ptr) {
-    UErrorCode err = U_ZERO_ERROR;
-    const UBiDiLevel* levels = ubidi_getLevels(uBiDi(ptr), &err);
-    if (icu4jni_error(env, err)) {
+    UErrorCode status = U_ZERO_ERROR;
+    const UBiDiLevel* levels = ubidi_getLevels(uBiDi(ptr), &status);
+    if (maybeThrowIcuException(env, status)) {
         return NULL;
     }
     int len = ubidi_getLength(uBiDi(ptr));
@@ -128,9 +128,9 @@
 }
 
 static jint Bidi_ubidi_countRuns(JNIEnv* env, jclass, jlong ptr) {
-    UErrorCode err = U_ZERO_ERROR;
-    int count = ubidi_countRuns(uBiDi(ptr), &err);
-    icu4jni_error(env, err);
+    UErrorCode status = U_ZERO_ERROR;
+    int count = ubidi_countRuns(uBiDi(ptr), &status);
+    maybeThrowIcuException(env, status);
     return count;
 }
 
@@ -139,9 +139,9 @@
  */
 static jobjectArray Bidi_ubidi_getRuns(JNIEnv* env, jclass, jlong ptr) {
     UBiDi* ubidi = uBiDi(ptr);
-    UErrorCode err = U_ZERO_ERROR;
-    int runCount = ubidi_countRuns(ubidi, &err);
-    if (icu4jni_error(env, err)) {
+    UErrorCode status = U_ZERO_ERROR;
+    int runCount = ubidi_countRuns(ubidi, &status);
+    if (maybeThrowIcuException(env, status)) {
         return NULL;
     }
     jmethodID bidiRunConstructor = env->GetMethodID(JniConstants::bidiRunClass, "<init>", "(III)V");
diff --git a/luni/src/main/native/java_util_regex_Matcher.cpp b/luni/src/main/native/java_util_regex_Matcher.cpp
index 2af1932..024a6f0 100644
--- a/luni/src/main/native/java_util_regex_Matcher.cpp
+++ b/luni/src/main/native/java_util_regex_Matcher.cpp
@@ -18,10 +18,9 @@
 
 #include <stdlib.h>
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
-#include "ScopedJavaUnicodeString.h"
+#include "JniException.h"
 #include "ScopedPrimitiveArray.h"
 #include "UniquePtr.h"
 #include "jni.h"
@@ -72,7 +71,7 @@
         if (mJavaInput) {
             mEnv->ReleaseStringChars(mJavaInput, mChars);
         }
-        icu4jni_error(mEnv, mStatus);
+        maybeThrowIcuException(mEnv, mStatus);
     }
 
     RegexMatcher* operator->() {
@@ -174,7 +173,7 @@
     RegexPattern* pattern = reinterpret_cast<RegexPattern*>(static_cast<uintptr_t>(patternAddr));
     UErrorCode status = U_ZERO_ERROR;
     RegexMatcher* result = pattern->matcher(status);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
     return static_cast<jint>(reinterpret_cast<uintptr_t>(result));
 }
 
diff --git a/luni/src/main/native/java_util_regex_Pattern.cpp b/luni/src/main/native/java_util_regex_Pattern.cpp
index 5541d33..1e0e1e3 100644
--- a/luni/src/main/native/java_util_regex_Pattern.cpp
+++ b/luni/src/main/native/java_util_regex_Pattern.cpp
@@ -18,7 +18,6 @@
 
 #include <stdlib.h>
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "ScopedJavaUnicodeString.h"
@@ -32,10 +31,41 @@
     return reinterpret_cast<RegexPattern*>(static_cast<uintptr_t>(addr));
 }
 
+static const char* regexDetailMessage(UErrorCode status) {
+    // These human-readable error messages were culled from "utypes.h", and then slightly tuned
+    // to make more sense in context.
+    // If we don't have a special-case, we'll just return the textual name of
+    // the enum value (such as U_REGEX_RULE_SYNTAX), which is better than nothing.
+    switch (status) {
+    case U_REGEX_INTERNAL_ERROR: return "An internal error was detected";
+    case U_REGEX_RULE_SYNTAX: return "Syntax error in regexp pattern";
+    case U_REGEX_INVALID_STATE: return "Matcher in invalid state for requested operation";
+    case U_REGEX_BAD_ESCAPE_SEQUENCE: return "Unrecognized backslash escape sequence in pattern";
+    case U_REGEX_PROPERTY_SYNTAX: return "Incorrect Unicode property";
+    case U_REGEX_UNIMPLEMENTED: return "Use of unimplemented feature";
+    case U_REGEX_MISMATCHED_PAREN: return "Incorrectly nested parentheses in regexp pattern";
+    case U_REGEX_NUMBER_TOO_BIG: return "Decimal number is too large";
+    case U_REGEX_BAD_INTERVAL: return "Error in {min,max} interval";
+    case U_REGEX_MAX_LT_MIN: return "In {min,max}, max is less than min";
+    case U_REGEX_INVALID_BACK_REF: return "Back-reference to a non-existent capture group";
+    case U_REGEX_INVALID_FLAG: return "Invalid value for match mode flags";
+    case U_REGEX_LOOK_BEHIND_LIMIT: return "Look-behind pattern matches must have a bounded maximum length";
+    case U_REGEX_SET_CONTAINS_STRING: return "Regular expressions cannot have UnicodeSets containing strings";
+    case U_REGEX_OCTAL_TOO_BIG: return "Octal character constants must be <= 0377.";
+    case U_REGEX_MISSING_CLOSE_BRACKET: return "Missing closing bracket in character class";
+    case U_REGEX_INVALID_RANGE: return "In a character range [x-y], x is greater than y";
+    case U_REGEX_STACK_OVERFLOW: return "Regular expression backtrack stack overflow";
+    case U_REGEX_TIME_OUT: return "Maximum allowed match time exceeded";
+    case U_REGEX_STOPPED_BY_CALLER: return "Matching operation aborted by user callback function";
+    default:
+        return u_errorName(status);
+    }
+}
+
 static void throwPatternSyntaxException(JNIEnv* env, UErrorCode status, jstring pattern, UParseError error) {
     static jmethodID method = env->GetMethodID(JniConstants::patternSyntaxExceptionClass,
             "<init>", "(Ljava/lang/String;Ljava/lang/String;I)V");
-    jstring message = env->NewStringUTF(u_errorName(status));
+    jstring message = env->NewStringUTF(regexDetailMessage(status));
     jclass exceptionClass = JniConstants::patternSyntaxExceptionClass;
     jobject exception = env->NewObject(exceptionClass, method, message, pattern, error.offset);
     env->Throw(reinterpret_cast<jthrowable>(exception));
diff --git a/luni/src/main/native/libcore_icu_ICU.cpp b/luni/src/main/native/libcore_icu_ICU.cpp
index f04238e..7329b8f 100644
--- a/luni/src/main/native/libcore_icu_ICU.cpp
+++ b/luni/src/main/native/libcore_icu_ICU.cpp
@@ -16,9 +16,9 @@
 
 #define LOG_TAG "ICU"
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "JniException.h"
 #include "ScopedFd.h"
 #include "ScopedJavaUnicodeString.h"
 #include "ScopedLocalRef.h"
@@ -551,7 +551,7 @@
     EnumerationCounter counter(uenum_count(e, &status));
     EnumerationGetter getter(e, &status);
     jobject result = toStringArray16(env, &counter, &getter);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
     uenum_close(e);
     return result;
 }
diff --git a/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp b/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp
index d430ede..18dca0d 100644
--- a/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp
+++ b/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp
@@ -18,7 +18,6 @@
 
 #include "JNIHelp.h"
 #include "JniConstants.h"
-#include "ErrorCode.h"
 #include "JniException.h"
 #include "ScopedUtfChars.h"
 #include "unicode/ubrk.h"
@@ -56,14 +55,14 @@
         size_t charCount = env->GetStringLength(mString);
         UErrorCode status = U_ZERO_ERROR;
         ubrk_setText(mIt, mChars, charCount, &status);
-        icu4jni_error(env, status);
+        maybeThrowIcuException(env, status);
     }
 
     BreakIteratorPeer* clone(JNIEnv* env) {
         UErrorCode status = U_ZERO_ERROR;
         jint bufferSize = U_BRK_SAFECLONE_BUFFERSIZE;
         UBreakIterator* it = ubrk_safeClone(mIt, NULL, &bufferSize, &status);
-        if (icu4jni_error(env, status)) {
+        if (maybeThrowIcuException(env, status)) {
             return NULL;
         }
         BreakIteratorPeer* result = new BreakIteratorPeer(it);
@@ -121,7 +120,7 @@
         return 0;
     }
     UBreakIterator* it = ubrk_open(type, localeChars.c_str(), NULL, 0, &status);
-    if (icu4jni_error(env, status)) {
+    if (maybeThrowIcuException(env, status)) {
         return NULL;
     }
     return (new BreakIteratorPeer(it))->toAddress();
diff --git a/luni/src/main/native/libcore_icu_NativeCollation.cpp b/luni/src/main/native/libcore_icu_NativeCollation.cpp
index 60c6488..fa46d59 100644
--- a/luni/src/main/native/libcore_icu_NativeCollation.cpp
+++ b/luni/src/main/native/libcore_icu_NativeCollation.cpp
@@ -9,10 +9,10 @@
 
 #define LOG_TAG "NativeCollation"
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
-#include "ScopedJavaUnicodeString.h"
+#include "JniException.h"
+#include "ScopedStringChars.h"
 #include "ScopedUtfChars.h"
 #include "UniquePtr.h"
 #include "ucol_imp.h"
@@ -35,27 +35,33 @@
     ucol_closeElements(toCollationElements(address));
 }
 
-static jint NativeCollation_compare(JNIEnv* env, jclass, jint address, jstring lhs0, jstring rhs0) {
-    ScopedJavaUnicodeString lhs(env, lhs0);
-    ScopedJavaUnicodeString rhs(env, rhs0);
-    return ucol_strcoll(toCollator(address),
-            lhs.unicodeString().getBuffer(), lhs.unicodeString().length(),
-            rhs.unicodeString().getBuffer(), rhs.unicodeString().length());
+static jint NativeCollation_compare(JNIEnv* env, jclass, jint address, jstring javaLhs, jstring javaRhs) {
+    ScopedStringChars lhs(env, javaLhs);
+    if (lhs.get() == NULL) {
+        return NULL;
+    }
+    ScopedStringChars rhs(env, javaRhs);
+    if (rhs.get() == NULL) {
+        return NULL;
+    }
+    return ucol_strcoll(toCollator(address), lhs.get(), lhs.size(), rhs.get(), rhs.size());
 }
 
 static jint NativeCollation_getAttribute(JNIEnv* env, jclass, jint address, jint type) {
     UErrorCode status = U_ZERO_ERROR;
     jint result = ucol_getAttribute(toCollator(address), (UColAttribute) type, &status);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
     return result;
 }
 
-static jint NativeCollation_getCollationElementIterator(JNIEnv* env, jclass, jint address, jstring source0) {
-    ScopedJavaUnicodeString source(env, source0);
+static jint NativeCollation_getCollationElementIterator(JNIEnv* env, jclass, jint address, jstring javaSource) {
+    ScopedStringChars source(env, javaSource);
+    if (source.get() == NULL) {
+        return -1;
+    }
     UErrorCode status = U_ZERO_ERROR;
-    UCollationElements* result = ucol_openElements(toCollator(address),
-            source.unicodeString().getBuffer(), source.unicodeString().length(), &status);
-    icu4jni_error(env, status);
+    UCollationElements* result = ucol_openElements(toCollator(address), source.get(), source.size(), &status);
+    maybeThrowIcuException(env, status);
     return static_cast<jint>(reinterpret_cast<uintptr_t>(result));
 }
 
@@ -73,20 +79,21 @@
     return env->NewString(rules, length);
 }
 
-static jbyteArray NativeCollation_getSortKey(JNIEnv* env, jclass, jint address, jstring source0) {
-    ScopedJavaUnicodeString source(env, source0);
+static jbyteArray NativeCollation_getSortKey(JNIEnv* env, jclass, jint address, jstring javaSource) {
+    ScopedStringChars source(env, javaSource);
+    if (source.get() == NULL) {
+        return NULL;
+    }
     const UCollator* collator  = toCollator(address);
     uint8_t byteArray[UCOL_MAX_BUFFER * 2];
     UniquePtr<uint8_t[]> largerByteArray;
     uint8_t* usedByteArray = byteArray;
-    const UChar* chars = source.unicodeString().getBuffer();
-    size_t charCount = source.unicodeString().length();
-    size_t byteArraySize = ucol_getSortKey(collator, chars, charCount, usedByteArray, sizeof(byteArray) - 1);
+    size_t byteArraySize = ucol_getSortKey(collator, source.get(), source.size(), usedByteArray, sizeof(byteArray) - 1);
     if (byteArraySize > sizeof(byteArray) - 1) {
         // didn't fit, try again with a larger buffer.
         largerByteArray.reset(new uint8_t[byteArraySize + 1]);
         usedByteArray = largerByteArray.get();
-        byteArraySize = ucol_getSortKey(collator, chars, charCount, usedByteArray, byteArraySize);
+        byteArraySize = ucol_getSortKey(collator, source.get(), source.size(), usedByteArray, byteArraySize);
     }
     if (byteArraySize == 0) {
         return NULL;
@@ -99,7 +106,7 @@
 static jint NativeCollation_next(JNIEnv* env, jclass, jint address) {
     UErrorCode status = U_ZERO_ERROR;
     jint result = ucol_next(toCollationElements(address), &status);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
     return result;
 }
 
@@ -110,23 +117,26 @@
     }
     UErrorCode status = U_ZERO_ERROR;
     UCollator* c = ucol_open(localeChars.c_str(), &status);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
     return static_cast<jint>(reinterpret_cast<uintptr_t>(c));
 }
 
-static jint NativeCollation_openCollatorFromRules(JNIEnv* env, jclass, jstring rules0, jint mode, jint strength) {
-    ScopedJavaUnicodeString rules(env, rules0);
+static jint NativeCollation_openCollatorFromRules(JNIEnv* env, jclass, jstring javaRules, jint mode, jint strength) {
+    ScopedStringChars rules(env, javaRules);
+    if (rules.get() == NULL) {
+        return -1;
+    }
     UErrorCode status = U_ZERO_ERROR;
-    UCollator* c = ucol_openRules(rules.unicodeString().getBuffer(), rules.unicodeString().length(),
+    UCollator* c = ucol_openRules(rules.get(), rules.size(),
             UColAttributeValue(mode), UCollationStrength(strength), NULL, &status);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
     return static_cast<jint>(reinterpret_cast<uintptr_t>(c));
 }
 
 static jint NativeCollation_previous(JNIEnv* env, jclass, jint address) {
     UErrorCode status = U_ZERO_ERROR;
     jint result = ucol_previous(toCollationElements(address), &status);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
     return result;
 }
 
@@ -138,28 +148,30 @@
     UErrorCode status = U_ZERO_ERROR;
     jint bufferSize = U_COL_SAFECLONE_BUFFERSIZE;
     UCollator* c = ucol_safeClone(toCollator(address), NULL, &bufferSize, &status);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
     return static_cast<jint>(reinterpret_cast<uintptr_t>(c));
 }
 
 static void NativeCollation_setAttribute(JNIEnv* env, jclass, jint address, jint type, jint value) {
     UErrorCode status = U_ZERO_ERROR;
     ucol_setAttribute(toCollator(address), (UColAttribute)type, (UColAttributeValue)value, &status);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
 }
 
 static void NativeCollation_setOffset(JNIEnv* env, jclass, jint address, jint offset) {
     UErrorCode status = U_ZERO_ERROR;
     ucol_setOffset(toCollationElements(address), offset, &status);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
 }
 
-static void NativeCollation_setText(JNIEnv* env, jclass, jint address, jstring source0) {
-    ScopedJavaUnicodeString source(env, source0);
+static void NativeCollation_setText(JNIEnv* env, jclass, jint address, jstring javaSource) {
+    ScopedStringChars source(env, javaSource);
+    if (source.get() == NULL) {
+        return;
+    }
     UErrorCode status = U_ZERO_ERROR;
-    ucol_setText(toCollationElements(address),
-            source.unicodeString().getBuffer(), source.unicodeString().length(), &status);
-    icu4jni_error(env, status);
+    ucol_setText(toCollationElements(address), source.get(), source.size(), &status);
+    maybeThrowIcuException(env, status);
 }
 
 static JNINativeMethod gMethods[] = {
diff --git a/luni/src/main/native/libcore_icu_NativeConverter.cpp b/luni/src/main/native/libcore_icu_NativeConverter.cpp
index 9ef1d04..6a7fbc5 100644
--- a/luni/src/main/native/libcore_icu_NativeConverter.cpp
+++ b/luni/src/main/native/libcore_icu_NativeConverter.cpp
@@ -15,9 +15,9 @@
 
 #define LOG_TAG "NativeConverter"
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "JniException.h"
 #include "ScopedLocalRef.h"
 #include "ScopedPrimitiveArray.h"
 #include "ScopedStringChars.h"
@@ -72,9 +72,9 @@
     if (converterNameChars.c_str() == NULL) {
         return 0;
     }
-    UErrorCode errorCode = U_ZERO_ERROR;
-    UConverter* cnv = ucnv_open(converterNameChars.c_str(), &errorCode);
-    icu4jni_error(env, errorCode);
+    UErrorCode status = U_ZERO_ERROR;
+    UConverter* cnv = ucnv_open(converterNameChars.c_str(), &status);
+    maybeThrowIcuException(env, status);
     return reinterpret_cast<uintptr_t>(cnv);
 }
 
diff --git a/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp b/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp
index 51113fa..96ffad8 100644
--- a/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp
+++ b/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp
@@ -16,11 +16,12 @@
 
 #define LOG_TAG "NativeDecimalFormat"
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "JniException.h"
 #include "ScopedJavaUnicodeString.h"
 #include "ScopedPrimitiveArray.h"
+#include "ScopedStringChars.h"
 #include "ScopedUtfChars.h"
 #include "UniquePtr.h"
 #include "cutils/log.h"
@@ -110,7 +111,7 @@
     if (fmt == NULL) {
         delete symbols;
     }
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
     return static_cast<jint>(reinterpret_cast<uintptr_t>(fmt));
 }
 
@@ -125,12 +126,14 @@
 }
 
 static void NativeDecimalFormat_setSymbol(JNIEnv* env, jclass, jint addr, jint javaSymbol, jstring javaValue) {
-    ScopedJavaUnicodeString value(env, javaValue);
-    UnicodeString& s(value.unicodeString());
+    ScopedStringChars value(env, javaValue);
+    if (value.get() == NULL) {
+        return;
+    }
     UErrorCode status = U_ZERO_ERROR;
     UNumberFormatSymbol symbol = static_cast<UNumberFormatSymbol>(javaSymbol);
-    unum_setSymbol(toUNumberFormat(addr), symbol, s.getBuffer(), s.length(), &status);
-    icu4jni_error(env, status);
+    unum_setSymbol(toUNumberFormat(addr), symbol, value.get(), value.size(), &status);
+    maybeThrowIcuException(env, status);
 }
 
 static void NativeDecimalFormat_setAttribute(JNIEnv*, jclass, jint addr, jint javaAttr, jint value) {
@@ -144,12 +147,14 @@
 }
 
 static void NativeDecimalFormat_setTextAttribute(JNIEnv* env, jclass, jint addr, jint javaAttr, jstring javaValue) {
-    ScopedJavaUnicodeString value(env, javaValue);
-    UnicodeString& s(value.unicodeString());
+    ScopedStringChars value(env, javaValue);
+    if (value.get() == NULL) {
+        return;
+    }
     UErrorCode status = U_ZERO_ERROR;
     UNumberFormatTextAttribute attr = static_cast<UNumberFormatTextAttribute>(javaAttr);
-    unum_setTextAttribute(toUNumberFormat(addr), attr, s.getBuffer(), s.length(), &status);
-    icu4jni_error(env, status);
+    unum_setTextAttribute(toUNumberFormat(addr), attr, value.get(), value.size(), &status);
+    maybeThrowIcuException(env, status);
 }
 
 static jstring NativeDecimalFormat_getTextAttribute(JNIEnv* env, jclass, jint addr, jint javaAttr) {
@@ -168,7 +173,7 @@
         chars.reset(new UChar[charCount]);
         charCount = unum_getTextAttribute(fmt, attr, chars.get(), charCount, &status);
     }
-    return icu4jni_error(env, status) ? NULL : env->NewString(chars.get(), charCount);
+    return maybeThrowIcuException(env, status) ? NULL : env->NewString(chars.get(), charCount);
 }
 
 static void NativeDecimalFormat_applyPatternImpl(JNIEnv* env, jclass, jint addr, jboolean localized, jstring pattern0) {
@@ -184,7 +189,7 @@
     } else {
         fmt->applyPattern(pattern.unicodeString(), status);
     }
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
 }
 
 static jstring NativeDecimalFormat_toPatternImpl(JNIEnv* env, jclass, jint addr, jboolean localized) {
diff --git a/luni/src/main/native/libcore_icu_NativeIDN.cpp b/luni/src/main/native/libcore_icu_NativeIDN.cpp
index 118bd7a..3d0cba3 100644
--- a/luni/src/main/native/libcore_icu_NativeIDN.cpp
+++ b/luni/src/main/native/libcore_icu_NativeIDN.cpp
@@ -16,10 +16,9 @@
 
 #define LOG_TAG "NativeIDN"
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
-#include "ScopedJavaUnicodeString.h"
+#include "ScopedStringChars.h"
 #include "unicode/uidna.h"
 
 static bool isLabelSeparator(const UChar ch) {
@@ -33,15 +32,16 @@
     }
 }
 
-static jstring NativeIDN_convertImpl(JNIEnv* env, jclass, jstring s, jint flags, jboolean toAscii) {
-    ScopedJavaUnicodeString sus(env, s);
-    const UChar* src = sus.unicodeString().getBuffer();
-    const size_t srcLength = sus.unicodeString().length();
+static jstring NativeIDN_convertImpl(JNIEnv* env, jclass, jstring javaSrc, jint flags, jboolean toAscii) {
+    ScopedStringChars src(env, javaSrc);
+    if (src.get() == NULL) {
+        return NULL;
+    }
     UChar dst[256];
     UErrorCode status = U_ZERO_ERROR;
     size_t resultLength = toAscii
-            ? uidna_IDNToASCII(src, srcLength, &dst[0], sizeof(dst), flags, NULL, &status)
-            : uidna_IDNToUnicode(src, srcLength, &dst[0], sizeof(dst), flags, NULL, &status);
+        ? uidna_IDNToASCII(src.get(), src.size(), &dst[0], sizeof(dst), flags, NULL, &status)
+        : uidna_IDNToUnicode(src.get(), src.size(), &dst[0], sizeof(dst), flags, NULL, &status);
     if (U_FAILURE(status)) {
         jniThrowException(env, "java/lang/IllegalArgumentException", u_errorName(status));
         return NULL;
diff --git a/luni/src/main/native/libcore_icu_NativeNormalizer.cpp b/luni/src/main/native/libcore_icu_NativeNormalizer.cpp
index 244c5be..57f31f4 100644
--- a/luni/src/main/native/libcore_icu_NativeNormalizer.cpp
+++ b/luni/src/main/native/libcore_icu_NativeNormalizer.cpp
@@ -16,28 +16,28 @@
 
 #define LOG_TAG "NativeNormalizer"
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "JniException.h"
 #include "ScopedJavaUnicodeString.h"
 #include "unicode/normlzr.h"
 
 static jstring NativeNormalizer_normalizeImpl(JNIEnv* env, jclass, jstring s, jint intMode) {
     ScopedJavaUnicodeString src(env, s);
     UNormalizationMode mode = static_cast<UNormalizationMode>(intMode);
-    UErrorCode errorCode = U_ZERO_ERROR;
+    UErrorCode status = U_ZERO_ERROR;
     UnicodeString dst;
-    Normalizer::normalize(src.unicodeString(), mode, 0, dst, errorCode);
-    icu4jni_error(env, errorCode);
+    Normalizer::normalize(src.unicodeString(), mode, 0, dst, status);
+    maybeThrowIcuException(env, status);
     return dst.isBogus() ? NULL : env->NewString(dst.getBuffer(), dst.length());
 }
 
 static jboolean NativeNormalizer_isNormalizedImpl(JNIEnv* env, jclass, jstring s, jint intMode) {
     ScopedJavaUnicodeString src(env, s);
     UNormalizationMode mode = static_cast<UNormalizationMode>(intMode);
-    UErrorCode errorCode = U_ZERO_ERROR;
-    UBool result = Normalizer::isNormalized(src.unicodeString(), mode, errorCode);
-    icu4jni_error(env, errorCode);
+    UErrorCode status = U_ZERO_ERROR;
+    UBool result = Normalizer::isNormalized(src.unicodeString(), mode, status);
+    maybeThrowIcuException(env, status);
     return result;
 }
 
diff --git a/luni/src/main/native/libcore_icu_NativePluralRules.cpp b/luni/src/main/native/libcore_icu_NativePluralRules.cpp
index 5a74be9..3a443d7 100644
--- a/luni/src/main/native/libcore_icu_NativePluralRules.cpp
+++ b/luni/src/main/native/libcore_icu_NativePluralRules.cpp
@@ -16,9 +16,9 @@
 
 #define LOG_TAG "NativePluralRules"
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "JniException.h"
 #include "ScopedUtfChars.h"
 #include "unicode/plurrule.h"
 
@@ -34,7 +34,7 @@
     Locale locale = Locale::createFromName(ScopedUtfChars(env, localeName).c_str());
     UErrorCode status = U_ZERO_ERROR;
     PluralRules* result = PluralRules::forLocale(locale, status);
-    icu4jni_error(env, status);
+    maybeThrowIcuException(env, status);
     return reinterpret_cast<uintptr_t>(result);
 }
 
diff --git a/luni/src/main/native/libcore_icu_TimeZones.cpp b/luni/src/main/native/libcore_icu_TimeZones.cpp
index e8d54cc..9da108d 100644
--- a/luni/src/main/native/libcore_icu_TimeZones.cpp
+++ b/luni/src/main/native/libcore_icu_TimeZones.cpp
@@ -19,9 +19,9 @@
 #include <map>
 #include <vector>
 
-#include "ErrorCode.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "JniException.h"
 #include "ScopedJavaUnicodeString.h"
 #include "ScopedLocalRef.h"
 #include "ScopedUtfChars.h"
@@ -43,16 +43,14 @@
     }
     UErrorCode status = U_ZERO_ERROR;
     int32_t idCount = ids->count(status);
-    if (U_FAILURE(status)) {
-        icu4jni_error(env, status);
+    if (maybeThrowIcuException(env, status)) {
         return NULL;
     }
 
     jobjectArray result = env->NewObjectArray(idCount, JniConstants::stringClass, NULL);
     for (int32_t i = 0; i < idCount; ++i) {
         const UnicodeString* id = ids->snext(status);
-        if (U_FAILURE(status)) {
-            icu4jni_error(env, status);
+        if (maybeThrowIcuException(env, status)) {
             return NULL;
         }
         ScopedLocalRef<jstring> idString(env, env->NewString(id->getBuffer(), id->length()));
diff --git a/luni/src/main/native/libcore_io_Memory.cpp b/luni/src/main/native/libcore_io_Memory.cpp
index 13ac4a0..e86f133 100644
--- a/luni/src/main/native/libcore_io_Memory.cpp
+++ b/luni/src/main/native/libcore_io_Memory.cpp
@@ -18,6 +18,7 @@
 
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "ScopedBytes.h"
 #include "ScopedPrimitiveArray.h"
 #include "UniquePtr.h"
 
@@ -76,8 +77,16 @@
     }
 }
 
-static void Memory_memmove(JNIEnv*, jclass, jint dstAddress, jint srcAddress, jlong length) {
-    memmove(cast<void*>(dstAddress), cast<const void*>(srcAddress), length);
+static void Memory_memmove(JNIEnv* env, jclass, jobject dstObject, jint dstOffset, jobject srcObject, jint srcOffset, jlong length) {
+    ScopedBytesRW dstBytes(env, dstObject);
+    if (dstBytes.get() == NULL) {
+        return;
+    }
+    ScopedBytesRO srcBytes(env, srcObject);
+    if (srcBytes.get() == NULL) {
+        return;
+    }
+    memmove(dstBytes.get() + dstOffset, srcBytes.get() + srcOffset, length);
 }
 
 static jbyte Memory_peekByte(JNIEnv*, jclass, jint srcAddress) {
@@ -316,7 +325,7 @@
 }
 
 static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(Memory, memmove, "(IIJ)V"),
+    NATIVE_METHOD(Memory, memmove, "(Ljava/lang/Object;ILjava/lang/Object;IJ)V"),
     NATIVE_METHOD(Memory, peekByte, "(I)B"),
     NATIVE_METHOD(Memory, peekByteArray, "(I[BII)V"),
     NATIVE_METHOD(Memory, peekCharArray, "(I[CIIZ)V"),
diff --git a/luni/src/main/native/libcore_io_OsConstants.cpp b/luni/src/main/native/libcore_io_OsConstants.cpp
index 6165256..dd8ebf2 100644
--- a/luni/src/main/native/libcore_io_OsConstants.cpp
+++ b/luni/src/main/native/libcore_io_OsConstants.cpp
@@ -22,8 +22,11 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <net/if.h>
+#include <netdb.h>
 #include <netinet/in.h>
 #include <netinet/tcp.h>
+#include <signal.h>
 #include <stdlib.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
@@ -42,12 +45,30 @@
     initConstant(env, c, "AF_INET6", AF_INET6);
     initConstant(env, c, "AF_UNIX", AF_UNIX);
     initConstant(env, c, "AF_UNSPEC", AF_UNSPEC);
+    initConstant(env, c, "AI_ADDRCONFIG", AI_ADDRCONFIG);
+    initConstant(env, c, "AI_ALL", AI_ALL);
+    initConstant(env, c, "AI_CANONNAME", AI_CANONNAME);
+    initConstant(env, c, "AI_NUMERICHOST", AI_NUMERICHOST);
+    initConstant(env, c, "AI_NUMERICSERV", AI_NUMERICSERV);
+    initConstant(env, c, "AI_PASSIVE", AI_PASSIVE);
+    initConstant(env, c, "AI_V4MAPPED", AI_V4MAPPED);
     initConstant(env, c, "E2BIG", E2BIG);
     initConstant(env, c, "EACCES", EACCES);
     initConstant(env, c, "EADDRINUSE", EADDRINUSE);
     initConstant(env, c, "EADDRNOTAVAIL", EADDRNOTAVAIL);
     initConstant(env, c, "EAFNOSUPPORT", EAFNOSUPPORT);
     initConstant(env, c, "EAGAIN", EAGAIN);
+    initConstant(env, c, "EAI_AGAIN", EAI_AGAIN);
+    initConstant(env, c, "EAI_BADFLAGS", EAI_BADFLAGS);
+    initConstant(env, c, "EAI_FAIL", EAI_FAIL);
+    initConstant(env, c, "EAI_FAMILY", EAI_FAMILY);
+    initConstant(env, c, "EAI_MEMORY", EAI_MEMORY);
+    initConstant(env, c, "EAI_NODATA", EAI_NODATA);
+    initConstant(env, c, "EAI_NONAME", EAI_NONAME);
+    initConstant(env, c, "EAI_OVERFLOW", EAI_OVERFLOW);
+    initConstant(env, c, "EAI_SERVICE", EAI_SERVICE);
+    initConstant(env, c, "EAI_SOCKTYPE", EAI_SOCKTYPE);
+    initConstant(env, c, "EAI_SYSTEM", EAI_SYSTEM);
     initConstant(env, c, "EALREADY", EALREADY);
     initConstant(env, c, "EBADF", EBADF);
     initConstant(env, c, "EBADMSG", EBADMSG);
@@ -142,6 +163,22 @@
     initConstant(env, c, "F_SETOWN", F_SETOWN);
     initConstant(env, c, "F_UNLCK", F_UNLCK);
     initConstant(env, c, "F_WRLCK", F_WRLCK);
+    initConstant(env, c, "IFF_ALLMULTI", IFF_ALLMULTI);
+    initConstant(env, c, "IFF_AUTOMEDIA", IFF_AUTOMEDIA);
+    initConstant(env, c, "IFF_BROADCAST", IFF_BROADCAST);
+    initConstant(env, c, "IFF_DEBUG", IFF_DEBUG);
+    initConstant(env, c, "IFF_DYNAMIC", IFF_DYNAMIC);
+    initConstant(env, c, "IFF_LOOPBACK", IFF_LOOPBACK);
+    initConstant(env, c, "IFF_MASTER", IFF_MASTER);
+    initConstant(env, c, "IFF_MULTICAST", IFF_MULTICAST);
+    initConstant(env, c, "IFF_NOARP", IFF_NOARP);
+    initConstant(env, c, "IFF_NOTRAILERS", IFF_NOTRAILERS);
+    initConstant(env, c, "IFF_POINTOPOINT", IFF_POINTOPOINT);
+    initConstant(env, c, "IFF_PORTSEL", IFF_PORTSEL);
+    initConstant(env, c, "IFF_PROMISC", IFF_PROMISC);
+    initConstant(env, c, "IFF_RUNNING", IFF_RUNNING);
+    initConstant(env, c, "IFF_SLAVE", IFF_SLAVE);
+    initConstant(env, c, "IFF_UP", IFF_UP);
     initConstant(env, c, "IPPROTO_ICMP", IPPROTO_ICMP);
     initConstant(env, c, "IPPROTO_IP", IPPROTO_IP);
     initConstant(env, c, "IPPROTO_IPV6", IPPROTO_IPV6);
@@ -183,6 +220,11 @@
     initConstant(env, c, "MS_ASYNC", MS_ASYNC);
     initConstant(env, c, "MS_INVALIDATE", MS_INVALIDATE);
     initConstant(env, c, "MS_SYNC", MS_SYNC);
+    initConstant(env, c, "NI_DGRAM", NI_DGRAM);
+    initConstant(env, c, "NI_NAMEREQD", NI_NAMEREQD);
+    initConstant(env, c, "NI_NOFQDN", NI_NOFQDN);
+    initConstant(env, c, "NI_NUMERICHOST", NI_NUMERICHOST);
+    initConstant(env, c, "NI_NUMERICSERV", NI_NUMERICSERV);
     initConstant(env, c, "O_ACCMODE", O_ACCMODE);
     initConstant(env, c, "O_APPEND", O_APPEND);
     initConstant(env, c, "O_CREAT", O_CREAT);
@@ -205,11 +247,49 @@
     initConstant(env, c, "SHUT_RD", SHUT_RD);
     initConstant(env, c, "SHUT_RDWR", SHUT_RDWR);
     initConstant(env, c, "SHUT_WR", SHUT_WR);
+    initConstant(env, c, "SIGABRT", SIGABRT);
+    initConstant(env, c, "SIGALRM", SIGALRM);
+    initConstant(env, c, "SIGBUS", SIGBUS);
+    initConstant(env, c, "SIGCHLD", SIGCHLD);
+    initConstant(env, c, "SIGCONT", SIGCONT);
+    initConstant(env, c, "SIGFPE", SIGFPE);
+    initConstant(env, c, "SIGHUP", SIGHUP);
+    initConstant(env, c, "SIGILL", SIGILL);
+    initConstant(env, c, "SIGINT", SIGINT);
+    initConstant(env, c, "SIGIO", SIGIO);
+    initConstant(env, c, "SIGKILL", SIGKILL);
+    initConstant(env, c, "SIGPIPE", SIGPIPE);
+    initConstant(env, c, "SIGPROF", SIGPROF);
+    initConstant(env, c, "SIGPWR", SIGPWR);
+    initConstant(env, c, "SIGQUIT", SIGQUIT);
+    initConstant(env, c, "SIGRTMAX", SIGRTMAX);
+    initConstant(env, c, "SIGRTMIN", SIGRTMIN);
+    initConstant(env, c, "SIGSEGV", SIGSEGV);
+    initConstant(env, c, "SIGSTKFLT", SIGSTKFLT);
+    initConstant(env, c, "SIGSTOP", SIGSTOP);
+    initConstant(env, c, "SIGSYS", SIGSYS);
+    initConstant(env, c, "SIGTERM", SIGTERM);
+    initConstant(env, c, "SIGTRAP", SIGTRAP);
+    initConstant(env, c, "SIGTSTP", SIGTSTP);
+    initConstant(env, c, "SIGTTIN", SIGTTIN);
+    initConstant(env, c, "SIGTTOU", SIGTTOU);
+    initConstant(env, c, "SIGURG", SIGURG);
+    initConstant(env, c, "SIGUSR1", SIGUSR1);
+    initConstant(env, c, "SIGUSR2", SIGUSR2);
+    initConstant(env, c, "SIGVTALRM", SIGVTALRM);
+    initConstant(env, c, "SIGWINCH", SIGWINCH);
+    initConstant(env, c, "SIGXCPU", SIGXCPU);
+    initConstant(env, c, "SIGXFSZ", SIGXFSZ);
+    initConstant(env, c, "SIOCGIFADDR", SIOCGIFADDR);
+    initConstant(env, c, "SIOCGIFBRDADDR", SIOCGIFBRDADDR);
+    initConstant(env, c, "SIOCGIFDSTADDR", SIOCGIFDSTADDR);
+    initConstant(env, c, "SIOCGIFNETMASK", SIOCGIFNETMASK);
     initConstant(env, c, "SOCK_DGRAM", SOCK_DGRAM);
     initConstant(env, c, "SOCK_RAW", SOCK_RAW);
     initConstant(env, c, "SOCK_SEQPACKET", SOCK_SEQPACKET);
     initConstant(env, c, "SOCK_STREAM", SOCK_STREAM);
     initConstant(env, c, "SOL_SOCKET", SOL_SOCKET);
+    initConstant(env, c, "SO_BINDTODEVICE", SO_BINDTODEVICE);
     initConstant(env, c, "SO_BROADCAST", SO_BROADCAST);
     initConstant(env, c, "SO_DEBUG", SO_DEBUG);
     initConstant(env, c, "SO_DONTROUTE", SO_DONTROUTE);
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp
index 6c86011..aa69eac 100644
--- a/luni/src/main/native/libcore_io_Posix.cpp
+++ b/luni/src/main/native/libcore_io_Posix.cpp
@@ -20,19 +20,27 @@
 #include "JniConstants.h"
 #include "JniException.h"
 #include "NetworkUtilities.h"
+#include "ScopedBytes.h"
 #include "ScopedPrimitiveArray.h"
 #include "ScopedUtfChars.h"
 #include "StaticAssert.h"
+#include "UniquePtr.h"
 #include "toStringArray.h"
 
+#include <arpa/inet.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <net/if.h>
+#include <netdb.h>
 #include <netinet/in.h>
+#include <netinet/in.h>
+#include <signal.h>
 #include <stdlib.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/sendfile.h>
 #include <sys/socket.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/types.h>
@@ -41,17 +49,24 @@
 #include <sys/vfs.h> // Bionic doesn't have <sys/statvfs.h>
 #include <unistd.h>
 
-static void throwErrnoException(JNIEnv* env, const char* name) {
-    int errnum = errno;
+struct addrinfo_deleter {
+    void operator()(addrinfo* p) const {
+        if (p != NULL) { // bionic's freeaddrinfo(3) crashes when passed NULL.
+            freeaddrinfo(p);
+        }
+    }
+};
 
+static void throwException(JNIEnv* env, jclass exceptionClass, jmethodID ctor3, jmethodID ctor2,
+        const char* functionName, int error) {
     jthrowable cause = NULL;
     if (env->ExceptionCheck()) {
         cause = env->ExceptionOccurred();
         env->ExceptionClear();
     }
 
-    ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
-    if (javaName.get() == NULL) {
+    ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
+    if (detailMessage.get() == NULL) {
         // Not really much we can do here. We're probably dead in the water,
         // but let's try to stumble on...
         env->ExceptionClear();
@@ -59,18 +74,36 @@
 
     jobject exception;
     if (cause != NULL) {
-        static jmethodID ctor = env->GetMethodID(JniConstants::errnoExceptionClass, "<init>",
-                "(Ljava/lang/String;ILjava/lang/Throwable;)V");
-        exception = env->NewObject(JniConstants::errnoExceptionClass, ctor,
-                javaName.get(), errnum, cause);
+        exception = env->NewObject(exceptionClass, ctor3, detailMessage.get(), error, cause);
     } else {
-        static jmethodID ctor = env->GetMethodID(JniConstants::errnoExceptionClass, "<init>",
-                "(Ljava/lang/String;I)V");
-        exception = env->NewObject(JniConstants::errnoExceptionClass, ctor, javaName.get(), errnum);
+        exception = env->NewObject(exceptionClass, ctor2, detailMessage.get(), error);
     }
     env->Throw(reinterpret_cast<jthrowable>(exception));
 }
 
+static void throwErrnoException(JNIEnv* env, const char* functionName) {
+    int error = errno;
+    static jmethodID ctor3 = env->GetMethodID(JniConstants::errnoExceptionClass,
+            "<init>", "(Ljava/lang/String;ILjava/lang/Throwable;)V");
+    static jmethodID ctor2 = env->GetMethodID(JniConstants::errnoExceptionClass,
+            "<init>", "(Ljava/lang/String;I)V");
+    throwException(env, JniConstants::errnoExceptionClass, ctor3, ctor2, functionName, error);
+}
+
+static void throwGaiException(JNIEnv* env, const char* functionName, int error) {
+    if (error == EAI_SYSTEM) {
+        // EAI_SYSTEM means "look at errno instead", so we want our GaiException to have the
+        // relevant ErrnoException as its cause.
+        throwErrnoException(env, functionName);
+        // Deliberately fall through to throw another exception...
+    }
+    static jmethodID ctor3 = env->GetMethodID(JniConstants::gaiExceptionClass,
+            "<init>", "(Ljava/lang/String;ILjava/lang/Throwable;)V");
+    static jmethodID ctor2 = env->GetMethodID(JniConstants::gaiExceptionClass,
+            "<init>", "(Ljava/lang/String;I)V");
+    throwException(env, JniConstants::gaiExceptionClass, ctor3, ctor2, functionName, error);
+}
+
 template <typename rc_t>
 static rc_t throwIfMinusOne(JNIEnv* env, const char* name, rc_t rc) {
     if (rc == rc_t(-1)) {
@@ -79,7 +112,7 @@
     return rc;
 }
 
-template <typename T>
+template <typename ScopedT>
 class IoVec {
 public:
     IoVec(JNIEnv* env, size_t bufferCount) : mEnv(env), mBufferCount(bufferCount) {
@@ -101,16 +134,8 @@
         // TODO: Linux actually has a 1024 buffer limit. glibc works around this, and we should too.
         for (size_t i = 0; i < mBufferCount; ++i) {
             jobject buffer = mEnv->GetObjectArrayElement(javaBuffers, i);
-            jbyte* ptr;
-            if (mEnv->IsInstanceOf(buffer, JniConstants::byteArrayClass)) {
-                // We need to pin the array for the duration.
-                jbyteArray byteArray = reinterpret_cast<jbyteArray>(buffer);
-                mScopedByteArrays.push_back(new T(mEnv, byteArray));
-                ptr = const_cast<jbyte*>(mScopedByteArrays.back()->get());
-            } else {
-                // A direct ByteBuffer is easier.
-                ptr = reinterpret_cast<jbyte*>(mEnv->GetDirectBufferAddress(buffer));
-            }
+            mScopedBuffers.push_back(new ScopedT(mEnv, buffer));
+            jbyte* ptr = const_cast<jbyte*>(mScopedBuffers.back()->get());
             if (ptr == NULL) {
                 return false;
             }
@@ -123,8 +148,8 @@
     }
 
     ~IoVec() {
-        for (size_t i = 0; i < mScopedByteArrays.size(); ++i) {
-            delete mScopedByteArrays[i];
+        for (size_t i = 0; i < mScopedBuffers.size(); ++i) {
+            delete mScopedBuffers[i];
         }
         mEnv->PopLocalFrame(NULL);
     }
@@ -141,7 +166,7 @@
     JNIEnv* mEnv;
     size_t mBufferCount;
     std::vector<iovec> mIoVec;
-    std::vector<T*> mScopedByteArrays;
+    std::vector<ScopedT*> mScopedBuffers;
 };
 
 static jobject makeInetSocketAddress(JNIEnv* env, const sockaddr_storage* ss, int port) {
@@ -222,6 +247,17 @@
             sysname, nodename, release, version, machine);
 };
 
+static bool fillIfreq(JNIEnv* env, jstring javaInterfaceName, struct ifreq& req) {
+    ScopedUtfChars interfaceName(env, javaInterfaceName);
+    if (interfaceName.c_str() == NULL) {
+        return false;
+    }
+    memset(&req, 0, sizeof(req));
+    strncpy(req.ifr_name, interfaceName.c_str(), sizeof(req.ifr_name));
+    req.ifr_name[sizeof(req.ifr_name) - 1] = '\0';
+    return true;
+}
+
 static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) {
     ScopedUtfChars path(env, javaPath);
     if (path.c_str() == NULL) {
@@ -348,6 +384,76 @@
     throwIfMinusOne(env, "ftruncate", TEMP_FAILURE_RETRY(ftruncate64(fd, length)));
 }
 
+static jstring Posix_gai_strerror(JNIEnv* env, jobject, jint error) {
+    return env->NewStringUTF(gai_strerror(error));
+}
+
+static jobjectArray Posix_getaddrinfo(JNIEnv* env, jobject, jstring javaNode, jobject javaHints) {
+    ScopedUtfChars node(env, javaNode);
+    if (node.c_str() == NULL) {
+        return NULL;
+    }
+
+    static jfieldID flagsFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_flags", "I");
+    static jfieldID familyFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_family", "I");
+    static jfieldID socktypeFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_socktype", "I");
+    static jfieldID protocolFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_protocol", "I");
+
+    addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_flags = env->GetIntField(javaHints, flagsFid);
+    hints.ai_family = env->GetIntField(javaHints, familyFid);
+    hints.ai_socktype = env->GetIntField(javaHints, socktypeFid);
+    hints.ai_protocol = env->GetIntField(javaHints, protocolFid);
+
+    addrinfo* addressList = NULL;
+    int rc = getaddrinfo(node.c_str(), NULL, &hints, &addressList);
+    UniquePtr<addrinfo, addrinfo_deleter> addressListDeleter(addressList);
+    if (rc != 0) {
+        throwGaiException(env, "getaddrinfo", rc);
+        return NULL;
+    }
+
+    // Count results so we know how to size the output array.
+    int addressCount = 0;
+    for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
+        if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
+            ++addressCount;
+        } else {
+            LOGE("getaddrinfo unexpected ai_family %i", ai->ai_family);
+        }
+    }
+    if (addressCount == 0) {
+        return NULL;
+    }
+
+    // Prepare output array.
+    jobjectArray result = env->NewObjectArray(addressCount, JniConstants::inetAddressClass, NULL);
+    if (result == NULL) {
+        return NULL;
+    }
+
+    // Examine returned addresses one by one, save them in the output array.
+    int index = 0;
+    for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
+        if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
+            // Unknown address family. Skip this address.
+            LOGE("getaddrinfo unexpected ai_family %i", ai->ai_family);
+            continue;
+        }
+
+        // Convert each IP address into a Java byte array.
+        sockaddr_storage* address = reinterpret_cast<sockaddr_storage*>(ai->ai_addr);
+        ScopedLocalRef<jobject> inetAddress(env, socketAddressToInetAddress(env, address));
+        if (inetAddress.get() == NULL) {
+            return NULL;
+        }
+        env->SetObjectArrayElement(result, index, inetAddress.get());
+        ++index;
+    }
+    return result;
+}
+
 static jstring Posix_getenv(JNIEnv* env, jobject, jstring javaName) {
     ScopedUtfChars name(env, javaName);
     if (name.c_str() == NULL) {
@@ -356,6 +462,25 @@
     return env->NewStringUTF(getenv(name.c_str()));
 }
 
+static jstring Posix_getnameinfo(JNIEnv* env, jobject, jobject javaAddress, jint flags) {
+    sockaddr_storage ss;
+    if (!inetAddressToSocketAddress(env, javaAddress, 0, &ss)) {
+        return NULL;
+    }
+    // TODO: bionic's getnameinfo(3) seems to want its length parameter to be exactly
+    // sizeof(sockaddr_in) for an IPv4 address and sizeof (sockaddr_in6) for an
+    // IPv6 address. Fix getnameinfo so it accepts sizeof(sockaddr_storage), and
+    // then remove this hack.
+    socklen_t size = (ss.ss_family == AF_INET) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6);
+    char buf[NI_MAXHOST]; // NI_MAXHOST is longer than INET6_ADDRSTRLEN.
+    int rc = getnameinfo(reinterpret_cast<sockaddr*>(&ss), size, buf, sizeof(buf), NULL, 0, flags);
+    if (rc != 0) {
+        throwGaiException(env, "getnameinfo", rc);
+        return NULL;
+    }
+    return env->NewStringUTF(buf);
+}
+
 static jobject Posix_getsockname(JNIEnv* env, jobject, jobject javaFd) {
     int fd = jniGetFDFromFileDescriptor(env, javaFd);
     sockaddr_storage ss;
@@ -427,6 +552,42 @@
     return makeStructTimeval(env, tv);
 }
 
+static jstring Posix_if_indextoname(JNIEnv* env, jobject, jint index) {
+    char buf[IF_NAMESIZE];
+    char* name = if_indextoname(index, buf);
+    // if_indextoname(3) returns NULL on failure, which will come out of NewStringUTF unscathed.
+    // There's no useful information in errno, so we don't bother throwing. Callers can null-check.
+    return env->NewStringUTF(name);
+}
+
+static jobject Posix_inet_aton(JNIEnv* env, jobject, jstring javaName) {
+    ScopedUtfChars name(env, javaName);
+    if (name.c_str() == NULL) {
+        return NULL;
+    }
+    sockaddr_storage ss;
+    memset(&ss, 0, sizeof(ss));
+    sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ss);
+    if (inet_aton(name.c_str(), &sin->sin_addr) == 0) {
+        return NULL;
+    }
+    sin->sin_family = AF_INET; // inet_aton only supports IPv4.
+    return socketAddressToInetAddress(env, &ss);
+}
+
+static jobject Posix_ioctlInetAddress(JNIEnv* env, jobject, jobject javaFd, jint cmd, jstring javaInterfaceName) {
+    struct ifreq req;
+    if (!fillIfreq(env, javaInterfaceName, req)) {
+        return NULL;
+    }
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int rc = throwIfMinusOne(env, "ioctl", TEMP_FAILURE_RETRY(ioctl(fd, cmd, &req)));
+    if (rc == -1) {
+        return NULL;
+    }
+    return socketAddressToInetAddress(env, reinterpret_cast<sockaddr_storage*>(&req.ifr_addr));
+}
+
 static jint Posix_ioctlInt(JNIEnv* env, jobject, jobject javaFd, jint cmd, jobject javaArg) {
     // This is complicated because ioctls may return their result by updating their argument
     // or via their return value, so we need to support both.
@@ -445,6 +606,10 @@
     return TEMP_FAILURE_RETRY(isatty(fd)) == 0;
 }
 
+static void Posix_kill(JNIEnv* env, jobject, jint pid, jint sig) {
+    throwIfMinusOne(env, "kill", TEMP_FAILURE_RETRY(kill(pid, sig)));
+}
+
 static void Posix_listen(JNIEnv* env, jobject, jobject javaFd, jint backlog) {
     int fd = jniGetFDFromFileDescriptor(env, javaFd);
     throwIfMinusOne(env, "listen", TEMP_FAILURE_RETRY(listen(fd, backlog)));
@@ -536,23 +701,17 @@
     return result;
 }
 
-static jint Posix_read(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount) {
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    ScopedByteArrayRW bytes(env, javaBytes);
+static jint Posix_readBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount) {
+    ScopedBytesRW bytes(env, javaBytes);
     if (bytes.get() == NULL) {
         return -1;
     }
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
     return throwIfMinusOne(env, "read", TEMP_FAILURE_RETRY(read(fd, bytes.get() + byteOffset, byteCount)));
 }
 
-static jint Posix_readDirectBuffer(JNIEnv* env, jobject, jobject javaFd, jobject byteBuffer, jint position, jint remaining) {
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    jbyte* ptr = reinterpret_cast<jbyte*>(env->GetDirectBufferAddress(byteBuffer));
-    return throwIfMinusOne(env, "read", TEMP_FAILURE_RETRY(read(fd, ptr + position, remaining)));
-}
-
 static jint Posix_readv(JNIEnv* env, jobject, jobject javaFd, jobjectArray buffers, jintArray offsets, jintArray byteCounts) {
-    IoVec<ScopedByteArrayRW> ioVec(env, env->GetArrayLength(buffers));
+    IoVec<ScopedBytesRW> ioVec(env, env->GetArrayLength(buffers));
     if (!ioVec.init(buffers, offsets, byteCounts)) {
         return -1;
     }
@@ -598,11 +757,84 @@
     return result;
 }
 
+static void Posix_setsockoptByte(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jint value) {
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    u_char byte = value;
+    throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &byte, sizeof(byte))));
+}
+
+static void Posix_setsockoptIfreq(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jstring javaInterfaceName) {
+    struct ifreq req;
+    if (!fillIfreq(env, javaInterfaceName, req)) {
+        return;
+    }
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &req, sizeof(req))));
+}
+
 static void Posix_setsockoptInt(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jint value) {
     int fd = jniGetFDFromFileDescriptor(env, javaFd);
     throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value))));
 }
 
+static void Posix_setsockoptIpMreqn(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jint value) {
+    ip_mreqn req;
+    memset(&req, 0, sizeof(req));
+    req.imr_ifindex = value;
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &req, sizeof(req))));
+}
+
+static void Posix_setsockoptGroupReq(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jobject javaGroupReq) {
+    struct group_req value;
+
+    static jfieldID grInterfaceFid = env->GetFieldID(JniConstants::structGroupReqClass, "gr_interface", "I");
+    value.gr_interface = env->GetIntField(javaGroupReq, grInterfaceFid);
+    // Get the IPv4 or IPv6 multicast address to join or leave.
+    static jfieldID grGroupFid = env->GetFieldID(JniConstants::structGroupReqClass, "gr_group", "Ljava/net/InetAddress;");
+    jobject javaGroup = env->GetObjectField(javaGroupReq, grGroupFid);
+    if (!inetAddressToSocketAddress(env, javaGroup, 0, &value.gr_group)) {
+        return;
+    }
+
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int rc = TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value)));
+    if (rc == -1 && errno == EINVAL) {
+        // Maybe we're a 32-bit binary talking to a 64-bit kernel?
+        // glibc doesn't automatically handle this.
+        struct GCC_HIDDEN group_req64 {
+            uint32_t gr_interface;
+            uint32_t my_padding;
+            sockaddr_storage gr_group;
+        };
+        group_req64 value64;
+        value64.gr_interface = value.gr_interface;
+        memcpy(&value64.gr_group, &value.gr_group, sizeof(value.gr_group));
+        rc = TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value64, sizeof(value64)));
+    }
+    throwIfMinusOne(env, "setsockopt", rc);
+}
+
+static void Posix_setsockoptLinger(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jobject javaLinger) {
+    static jfieldID lOnoffFid = env->GetFieldID(JniConstants::structLingerClass, "l_onoff", "I");
+    static jfieldID lLingerFid = env->GetFieldID(JniConstants::structLingerClass, "l_linger", "I");
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    struct linger value;
+    value.l_onoff = env->GetIntField(javaLinger, lOnoffFid);
+    value.l_linger = env->GetIntField(javaLinger, lLingerFid);
+    throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value))));
+}
+
+static void Posix_setsockoptTimeval(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jobject javaTimeval) {
+    static jfieldID tvSecFid = env->GetFieldID(JniConstants::structTimevalClass, "tv_sec", "J");
+    static jfieldID tvUsecFid = env->GetFieldID(JniConstants::structTimevalClass, "tv_usec", "J");
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    struct timeval value;
+    value.tv_sec = env->GetIntField(javaTimeval, tvSecFid);
+    value.tv_usec = env->GetIntField(javaTimeval, tvUsecFid);
+    throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value))));
+}
+
 static void Posix_shutdown(JNIEnv* env, jobject, jobject javaFd, jint how) {
     int fd = jniGetFDFromFileDescriptor(env, javaFd);
     throwIfMinusOne(env, "shutdown", TEMP_FAILURE_RETRY(shutdown(fd, how)));
@@ -667,23 +899,17 @@
     return makeStructUtsname(env, buf);
 }
 
-static jint Posix_write(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount) {
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    ScopedByteArrayRO bytes(env, javaBytes);
+static jint Posix_writeBytes(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount) {
+    ScopedBytesRO bytes(env, javaBytes);
     if (bytes.get() == NULL) {
         return -1;
     }
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
     return throwIfMinusOne(env, "write", TEMP_FAILURE_RETRY(write(fd, bytes.get() + byteOffset, byteCount)));
 }
 
-static jint Posix_writeDirectBuffer(JNIEnv* env, jobject, jobject javaFd, jobject byteBuffer, jint position, jint remaining) {
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    jbyte* ptr = reinterpret_cast<jbyte*>(env->GetDirectBufferAddress(byteBuffer));
-    return throwIfMinusOne(env, "write", TEMP_FAILURE_RETRY(write(fd, ptr + position, remaining)));
-}
-
 static jint Posix_writev(JNIEnv* env, jobject, jobject javaFd, jobjectArray buffers, jintArray offsets, jintArray byteCounts) {
-    IoVec<ScopedByteArrayRO> ioVec(env, env->GetArrayLength(buffers));
+    IoVec<ScopedBytesRO> ioVec(env, env->GetArrayLength(buffers));
     if (!ioVec.init(buffers, offsets, byteCounts)) {
         return -1;
     }
@@ -704,15 +930,22 @@
     NATIVE_METHOD(Posix, fstatfs, "(Ljava/io/FileDescriptor;)Llibcore/io/StructStatFs;"),
     NATIVE_METHOD(Posix, fsync, "(Ljava/io/FileDescriptor;)V"),
     NATIVE_METHOD(Posix, ftruncate, "(Ljava/io/FileDescriptor;J)V"),
+    NATIVE_METHOD(Posix, gai_strerror, "(I)Ljava/lang/String;"),
+    NATIVE_METHOD(Posix, getaddrinfo, "(Ljava/lang/String;Llibcore/io/StructAddrinfo;)[Ljava/net/InetAddress;"),
     NATIVE_METHOD(Posix, getenv, "(Ljava/lang/String;)Ljava/lang/String;"),
+    NATIVE_METHOD(Posix, getnameinfo, "(Ljava/net/InetAddress;I)Ljava/lang/String;"),
     NATIVE_METHOD(Posix, getsockname, "(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;"),
     NATIVE_METHOD(Posix, getsockoptByte, "(Ljava/io/FileDescriptor;II)I"),
     NATIVE_METHOD(Posix, getsockoptInAddr, "(Ljava/io/FileDescriptor;II)Ljava/net/InetAddress;"),
     NATIVE_METHOD(Posix, getsockoptInt, "(Ljava/io/FileDescriptor;II)I"),
     NATIVE_METHOD(Posix, getsockoptLinger, "(Ljava/io/FileDescriptor;II)Llibcore/io/StructLinger;"),
     NATIVE_METHOD(Posix, getsockoptTimeval, "(Ljava/io/FileDescriptor;II)Llibcore/io/StructTimeval;"),
+    NATIVE_METHOD(Posix, if_indextoname, "(I)Ljava/lang/String;"),
+    NATIVE_METHOD(Posix, inet_aton, "(Ljava/lang/String;)Ljava/net/InetAddress;"),
+    NATIVE_METHOD(Posix, ioctlInetAddress, "(Ljava/io/FileDescriptor;ILjava/lang/String;)Ljava/net/InetAddress;"),
     NATIVE_METHOD(Posix, ioctlInt, "(Ljava/io/FileDescriptor;ILlibcore/util/MutableInt;)I"),
     NATIVE_METHOD(Posix, isatty, "(Ljava/io/FileDescriptor;)Z"),
+    NATIVE_METHOD(Posix, kill, "(II)V"),
     NATIVE_METHOD(Posix, listen, "(Ljava/io/FileDescriptor;I)V"),
     NATIVE_METHOD(Posix, lseek, "(Ljava/io/FileDescriptor;JI)J"),
     NATIVE_METHOD(Posix, lstat, "(Ljava/lang/String;)Llibcore/io/StructStat;"),
@@ -725,13 +958,18 @@
     NATIVE_METHOD(Posix, munmap, "(JJ)V"),
     NATIVE_METHOD(Posix, open, "(Ljava/lang/String;II)Ljava/io/FileDescriptor;"),
     NATIVE_METHOD(Posix, pipe, "()[Ljava/io/FileDescriptor;"),
-    NATIVE_METHOD(Posix, read, "(Ljava/io/FileDescriptor;[BII)I"),
-    NATIVE_METHOD(Posix, readDirectBuffer, "(Ljava/io/FileDescriptor;Ljava/nio/ByteBuffer;II)I"),
+    NATIVE_METHOD(Posix, readBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;II)I"),
     NATIVE_METHOD(Posix, readv, "(Ljava/io/FileDescriptor;[Ljava/lang/Object;[I[I)I"),
     NATIVE_METHOD(Posix, remove, "(Ljava/lang/String;)V"),
     NATIVE_METHOD(Posix, rename, "(Ljava/lang/String;Ljava/lang/String;)V"),
     NATIVE_METHOD(Posix, sendfile, "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Llibcore/util/MutableLong;J)J"),
+    NATIVE_METHOD(Posix, setsockoptByte, "(Ljava/io/FileDescriptor;III)V"),
+    NATIVE_METHOD(Posix, setsockoptIfreq, "(Ljava/io/FileDescriptor;IILjava/lang/String;)V"),
     NATIVE_METHOD(Posix, setsockoptInt, "(Ljava/io/FileDescriptor;III)V"),
+    NATIVE_METHOD(Posix, setsockoptIpMreqn, "(Ljava/io/FileDescriptor;III)V"),
+    NATIVE_METHOD(Posix, setsockoptGroupReq, "(Ljava/io/FileDescriptor;IILlibcore/io/StructGroupReq;)V"),
+    NATIVE_METHOD(Posix, setsockoptLinger, "(Ljava/io/FileDescriptor;IILlibcore/io/StructLinger;)V"),
+    NATIVE_METHOD(Posix, setsockoptTimeval, "(Ljava/io/FileDescriptor;IILlibcore/io/StructTimeval;)V"),
     NATIVE_METHOD(Posix, shutdown, "(Ljava/io/FileDescriptor;I)V"),
     NATIVE_METHOD(Posix, socket, "(III)Ljava/io/FileDescriptor;"),
     NATIVE_METHOD(Posix, stat, "(Ljava/lang/String;)Llibcore/io/StructStat;"),
@@ -740,8 +978,7 @@
     NATIVE_METHOD(Posix, symlink, "(Ljava/lang/String;Ljava/lang/String;)V"),
     NATIVE_METHOD(Posix, sysconf, "(I)J"),
     NATIVE_METHOD(Posix, uname, "()Llibcore/io/StructUtsname;"),
-    NATIVE_METHOD(Posix, write, "(Ljava/io/FileDescriptor;[BII)I"),
-    NATIVE_METHOD(Posix, writeDirectBuffer, "(Ljava/io/FileDescriptor;Ljava/nio/ByteBuffer;II)I"),
+    NATIVE_METHOD(Posix, writeBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;II)I"),
     NATIVE_METHOD(Posix, writev, "(Ljava/io/FileDescriptor;[Ljava/lang/Object;[I[I)I"),
 };
 int register_libcore_io_Posix(JNIEnv* env) {
diff --git a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
index b68f033..d7208b1 100644
--- a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
+++ b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
@@ -40,29 +40,15 @@
 #include <sys/un.h>
 #include <unistd.h>
 
-#define JAVASOCKOPT_IP_MULTICAST_IF 16
-#define JAVASOCKOPT_IP_MULTICAST_IF2 31
-#define JAVASOCKOPT_IP_MULTICAST_LOOP 18
-#define JAVASOCKOPT_IP_TOS 3
-#define JAVASOCKOPT_MCAST_JOIN_GROUP 19
-#define JAVASOCKOPT_MCAST_LEAVE_GROUP 20
-#define JAVASOCKOPT_MULTICAST_TTL 17
-#define JAVASOCKOPT_SO_BROADCAST 32
-#define JAVASOCKOPT_SO_KEEPALIVE 8
-#define JAVASOCKOPT_SO_LINGER 128
-#define JAVASOCKOPT_SO_OOBINLINE  4099
-#define JAVASOCKOPT_SO_RCVBUF 4098
-#define JAVASOCKOPT_SO_TIMEOUT  4102
-#define JAVASOCKOPT_SO_REUSEADDR 4
-#define JAVASOCKOPT_SO_SNDBUF 4097
-#define JAVASOCKOPT_SO_BINDTODEVICE 8192
-#define JAVASOCKOPT_TCP_NODELAY 1
-
 /* constants for OSNetworkSystem_selectImpl */
 #define SOCKET_OP_NONE 0
 #define SOCKET_OP_READ 1
 #define SOCKET_OP_WRITE 2
 
+static void jniThrowSocketTimeoutException(JNIEnv* env, int error) {
+    jniThrowExceptionWithErrno(env, "java/net/SocketTimeoutException", error);
+}
+
 /**
  * Returns the port number in a sockaddr_storage structure.
  *
@@ -81,80 +67,33 @@
     }
 }
 
-/**
- * Obtain the socket address family from an existing socket.
- *
- * @param socket the file descriptor of the socket to examine
- * @return an integer, the address family of the socket
- */
-static int getSocketAddressFamily(int socket) {
-    sockaddr_storage ss;
-    socklen_t namelen = sizeof(ss);
-    int ret = getsockname(socket, reinterpret_cast<sockaddr*>(&ss), &namelen);
-    if (ret != 0) {
-        return AF_UNSPEC;
-    } else {
-        return ss.ss_family;
-    }
-}
-
-// Handles translating between IPv4 and IPv6 addresses so -- where possible --
-// we can use either class of address with either an IPv4 or IPv6 socket.
+// Creates mapped addresses.
+// TODO: can this move to inetAddressToSocketAddress?
 class CompatibleSocketAddress {
 public:
-    // Constructs an address corresponding to 'ss' that's compatible with 'fd'.
-    CompatibleSocketAddress(int fd, const sockaddr_storage& ss, bool mapUnspecified) {
-        const int desiredFamily = getSocketAddressFamily(fd);
-        if (ss.ss_family == AF_INET6) {
-            if (desiredFamily == AF_INET6) {
-                // Nothing to do.
-                mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
-            } else {
-                sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&mTmp);
-                const sockaddr_in6* sin6 = reinterpret_cast<const sockaddr_in6*>(&ss);
-                memset(sin, 0, sizeof(*sin));
-                sin->sin_family = AF_INET;
-                sin->sin_port = sin6->sin6_port;
-                if (IN6_IS_ADDR_V4COMPAT(&sin6->sin6_addr)) {
-                    // We have an IPv6-mapped IPv4 address, but need plain old IPv4.
-                    // Unmap the mapped address in ss into an IPv6 address in mTmp.
-                    memcpy(&sin->sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], 4);
-                    mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
-                } else if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) {
-                    // Translate the IPv6 loopback address to the IPv4 one.
-                    sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-                    mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
-                } else {
-                    // We can't help you. We return what you gave us, and assume you'll
-                    // get a sensible error when you use the address.
-                    mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
-                }
+    CompatibleSocketAddress(const sockaddr_storage& ss, bool mapUnspecified) {
+        mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
+        if (ss.ss_family == AF_INET) {
+            // Map the IPv4 address in ss into an IPv6 address in mTmp.
+            const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
+            sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(&mTmp);
+            memset(sin6, 0, sizeof(*sin6));
+            sin6->sin6_family = AF_INET6;
+            sin6->sin6_port = sin->sin_port;
+            // TODO: mapUnspecified was introduced because kernels < 2.6.31 don't allow
+            // you to bind to ::ffff:0.0.0.0. When we move to something >= 2.6.31, we
+            // should make the code behave as if mapUnspecified were always true, and
+            // remove the parameter.
+            // TODO: this code still appears to be necessary on 2.6.32, so there's something
+            // wrong in the above comment.
+            if (sin->sin_addr.s_addr != 0 || mapUnspecified) {
+                memset(&(sin6->sin6_addr.s6_addr[10]), 0xff, 2);
             }
-        } else /* ss.ss_family == AF_INET */ {
-            if (desiredFamily == AF_INET) {
-                // Nothing to do.
-                mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
-            } else {
-                // We have IPv4 and need IPv6.
-                // Map the IPv4 address in ss into an IPv6 address in mTmp.
-                const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
-                sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(&mTmp);
-                memset(sin6, 0, sizeof(*sin6));
-                sin6->sin6_family = AF_INET6;
-                sin6->sin6_port = sin->sin_port;
-                // TODO: mapUnspecified was introduced because kernels < 2.6.31 don't allow
-                // you to bind to ::ffff:0.0.0.0. When we move to something >= 2.6.31, we
-                // should make the code behave as if mapUnspecified were always true, and
-                // remove the parameter.
-                if (sin->sin_addr.s_addr != 0 || mapUnspecified) {
-                    memset(&(sin6->sin6_addr.s6_addr[10]), 0xff, 2);
-                }
-                memcpy(&sin6->sin6_addr.s6_addr[12], &sin->sin_addr.s_addr, 4);
-                mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
-            }
+            memcpy(&sin6->sin6_addr.s6_addr[12], &sin->sin_addr.s_addr, 4);
+            mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
         }
     }
-    // Returns a pointer to an address compatible with the socket.
+    // Returns a pointer to an IPv6 address.
     const sockaddr* get() const {
         return mCompatibleAddress;
     }
@@ -171,174 +110,16 @@
     return tv;
 }
 
-/**
- * Query OS for timestamp.
- * Retrieve the current value of system clock and convert to milliseconds.
- *
- * @param[in] portLibrary The port library.
- *
- * @return 0 on failure, time value in milliseconds on success.
- * @deprecated Use @ref time_hires_clock and @ref time_hires_delta
- *
- * technically, this should return uint64_t since both timeval.tv_sec and
- * timeval.tv_usec are long
- */
-static int time_msec_clock() {
-    timeval tv;
-    struct timezone tzp;
-    gettimeofday(&tv, &tzp);
-    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
-}
-
-/**
- * Establish a connection to a peer with a timeout.  The member functions are called
- * repeatedly in order to carry out the connect and to allow other tasks to
- * proceed on certain platforms. The caller must first call ConnectHelper::start.
- * if the result is -EINPROGRESS it will then
- * call ConnectHelper::isConnected until either another error or 0 is returned to
- * indicate the connect is complete.  Each time the function should sleep for no
- * more than 'timeout' milliseconds.  If the connect succeeds or an error occurs,
- * the caller must always end the process by calling ConnectHelper::done.
- *
- * Member functions return 0 if no errors occur, otherwise -errno. TODO: use +errno.
- */
-class ConnectHelper {
-public:
-    ConnectHelper(JNIEnv* env) : mEnv(env) {
-    }
-
-    int start(NetFd& fd, jobject inetAddr, jint port) {
-        sockaddr_storage ss;
-        if (!inetAddressToSocketAddress(mEnv, inetAddr, port, &ss)) {
-            return -EINVAL; // Bogus, but clearly a failure, and we've already thrown.
-        }
-
-        // Set the socket to non-blocking and initiate a connection attempt...
-        const CompatibleSocketAddress compatibleAddress(fd.get(), ss, true);
-        if (!setBlocking(fd.get(), false) ||
-                connect(fd.get(), compatibleAddress.get(), sizeof(sockaddr_storage)) == -1) {
-            if (fd.isClosed()) {
-                return -EINVAL; // Bogus, but clearly a failure, and we've already thrown.
-            }
-            if (errno != EINPROGRESS) {
-                didFail(fd.get(), -errno);
-            }
-            return -errno;
-        }
-        // We connected straight away!
-        didConnect(fd.get());
-        return 0;
-    }
-
-    // Returns 0 if we're connected; -EINPROGRESS if we're still hopeful, -errno if we've failed.
-    // 'timeout' the timeout in milliseconds. If timeout is negative, perform a blocking operation.
-    int isConnected(int fd, int timeout) {
-        timeval passedTimeout(toTimeval(timeout));
-
-        // Initialize the fd sets for the select.
-        fd_set readSet;
-        fd_set writeSet;
-        FD_ZERO(&readSet);
-        FD_ZERO(&writeSet);
-        FD_SET(fd, &readSet);
-        FD_SET(fd, &writeSet);
-
-        int nfds = fd + 1;
-        timeval* tp = timeout >= 0 ? &passedTimeout : NULL;
-        int rc = select(nfds, &readSet, &writeSet, NULL, tp);
-        if (rc == -1) {
-            if (errno == EINTR) {
-                // We can't trivially retry a select with TEMP_FAILURE_RETRY, so punt and ask the
-                // caller to try again.
-                return -EINPROGRESS;
-            }
-            return -errno;
-        }
-
-        // If the fd is just in the write set, we're connected.
-        if (FD_ISSET(fd, &writeSet) && !FD_ISSET(fd, &readSet)) {
-            return 0;
-        }
-
-        // If the fd is in both the read and write set, there was an error.
-        if (FD_ISSET(fd, &readSet) || FD_ISSET(fd, &writeSet)) {
-            // Get the pending error.
-            int error = 0;
-            socklen_t errorLen = sizeof(error);
-            if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &errorLen) == -1) {
-                return -errno; // Couldn't get the real error, so report why not.
-            }
-            return -error;
-        }
-
-        // Timeout expired.
-        return -EINPROGRESS;
-    }
-
-    void didConnect(int fd) {
-        if (fd != -1) {
-            setBlocking(fd, true);
-        }
-    }
-
-    void didFail(int fd, int result) {
-        if (fd != -1) {
-            setBlocking(fd, true);
-        }
-
-        if (result == -ECONNRESET || result == -ECONNREFUSED || result == -EADDRNOTAVAIL ||
-                result == -EADDRINUSE || result == -ENETUNREACH) {
-            jniThrowConnectException(mEnv, -result);
-        } else if (result == -EACCES) {
-            jniThrowSecurityException(mEnv, -result);
-        } else if (result == -ETIMEDOUT) {
-            jniThrowSocketTimeoutException(mEnv, -result);
-        } else {
-            jniThrowSocketException(mEnv, -result);
-        }
-    }
-
-private:
-    JNIEnv* mEnv;
-};
-
-static void mcastJoinLeaveGroup(JNIEnv* env, int fd, jobject javaGroupRequest, bool join) {
-    group_req groupRequest;
-
-    // Get the IPv4 or IPv6 multicast address to join or leave.
-    static jfieldID grGroupFid = env->GetFieldID(JniConstants::multicastGroupRequestClass,
-            "gr_group", "Ljava/net/InetAddress;");
-    jobject group = env->GetObjectField(javaGroupRequest, grGroupFid);
-    if (!inetAddressToSocketAddress(env, group, 0, &groupRequest.gr_group)) {
-        return;
-    }
-
-    // Get the interface index to use (or 0 for "whatever").
-    static jfieldID grInterfaceFid =
-            env->GetFieldID(JniConstants::multicastGroupRequestClass, "gr_interface", "I");
-    groupRequest.gr_interface = env->GetIntField(javaGroupRequest, grInterfaceFid);
-
-    // Decide exactly what we're trying to do...
-    int level = groupRequest.gr_group.ss_family == AF_INET ? IPPROTO_IP : IPPROTO_IPV6;
-    int option = join ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP;
-
-    int rc = setsockopt(fd, level, option, &groupRequest, sizeof(groupRequest));
-    if (rc == -1 && errno == EINVAL) {
-        // Maybe we're a 32-bit binary talking to a 64-bit kernel?
-        // glibc doesn't automatically handle this.
-        struct GCC_HIDDEN group_req64 {
-            uint32_t gr_interface;
-            uint32_t my_padding;
-            sockaddr_storage gr_group;
-        };
-        group_req64 groupRequest64;
-        groupRequest64.gr_interface = groupRequest.gr_interface;
-        memcpy(&groupRequest64.gr_group, &groupRequest.gr_group, sizeof(groupRequest.gr_group));
-        rc = setsockopt(fd, level, option, &groupRequest64, sizeof(groupRequest64));
-    }
-    if (rc == -1) {
-        jniThrowSocketException(env, errno);
-        return;
+static void throwConnectException(JNIEnv* env, int error) {
+    if (error == ECONNRESET || error == ECONNREFUSED || error == EADDRNOTAVAIL ||
+            error == EADDRINUSE || error == ENETUNREACH) {
+        jniThrowExceptionWithErrno(env, "java/net/ConnectException", error);
+    } else if (error == EACCES) {
+        jniThrowExceptionWithErrno(env, "java/lang/SecurityException", error);
+    } else if (error == ETIMEDOUT) {
+        jniThrowSocketTimeoutException(env, error);
+    } else {
+        jniThrowSocketException(env, error);
     }
 }
 
@@ -389,89 +170,91 @@
     return result;
 }
 
-static jboolean OSNetworkSystem_connectNonBlocking(JNIEnv* env, jobject, jobject fileDescriptor, jobject inetAddr, jint port) {
+static jboolean OSNetworkSystem_connect(JNIEnv* env, jobject, jobject fileDescriptor, jobject inetAddr, jint port) {
     NetFd fd(env, fileDescriptor);
     if (fd.isClosed()) {
         return JNI_FALSE;
     }
 
-    ConnectHelper context(env);
-    return context.start(fd, inetAddr, port) == 0;
+    sockaddr_storage ss;
+    if (!inetAddressToSocketAddress(env, inetAddr, port, &ss)) {
+        return JNI_FALSE;
+    }
+
+    // Initiate a connection attempt...
+    const CompatibleSocketAddress compatibleAddress(ss, true);
+    int rc = connect(fd.get(), compatibleAddress.get(), sizeof(sockaddr_storage));
+    int connectErrno = errno;
+
+    // Did we get interrupted?
+    if (fd.isClosed()) {
+        return JNI_FALSE;
+    }
+
+    // Did we fail to connect?
+    if (rc == -1) {
+        if (connectErrno != EINPROGRESS) {
+            throwConnectException(env, connectErrno); // Permanent failure, so throw.
+        }
+        return JNI_FALSE;
+    }
+
+    // We connected straight away!
+    return JNI_TRUE;
 }
 
 static jboolean OSNetworkSystem_isConnected(JNIEnv* env, jobject, jobject fileDescriptor, jint timeout) {
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
+    NetFd netFd(env, fileDescriptor);
+    if (netFd.isClosed()) {
         return JNI_FALSE;
     }
 
-    ConnectHelper context(env);
-    int result = context.isConnected(fd.get(), timeout);
-    if (result == 0) {
-        context.didConnect(fd.get());
-        return JNI_TRUE;
-    } else if (result == -EINPROGRESS) {
-        // Not yet connected, but not yet denied either... Try again later.
-        return JNI_FALSE;
-    } else {
-        context.didFail(fd.get(), result);
-        return JNI_FALSE;
-    }
-}
-
-// TODO: move this into Java, using connectNonBlocking and isConnected!
-static void OSNetworkSystem_connect(JNIEnv* env, jobject, jobject fileDescriptor,
-        jobject inetAddr, jint port, jint timeout) {
-
-    /* if a timeout was specified calculate the finish time value */
-    bool hasTimeout = timeout > 0;
-    int finishTime = 0;
-    if (hasTimeout)  {
-        finishTime = time_msec_clock() + (int) timeout;
-    }
-
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        return;
-    }
-
-    ConnectHelper context(env);
-    int result = context.start(fd, inetAddr, port);
-    int remainingTimeout = timeout;
-    while (result == -EINPROGRESS) {
-        /*
-         * ok now try and connect. Depending on the platform this may sleep
-         * for up to passedTimeout milliseconds
-         */
-        result = context.isConnected(fd.get(), remainingTimeout);
-        if (fd.isClosed()) {
-            return;
-        }
-        if (result == 0) {
-            context.didConnect(fd.get());
-            return;
-        } else if (result != -EINPROGRESS) {
-            context.didFail(fd.get(), result);
-            return;
-        }
-
-        /* check if the timeout has expired */
-        if (hasTimeout) {
-            remainingTimeout = finishTime - time_msec_clock();
-            if (remainingTimeout <= 0) {
-                context.didFail(fd.get(), -ETIMEDOUT);
-                return;
-            }
+    // Initialize the fd sets and call select.
+    int fd = netFd.get();
+    int nfds = fd + 1;
+    fd_set readSet;
+    fd_set writeSet;
+    FD_ZERO(&readSet);
+    FD_ZERO(&writeSet);
+    FD_SET(fd, &readSet);
+    FD_SET(fd, &writeSet);
+    timeval passedTimeout(toTimeval(timeout));
+    int rc = select(nfds, &readSet, &writeSet, NULL, &passedTimeout);
+    if (rc == -1) {
+        if (errno == EINTR) {
+            // We can't trivially retry a select with TEMP_FAILURE_RETRY, so punt and ask the
+            // caller to try again.
         } else {
-            remainingTimeout = 100;
+            throwConnectException(env, errno);
         }
+        return JNI_FALSE;
     }
+
+    // If the fd is just in the write set, we're connected.
+    if (FD_ISSET(fd, &writeSet) && !FD_ISSET(fd, &readSet)) {
+        return JNI_TRUE;
+    }
+
+    // If the fd is in both the read and write set, there was an error.
+    if (FD_ISSET(fd, &readSet) || FD_ISSET(fd, &writeSet)) {
+        // Get the pending error.
+        int error = 0;
+        socklen_t errorLen = sizeof(error);
+        if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &errorLen) == -1) {
+            error = errno; // Couldn't get the real error, so report why getsockopt failed.
+        }
+        throwConnectException(env, error);
+        return JNI_FALSE;
+    }
+
+    // Timeout expired.
+    return JNI_FALSE;
 }
 
 static void OSNetworkSystem_bind(JNIEnv* env, jobject, jobject fileDescriptor,
         jobject inetAddress, jint port) {
-    sockaddr_storage socketAddress;
-    if (!inetAddressToSocketAddress(env, inetAddress, port, &socketAddress)) {
+    sockaddr_storage ss;
+    if (!inetAddressToSocketAddress(env, inetAddress, port, &ss)) {
         return;
     }
 
@@ -480,10 +263,10 @@
         return;
     }
 
-    const CompatibleSocketAddress compatibleAddress(fd.get(), socketAddress, false);
+    const CompatibleSocketAddress compatibleAddress(ss, false);
     int rc = TEMP_FAILURE_RETRY(bind(fd.get(), compatibleAddress.get(), sizeof(sockaddr_storage)));
     if (rc == -1) {
-        jniThrowBindException(env, errno);
+        jniThrowExceptionWithErrno(env, "java/net/BindException", errno);
     }
 }
 
@@ -682,15 +465,11 @@
     if (packet != NULL) {
         env->SetIntField(packet, lengthFid, bytesReceived);
         if (!connected) {
-            jbyteArray addr = socketAddressToByteArray(env, &ss);
-            if (addr == NULL) {
-                return 0;
-            }
-            int port = getSocketAddressPort(&ss);
-            jobject sender = byteArrayToInetAddress(env, addr);
+            jobject sender = socketAddressToInetAddress(env, &ss);
             if (sender == NULL) {
                 return 0;
             }
+            int port = getSocketAddressPort(&ss);
             env->SetObjectField(packet, addressFid, sender);
             env->SetIntField(packet, portFid, port);
         }
@@ -866,170 +645,6 @@
             translateFdSet(env, writeFDArray, countWriteC, writeFds, flagArray.get(), countReadC, SOCKET_OP_WRITE);
 }
 
-template <typename T>
-static void setSocketOption(JNIEnv* env, const NetFd& fd, int level, int option, T* value) {
-    int rc = setsockopt(fd.get(), level, option, value, sizeof(*value));
-    if (rc == -1) {
-        LOGE("setSocketOption(fd=%i, level=%i, option=%i) failed: %s (errno=%i)",
-                fd.get(), level, option, strerror(errno), errno);
-        jniThrowSocketException(env, errno);
-    }
-}
-
-static void OSNetworkSystem_setSocketOption(JNIEnv* env, jobject, jobject fileDescriptor, jint option, jobject optVal) {
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        return;
-    }
-
-    int intVal;
-    bool wasBoolean = false;
-    if (env->IsInstanceOf(optVal, JniConstants::integerClass)) {
-        intVal = intValue(env, optVal);
-    } else if (env->IsInstanceOf(optVal, JniConstants::booleanClass)) {
-        intVal = (int) booleanValue(env, optVal);
-        wasBoolean = true;
-    } else if (env->IsInstanceOf(optVal, JniConstants::inetAddressClass)) {
-        // We use optVal directly as an InetAddress for IP_MULTICAST_IF.
-    } else if (env->IsInstanceOf(optVal, JniConstants::multicastGroupRequestClass)) {
-        // We use optVal directly as a MulticastGroupRequest for MCAST_JOIN_GROUP/MCAST_LEAVE_GROUP.
-    } else {
-        jniThrowSocketException(env, EINVAL);
-        return;
-    }
-
-    int family = getSocketAddressFamily(fd.get());
-    if (family != AF_INET && family != AF_INET6) {
-        jniThrowSocketException(env, EAFNOSUPPORT);
-        return;
-    }
-
-    // Since we expect to have a AF_INET6 socket even if we're communicating via IPv4, we always
-    // set the IPPROTO_IP options. As long as we fall back to creating IPv4 sockets if creating
-    // an IPv6 socket fails, we need to make setting the IPPROTO_IPV6 options conditional.
-    switch (option) {
-    case JAVASOCKOPT_IP_TOS:
-        setSocketOption(env, fd, IPPROTO_IP, IP_TOS, &intVal);
-        if (family == AF_INET6) {
-            setSocketOption(env, fd, IPPROTO_IPV6, IPV6_TCLASS, &intVal);
-        }
-        return;
-    case JAVASOCKOPT_SO_BROADCAST:
-        setSocketOption(env, fd, SOL_SOCKET, SO_BROADCAST, &intVal);
-        return;
-    case JAVASOCKOPT_SO_KEEPALIVE:
-        setSocketOption(env, fd, SOL_SOCKET, SO_KEEPALIVE, &intVal);
-        return;
-    case JAVASOCKOPT_SO_LINGER:
-        {
-            linger l;
-            l.l_onoff = !wasBoolean;
-            l.l_linger = intVal <= 65535 ? intVal : 65535;
-            setSocketOption(env, fd, SOL_SOCKET, SO_LINGER, &l);
-            return;
-        }
-    case JAVASOCKOPT_SO_OOBINLINE:
-        setSocketOption(env, fd, SOL_SOCKET, SO_OOBINLINE, &intVal);
-        return;
-    case JAVASOCKOPT_SO_RCVBUF:
-        setSocketOption(env, fd, SOL_SOCKET, SO_RCVBUF, &intVal);
-        return;
-    case JAVASOCKOPT_SO_REUSEADDR:
-        setSocketOption(env, fd, SOL_SOCKET, SO_REUSEADDR, &intVal);
-        return;
-    case JAVASOCKOPT_SO_SNDBUF:
-        setSocketOption(env, fd, SOL_SOCKET, SO_SNDBUF, &intVal);
-        return;
-    case JAVASOCKOPT_SO_BINDTODEVICE: {
-          // intVal contains the interface index
-          char ifname[IF_NAMESIZE];
-
-          if (if_indextoname(intVal, ifname) == NULL) {
-              jniThrowSocketException(env, ENODEV);
-          } else {
-              ifreq ifr;
-
-              memset(&ifr, 0, sizeof(ifr));
-              strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
-              ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0';
-              setSocketOption(env, fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr);
-          }
-          return;
-        }
-    case JAVASOCKOPT_SO_TIMEOUT:
-        {
-            timeval timeout(toTimeval(intVal));
-            setSocketOption(env, fd, SOL_SOCKET, SO_RCVTIMEO, &timeout);
-            return;
-        }
-    case JAVASOCKOPT_TCP_NODELAY:
-        setSocketOption(env, fd, IPPROTO_TCP, TCP_NODELAY, &intVal);
-        return;
-    case JAVASOCKOPT_MCAST_JOIN_GROUP:
-        mcastJoinLeaveGroup(env, fd.get(), optVal, true);
-        return;
-    case JAVASOCKOPT_MCAST_LEAVE_GROUP:
-        mcastJoinLeaveGroup(env, fd.get(), optVal, false);
-        return;
-    case JAVASOCKOPT_IP_MULTICAST_IF:
-        {
-            sockaddr_storage sockVal;
-            if (!env->IsInstanceOf(optVal, JniConstants::inetAddressClass) ||
-                    !inetAddressToSocketAddress(env, optVal, 0, &sockVal)) {
-                return;
-            }
-            // This call is IPv4 only. The socket may be IPv6, but the address
-            // that identifies the interface to join must be an IPv4 address.
-            if (sockVal.ss_family != AF_INET) {
-                jniThrowSocketException(env, EAFNOSUPPORT);
-                return;
-            }
-            ip_mreqn mcast_req;
-            memset(&mcast_req, 0, sizeof(mcast_req));
-            mcast_req.imr_address = reinterpret_cast<sockaddr_in*>(&sockVal)->sin_addr;
-            setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_IF, &mcast_req);
-            return;
-        }
-    case JAVASOCKOPT_IP_MULTICAST_IF2:
-        // TODO: is this right? should we unconditionally set the IPPROTO_IP state in case
-        // we have an IPv6 socket communicating via IPv4?
-        if (family == AF_INET) {
-            // IP_MULTICAST_IF expects a pointer to an ip_mreqn struct.
-            ip_mreqn multicastRequest;
-            memset(&multicastRequest, 0, sizeof(multicastRequest));
-            multicastRequest.imr_ifindex = intVal;
-            setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_IF, &multicastRequest);
-        } else {
-            // IPV6_MULTICAST_IF expects a pointer to an integer.
-            setSocketOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &intVal);
-        }
-        return;
-    case JAVASOCKOPT_MULTICAST_TTL:
-        {
-            // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int,
-            // IPv4 multicast TTL uses a byte.
-            u_char ttl = intVal;
-            setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl);
-            if (family == AF_INET6) {
-                setSocketOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &intVal);
-            }
-            return;
-        }
-    case JAVASOCKOPT_IP_MULTICAST_LOOP:
-        {
-            // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte.
-            u_char loopback = intVal;
-            setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loopback);
-            if (family == AF_INET6) {
-                setSocketOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &intVal);
-            }
-            return;
-        }
-    default:
-        jniThrowSocketException(env, ENOPROTOOPT);
-    }
-}
-
 static void OSNetworkSystem_close(JNIEnv* env, jobject, jobject fileDescriptor) {
     NetFd fd(env, fileDescriptor);
     if (fd.isClosed()) {
@@ -1048,8 +663,7 @@
     NATIVE_METHOD(OSNetworkSystem, accept, "(Ljava/io/FileDescriptor;Ljava/net/SocketImpl;Ljava/io/FileDescriptor;)V"),
     NATIVE_METHOD(OSNetworkSystem, bind, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V"),
     NATIVE_METHOD(OSNetworkSystem, close, "(Ljava/io/FileDescriptor;)V"),
-    NATIVE_METHOD(OSNetworkSystem, connectNonBlocking, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)Z"),
-    NATIVE_METHOD(OSNetworkSystem, connect, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;II)V"),
+    NATIVE_METHOD(OSNetworkSystem, connect, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)Z"),
     NATIVE_METHOD(OSNetworkSystem, disconnectDatagram, "(Ljava/io/FileDescriptor;)V"),
     NATIVE_METHOD(OSNetworkSystem, isConnected, "(Ljava/io/FileDescriptor;I)Z"),
     NATIVE_METHOD(OSNetworkSystem, read, "(Ljava/io/FileDescriptor;[BII)I"),
@@ -1060,7 +674,6 @@
     NATIVE_METHOD(OSNetworkSystem, send, "(Ljava/io/FileDescriptor;[BIIILjava/net/InetAddress;)I"),
     NATIVE_METHOD(OSNetworkSystem, sendDirect, "(Ljava/io/FileDescriptor;IIIILjava/net/InetAddress;)I"),
     NATIVE_METHOD(OSNetworkSystem, sendUrgentData, "(Ljava/io/FileDescriptor;B)V"),
-    NATIVE_METHOD(OSNetworkSystem, setSocketOption, "(Ljava/io/FileDescriptor;ILjava/lang/Object;)V"),
     NATIVE_METHOD(OSNetworkSystem, write, "(Ljava/io/FileDescriptor;[BII)I"),
     NATIVE_METHOD(OSNetworkSystem, writeDirect, "(Ljava/io/FileDescriptor;III)I"),
 };
diff --git a/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp b/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
index 4ef4378..1756a44 100644
--- a/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
+++ b/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
@@ -20,9 +20,9 @@
 #include "JniConstants.h"
 #include "JniException.h"
 #include "LocalArray.h"
-#include "ScopedJavaUnicodeString.h"
 #include "ScopedLocalRef.h"
 #include "ScopedPrimitiveArray.h"
+#include "ScopedStringChars.h"
 #include "ScopedUtfChars.h"
 #include "UniquePtr.h"
 #include "jni.h"
@@ -1040,11 +1040,13 @@
     append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
 }
 
-static void ExpatParser_appendString(JNIEnv* env, jobject object, jint pointer,
-        jstring javaXml, jboolean isFinal) {
-    ScopedJavaUnicodeString xml(env, javaXml);
-    const char* bytes = reinterpret_cast<const char*>(xml.unicodeString().getBuffer());
-    size_t byteCount = 2 * xml.unicodeString().length();
+static void ExpatParser_appendString(JNIEnv* env, jobject object, jint pointer, jstring javaXml, jboolean isFinal) {
+    ScopedStringChars xml(env, javaXml);
+    if (xml.get() == NULL) {
+        return;
+    }
+    const char* bytes = reinterpret_cast<const char*>(xml.get());
+    size_t byteCount = 2 * xml.size();
     append(env, object, pointer, bytes, 0, byteCount, isFinal);
 }
 
diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk
index 1ab475c..c7620b3 100644
--- a/luni/src/main/native/sub.mk
+++ b/luni/src/main/native/sub.mk
@@ -5,13 +5,11 @@
 
 LOCAL_SRC_FILES := \
 	AsynchronousSocketCloseMonitor.cpp \
-	ErrorCode.cpp \
 	JniConstants.cpp \
 	JniException.cpp \
 	NetworkUtilities.cpp \
 	Register.cpp \
 	cbigint.cpp \
-	ifaddrs-android.cpp \
 	java_io_Console.cpp \
 	java_io_File.cpp \
 	java_io_ObjectStreamClass.cpp \
@@ -22,8 +20,6 @@
 	java_lang_StrictMath.cpp \
 	java_lang_System.cpp \
 	java_math_NativeBN.cpp \
-	java_net_InetAddress.cpp \
-	java_net_NetworkInterface.cpp \
 	java_nio_ByteOrder.cpp \
 	java_nio_charset_Charsets.cpp \
 	java_text_Bidi.cpp \
diff --git a/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
new file mode 100644
index 0000000..6835c7b
--- /dev/null
+++ b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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 libcore.java.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PipedReader;
+import java.io.PipedWriter;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.Pipe;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.WritableByteChannel;
+import junit.framework.TestCase;
+
+/**
+ * Test that interrupting a thread blocked on I/O causes that thread to throw
+ * an InterruptedIOException.
+ */
+public final class InterruptedStreamTest extends TestCase {
+
+    private static final int BUFFER_SIZE = 1024 * 1024;
+
+    private Socket[] sockets;
+
+    @Override protected void tearDown() throws Exception {
+        if (sockets != null) {
+            sockets[0].close();
+            sockets[1].close();
+        }
+        super.tearDown();
+    }
+
+    public void testInterruptPipedInputStream() throws Exception {
+        PipedOutputStream out = new PipedOutputStream();
+        PipedInputStream in = new PipedInputStream(out);
+        testInterruptInputStream(in);
+    }
+
+    public void testInterruptPipedOutputStream() throws Exception {
+        PipedOutputStream out = new PipedOutputStream();
+        new PipedInputStream(out);
+        testInterruptOutputStream(out);
+    }
+
+    public void testInterruptPipedReader() throws Exception {
+        PipedWriter writer = new PipedWriter();
+        PipedReader reader = new PipedReader(writer);
+        testInterruptReader(reader);
+    }
+
+    public void testInterruptPipedWriter() throws Exception {
+        final PipedWriter writer = new PipedWriter();
+        new PipedReader(writer);
+        testInterruptWriter(writer);
+    }
+
+    public void testInterruptReadablePipeChannel() throws Exception {
+        testInterruptReadableChannel(Pipe.open().source());
+    }
+
+    public void testInterruptWritablePipeChannel() throws Exception {
+        testInterruptWritableChannel(Pipe.open().sink());
+    }
+
+    /**
+     * Neither the RI nor Dalvik support interrupting sockets. We'd like to add
+     * support for that. http://b/4181738
+     */
+    public void testInterruptSocketInputStream() throws Exception {
+        sockets = newSocketPair();
+        testInterruptInputStream(sockets[0].getInputStream());
+    }
+
+    public void testInterruptSocketOutputStream() throws Exception {
+        sockets = newSocketPair();
+        testInterruptOutputStream(sockets[0].getOutputStream());
+    }
+
+    public void testInterruptReadableSocketChannel() throws Exception {
+        sockets = newSocketChannelPair();
+        testInterruptReadableChannel(sockets[0].getChannel());
+    }
+
+    public void testInterruptWritableSocketChannel() throws Exception {
+        sockets = newSocketChannelPair();
+        testInterruptReadableChannel(sockets[0].getChannel());
+    }
+
+    /**
+     * Returns a pair of connected sockets backed by IO sockets.
+     */
+    private Socket[] newSocketPair() throws IOException {
+        ServerSocket serverSocket = new ServerSocket(0);
+        serverSocket.setReuseAddress(true);
+        Socket client = new Socket();
+        client.connect(serverSocket.getLocalSocketAddress());
+        Socket server = serverSocket.accept();
+        return new Socket[] { client, server };
+    }
+
+    /**
+     * Returns a pair of connected sockets backed by NIO socket channels.
+     */
+    private Socket[] newSocketChannelPair() throws IOException {
+        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
+        serverSocketChannel.socket().bind(new InetSocketAddress(0));
+        SocketChannel clientSocketChannel = SocketChannel.open();
+        clientSocketChannel.connect(serverSocketChannel.socket().getLocalSocketAddress());
+        SocketChannel server = serverSocketChannel.accept();
+        serverSocketChannel.close();
+        return new Socket[] { clientSocketChannel.socket(), server.socket() };
+    }
+
+    private void testInterruptInputStream(final InputStream in) throws Exception {
+        interruptMeLater();
+        try {
+            in.read();
+            fail();
+        } catch (InterruptedIOException expected) {
+        }
+    }
+
+    private void testInterruptReader(final PipedReader reader) throws Exception {
+        interruptMeLater();
+        try {
+            reader.read();
+            fail();
+        } catch (InterruptedIOException expected) {
+        }
+    }
+
+    private void testInterruptReadableChannel(final ReadableByteChannel channel) throws Exception {
+        interruptMeLater();
+        try {
+            channel.read(ByteBuffer.allocate(BUFFER_SIZE));
+            fail();
+        } catch (ClosedByInterruptException expected) {
+        }
+    }
+
+    private void testInterruptOutputStream(final OutputStream out) throws Exception {
+        interruptMeLater();
+        try {
+            // this will block when the receiving buffer fills up
+            while (true) {
+                out.write(new byte[BUFFER_SIZE]);
+            }
+        } catch (InterruptedIOException expected) {
+        }
+    }
+
+    private void testInterruptWriter(final PipedWriter writer) throws Exception {
+        interruptMeLater();
+        try {
+            // this will block when the receiving buffer fills up
+            while (true) {
+                writer.write(new char[BUFFER_SIZE]);
+            }
+        } catch (InterruptedIOException expected) {
+        }
+    }
+
+    private void testInterruptWritableChannel(final WritableByteChannel channel) throws Exception {
+        interruptMeLater();
+        try {
+            // this will block when the receiving buffer fills up
+            while (true) {
+                channel.write(ByteBuffer.allocate(BUFFER_SIZE));
+            }
+        } catch (ClosedByInterruptException expected) {
+        }
+    }
+
+    private void interruptMeLater() throws Exception {
+        final Thread toInterrupt = Thread.currentThread();
+        new Thread(new Runnable () {
+            @Override public void run() {
+                toInterrupt.interrupt();
+            }
+        }).start();
+    }
+}
diff --git a/luni/src/test/java/libcore/java/lang/PackageTest.java b/luni/src/test/java/libcore/java/lang/PackageTest.java
new file mode 100644
index 0000000..dc862f3
--- /dev/null
+++ b/luni/src/test/java/libcore/java/lang/PackageTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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 libcore.java.lang;
+
+public class PackageTest extends junit.framework.TestCase {
+    public void test_getAnnotations() throws Exception {
+        // Package annotations aren't supported, but pre-ICS we crashed.
+        assertEquals(0, getClass().getPackage().getAnnotations().length);
+        assertEquals(0, getClass().getPackage().getDeclaredAnnotations().length);
+    }
+}
diff --git a/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java b/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java
index 405de6e..d03ae65 100644
--- a/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java
+++ b/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java
@@ -33,7 +33,7 @@
         return new File(deviceSh).exists() ? deviceSh : desktopSh;
     }
 
-    public void testRedirectErrorStream(boolean doRedirect,
+    private static void assertRedirectErrorStream(boolean doRedirect,
             String expectedOut, String expectedErr) throws Exception {
         ProcessBuilder pb = new ProcessBuilder(shell(), "-c", "echo out; echo err 1>&2");
         pb.redirectErrorStream(doRedirect);
@@ -41,11 +41,11 @@
     }
 
     public void test_redirectErrorStream_true() throws Exception {
-        testRedirectErrorStream(true, "out\nerr\n", "");
+        assertRedirectErrorStream(true, "out\nerr\n", "");
     }
 
     public void test_redirectErrorStream_false() throws Exception {
-        testRedirectErrorStream(false, "out\n", "err\n");
+        assertRedirectErrorStream(false, "out\n", "err\n");
     }
 
     public void testEnvironment() throws Exception {
diff --git a/luni/src/test/java/libcore/java/lang/SystemTest.java b/luni/src/test/java/libcore/java/lang/SystemTest.java
index a7b6225..34ca6c7 100644
--- a/luni/src/test/java/libcore/java/lang/SystemTest.java
+++ b/luni/src/test/java/libcore/java/lang/SystemTest.java
@@ -72,8 +72,7 @@
             System.arraycopy(new char[5], 0, "Hello", 0, 3);
             fail();
         } catch (ArrayStoreException e) {
-            assertEquals("source and destination must be arrays, but were "
-                    + "[C and Ljava/lang/String;", e.getMessage());
+            assertEquals("destination of type java.lang.String is not an array", e.getMessage());
         }
     }
 
@@ -82,8 +81,7 @@
             System.arraycopy("Hello", 0, new char[5], 0, 3);
             fail();
         } catch (ArrayStoreException e) {
-            assertEquals("source and destination must be arrays, but were "
-                    + "Ljava/lang/String; and [C", e.getMessage());
+            assertEquals("source of type java.lang.String is not an array", e.getMessage());
         }
     }
 
@@ -92,8 +90,7 @@
             System.arraycopy(new char[5], 0, new Object[5], 0, 3);
             fail();
         } catch (ArrayStoreException e) {
-            assertEquals("source and destination arrays are incompatible: "
-                    + "[C and [Ljava/lang/Object;", e.getMessage());
+            assertEquals("char[] and java.lang.Object[] are incompatible array types", e.getMessage());
         }
     }
 
@@ -103,9 +100,7 @@
                     new Integer[] { 1, 2, 3, null, null }, 0, 3);
             fail();
         } catch (ArrayStoreException e) {
-            assertEquals("source[2] of type Ljava/lang/String; cannot be "
-                    + "stored in destination array of type [Ljava/lang/Integer;",
-                    e.getMessage());
+            assertEquals("source[2] of type java.lang.String cannot be stored in destination array of type java.lang.Integer[]", e.getMessage());
         }
     }
 }
diff --git a/luni/src/test/java/libcore/java/lang/ThrowableTest.java b/luni/src/test/java/libcore/java/lang/ThrowableTest.java
index 1814932..700f616 100644
--- a/luni/src/test/java/libcore/java/lang/ThrowableTest.java
+++ b/luni/src/test/java/libcore/java/lang/ThrowableTest.java
@@ -18,7 +18,9 @@
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.Arrays;
 import junit.framework.TestCase;
+import libcore.java.util.SerializableTester;
 
 public class ThrowableTest extends TestCase {
     private static class NoStackTraceException extends Exception {
@@ -35,4 +37,286 @@
             ex.printStackTrace(new PrintWriter(new StringWriter()));
         }
     }
+
+    private static class SuppressionsThrowable extends Throwable {
+        public SuppressionsThrowable(String detailMessage, Throwable throwable,
+                boolean enableSuppression) {
+            super(detailMessage, throwable, enableSuppression);
+        }
+    }
+
+    public void testAddSuppressed() {
+        Throwable throwable = new Throwable();
+        assertSuppressed(throwable);
+        Throwable suppressedA = new Throwable();
+        throwable.addSuppressed(suppressedA);
+        assertSuppressed(throwable, suppressedA);
+        Throwable suppressedB = new Throwable();
+        throwable.addSuppressed(suppressedB);
+        assertSuppressed(throwable, suppressedA, suppressedB);
+    }
+
+    public void testAddDuplicateSuppressed() {
+        Throwable throwable = new Throwable();
+        Throwable suppressedA = new Throwable();
+        throwable.addSuppressed(suppressedA);
+        throwable.addSuppressed(suppressedA);
+        throwable.addSuppressed(suppressedA);
+        assertSuppressed(throwable, suppressedA, suppressedA, suppressedA);
+    }
+
+    public void testGetSuppressedReturnsCopy() {
+        Throwable throwable = new Throwable();
+        Throwable suppressedA = new Throwable();
+        Throwable suppressedB = new Throwable();
+        throwable.addSuppressed(suppressedA);
+        throwable.addSuppressed(suppressedB);
+        Throwable[] mutable = throwable.getSuppressed();
+        mutable[0] = null;
+        mutable[1] = null;
+        assertSuppressed(throwable, suppressedA, suppressedB);
+    }
+
+    public void testAddSuppressedWithSuppressionDisabled() {
+        Throwable throwable = new SuppressionsThrowable("foo", null, false);
+        assertSuppressed(throwable);
+        throwable.addSuppressed(new Throwable());
+        assertSuppressed(throwable);
+        throwable.addSuppressed(new Throwable());
+        assertSuppressed(throwable);
+    }
+
+    public void testAddSuppressedSelf() {
+        Throwable throwable = new Throwable();
+        try {
+            throwable.addSuppressed(throwable);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testAddSuppressedNull() {
+        Throwable throwable = new Throwable();
+        try {
+            throwable.addSuppressed(null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    public void testPrintStackTraceWithCause() {
+        Throwable throwable = newThrowable("Throwable", "A", "B");
+        throwable.initCause(newThrowable("Cause", "A", "B", "C", "D"));
+
+        assertEquals("java.lang.Throwable: Throwable\n"
+                + "\tat ClassB.doB(ClassB.java:1)\n"
+                + "\tat ClassA.doA(ClassA.java:0)\n"
+                + "Caused by: java.lang.Throwable: Cause\n"
+                + "\tat ClassD.doD(ClassD.java:3)\n"
+                + "\tat ClassC.doC(ClassC.java:2)\n"
+                + "\t... 2 more\n", printStackTraceToString(throwable));
+    }
+
+    public void testPrintStackTraceWithCauseAndSuppressed() {
+        Throwable throwable = newThrowable("Throwable", "A", "B");
+        throwable.initCause(newThrowable("Cause", "A", "B", "C", "D"));
+        throwable.addSuppressed(newThrowable("Suppressed", "A", "B", "E", "F"));
+        throwable.addSuppressed(newThrowable("Suppressed", "A", "B", "G", "H"));
+
+        assertEquals("java.lang.Throwable: Throwable\n"
+                + "\tat ClassB.doB(ClassB.java:1)\n"
+                + "\tat ClassA.doA(ClassA.java:0)\n"
+                + "\tSuppressed: java.lang.Throwable: Suppressed\n"
+                + "\t\tat ClassF.doF(ClassF.java:3)\n"
+                + "\t\tat ClassE.doE(ClassE.java:2)\n"
+                + "\t\t... 2 more\n"
+                + "\tSuppressed: java.lang.Throwable: Suppressed\n"
+                + "\t\tat ClassH.doH(ClassH.java:3)\n"
+                + "\t\tat ClassG.doG(ClassG.java:2)\n"
+                + "\t\t... 2 more\n"
+                + "Caused by: java.lang.Throwable: Cause\n"
+                + "\tat ClassD.doD(ClassD.java:3)\n"
+                + "\tat ClassC.doC(ClassC.java:2)\n"
+                + "\t... 2 more\n", printStackTraceToString(throwable));
+    }
+
+    public void testPrintStackTraceWithEverything() {
+        Throwable throwable = newThrowable("Throwable", "A", "B");
+        Throwable cause = newThrowable("Cause", "A", "B", "C", "D");
+        Throwable suppressed = newThrowable("Suppressed", "A", "B", "E", "F");
+
+        throwable.addSuppressed(suppressed);
+        suppressed.addSuppressed(newThrowable("Suppressed/Suppressed", "A", "B", "E", "G"));
+        suppressed.initCause(newThrowable("Suppressed/Cause", "A", "B", "E", "H"));
+
+        throwable.initCause(cause);
+        cause.addSuppressed(newThrowable("Cause/Suppressed", "A", "B", "C", "I"));
+        cause.initCause(newThrowable("Cause/Cause", "A", "B", "C", "J"));
+
+        assertEquals("java.lang.Throwable: Throwable\n"
+                + "\tat ClassB.doB(ClassB.java:1)\n"
+                + "\tat ClassA.doA(ClassA.java:0)\n"
+                + "\tSuppressed: java.lang.Throwable: Suppressed\n"
+                + "\t\tat ClassF.doF(ClassF.java:3)\n"
+                + "\t\tat ClassE.doE(ClassE.java:2)\n"
+                + "\t\t... 2 more\n"
+                + "\t\tSuppressed: java.lang.Throwable: Suppressed/Suppressed\n"
+                + "\t\t\tat ClassG.doG(ClassG.java:3)\n"
+                + "\t\t\t... 3 more\n"
+                + "\tCaused by: java.lang.Throwable: Suppressed/Cause\n"
+                + "\t\tat ClassH.doH(ClassH.java:3)\n"
+                + "\t\t... 3 more\n"
+                + "Caused by: java.lang.Throwable: Cause\n"
+                + "\tat ClassD.doD(ClassD.java:3)\n"
+                + "\tat ClassC.doC(ClassC.java:2)\n"
+                + "\t... 2 more\n"
+                + "\tSuppressed: java.lang.Throwable: Cause/Suppressed\n"
+                + "\t\tat ClassI.doI(ClassI.java:3)\n"
+                + "\t\t... 3 more\n"
+                + "Caused by: java.lang.Throwable: Cause/Cause\n"
+                + "\tat ClassJ.doJ(ClassJ.java:3)\n"
+                + "\t... 3 more\n", printStackTraceToString(throwable));
+    }
+
+    public void testSetStackTraceWithNullElement() {
+        Throwable throwable = new Throwable();
+        try {
+            throwable.setStackTrace(new StackTraceElement[]{ null });
+            fail();
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    public void testCauseSerialization() {
+        String s = "aced0005737200136a6176612e6c616e672e5468726f7761626c65d5c635273977b8cb0300034c0"
+                + "00563617573657400154c6a6176612f6c616e672f5468726f7761626c653b4c000d64657461696c4"
+                + "d6573736167657400124c6a6176612f6c616e672f537472696e673b5b000a737461636b547261636"
+                + "574001e5b4c6a6176612f6c616e672f537461636b5472616365456c656d656e743b78707371007e0"
+                + "00071007e000574000543617573657572001e5b4c6a6176612e6c616e672e537461636b547261636"
+                + "5456c656d656e743b02462a3c3cfd22390200007870000000047372001b6a6176612e6c616e672e5"
+                + "37461636b5472616365456c656d656e746109c59a2636dd8502000449000a6c696e654e756d62657"
+                + "24c000e6465636c6172696e67436c61737371007e00024c000866696c654e616d6571007e00024c0"
+                + "00a6d6574686f644e616d6571007e0002787000000003740006436c6173734474000b436c6173734"
+                + "42e6a617661740003646f447371007e000900000002740006436c6173734374000b436c617373432"
+                + "e6a617661740003646f437371007e000900000001740006436c6173734274000b436c617373422e6"
+                + "a617661740003646f427371007e000900000000740006436c6173734174000b436c617373412e6a6"
+                + "17661740003646f41787400095468726f7761626c657571007e0007000000027371007e000900000"
+                + "001740006436c6173734274000b436c617373422e6a617661740003646f427371007e00090000000"
+                + "0740006436c6173734174000b436c617373412e6a617661740003646f4178";
+        Throwable throwable = newThrowable("Throwable", "A", "B");
+        throwable.initCause(newThrowable("Cause", "A", "B", "C", "D"));
+        assertSerialized(throwable, s);
+    }
+
+    public void testSuppressedSerialization() {
+        String s = "aced0005737200136a6176612e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c0"
+                + "00563617573657400154c6a6176612f6c616e672f5468726f7761626c653b4c000d64657461696c4"
+                + "d6573736167657400124c6a6176612f6c616e672f537472696e673b5b000a737461636b547261636"
+                + "574001e5b4c6a6176612f6c616e672f537461636b5472616365456c656d656e743b4c00147375707"
+                + "0726573736564457863657074696f6e737400104c6a6176612f7574696c2f4c6973743b787071007"
+                + "e000574000a53657269616c697a65647572001e5b4c6a6176612e6c616e672e537461636b5472616"
+                + "365456c656d656e743b02462a3c3cfd22390200007870000000027372001b6a6176612e6c616e672"
+                + "e537461636b5472616365456c656d656e746109c59a2636dd8502000449000a6c696e654e756d626"
+                + "5724c000e6465636c6172696e67436c61737371007e00024c000866696c654e616d6571007e00024"
+                + "c000a6d6574686f644e616d6571007e0002787000000001740006436c6173734274000b436c61737"
+                + "3422e6a617661740003646f427371007e000900000000740006436c6173734174000b436c6173734"
+                + "12e6a617661740003646f41737200136a6176612e7574696c2e41727261794c6973747881d21d99c"
+                + "7619d03000149000473697a657870000000017704000000017371007e000071007e001474000a537"
+                + "570707265737365647571007e0007000000047371007e000900000003740006436c6173734474000"
+                + "b436c617373442e6a617661740003646f447371007e000900000002740006436c6173734374000b4"
+                + "36c617373432e6a617661740003646f437371007e000900000001740006436c6173734274000b436"
+                + "c617373422e6a617661740003646f427371007e000900000000740006436c6173734174000b436c6"
+                + "17373412e6a617661740003646f41737200266a6176612e7574696c2e436f6c6c656374696f6e732"
+                + "4556e6d6f6469666961626c654c697374fc0f2531b5ec8e100200014c00046c69737471007e00047"
+                + "872002c6a6176612e7574696c2e436f6c6c656374696f6e7324556e6d6f6469666961626c65436f6"
+                + "c6c656374696f6e19420080cb5ef71e0200014c0001637400164c6a6176612f7574696c2f436f6c6"
+                + "c656374696f6e3b78707371007e0012000000007704000000007871007e002b787878";
+        Throwable throwable = newThrowable("Serialized", "A", "B");
+        throwable.addSuppressed(newThrowable("Suppressed", "A", "B", "C", "D"));
+        assertSerialized(throwable, s);
+    }
+
+    public void testDisableSuppressionSerialization() {
+        String s = "aced0005737200356c6962636f72652e6a6176612e6c616e672e5468726f7761626c65546573742"
+                + "45375707072657373696f6e735468726f7761626c6502cff43b5390d137020000787200136a61766"
+                + "12e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c000563617573657400154c6a6"
+                + "176612f6c616e672f5468726f7761626c653b4c000d64657461696c4d6573736167657400124c6a6"
+                + "176612f6c616e672f537472696e673b5b000a737461636b547261636574001e5b4c6a6176612f6c6"
+                + "16e672f537461636b5472616365456c656d656e743b4c00147375707072657373656445786365707"
+                + "4696f6e737400104c6a6176612f7574696c2f4c6973743b787070740003666f6f7572001e5b4c6a6"
+                + "176612e6c616e672e537461636b5472616365456c656d656e743b02462a3c3cfd223902000078700"
+                + "00000007078";
+        Throwable throwable = new SuppressionsThrowable("foo", null, false);
+        throwable.setStackTrace(new StackTraceElement[0]);
+        new SerializableTester<Throwable>(throwable, s) {
+            @Override protected boolean equals(Throwable a, Throwable b) {
+                return printStackTraceToString(a).equals(printStackTraceToString(b));
+            }
+            @Override protected void verify(Throwable deserialized) {
+                // the suppressed exception is silently discarded
+                deserialized.addSuppressed(newThrowable("Suppressed"));
+                assertSuppressed(deserialized);
+            }
+        }.test();
+    }
+
+    public void testEnableSuppressionSerialization() {
+        String s = "aced0005737200356c6962636f72652e6a6176612e6c616e672e5468726f7761626c65546573742"
+                + "45375707072657373696f6e735468726f7761626c6502cff43b5390d137020000787200136a61766"
+                + "12e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c000563617573657400154c6a6"
+                + "176612f6c616e672f5468726f7761626c653b4c000d64657461696c4d6573736167657400124c6a6"
+                + "176612f6c616e672f537472696e673b5b000a737461636b547261636574001e5b4c6a6176612f6c6"
+                + "16e672f537461636b5472616365456c656d656e743b4c00147375707072657373656445786365707"
+                + "4696f6e737400104c6a6176612f7574696c2f4c6973743b787070740003666f6f7572001e5b4c6a6"
+                + "176612e6c616e672e537461636b5472616365456c656d656e743b02462a3c3cfd223902000078700"
+                + "0000000737200266a6176612e7574696c2e436f6c6c656374696f6e7324556e6d6f6469666961626"
+                + "c654c697374fc0f2531b5ec8e100200014c00046c69737471007e00057872002c6a6176612e75746"
+                + "96c2e436f6c6c656374696f6e7324556e6d6f6469666961626c65436f6c6c656374696f6e1942008"
+                + "0cb5ef71e0200014c0001637400164c6a6176612f7574696c2f436f6c6c656374696f6e3b7870737"
+                + "200136a6176612e7574696c2e41727261794c6973747881d21d99c7619d03000149000473697a657"
+                + "870000000007704000000007871007e000f78";
+        Throwable throwable = new SuppressionsThrowable("foo", null, true);
+        throwable.setStackTrace(new StackTraceElement[0]);
+        new SerializableTester<Throwable>(throwable, s) {
+            @Override protected boolean equals(Throwable a, Throwable b) {
+                return printStackTraceToString(a).equals(printStackTraceToString(b));
+            }
+            @Override protected void verify(Throwable deserialized) {
+                // the suppressed exception is permitted
+                Throwable suppressed = newThrowable("Suppressed");
+                deserialized.addSuppressed(suppressed);
+                assertSuppressed(deserialized, suppressed);
+            }
+        }.test();
+    }
+
+    private void assertSerialized(final Throwable throwable, String golden) {
+        new SerializableTester<Throwable>(throwable, golden) {
+            @Override protected boolean equals(Throwable a, Throwable b) {
+                return printStackTraceToString(a).equals(printStackTraceToString(b));
+            }
+        }.test();
+    }
+
+    private Throwable newThrowable(String message, String... stackTraceElements) {
+        StackTraceElement[] array = new StackTraceElement[stackTraceElements.length];
+        for (int i = 0; i < stackTraceElements.length; i++) {
+            String s = stackTraceElements[i];
+            array[stackTraceElements.length - 1 - i]
+                    = new StackTraceElement("Class" + s, "do" + s, "Class" + s + ".java", i);
+        }
+        Throwable result = new Throwable(message);
+        result.setStackTrace(array);
+        return result;
+    }
+
+    private String printStackTraceToString(Throwable throwable) {
+        StringWriter writer = new StringWriter();
+        throwable.printStackTrace(new PrintWriter(writer));
+        return writer.toString();
+    }
+
+    private void assertSuppressed(Throwable throwable, Throwable... expectedSuppressed) {
+        assertEquals(Arrays.asList(throwable.getSuppressed()), Arrays.asList(expectedSuppressed));
+    }
 }
diff --git a/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java b/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java
index 0c268ff..7b87ae0 100644
--- a/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java
+++ b/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java
@@ -45,13 +45,13 @@
             m.invoke(new Integer(5)); // Wrong type for 'this'.
             fail();
         } catch (IllegalArgumentException iae) {
-            assertEquals("expected receiver of type java.lang.String, not java.lang.Integer", iae.getMessage());
+            assertEquals("expected receiver of type java.lang.String, but got java.lang.Integer", iae.getMessage());
         }
         try {
             m.invoke(null); // Null for 'this'.
             fail();
         } catch (NullPointerException npe) {
-            assertEquals("expected receiver of type java.lang.String, not null", npe.getMessage());
+            assertEquals("expected receiver of type java.lang.String, but got null", npe.getMessage());
         }
     }
 
diff --git a/luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java b/luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java
new file mode 100644
index 0000000..ca8c790
--- /dev/null
+++ b/luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java
@@ -0,0 +1,1287 @@
+/*
+ * 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 libcore.java.net;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ResponseCache;
+import java.net.SecureCacheResponse;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.GZIPOutputStream;
+import javax.net.ssl.HttpsURLConnection;
+import junit.framework.TestCase;
+import libcore.javax.net.ssl.TestSSLContext;
+import libcore.net.http.HttpResponseCache;
+import tests.http.MockResponse;
+import tests.http.MockWebServer;
+import tests.http.RecordedRequest;
+import static tests.http.SocketPolicy.DISCONNECT_AT_END;
+
+public final class HttpResponseCacheTest extends TestCase {
+    private MockWebServer server = new MockWebServer();
+    private HttpResponseCache cache = new HttpResponseCache();
+
+    @Override protected void setUp() throws Exception {
+        super.setUp();
+        ResponseCache.setDefault(cache);
+    }
+
+    @Override protected void tearDown() throws Exception {
+        ResponseCache.setDefault(null);
+        server.shutdown();
+        super.tearDown();
+    }
+
+    /**
+     * Test that response caching is consistent with the RI and the spec.
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
+     */
+    public void testResponseCachingByResponseCode() throws Exception {
+        // Test each documented HTTP/1.1 code, plus the first unused value in each range.
+        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+
+        // We can't test 100 because it's not really a response.
+        // assertCached(false, 100);
+        assertCached(false, 101);
+        assertCached(false, 102);
+        assertCached(true,  200);
+        assertCached(false, 201);
+        assertCached(false, 202);
+        assertCached(true,  203);
+        assertCached(false, 204);
+        assertCached(false, 205);
+        assertCached(true,  206);
+        assertCached(false, 207);
+        assertCached(true,  300);
+        assertCached(true,  301);
+        for (int i = 302; i <= 308; ++i) {
+            assertCached(false, i);
+        }
+        for (int i = 400; i <= 406; ++i) {
+            assertCached(false, i);
+        }
+        // (See test_responseCaching_407.)
+        assertCached(false, 408);
+        assertCached(false, 409);
+        // (See test_responseCaching_410.)
+        for (int i = 411; i <= 418; ++i) {
+            assertCached(false, i);
+        }
+        for (int i = 500; i <= 506; ++i) {
+            assertCached(false, i);
+        }
+    }
+
+    /**
+     * Response code 407 should only come from proxy servers. Android's client
+     * throws if it is sent by an origin server.
+     */
+    public void testOriginServerSends407() throws Exception {
+        server.enqueue(new MockResponse().setResponseCode(407));
+        server.play();
+
+        URL url = server.getUrl("/");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        try {
+            conn.getResponseCode();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    public void test_responseCaching_410() throws Exception {
+        // the HTTP spec permits caching 410s, but the RI doesn't.
+        assertCached(true, 410);
+    }
+
+    private void assertCached(boolean shouldPut, int responseCode) throws Exception {
+        server = new MockWebServer();
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .setResponseCode(responseCode)
+                .setBody("ABCDE")
+                .addHeader("WWW-Authenticate: challenge"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        assertEquals(responseCode, conn.getResponseCode());
+
+        // exhaust the content stream
+        readAscii(conn);
+
+        Set<URI> expectedCachedUris = shouldPut
+                ? Collections.singleton(url.toURI())
+                : Collections.<URI>emptySet();
+        assertEquals(Integer.toString(responseCode),
+                expectedCachedUris, cache.getContents().keySet());
+        server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers
+    }
+
+    /**
+     * Test that we can interrogate the response when the cache is being
+     * populated. http://code.google.com/p/android/issues/detail?id=7787
+     */
+    public void testResponseCacheCallbackApis() throws Exception {
+        final String body = "ABCDE";
+        final AtomicInteger cacheCount = new AtomicInteger();
+
+        server.enqueue(new MockResponse()
+                .setStatus("HTTP/1.1 200 Fantastic")
+                .addHeader("fgh: ijk")
+                .setBody(body));
+        server.play();
+
+        ResponseCache.setDefault(new ResponseCache() {
+            @Override public CacheResponse get(URI uri, String requestMethod,
+                    Map<String, List<String>> requestHeaders) throws IOException {
+                return null;
+            }
+            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
+                HttpURLConnection httpConnection = (HttpURLConnection) conn;
+                try {
+                    httpConnection.getRequestProperties();
+                    fail();
+                } catch (IllegalStateException expected) {
+                }
+                try {
+                    httpConnection.addRequestProperty("K", "V");
+                    fail();
+                } catch (IllegalStateException expected) {
+                }
+                assertEquals("HTTP/1.1 200 Fantastic", httpConnection.getHeaderField(null));
+                assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"),
+                        httpConnection.getHeaderFields().get(null));
+                assertEquals(200, httpConnection.getResponseCode());
+                assertEquals("Fantastic", httpConnection.getResponseMessage());
+                assertEquals(body.length(), httpConnection.getContentLength());
+                assertEquals("ijk", httpConnection.getHeaderField("fgh"));
+                try {
+                    httpConnection.getInputStream(); // the RI doesn't forbid this, but it should
+                    fail();
+                } catch (IOException expected) {
+                }
+                cacheCount.incrementAndGet();
+                return null;
+            }
+        });
+
+        URL url = server.getUrl("/");
+        URLConnection connection = url.openConnection();
+        assertEquals(body, readAscii(connection));
+        assertEquals(1, cacheCount.get());
+    }
+
+
+    public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException {
+        testResponseCaching(TransferKind.FIXED_LENGTH);
+    }
+
+    public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException {
+        testResponseCaching(TransferKind.CHUNKED);
+    }
+
+    public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException {
+        testResponseCaching(TransferKind.END_OF_STREAM);
+    }
+
+    /**
+     * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption
+     * http://code.google.com/p/android/issues/detail?id=8175
+     */
+    private void testResponseCaching(TransferKind transferKind) throws IOException {
+        MockResponse response = new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .setStatus("HTTP/1.1 200 Fantastic");
+        transferKind.setBody(response, "I love puppies but hate spiders", 1);
+        server.enqueue(response);
+        server.play();
+
+        // Make sure that calling skip() doesn't omit bytes from the cache.
+        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
+        InputStream in = urlConnection.getInputStream();
+        assertEquals("I love ", readAscii(urlConnection, "I love ".length()));
+        reliableSkip(in, "puppies but hate ".length());
+        assertEquals("spiders", readAscii(urlConnection, "spiders".length()));
+        assertEquals(-1, in.read());
+        in.close();
+        assertEquals(1, cache.getSuccessCount());
+        assertEquals(0, cache.getAbortCount());
+
+        urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached!
+        in = urlConnection.getInputStream();
+        assertEquals("I love puppies but hate spiders",
+                readAscii(urlConnection, "I love puppies but hate spiders".length()));
+        assertEquals(200, urlConnection.getResponseCode());
+        assertEquals("Fantastic", urlConnection.getResponseMessage());
+
+        assertEquals(-1, in.read());
+        assertEquals(1, cache.getMissCount());
+        assertEquals(1, cache.getHitCount());
+        assertEquals(1, cache.getSuccessCount());
+        assertEquals(0, cache.getAbortCount());
+    }
+
+    public void testSecureResponseCaching() throws IOException {
+        TestSSLContext testSSLContext = TestSSLContext.create();
+        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .setBody("ABC"));
+        server.play();
+
+        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
+        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+        assertEquals("ABC", readAscii(connection));
+
+        // OpenJDK 6 fails on this line, complaining that the connection isn't open yet
+        String suite = connection.getCipherSuite();
+        List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates());
+        List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates());
+        Principal peerPrincipal = connection.getPeerPrincipal();
+        Principal localPrincipal = connection.getLocalPrincipal();
+
+        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
+        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+        assertEquals("ABC", readAscii(connection));
+
+        assertEquals(1, cache.getMissCount());
+        assertEquals(1, cache.getHitCount());
+
+        assertEquals(suite, connection.getCipherSuite());
+        assertEquals(localCerts, toListOrNull(connection.getLocalCertificates()));
+        assertEquals(serverCerts, toListOrNull(connection.getServerCertificates()));
+        assertEquals(peerPrincipal, connection.getPeerPrincipal());
+        assertEquals(localPrincipal, connection.getLocalPrincipal());
+    }
+
+    public void testCacheReturnsInsecureResponseForSecureRequest() throws IOException {
+        TestSSLContext testSSLContext = TestSSLContext.create();
+        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
+        server.enqueue(new MockResponse().setBody("ABC"));
+        server.enqueue(new MockResponse().setBody("DEF"));
+        server.play();
+
+        ResponseCache.setDefault(new InsecureResponseCache());
+
+        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
+        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+        assertEquals("ABC", readAscii(connection));
+
+        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // not cached!
+        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+        assertEquals("DEF", readAscii(connection));
+    }
+
+    public void testResponseCachingAndRedirects() throws IOException {
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
+                .addHeader("Location: /foo"));
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .setBody("ABC"));
+        server.enqueue(new MockResponse().setBody("DEF"));
+        server.play();
+
+        URLConnection connection = server.getUrl("/").openConnection();
+        assertEquals("ABC", readAscii(connection));
+
+        connection = server.getUrl("/").openConnection(); // cached!
+        assertEquals("ABC", readAscii(connection));
+
+        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
+        assertEquals(2, cache.getHitCount());
+    }
+
+    public void testSecureResponseCachingAndRedirects() throws IOException {
+        TestSSLContext testSSLContext = TestSSLContext.create();
+        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
+                .addHeader("Location: /foo"));
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .setBody("ABC"));
+        server.enqueue(new MockResponse().setBody("DEF"));
+        server.play();
+
+        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
+        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+        assertEquals("ABC", readAscii(connection));
+
+        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
+        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+        assertEquals("ABC", readAscii(connection));
+
+        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
+        assertEquals(2, cache.getHitCount());
+    }
+
+    public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException {
+        server.enqueue(new MockResponse().setBody("ABC"));
+        server.play();
+
+        final AtomicReference<Map<String, List<String>>> requestHeadersRef
+                = new AtomicReference<Map<String, List<String>>>();
+        ResponseCache.setDefault(new ResponseCache() {
+            @Override public CacheResponse get(URI uri, String requestMethod,
+                    Map<String, List<String>> requestHeaders) throws IOException {
+                requestHeadersRef.set(requestHeaders);
+                return null;
+            }
+            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
+                return null;
+            }
+        });
+
+        URL url = server.getUrl("/");
+        URLConnection urlConnection = url.openConnection();
+        urlConnection.addRequestProperty("A", "android");
+        readAscii(urlConnection);
+        assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A"));
+    }
+
+
+    public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException {
+        testServerPrematureDisconnect(TransferKind.FIXED_LENGTH);
+    }
+
+    public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException {
+        testServerPrematureDisconnect(TransferKind.CHUNKED);
+    }
+
+    public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException {
+        /*
+         * Intentionally empty. This case doesn't make sense because there's no
+         * such thing as a premature disconnect when the disconnect itself
+         * indicates the end of the data stream.
+         */
+    }
+
+    private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException {
+        MockResponse response = new MockResponse();
+        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16);
+        server.enqueue(truncateViolently(response, 16));
+        server.enqueue(new MockResponse().setBody("Request #2"));
+        server.play();
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(
+                server.getUrl("/").openConnection().getInputStream()));
+        assertEquals("ABCDE", reader.readLine());
+        try {
+            reader.readLine();
+            fail("This implementation silently ignored a truncated HTTP body.");
+        } catch (IOException expected) {
+        }
+
+        assertEquals(1, cache.getAbortCount());
+        assertEquals(0, cache.getSuccessCount());
+        URLConnection connection = server.getUrl("/").openConnection();
+        assertEquals("Request #2", readAscii(connection));
+        assertEquals(1, cache.getAbortCount());
+        assertEquals(1, cache.getSuccessCount());
+    }
+
+    public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException {
+        testClientPrematureDisconnect(TransferKind.FIXED_LENGTH);
+    }
+
+    public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException {
+        testClientPrematureDisconnect(TransferKind.CHUNKED);
+    }
+
+    public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException {
+        testClientPrematureDisconnect(TransferKind.END_OF_STREAM);
+    }
+
+    private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException {
+        MockResponse response = new MockResponse();
+        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024);
+        server.enqueue(response);
+        server.enqueue(new MockResponse().setBody("Request #2"));
+        server.play();
+
+        URLConnection connection = server.getUrl("/").openConnection();
+        InputStream in = connection.getInputStream();
+        assertEquals("ABCDE", readAscii(connection, 5));
+        in.close();
+        try {
+            in.read();
+            fail("Expected an IOException because the stream is closed.");
+        } catch (IOException expected) {
+        }
+
+        assertEquals(1, cache.getAbortCount());
+        assertEquals(0, cache.getSuccessCount());
+        connection = server.getUrl("/").openConnection();
+        assertEquals("Request #2", readAscii(connection));
+        assertEquals(1, cache.getAbortCount());
+        assertEquals(1, cache.getSuccessCount());
+    }
+
+    public void testDefaultExpirationDateFullyCachedForLessThan24Hours() throws Exception {
+        //      last modified: 105 seconds ago
+        //             served:   5 seconds ago
+        //   default lifetime: (105 - 5) / 10 = 10 seconds
+        //            expires:  10 seconds from served date = 5 seconds from now
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
+                .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
+                .setBody("A"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        assertEquals("A", readAscii(url.openConnection()));
+        URLConnection connection = url.openConnection();
+        assertEquals("A", readAscii(connection));
+        assertNull(connection.getHeaderField("Warning"));
+    }
+
+    public void testDefaultExpirationDateConditionallyCached() throws Exception {
+        //      last modified: 115 seconds ago
+        //             served:  15 seconds ago
+        //   default lifetime: (115 - 15) / 10 = 10 seconds
+        //            expires:  10 seconds from served date = 5 seconds ago
+        String lastModifiedDate = formatDate(-115, TimeUnit.SECONDS);
+        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
+                .addHeader("Last-Modified: " + lastModifiedDate)
+                .addHeader("Date: " + formatDate(-15, TimeUnit.SECONDS)));
+        List<String> headers = conditionalRequest.getHeaders();
+        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
+    }
+
+    public void testDefaultExpirationDateFullyCachedForMoreThan24Hours() throws Exception {
+        //      last modified: 105 days ago
+        //             served:   5 days ago
+        //   default lifetime: (105 - 5) / 10 = 10 days
+        //            expires:  10 days from served date = 5 days from now
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.DAYS))
+                .addHeader("Date: " + formatDate(-5, TimeUnit.DAYS))
+                .setBody("A"));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        URLConnection connection = server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection));
+        assertEquals("113 HttpURLConnection \"Heuristic expiration\"",
+                connection.getHeaderField("Warning"));
+    }
+
+    public void testNoDefaultExpirationForUrlsWithQueryString() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
+                .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/?foo=bar");
+        assertEquals("A", readAscii(url.openConnection()));
+        assertEquals("B", readAscii(url.openConnection()));
+    }
+
+    public void testExpirationDateInThePastWithLastModifiedHeader() throws Exception {
+        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
+        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
+                .addHeader("Last-Modified: " + lastModifiedDate)
+                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
+        List<String> headers = conditionalRequest.getHeaders();
+        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
+    }
+
+    public void testExpirationDateInThePastWithNoLastModifiedHeader() throws Exception {
+        assertNotCached(new MockResponse()
+                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
+    }
+
+    public void testExpirationDateInTheFuture() throws Exception {
+        assertFullyCached(new MockResponse()
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
+    }
+
+    public void testMaxAgePreferredWithMaxAgeAndExpires() throws Exception {
+        assertFullyCached(new MockResponse()
+                .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: max-age=60"));
+    }
+
+    public void testMaxAgeInThePastWithDateAndLastModifiedHeaders() throws Exception {
+        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
+        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
+                .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
+                .addHeader("Last-Modified: " + lastModifiedDate)
+                .addHeader("Cache-Control: max-age=60"));
+        List<String> headers = conditionalRequest.getHeaders();
+        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
+    }
+
+    public void testMaxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception {
+        /*
+         * Chrome interprets max-age relative to the local clock. Both our cache
+         * and Firefox both use the earlier of the local and server's clock.
+         */
+        assertNotCached(new MockResponse()
+                .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
+                .addHeader("Cache-Control: max-age=60"));
+    }
+
+    public void testMaxAgeInTheFutureWithDateHeader() throws Exception {
+        assertFullyCached(new MockResponse()
+                .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
+                .addHeader("Cache-Control: max-age=60"));
+    }
+
+    public void testMaxAgeInTheFutureWithNoDateHeader() throws Exception {
+        assertFullyCached(new MockResponse()
+                .addHeader("Cache-Control: max-age=60"));
+    }
+
+    public void testMaxAgeWithLastModifiedButNoServedDate() throws Exception {
+        assertFullyCached(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
+                .addHeader("Cache-Control: max-age=60"));
+    }
+
+    public void testMaxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception {
+        assertFullyCached(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
+                .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
+                .addHeader("Cache-Control: max-age=60"));
+    }
+
+    public void testMaxAgePreferredOverLowerSharedMaxAge() throws Exception {
+        assertFullyCached(new MockResponse()
+                .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
+                .addHeader("Cache-Control: s-maxage=60")
+                .addHeader("Cache-Control: max-age=180"));
+    }
+
+    public void testMaxAgePreferredOverHigherMaxAge() throws Exception {
+        assertNotCached(new MockResponse()
+                .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
+                .addHeader("Cache-Control: s-maxage=180")
+                .addHeader("Cache-Control: max-age=60"));
+    }
+
+    public void testRequestMethodOptionsIsNotCached() throws Exception {
+        testRequestMethod("OPTIONS", false);
+    }
+
+    public void testRequestMethodGetIsCached() throws Exception {
+        testRequestMethod("GET", true);
+    }
+
+    public void testRequestMethodHeadIsNotCached() throws Exception {
+        // We could support this but choose not to for implementation simplicity
+        testRequestMethod("HEAD", false);
+    }
+
+    public void testRequestMethodPostIsNotCached() throws Exception {
+        // We could support this but choose not to for implementation simplicity
+        testRequestMethod("POST", false);
+    }
+
+    public void testRequestMethodPutIsNotCached() throws Exception {
+        testRequestMethod("PUT", false);
+    }
+
+    public void testRequestMethodDeleteIsNotCached() throws Exception {
+        testRequestMethod("DELETE", false);
+    }
+
+    public void testRequestMethodTraceIsNotCached() throws Exception {
+        testRequestMethod("TRACE", false);
+    }
+
+    private void testRequestMethod(String requestMethod, boolean expectCached) throws Exception {
+        /*
+         * 1. seed the cache (potentially)
+         * 2. expect a cache hit or miss
+         */
+        server.enqueue(new MockResponse()
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .addHeader("X-Response-ID: 1"));
+        server.enqueue(new MockResponse()
+                .addHeader("X-Response-ID: 2"));
+        server.play();
+
+        URL url = server.getUrl("/");
+
+        HttpURLConnection request1 = (HttpURLConnection) url.openConnection();
+        request1.setRequestMethod(requestMethod);
+        addRequestBodyIfNecessary(requestMethod, request1);
+        assertEquals("1", request1.getHeaderField("X-Response-ID"));
+
+        URLConnection request2 = url.openConnection();
+        if (expectCached) {
+            assertEquals("1", request1.getHeaderField("X-Response-ID"));
+        } else {
+            assertEquals("2", request2.getHeaderField("X-Response-ID"));
+        }
+    }
+
+    public void testPostInvalidatesCache() throws Exception {
+        testMethodInvalidates("POST");
+    }
+
+    public void testPutInvalidatesCache() throws Exception {
+        testMethodInvalidates("PUT");
+    }
+
+    public void testDeleteMethodInvalidatesCache() throws Exception {
+        testMethodInvalidates("DELETE");
+    }
+
+    private void testMethodInvalidates(String requestMethod) throws Exception {
+        /*
+         * 1. seed the cache
+         * 2. invalidate it
+         * 3. expect a cache miss
+         */
+        server.enqueue(new MockResponse().setBody("A")
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.enqueue(new MockResponse().setBody("C"));
+        server.play();
+
+        URL url = server.getUrl("/");
+
+        assertEquals("A", readAscii(url.openConnection()));
+
+        HttpURLConnection invalidate = (HttpURLConnection) url.openConnection();
+        invalidate.setRequestMethod(requestMethod);
+        addRequestBodyIfNecessary(requestMethod, invalidate);
+        assertEquals("B", readAscii(invalidate));
+
+        assertEquals("C", readAscii(url.openConnection()));
+    }
+
+    public void testEtag() throws Exception {
+        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
+                .addHeader("ETag: v1"));
+        assertTrue(conditionalRequest.getHeaders().contains("If-None-Match: v1"));
+    }
+
+    public void testEtagAndExpirationDateInThePast() throws Exception {
+        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
+        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
+                .addHeader("ETag: v1")
+                .addHeader("Last-Modified: " + lastModifiedDate)
+                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
+        List<String> headers = conditionalRequest.getHeaders();
+        assertTrue(headers.contains("If-None-Match: v1"));
+        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
+    }
+
+    public void testEtagAndExpirationDateInTheFuture() throws Exception {
+        assertFullyCached(new MockResponse()
+                .addHeader("ETag: v1")
+                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
+    }
+
+    public void testCacheControlNoCache() throws Exception {
+        assertNotCached(new MockResponse().addHeader("Cache-Control: no-cache"));
+    }
+
+    public void testCacheControlNoCacheAndExpirationDateInTheFuture() throws Exception {
+        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
+        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
+                .addHeader("Last-Modified: " + lastModifiedDate)
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: no-cache"));
+        List<String> headers = conditionalRequest.getHeaders();
+        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
+    }
+
+    public void testPragmaNoCache() throws Exception {
+        assertNotCached(new MockResponse().addHeader("Pragma: no-cache"));
+    }
+
+    public void testPragmaNoCacheAndExpirationDateInTheFuture() throws Exception {
+        String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
+        RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
+                .addHeader("Last-Modified: " + lastModifiedDate)
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .addHeader("Pragma: no-cache"));
+        List<String> headers = conditionalRequest.getHeaders();
+        assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
+    }
+
+    public void testCacheControlNoStore() throws Exception {
+        assertNotCached(new MockResponse().addHeader("Cache-Control: no-store"));
+    }
+
+    public void testCacheControlNoStoreAndExpirationDateInTheFuture() throws Exception {
+        assertNotCached(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: no-store"));
+    }
+
+    public void testPartialRangeResponsesDoNotCorruptCache() throws Exception {
+        /*
+         * 1. request a range
+         * 2. request a full document, expecting a cache miss
+         */
+        server.enqueue(new MockResponse().setBody("AA")
+                .setResponseCode(HttpURLConnection.HTTP_PARTIAL)
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .addHeader("Content-Range: bytes 1000-1001/2000"));
+        server.enqueue(new MockResponse().setBody("BB"));
+        server.play();
+
+        URL url = server.getUrl("/");
+
+        URLConnection range = url.openConnection();
+        range.addRequestProperty("Range", "bytes=1000-1001");
+        assertEquals("AA", readAscii(range));
+
+        assertEquals("BB", readAscii(url.openConnection()));
+    }
+
+    public void testServerReturnsDocumentOlderThanCache() throws Exception {
+        server.enqueue(new MockResponse().setBody("A")
+                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
+        server.enqueue(new MockResponse().setBody("B")
+                .addHeader("Last-Modified: " + formatDate(-4, TimeUnit.HOURS)));
+        server.play();
+
+        URL url = server.getUrl("/");
+
+        assertEquals("A", readAscii(url.openConnection()));
+        assertEquals("A", readAscii(url.openConnection()));
+    }
+
+    public void testNonIdentityEncodingAndConditionalCache() throws Exception {
+        assertNonIdentityEncodingCached(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
+    }
+
+    public void testNonIdentityEncodingAndFullCache() throws Exception {
+        assertNonIdentityEncodingCached(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
+    }
+
+    private void assertNonIdentityEncodingCached(MockResponse response) throws Exception {
+        server.enqueue(response
+                .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
+                .addHeader("Content-Encoding: gzip"));
+        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+
+        server.play();
+        assertEquals("ABCABCABC", readAscii(server.getUrl("/").openConnection()));
+        assertEquals("ABCABCABC", readAscii(server.getUrl("/").openConnection()));
+    }
+
+    public void testExpiresDateBeforeModifiedDate() throws Exception {
+        assertConditionallyCached(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(-2, TimeUnit.HOURS)));
+    }
+
+    public void testRequestMaxAge() throws IOException {
+        server.enqueue(new MockResponse().setBody("A")
+                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
+                .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
+        server.enqueue(new MockResponse().setBody("B"));
+
+        server.play();
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+
+        URLConnection connection = server.getUrl("/").openConnection();
+        connection.addRequestProperty("Cache-Control", "max-age=30");
+        assertEquals("B", readAscii(connection));
+    }
+
+    public void testRequestMinFresh() throws IOException {
+        server.enqueue(new MockResponse().setBody("A")
+                .addHeader("Cache-Control: max-age=60")
+                .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
+        server.enqueue(new MockResponse().setBody("B"));
+
+        server.play();
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+
+        URLConnection connection = server.getUrl("/").openConnection();
+        connection.addRequestProperty("Cache-Control", "min-fresh=120");
+        assertEquals("B", readAscii(connection));
+    }
+
+    public void testRequestMaxStale() throws IOException {
+        server.enqueue(new MockResponse().setBody("A")
+                .addHeader("Cache-Control: max-age=120")
+                .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES)));
+        server.enqueue(new MockResponse().setBody("B"));
+
+        server.play();
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+
+        URLConnection connection = server.getUrl("/").openConnection();
+        connection.addRequestProperty("Cache-Control", "max-stale=180");
+        assertEquals("A", readAscii(connection));
+        assertEquals("110 HttpURLConnection \"Response is stale\"",
+                connection.getHeaderField("Warning"));
+    }
+
+    public void testRequestOnlyIfCachedWithNoResponseCached() throws IOException {
+        // (no responses enqueued)
+        server.play();
+
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.addRequestProperty("Cache-Control", "only-if-cached");
+        assertBadGateway(connection);
+    }
+
+    public void testRequestOnlyIfCachedWithFullResponseCached() throws IOException {
+        server.enqueue(new MockResponse().setBody("A")
+                .addHeader("Cache-Control: max-age=30")
+                .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        URLConnection connection = server.getUrl("/").openConnection();
+        connection.addRequestProperty("Cache-Control", "only-if-cached");
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+    }
+
+    public void testRequestOnlyIfCachedWithConditionalResponseCached() throws IOException {
+        server.enqueue(new MockResponse().setBody("A")
+                .addHeader("Cache-Control: max-age=30")
+                .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES)));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.addRequestProperty("Cache-Control", "only-if-cached");
+        assertBadGateway(connection);
+    }
+
+    public void testRequestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException {
+        server.enqueue(new MockResponse().setBody("A"));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.addRequestProperty("Cache-Control", "only-if-cached");
+        assertBadGateway(connection);
+    }
+
+    public void testRequestCacheControlNoCache() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
+                .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
+                .addHeader("Cache-Control: max-age=60")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        assertEquals("A", readAscii(url.openConnection()));
+        URLConnection connection = url.openConnection();
+        connection.setRequestProperty("Cache-Control", "no-cache");
+        assertEquals("B", readAscii(connection));
+    }
+
+    public void testRequestPragmaNoCache() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
+                .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
+                .addHeader("Cache-Control: max-age=60")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        assertEquals("A", readAscii(url.openConnection()));
+        URLConnection connection = url.openConnection();
+        connection.setRequestProperty("Pragma", "no-cache");
+        assertEquals("B", readAscii(connection));
+    }
+
+    public void testClientSuppliedIfModifiedSinceWithCachedResult() throws Exception {
+        MockResponse response = new MockResponse()
+                .addHeader("ETag: v3")
+                .addHeader("Cache-Control: max-age=0");
+        String ifModifiedSinceDate = formatDate(-24, TimeUnit.HOURS);
+        RecordedRequest request = assertClientSuppliedCondition(
+                response, "If-Modified-Since", ifModifiedSinceDate);
+        List<String> headers = request.getHeaders();
+        assertTrue(headers.contains("If-Modified-Since: " + ifModifiedSinceDate));
+        assertFalse(headers.contains("If-None-Match: v3"));
+    }
+
+    public void testClientSuppliedIfNoneMatchSinceWithCachedResult() throws Exception {
+        String lastModifiedDate = formatDate(-3, TimeUnit.MINUTES);
+        MockResponse response = new MockResponse()
+                .addHeader("Last-Modified: " + lastModifiedDate)
+                .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
+                .addHeader("Cache-Control: max-age=0");
+        RecordedRequest request = assertClientSuppliedCondition(
+                response, "If-None-Match", "v1");
+        List<String> headers = request.getHeaders();
+        assertTrue(headers.contains("If-None-Match: v1"));
+        assertFalse(headers.contains("If-Modified-Since: " + lastModifiedDate));
+    }
+
+    private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String conditionName,
+            String conditionValue) throws Exception {
+        server.enqueue(seed.setBody("A"));
+        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+        server.play();
+
+        URL url = server.getUrl("/");
+        assertEquals("A", readAscii(url.openConnection()));
+
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.addRequestProperty(conditionName, conditionValue);
+        assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
+        assertEquals("", readAscii(connection));
+
+        server.takeRequest(); // seed
+        return server.takeRequest();
+    }
+
+    public void testClientSuppliedConditionWithoutCachedResult() throws Exception {
+        server.enqueue(new MockResponse()
+                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+        server.play();
+
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        String clientIfModifiedSince = formatDate(-24, TimeUnit.HOURS);
+        connection.addRequestProperty("If-Modified-Since", clientIfModifiedSince);
+        assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
+        assertEquals("", readAscii(connection));
+    }
+
+    public void testAuthorizationRequestHeaderPreventsCaching() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.MINUTES))
+                .addHeader("Cache-Control: max-age=60")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        URLConnection connection = url.openConnection();
+        connection.addRequestProperty("Authorization", "password");
+        assertEquals("A", readAscii(connection));
+        assertEquals("B", readAscii(url.openConnection()));
+    }
+
+    public void testAuthorizationResponseCachedWithSMaxAge() throws Exception {
+        assertAuthorizationRequestFullyCached(new MockResponse()
+                .addHeader("Cache-Control: s-maxage=60"));
+    }
+
+    public void testAuthorizationResponseCachedWithPublic() throws Exception {
+        assertAuthorizationRequestFullyCached(new MockResponse()
+                .addHeader("Cache-Control: public"));
+    }
+
+    public void testAuthorizationResponseCachedWithMustRevalidate() throws Exception {
+        assertAuthorizationRequestFullyCached(new MockResponse()
+                .addHeader("Cache-Control: must-revalidate"));
+    }
+
+    public void assertAuthorizationRequestFullyCached(MockResponse response) throws Exception {
+        server.enqueue(response
+                .addHeader("Cache-Control: max-age=60")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        URLConnection connection = url.openConnection();
+        connection.addRequestProperty("Authorization", "password");
+        assertEquals("A", readAscii(connection));
+        assertEquals("A", readAscii(url.openConnection()));
+    }
+
+    public void testCacheControlMustRevalidate() throws Exception {
+        fail("Cache-Control: must-revalidate"); // TODO
+    }
+
+    public void testVaryResponsesAreNotSupported() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .addHeader("Vary: Accept-Language")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        URLConnection connection1 = url.openConnection();
+        connection1.addRequestProperty("Accept-Language", "fr-CA");
+        assertEquals("A", readAscii(connection1));
+
+        URLConnection connection2 = url.openConnection();
+        connection2.addRequestProperty("Accept-Language", "fr-CA");
+        assertEquals("B", readAscii(connection2));
+    }
+
+    public void testContentLocationDoesNotPopulateCache() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .addHeader("Content-Location: /bar")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/foo").openConnection()));
+        assertEquals("B", readAscii(server.getUrl("/bar").openConnection()));
+    }
+
+    /**
+     * @param delta the offset from the current date to use. Negative
+     *     values yield dates in the past; positive values yield dates in the
+     *     future.
+     */
+    private String formatDate(long delta, TimeUnit timeUnit) {
+        Date date = new Date(System.currentTimeMillis() + timeUnit.toMillis(delta));
+        DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+        rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
+        return rfc1123.format(date);
+    }
+
+    private void addRequestBodyIfNecessary(String requestMethod, HttpURLConnection invalidate)
+            throws IOException {
+        if (requestMethod.equals("POST") || requestMethod.equals("PUT")) {
+            invalidate.setDoOutput(true);
+            OutputStream requestBody = invalidate.getOutputStream();
+            requestBody.write('x');
+            requestBody.close();
+        }
+    }
+
+    private void assertNotCached(MockResponse response) throws Exception {
+        server.enqueue(response.setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        assertEquals("A", readAscii(url.openConnection()));
+        assertEquals("B", readAscii(url.openConnection()));
+    }
+
+    /**
+     * @return the request with the conditional get headers.
+     */
+    private RecordedRequest assertConditionallyCached(MockResponse response) throws Exception {
+        // scenario 1: condition succeeds
+        server.enqueue(response.setBody("A"));
+        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+
+        // scenario 2: condition fails
+        server.enqueue(response.setBody("B"));
+        server.enqueue(new MockResponse().setBody("C"));
+
+        server.play();
+
+        URL valid = server.getUrl("/valid");
+        assertEquals("A", readAscii(valid.openConnection()));
+        assertEquals("A", readAscii(valid.openConnection()));
+
+        URL invalid = server.getUrl("/invalid");
+        assertEquals("B", readAscii(invalid.openConnection()));
+        assertEquals("C", readAscii(invalid.openConnection()));
+
+        server.takeRequest(); // regular get
+        return server.takeRequest(); // conditional get
+    }
+
+    private void assertFullyCached(MockResponse response) throws Exception {
+        server.enqueue(response.setBody("A"));
+        server.enqueue(response.setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        assertEquals("A", readAscii(url.openConnection()));
+        assertEquals("A", readAscii(url.openConnection()));
+    }
+
+    /**
+     * Shortens the body of {@code response} but not the corresponding headers.
+     * Only useful to test how clients respond to the premature conclusion of
+     * the HTTP body.
+     */
+    private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) {
+        response.setSocketPolicy(DISCONNECT_AT_END);
+        List<String> headers = new ArrayList<String>(response.getHeaders());
+        response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep));
+        response.getHeaders().clear();
+        response.getHeaders().addAll(headers);
+        return response;
+    }
+
+    /**
+     * Reads {@code count} characters from the stream. If the stream is
+     * exhausted before {@code count} characters can be read, the remaining
+     * characters are returned and the stream is closed.
+     */
+    private String readAscii(URLConnection connection, int count) throws IOException {
+        HttpURLConnection httpConnection = (HttpURLConnection) connection;
+        InputStream in = httpConnection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST
+                ? connection.getInputStream()
+                : httpConnection.getErrorStream();
+        StringBuilder result = new StringBuilder();
+        for (int i = 0; i < count; i++) {
+            int value = in.read();
+            if (value == -1) {
+                in.close();
+                break;
+            }
+            result.append((char) value);
+        }
+        return result.toString();
+    }
+
+    private String readAscii(URLConnection connection) throws IOException {
+        return readAscii(connection, Integer.MAX_VALUE);
+    }
+
+    private void reliableSkip(InputStream in, int length) throws IOException {
+        while (length > 0) {
+            length -= in.skip(length);
+        }
+    }
+
+    private void assertBadGateway(HttpURLConnection connection) throws IOException {
+        try {
+            connection.getInputStream();
+            fail();
+        } catch (FileNotFoundException expected) {
+        }
+        assertEquals(HttpURLConnection.HTTP_BAD_GATEWAY, connection.getResponseCode());
+        assertEquals(-1, connection.getErrorStream().read());
+    }
+
+    enum TransferKind {
+        CHUNKED() {
+            @Override void setBody(MockResponse response, byte[] content, int chunkSize)
+                    throws IOException {
+                response.setChunkedBody(content, chunkSize);
+            }
+        },
+        FIXED_LENGTH() {
+            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
+                response.setBody(content);
+            }
+        },
+        END_OF_STREAM() {
+            @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
+                response.setBody(content);
+                response.setSocketPolicy(DISCONNECT_AT_END);
+                for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
+                    if (h.next().startsWith("Content-Length:")) {
+                        h.remove();
+                        break;
+                    }
+                }
+            }
+        };
+
+        abstract void setBody(MockResponse response, byte[] content, int chunkSize)
+                throws IOException;
+
+        void setBody(MockResponse response, String content, int chunkSize) throws IOException {
+            setBody(response, content.getBytes("UTF-8"), chunkSize);
+        }
+    }
+
+    private <T> List<T> toListOrNull(T[] arrayOrNull) {
+        return arrayOrNull != null ? Arrays.asList(arrayOrNull) : null;
+    }
+
+    /**
+     * Returns a gzipped copy of {@code bytes}.
+     */
+    public byte[] gzip(byte[] bytes) throws IOException {
+        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+        OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
+        gzippedOut.write(bytes);
+        gzippedOut.close();
+        return bytesOut.toByteArray();
+    }
+
+    private static class InsecureResponseCache extends ResponseCache {
+        private final HttpResponseCache delegate = new HttpResponseCache();
+
+        @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
+            return delegate.put(uri, connection);
+        }
+
+        @Override public CacheResponse get(URI uri, String requestMethod,
+                Map<String, List<String>> requestHeaders) throws IOException {
+            final CacheResponse response = delegate.get(uri, requestMethod, requestHeaders);
+            if (response instanceof SecureCacheResponse) {
+                return new CacheResponse() {
+                    @Override public InputStream getBody() throws IOException {
+                        return response.getBody();
+                    }
+                    @Override public Map<String, List<String>> getHeaders() throws IOException {
+                        return response.getHeaders();
+                    }
+                };
+            }
+            return response;
+        }
+    }
+}
diff --git a/luni/src/test/java/libcore/java/net/InetAddressTest.java b/luni/src/test/java/libcore/java/net/InetAddressTest.java
index b23dbf8..59200c5 100644
--- a/luni/src/test/java/libcore/java/net/InetAddressTest.java
+++ b/luni/src/test/java/libcore/java/net/InetAddressTest.java
@@ -28,6 +28,16 @@
         assertEquals("/1.2.0.3", InetAddress.parseNumericAddress("1.2.3").toString());
         assertEquals("/1.0.0.2", InetAddress.parseNumericAddress("1.2").toString());
         assertEquals("/0.0.0.1", InetAddress.parseNumericAddress("1").toString());
+        assertEquals("/0.0.4.210", InetAddress.parseNumericAddress("1234").toString());
+        // Optional square brackets around IPv6 addresses, including mapped IPv4.
+        assertEquals("/2001:4860:800d::68", InetAddress.parseNumericAddress("[2001:4860:800d::68]").toString());
+        assertEquals("/127.0.0.1", InetAddress.parseNumericAddress("[::ffff:127.0.0.1]").toString());
+        try {
+            // Actual IPv4 addresses may not be surrounded by square brackets.
+            assertEquals("/127.0.0.1", InetAddress.parseNumericAddress("[127.0.0.1]").toString());
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
         try {
             // Almost numeric but invalid...
             InetAddress.parseNumericAddress("1.");
@@ -48,4 +58,9 @@
     public void test_getLoopbackAddress() throws Exception {
         assertTrue(InetAddress.getLoopbackAddress().isLoopbackAddress());
     }
+
+    public void test_0() throws Exception {
+        // The RI special-cases "0" for legacy IPv4 applications.
+        assertTrue(InetAddress.getByName("0").isAnyLocalAddress());
+    }
 }
diff --git a/luni/src/test/java/libcore/java/net/NetworkInterfaceTest.java b/luni/src/test/java/libcore/java/net/NetworkInterfaceTest.java
index fe89adb..bdde645 100644
--- a/luni/src/test/java/libcore/java/net/NetworkInterfaceTest.java
+++ b/luni/src/test/java/libcore/java/net/NetworkInterfaceTest.java
@@ -17,6 +17,7 @@
 package libcore.java.net;
 
 import java.net.*;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
@@ -34,4 +35,65 @@
 
         assertEquals(expected, actual);
     }
+
+    public void testLoopback() throws Exception {
+        // We know lo shouldn't have a hardware address or an IPv4 broadcast address.
+        NetworkInterface lo = NetworkInterface.getByName("lo");
+        assertNull(lo.getHardwareAddress());
+        for (InterfaceAddress ia : lo.getInterfaceAddresses()) {
+            assertNull(ia.getBroadcast());
+        }
+
+        // But eth0, if it exists, should...
+        NetworkInterface eth0 = NetworkInterface.getByName("eth0");
+        if (eth0 != null) {
+            assertEquals(6, eth0.getHardwareAddress().length);
+            for (InterfaceAddress ia : eth0.getInterfaceAddresses()) {
+                if (ia.getAddress() instanceof Inet4Address) {
+                    assertNotNull(ia.getBroadcast());
+                }
+            }
+        }
+    }
+
+    public void testDumpAll() throws Exception {
+        Set<String> allNames = new HashSet<String>();
+        Set<Integer> allIndexes = new HashSet<Integer>();
+        for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) {
+            System.err.println(nif);
+            System.err.println(nif.getInterfaceAddresses());
+            String flags = nif.isUp() ? "UP" : "DOWN";
+            if (nif.isLoopback()) {
+                flags += " LOOPBACK";
+            }
+            if (nif.isPointToPoint()) {
+                flags += " PTP";
+            }
+            if (nif.isVirtual()) {
+                flags += " VIRTUAL";
+            }
+            if (nif.supportsMulticast()) {
+                flags += " MULTICAST";
+            }
+            flags += " MTU=" + nif.getMTU();
+            byte[] mac = nif.getHardwareAddress();
+            if (mac != null) {
+                flags += " HWADDR=";
+                for (int i = 0; i < mac.length; ++i) {
+                    if (i > 0) {
+                        flags += ":";
+                    }
+                    flags += String.format("%02x", mac[i]);
+                }
+            }
+            System.err.println(flags);
+            System.err.println("-");
+
+            assertFalse(allNames.contains(nif.getName()));
+            allNames.add(nif.getName());
+
+            assertFalse(allIndexes.contains(nif.getIndex()));
+            allIndexes.add(nif.getIndex());
+        }
+    }
 }
diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
index 5a67d8d..90161ea 100644
--- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java
+++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
@@ -16,11 +16,9 @@
 
 package libcore.java.net;
 
-import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.Authenticator;
 import java.net.CacheRequest;
@@ -32,14 +30,10 @@
 import java.net.ProtocolException;
 import java.net.Proxy;
 import java.net.ResponseCache;
-import java.net.SecureCacheResponse;
 import java.net.SocketTimeoutException;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLConnection;
-import java.security.Principal;
-import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
@@ -50,7 +44,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.zip.GZIPInputStream;
@@ -66,7 +59,6 @@
 import javax.net.ssl.X509TrustManager;
 import libcore.java.security.TestKeyStore;
 import libcore.javax.net.ssl.TestSSLContext;
-import tests.http.DefaultResponseCache;
 import tests.http.MockResponse;
 import tests.http.MockWebServer;
 import tests.http.RecordedRequest;
@@ -329,148 +321,6 @@
         }
     }
 
-    /**
-     * Test that response caching is consistent with the RI and the spec.
-     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
-     */
-    public void test_responseCaching() throws Exception {
-        // Test each documented HTTP/1.1 code, plus the first unused value in each range.
-        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
-
-        // We can't test 100 because it's not really a response.
-        // assertCached(false, 100);
-        assertCached(false, 101);
-        assertCached(false, 102);
-        assertCached(true,  200);
-        assertCached(false, 201);
-        assertCached(false, 202);
-        assertCached(true,  203);
-        assertCached(false, 204);
-        assertCached(false, 205);
-        assertCached(true,  206);
-        assertCached(false, 207);
-        // (See test_responseCaching_300.)
-        assertCached(true,  301);
-        for (int i = 302; i <= 308; ++i) {
-            assertCached(false, i);
-        }
-        for (int i = 400; i <= 406; ++i) {
-            assertCached(false, i);
-        }
-        // (See test_responseCaching_407.)
-        assertCached(false, 408);
-        assertCached(false, 409);
-        // (See test_responseCaching_410.)
-        for (int i = 411; i <= 418; ++i) {
-            assertCached(false, i);
-        }
-        for (int i = 500; i <= 506; ++i) {
-            assertCached(false, i);
-        }
-    }
-
-    public void test_responseCaching_300() throws Exception {
-        // TODO: fix this for android
-        assertCached(false, 300);
-    }
-
-    /**
-     * Response code 407 should only come from proxy servers. Android's client
-     * throws if it is sent by an origin server.
-     */
-    public void testOriginServerSends407() throws Exception {
-        server.enqueue(new MockResponse().setResponseCode(407));
-        server.play();
-
-        URL url = server.getUrl("/");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        try {
-            conn.getResponseCode();
-            fail();
-        } catch (IOException expected) {
-        }
-    }
-
-    public void test_responseCaching_410() throws Exception {
-        // the HTTP spec permits caching 410s, but the RI doesn't.
-        assertCached(false, 410);
-    }
-
-    private void assertCached(boolean shouldPut, int responseCode) throws Exception {
-        server = new MockWebServer();
-        server.enqueue(new MockResponse()
-                .setResponseCode(responseCode)
-                .setBody("ABCDE")
-                .addHeader("WWW-Authenticate: challenge"));
-        server.play();
-
-        DefaultResponseCache responseCache = new DefaultResponseCache();
-        ResponseCache.setDefault(responseCache);
-        URL url = server.getUrl("/");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        assertEquals(responseCode, conn.getResponseCode());
-
-        // exhaust the content stream
-        try {
-            // TODO: remove special case once testUnauthorizedResponseHandling() is fixed
-            if (responseCode != 401) {
-                readAscii(conn.getInputStream(), Integer.MAX_VALUE);
-            }
-        } catch (IOException expected) {
-        }
-
-        Set<URI> expectedCachedUris = shouldPut
-                ? Collections.singleton(url.toURI())
-                : Collections.<URI>emptySet();
-        assertEquals(Integer.toString(responseCode),
-                expectedCachedUris, responseCache.getContents().keySet());
-        server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers
-    }
-
-    /**
-     * Test that we can interrogate the response when the cache is being
-     * populated. http://code.google.com/p/android/issues/detail?id=7787
-     */
-    public void testResponseCacheCallbackApis() throws Exception {
-        final String body = "ABCDE";
-        final AtomicInteger cacheCount = new AtomicInteger();
-
-        server.enqueue(new MockResponse()
-                .setStatus("HTTP/1.1 200 Fantastic")
-                .addHeader("fgh: ijk")
-                .setBody(body));
-        server.play();
-
-        ResponseCache.setDefault(new ResponseCache() {
-            @Override public CacheResponse get(URI uri, String requestMethod,
-                    Map<String, List<String>> requestHeaders) throws IOException {
-                return null;
-            }
-            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
-                HttpURLConnection httpConnection = (HttpURLConnection) conn;
-                assertEquals("HTTP/1.1 200 Fantastic", httpConnection.getHeaderField(null));
-                assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"),
-                        httpConnection.getHeaderFields().get(null));
-                assertEquals(200, httpConnection.getResponseCode());
-                assertEquals("Fantastic", httpConnection.getResponseMessage());
-                assertEquals(body.length(), httpConnection.getContentLength());
-                assertEquals("ijk", httpConnection.getHeaderField("fgh"));
-                try {
-                    httpConnection.getInputStream(); // the RI doesn't forbid this, but it should
-                    fail();
-                } catch (IOException expected) {
-                }
-                cacheCount.incrementAndGet();
-                return null;
-            }
-        });
-
-        URL url = server.getUrl("/");
-        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-        assertEquals(body, readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-        assertEquals(1, cacheCount.get());
-    }
-
     public void testGetResponseCodeNoResponseBody() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("abc: def"));
@@ -763,186 +613,6 @@
         }
     }
 
-    public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException {
-        testResponseCaching(TransferKind.FIXED_LENGTH);
-    }
-
-    public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException {
-        testResponseCaching(TransferKind.CHUNKED);
-    }
-
-    public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException {
-        testResponseCaching(TransferKind.END_OF_STREAM);
-    }
-
-    /**
-     * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption
-     * http://code.google.com/p/android/issues/detail?id=8175
-     */
-    private void testResponseCaching(TransferKind transferKind) throws IOException {
-        MockResponse response = new MockResponse()
-                .setStatus("HTTP/1.1 200 Fantastic");
-        transferKind.setBody(response, "I love puppies but hate spiders", 1);
-        server.enqueue(response);
-        server.play();
-
-        DefaultResponseCache cache = new DefaultResponseCache();
-        ResponseCache.setDefault(cache);
-
-        // Make sure that calling skip() doesn't omit bytes from the cache.
-        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
-        InputStream in = urlConnection.getInputStream();
-        assertEquals("I love ", readAscii(in, "I love ".length()));
-        reliableSkip(in, "puppies but hate ".length());
-        assertEquals("spiders", readAscii(in, "spiders".length()));
-        assertEquals(-1, in.read());
-        in.close();
-        assertEquals(1, cache.getSuccessCount());
-        assertEquals(0, cache.getAbortCount());
-
-        urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached!
-        in = urlConnection.getInputStream();
-        assertEquals("I love puppies but hate spiders",
-                readAscii(in, "I love puppies but hate spiders".length()));
-        assertEquals(200, urlConnection.getResponseCode());
-        assertEquals("Fantastic", urlConnection.getResponseMessage());
-
-        assertEquals(-1, in.read());
-        assertEquals(1, cache.getMissCount());
-        assertEquals(1, cache.getHitCount());
-        assertEquals(1, cache.getSuccessCount());
-        assertEquals(0, cache.getAbortCount());
-    }
-
-    public void testSecureResponseCaching() throws IOException {
-        TestSSLContext testSSLContext = TestSSLContext.create();
-        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
-        server.enqueue(new MockResponse().setBody("ABC"));
-        server.play();
-
-        DefaultResponseCache cache = new DefaultResponseCache();
-        ResponseCache.setDefault(cache);
-
-        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
-        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
-        // OpenJDK 6 fails on this line, complaining that the connection isn't open yet
-        String suite = connection.getCipherSuite();
-        List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates());
-        List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates());
-        Principal peerPrincipal = connection.getPeerPrincipal();
-        Principal localPrincipal = connection.getLocalPrincipal();
-
-        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
-        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
-        assertEquals(1, cache.getMissCount());
-        assertEquals(1, cache.getHitCount());
-
-        assertEquals(suite, connection.getCipherSuite());
-        assertEquals(localCerts, toListOrNull(connection.getLocalCertificates()));
-        assertEquals(serverCerts, toListOrNull(connection.getServerCertificates()));
-        assertEquals(peerPrincipal, connection.getPeerPrincipal());
-        assertEquals(localPrincipal, connection.getLocalPrincipal());
-    }
-
-    public void testCacheReturnsInsecureResponseForSecureRequest() throws IOException {
-        TestSSLContext testSSLContext = TestSSLContext.create();
-        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
-        server.enqueue(new MockResponse().setBody("ABC"));
-        server.enqueue(new MockResponse().setBody("DEF"));
-        server.play();
-
-        ResponseCache insecureResponseCache = new InsecureResponseCache();
-        ResponseCache.setDefault(insecureResponseCache);
-
-        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
-        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
-        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // not cached!
-        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-        assertEquals("DEF", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-    }
-
-    public void testResponseCachingAndRedirects() throws IOException {
-        server.enqueue(new MockResponse()
-                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
-                .addHeader("Location: /foo"));
-        server.enqueue(new MockResponse().setBody("ABC"));
-        server.enqueue(new MockResponse().setBody("DEF"));
-        server.play();
-
-        DefaultResponseCache cache = new DefaultResponseCache();
-        ResponseCache.setDefault(cache);
-
-        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
-        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
-        connection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached!
-        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
-        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
-        assertEquals(2, cache.getHitCount());
-    }
-
-    public void testSecureResponseCachingAndRedirects() throws IOException {
-        TestSSLContext testSSLContext = TestSSLContext.create();
-        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
-        server.enqueue(new MockResponse()
-                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
-                .addHeader("Location: /foo"));
-        server.enqueue(new MockResponse().setBody("ABC"));
-        server.enqueue(new MockResponse().setBody("DEF"));
-        server.play();
-
-        DefaultResponseCache cache = new DefaultResponseCache();
-        ResponseCache.setDefault(cache);
-
-        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
-        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
-        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
-        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-        assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
-        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
-        assertEquals(2, cache.getHitCount());
-    }
-
-    public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException {
-        server.enqueue(new MockResponse().setBody("ABC"));
-        server.play();
-
-        final AtomicReference<Map<String, List<String>>> requestHeadersRef
-                = new AtomicReference<Map<String, List<String>>>();
-        ResponseCache.setDefault(new ResponseCache() {
-            @Override public CacheResponse get(URI uri, String requestMethod,
-                    Map<String, List<String>> requestHeaders) throws IOException {
-                requestHeadersRef.set(requestHeaders);
-                return null;
-            }
-            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
-                return null;
-            }
-        });
-
-        URL url = server.getUrl("/");
-        URLConnection urlConnection = url.openConnection();
-        urlConnection.addRequestProperty("A", "android");
-        readAscii(urlConnection.getInputStream(), Integer.MAX_VALUE);
-        assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A"));
-    }
-
-    private void reliableSkip(InputStream in, int length) throws IOException {
-        while (length > 0) {
-            length -= in.skip(length);
-        }
-    }
-
     /**
      * Reads {@code count} characters from the stream. If the stream is
      * exhausted before {@code count} characters can be read, the remaining
@@ -961,100 +631,6 @@
         return result.toString();
     }
 
-    public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException {
-        testServerPrematureDisconnect(TransferKind.FIXED_LENGTH);
-    }
-
-    public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException {
-        testServerPrematureDisconnect(TransferKind.CHUNKED);
-    }
-
-    public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException {
-        /*
-         * Intentionally empty. This case doesn't make sense because there's no
-         * such thing as a premature disconnect when the disconnect itself
-         * indicates the end of the data stream.
-         */
-    }
-
-    private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException {
-        MockResponse response = new MockResponse();
-        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16);
-        server.enqueue(truncateViolently(response, 16));
-        server.enqueue(new MockResponse().setBody("Request #2"));
-        server.play();
-
-        DefaultResponseCache cache = new DefaultResponseCache();
-        ResponseCache.setDefault(cache);
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(
-                server.getUrl("/").openConnection().getInputStream()));
-        assertEquals("ABCDE", reader.readLine());
-        try {
-            reader.readLine();
-            fail("This implementation silently ignored a truncated HTTP body.");
-        } catch (IOException expected) {
-        }
-
-        assertEquals(1, cache.getAbortCount());
-        assertEquals(0, cache.getSuccessCount());
-        assertContent("Request #2", server.getUrl("/").openConnection());
-        assertEquals(1, cache.getAbortCount());
-        assertEquals(1, cache.getSuccessCount());
-    }
-
-    public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException {
-        testClientPrematureDisconnect(TransferKind.FIXED_LENGTH);
-    }
-
-    public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException {
-        testClientPrematureDisconnect(TransferKind.CHUNKED);
-    }
-
-    public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException {
-        testClientPrematureDisconnect(TransferKind.END_OF_STREAM);
-    }
-
-    private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException {
-        MockResponse response = new MockResponse();
-        transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024);
-        server.enqueue(response);
-        server.enqueue(new MockResponse().setBody("Request #2"));
-        server.play();
-
-        DefaultResponseCache cache = new DefaultResponseCache();
-        ResponseCache.setDefault(cache);
-
-        InputStream in = server.getUrl("/").openConnection().getInputStream();
-        assertEquals("ABCDE", readAscii(in, 5));
-        in.close();
-        try {
-            in.read();
-            fail("Expected an IOException because the stream is closed.");
-        } catch (IOException expected) {
-        }
-
-        assertEquals(1, cache.getAbortCount());
-        assertEquals(0, cache.getSuccessCount());
-        assertContent("Request #2", server.getUrl("/").openConnection());
-        assertEquals(1, cache.getAbortCount());
-        assertEquals(1, cache.getSuccessCount());
-    }
-
-    /**
-     * Shortens the body of {@code response} but not the corresponding headers.
-     * Only useful to test how clients respond to the premature conclusion of
-     * the HTTP body.
-     */
-    private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) {
-        response.setSocketPolicy(DISCONNECT_AT_END);
-        List<String> headers = new ArrayList<String>(response.getHeaders());
-        response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep));
-        response.getHeaders().clear();
-        response.getHeaders().addAll(headers);
-        return response;
-    }
-
     public void testMarkAndResetWithContentLengthHeader() throws IOException {
         testMarkAndReset(TransferKind.FIXED_LENGTH);
     }
@@ -1071,11 +647,9 @@
         MockResponse response = new MockResponse();
         transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024);
         server.enqueue(response);
+        server.enqueue(response);
         server.play();
 
-        DefaultResponseCache cache = new DefaultResponseCache();
-        ResponseCache.setDefault(cache);
-
         InputStream in = server.getUrl("/").openConnection().getInputStream();
         assertFalse("This implementation claims to support mark().", in.markSupported());
         in.mark(5);
@@ -1086,10 +660,7 @@
         } catch (IOException expected) {
         }
         assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE));
-
         assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection());
-        assertEquals(1, cache.getSuccessCount());
-        assertEquals(1, cache.getHitCount());
     }
 
     /**
@@ -1923,7 +1494,7 @@
     }
 
     /**
-     * Encodes the response body using GZIP and adds the corresponding header.
+     * Returns a gzipped copy of {@code bytes}.
      */
     public byte[] gzip(byte[] bytes) throws IOException {
         ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
@@ -1933,10 +1504,6 @@
         return bytesOut.toByteArray();
     }
 
-    private <T> List<T> toListOrNull(T[] arrayOrNull) {
-        return arrayOrNull != null ? Arrays.asList(arrayOrNull) : null;
-    }
-
     /**
      * Reads at most {@code limit} characters from {@code in} and asserts that
      * content equals {@code expected}.
@@ -2081,28 +1648,4 @@
             return true;
         }
     }
-
-    private static class InsecureResponseCache extends ResponseCache {
-        private final DefaultResponseCache delegate = new DefaultResponseCache();
-
-        @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
-            return delegate.put(uri, connection);
-        }
-
-        @Override public CacheResponse get(URI uri, String requestMethod,
-                Map<String, List<String>> requestHeaders) throws IOException {
-            final CacheResponse response = delegate.get(uri, requestMethod, requestHeaders);
-            if (response instanceof SecureCacheResponse) {
-                return new CacheResponse() {
-                    @Override public InputStream getBody() throws IOException {
-                        return response.getBody();
-                    }
-                    @Override public Map<String, List<String>> getHeaders() throws IOException {
-                        return response.getHeaders();
-                    }
-                };
-            }
-            return response;
-        }
-    }
 }
diff --git a/luni/src/test/java/libcore/java/nio/BufferTest.java b/luni/src/test/java/libcore/java/nio/BufferTest.java
index 5787a13..6f3f948 100644
--- a/luni/src/test/java/libcore/java/nio/BufferTest.java
+++ b/luni/src/test/java/libcore/java/nio/BufferTest.java
@@ -556,4 +556,86 @@
         assertEquals(0, directBuffer.arrayOffset());
         assertEquals(1, directSlice.arrayOffset());
     }
+
+    // http://code.google.com/p/android/issues/detail?id=16184
+    public void testPutByteBuffer() throws Exception {
+        ByteBuffer dst = ByteBuffer.allocate(10).asReadOnlyBuffer();
+
+        // Can't put into a read-only buffer.
+        try {
+            dst.put(ByteBuffer.allocate(5));
+            fail();
+        } catch (ReadOnlyBufferException expected) {
+        }
+
+        // Can't put a buffer into itself.
+        dst = ByteBuffer.allocate(10);
+        try {
+            dst.put(dst);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        // Can't put the null ByteBuffer.
+        try {
+            dst.put((ByteBuffer) null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+
+        // Can't put a larger source into a smaller destination.
+        try {
+            dst.put(ByteBuffer.allocate(dst.capacity() + 1));
+            fail();
+        } catch (BufferOverflowException expected) {
+        }
+
+        assertPutByteBuffer(ByteBuffer.allocate(10), ByteBuffer.allocate(8), false);
+        assertPutByteBuffer(ByteBuffer.allocate(10), ByteBuffer.allocateDirect(8), false);
+        assertPutByteBuffer(ByteBuffer.allocate(10), allocateMapped(8), false);
+        assertPutByteBuffer(ByteBuffer.allocate(10), ByteBuffer.allocate(8), true);
+        assertPutByteBuffer(ByteBuffer.allocate(10), ByteBuffer.allocateDirect(8), true);
+        assertPutByteBuffer(ByteBuffer.allocate(10), allocateMapped(8), true);
+
+        assertPutByteBuffer(ByteBuffer.allocateDirect(10), ByteBuffer.allocate(8), false);
+        assertPutByteBuffer(ByteBuffer.allocateDirect(10), ByteBuffer.allocateDirect(8), false);
+        assertPutByteBuffer(ByteBuffer.allocateDirect(10), allocateMapped(8), false);
+        assertPutByteBuffer(ByteBuffer.allocateDirect(10), ByteBuffer.allocate(8), true);
+        assertPutByteBuffer(ByteBuffer.allocateDirect(10), ByteBuffer.allocateDirect(8), true);
+        assertPutByteBuffer(ByteBuffer.allocateDirect(10), allocateMapped(8), true);
+    }
+
+    private void assertPutByteBuffer(ByteBuffer dst, ByteBuffer src, boolean readOnly) {
+        // Start 'dst' off as the index-identity pattern.
+        for (int i = 0; i < dst.capacity(); ++i) {
+            dst.put(i, (byte) i);
+        }
+        // Deliberately offset the position we'll write to by 1.
+        dst.position(1);
+
+        // Make the source more interesting.
+        for (int i = 0; i < src.capacity(); ++i) {
+            src.put(i, (byte) (16 + i));
+        }
+        if (readOnly) {
+            src = src.asReadOnlyBuffer();
+        }
+
+        ByteBuffer dst2 = dst.put(src);
+        assertSame(dst, dst2);
+        assertEquals(0, src.remaining());
+        assertEquals(src.position(), src.capacity());
+        assertEquals(dst.position(), src.capacity() + 1);
+        for (int i = 0; i < src.capacity(); ++i) {
+            assertEquals(src.get(i), dst.get(i + 1));
+        }
+
+        // No room for another.
+        src.position(0);
+        try {
+            dst.put(src);
+            fail();
+        } catch (BufferOverflowException expected) {
+        }
+    }
 }
diff --git a/luni/src/test/java/libcore/java/security/KeyStoreTest.java b/luni/src/test/java/libcore/java/security/KeyStoreTest.java
index bd062dc..f255392 100644
--- a/luni/src/test/java/libcore/java/security/KeyStoreTest.java
+++ b/luni/src/test/java/libcore/java/security/KeyStoreTest.java
@@ -154,6 +154,11 @@
         return (ks.getType().equals("PKCS12") && ks.getProvider().getName().equals("BC"));
     }
 
+    private static boolean isLoadStoreParameterSupported(KeyStore ks) {
+        // BouncyCastle's PKCS12 allows a JDKPKCS12StoreParameter
+        return (ks.getType().equals("PKCS12") && ks.getProvider().getName().equals("BC"));
+    }
+
     private static boolean isSetKeyByteArrayUnimplemented(KeyStore ks) {
         // All of BouncyCastle's
         // KeyStore.setKeyEntry(String,byte[],char[]) implementations
@@ -1556,6 +1561,10 @@
                 keyStore.store(null);
                 fail();
             } catch (UnsupportedOperationException expected) {
+                assertFalse(isLoadStoreParameterSupported(keyStore));
+            } catch (IllegalArgumentException expected) {
+                // its supported, but null causes an exception
+                assertTrue(isLoadStoreParameterSupported(keyStore));
             }
         }
     }
diff --git a/luni/src/test/java/libcore/java/util/SerializableTester.java b/luni/src/test/java/libcore/java/util/SerializableTester.java
index 1a28b64..20318d7 100644
--- a/luni/src/test/java/libcore/java/util/SerializableTester.java
+++ b/luni/src/test/java/libcore/java/util/SerializableTester.java
@@ -21,9 +21,7 @@
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
+import static junit.framework.Assert.*;
 import junit.framework.AssertionFailedError;
 
 public class SerializableTester<T> {
@@ -45,6 +43,10 @@
         return a.equals(b);
     }
 
+    /**
+     * Verifies that {@code deserialized} is valid. Implementations of this
+     * method may mutate {@code deserialized}.
+     */
     protected void verify(T deserialized) {}
 
     public void test() {
@@ -54,19 +56,19 @@
                         + hexEncode(serialize(value)));
             }
 
-            // just a sanity check! if this fails, verify() is probably broken
-            verify(value);
-
             @SuppressWarnings("unchecked") // deserialize should return the proper type
             T deserialized = (T) deserialize(hexDecode(golden));
             assertTrue("User-constructed value doesn't equal deserialized golden value",
                     equals(value, deserialized));
-            verify(deserialized);
 
             @SuppressWarnings("unchecked") // deserialize should return the proper type
             T reserialized = (T) deserialize(serialize(value));
             assertTrue("User-constructed value doesn't equal itself, reserialized",
                     equals(value, reserialized));
+
+            // just a sanity check! if this fails, verify() is probably broken
+            verify(value);
+            verify(deserialized);
             verify(reserialized);
 
         } catch (Exception e) {
diff --git a/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java b/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java
index cd6f886..29efd3a 100644
--- a/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java
+++ b/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java
@@ -92,9 +92,12 @@
         putKeySize("AES", 128);
         putKeySize("AES", 192);
         putKeySize("AES", 256);
+        putKeySize("ARC4", 1024);
+        putKeySize("ARC4", 40);
+        putKeySize("ARC4", 41);
+        putKeySize("ARCFOUR", 1024);
         putKeySize("ARCFOUR", 40);
         putKeySize("ARCFOUR", 41);
-        putKeySize("ARCFOUR", 1024);
         putKeySize("Blowfish", 32);
         putKeySize("Blowfish", 32+8);
         putKeySize("Blowfish", 448);
diff --git a/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java b/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java
new file mode 100644
index 0000000..0f38a5d
--- /dev/null
+++ b/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java
@@ -0,0 +1,194 @@
+/*
+* 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 libcore.net.http;
+
+import java.net.URI;
+import java.util.Date;
+import junit.framework.TestCase;
+
+public final class ParsedHeadersTest extends TestCase {
+
+    private URI uri;
+
+    @Override protected void setUp() throws Exception {
+        super.setUp();
+        uri = new URI("http", "localhost", "/");
+    }
+
+    public void testUpperCaseHttpHeaders() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("CACHE-CONTROL", "no-store");
+        headers.add("DATE", "Thu, 01 Jan 1970 00:00:01 UTC");
+        headers.add("EXPIRES", "Thu, 01 Jan 1970 00:00:02 UTC");
+        headers.add("LAST-MODIFIED", "Thu, 01 Jan 1970 00:00:03 UTC");
+        headers.add("ETAG", "v1");
+        headers.add("PRAGMA", "no-cache");
+        ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+        assertTrue(parsedHeaders.noStore);
+        assertEquals(new Date(1000), parsedHeaders.servedDate);
+        assertEquals(new Date(2000), parsedHeaders.expires);
+        assertEquals(new Date(3000), parsedHeaders.lastModified);
+        assertEquals("v1", parsedHeaders.etag);
+        assertTrue(parsedHeaders.noCache);
+    }
+
+    public void testCommaSeparatedCacheControlHeaders() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "no-store, max-age=60, public");
+        ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+        assertTrue(parsedHeaders.noStore);
+        assertEquals(60, parsedHeaders.maxAgeSeconds);
+        assertTrue(parsedHeaders.isPublic);
+    }
+
+    public void testQuotedFieldName() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "private=\"Set-Cookie\"");
+        ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+        assertEquals("Set-Cookie", parsedHeaders.privateField);
+    }
+
+    public void testUnquotedValue() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "private=Set-Cookie, no-store");
+        ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+        assertEquals("Set-Cookie", parsedHeaders.privateField);
+        assertTrue(parsedHeaders.noStore);
+    }
+
+    public void testQuotedValue() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "private=\" a, no-cache, c \", no-store");
+        ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+        assertEquals(" a, no-cache, c ", parsedHeaders.privateField);
+        assertTrue(parsedHeaders.noStore);
+        assertFalse(parsedHeaders.noCache);
+    }
+
+    public void testDanglingQuote() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "private=\"a, no-cache, c");
+        ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+        assertEquals("a, no-cache, c", parsedHeaders.privateField);
+        assertFalse(parsedHeaders.noCache);
+    }
+
+    public void testTrailingComma() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "public,");
+        ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+        assertTrue(parsedHeaders.isPublic);
+        assertNull(parsedHeaders.privateField);
+    }
+
+    public void testTrailingEquals() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "private=");
+        ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+        assertEquals("", parsedHeaders.privateField);
+    }
+
+    public void testSpaceBeforeEquals() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "max-age =60");
+        RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+        assertEquals(60, parsedHeaders.maxAgeSeconds);
+    }
+
+    public void testSpaceAfterEqualsWithQuotes() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "max-age= \"60\"");
+        RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+        assertEquals(60, parsedHeaders.maxAgeSeconds);
+    }
+
+    public void testSpaceAfterEqualsWithoutQuotes() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "max-age= 60");
+        RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+        assertEquals(60, parsedHeaders.maxAgeSeconds);
+    }
+
+    public void testCacheControlRequestDirectivesAreCaseInsensitive() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "NO-CACHE");
+        headers.add("Cache-Control", "MAX-AGE=60");
+        headers.add("Cache-Control", "MAX-STALE=70");
+        headers.add("Cache-Control", "MIN-FRESH=80");
+        headers.add("Cache-Control", "ONLY-IF-CACHED");
+        RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+        assertTrue(parsedHeaders.noCache);
+        assertEquals(60, parsedHeaders.maxAgeSeconds);
+        assertEquals(70, parsedHeaders.maxStaleSeconds);
+        assertEquals(80, parsedHeaders.minFreshSeconds);
+        assertTrue(parsedHeaders.onlyIfCached);
+    }
+
+    public void testCacheControlResponseDirectivesAreCaseInsensitive() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "NO-CACHE");
+        headers.add("Cache-Control", "NO-STORE");
+        headers.add("Cache-Control", "MAX-AGE=60");
+        headers.add("Cache-Control", "S-MAXAGE=70");
+        headers.add("Cache-Control", "PUBLIC");
+        headers.add("Cache-Control", "PRIVATE=a");
+        headers.add("Cache-Control", "MUST-REVALIDATE");
+        ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+        assertTrue(parsedHeaders.noCache);
+        assertTrue(parsedHeaders.noStore);
+        assertEquals(60, parsedHeaders.maxAgeSeconds);
+        assertEquals(70, parsedHeaders.sMaxAgeSeconds);
+        assertTrue(parsedHeaders.isPublic);
+        assertEquals("a", parsedHeaders.privateField);
+        assertTrue(parsedHeaders.mustRevalidate);
+    }
+
+    public void testPragmaDirectivesAreCaseInsensitive() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Pragma", "NO-CACHE");
+        RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+        assertTrue(parsedHeaders.noCache);
+    }
+
+    public void testMissingInteger() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "max-age");
+        RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+        assertEquals(-1, parsedHeaders.maxAgeSeconds);
+    }
+
+    public void testInvalidInteger() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "MAX-AGE=pi");
+        RequestHeaders requestHeaders = new RequestHeaders(uri, headers);
+        assertEquals(-1, requestHeaders.maxAgeSeconds);
+    }
+
+    public void testVeryLargeInteger() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "MAX-AGE=" + (Integer.MAX_VALUE + 1L));
+        RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+        assertEquals(Integer.MAX_VALUE, parsedHeaders.maxAgeSeconds);
+    }
+
+    public void testNegativeInteger() {
+        RawHeaders headers = new RawHeaders();
+        headers.add("Cache-Control", "MAX-AGE=-2");
+        RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+        assertEquals(0, parsedHeaders.maxAgeSeconds);
+    }
+}
diff --git a/luni/src/test/java/libcore/java/net/HttpHeadersTest.java b/luni/src/test/java/libcore/net/http/RawHeadersTest.java
similarity index 84%
rename from luni/src/test/java/libcore/java/net/HttpHeadersTest.java
rename to luni/src/test/java/libcore/net/http/RawHeadersTest.java
index 4650184..d99e851 100644
--- a/luni/src/test/java/libcore/java/net/HttpHeadersTest.java
+++ b/luni/src/test/java/libcore/net/http/RawHeadersTest.java
@@ -14,16 +14,15 @@
  * limitations under the License.
  */
 
-package libcore.java.net;
+package libcore.net.http;
 
 import java.util.Arrays;
 import junit.framework.TestCase;
-import libcore.net.http.HttpHeaders;
 
-public class HttpHeadersTest extends TestCase {
+public class RawHeadersTest extends TestCase {
     // http://code.google.com/p/android/issues/detail?id=6684
     public void test_caseInsensitiveButCasePreserving() {
-        HttpHeaders h = new HttpHeaders();
+        RawHeaders h = new RawHeaders();
         h.add("Content-Type", "text/plain");
         // Case-insensitive:
         assertEquals("text/plain", h.get("Content-Type"));
@@ -42,14 +41,14 @@
     // The copy constructor used to be broken for headers with multiple values.
     // http://code.google.com/p/android/issues/detail?id=6722
     public void test_copyConstructor() {
-        HttpHeaders h1 = new HttpHeaders();
+        RawHeaders h1 = new RawHeaders();
         h1.add("key", "value1");
         h1.add("key", "value2");
-        HttpHeaders h2 = HttpHeaders.fromMultimap(h1.toMultimap());
+        RawHeaders h2 = RawHeaders.fromMultimap(h1.toMultimap());
         assertEquals(2, h2.length());
-        assertEquals("key", h2.getKey(0));
+        assertEquals("key", h2.getFieldName(0));
         assertEquals("value1", h2.getValue(0));
-        assertEquals("key", h2.getKey(1));
+        assertEquals("key", h2.getFieldName(1));
         assertEquals("value2", h2.getValue(1));
     }
 }
diff --git a/luni/src/test/java/libcore/xml/XsltXPathConformanceTestSuite.java b/luni/src/test/java/libcore/xml/XsltXPathConformanceTestSuite.java
index cfc46b3..f59f457 100644
--- a/luni/src/test/java/libcore/xml/XsltXPathConformanceTestSuite.java
+++ b/luni/src/test/java/libcore/xml/XsltXPathConformanceTestSuite.java
@@ -16,6 +16,33 @@
 
 package libcore.xml;
 
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
 import junit.framework.Assert;
 import junit.framework.AssertionFailedError;
 import junit.framework.Test;
@@ -36,34 +63,6 @@
 import org.xmlpull.v1.XmlPullParserFactory;
 import org.xmlpull.v1.XmlSerializer;
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.ErrorListener;
-import javax.xml.transform.Result;
-import javax.xml.transform.Source;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMResult;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.transform.stream.StreamSource;
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
 /**
  * The <a href="http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=xslt">OASIS
  * XSLT conformance test suite</a>, adapted for use by JUnit. To run these tests
diff --git a/luni/src/test/java/tests/security/cert/CertificateTest.java b/luni/src/test/java/tests/security/cert/CertificateTest.java
index 6570416..d13e16b 100644
--- a/luni/src/test/java/tests/security/cert/CertificateTest.java
+++ b/luni/src/test/java/tests/security/cert/CertificateTest.java
@@ -434,8 +434,8 @@
      * serialization/deserialization compatibility with RI.
      */
     public void testSerializationCompatibility() throws Exception {
-        //create test file (once)
-//        SerializationTest.createGoldenFile("device/dalvik/libcore/security/src/test/resources/serialization", this, TestUtils.rootCertificateSS);
+        // create test file (once)
+        // SerializationTest.createGoldenFile("/sdcard", this, TestUtils.rootCertificateSS);
         TestUtils.initCertPathSSCertChain();
 
         SerializationTest.verifyGolden(this, TestUtils.rootCertificateSS);
diff --git a/luni/src/test/java/tests/targets/security/KeyStoreTest.java b/luni/src/test/java/tests/targets/security/KeyStoreTest.java
index 41206c8..e5bbb62 100644
--- a/luni/src/test/java/tests/targets/security/KeyStoreTest.java
+++ b/luni/src/test/java/tests/targets/security/KeyStoreTest.java
@@ -35,7 +35,7 @@
 import java.security.cert.CertificateFactory;
 import junit.framework.TestCase;
 
-public class KeyStoreTest extends TestCase {
+public abstract class KeyStoreTest extends TestCase {
 
     private final String algorithmName;
     private final byte[] keyStoreData;
diff --git a/luni/src/test/java/tests/targets/security/cert/CertificateTest.java b/luni/src/test/java/tests/targets/security/cert/CertificateTest.java
index dde7664..c24051a 100644
--- a/luni/src/test/java/tests/targets/security/cert/CertificateTest.java
+++ b/luni/src/test/java/tests/targets/security/cert/CertificateTest.java
@@ -20,10 +20,10 @@
 import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
 import java.security.Provider;
-import java.security.PublicKey;
 import java.security.Security;
 import java.security.cert.CertPath;
 import java.security.cert.CertPathValidator;
+import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertPathValidatorResult;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
@@ -33,6 +33,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import junit.framework.TestCase;
+import libcore.java.security.StandardNames;
 
 public class CertificateTest extends TestCase {
 
@@ -591,8 +592,7 @@
     public void testVerifyMD5() throws Exception {
         Provider[] providers = Security.getProviders("CertificateFactory.X509");
         for (Provider provider : providers) {
-            CertificateFactory certificateFactory = CertificateFactory
-                    .getInstance("X509", provider);
+            CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", provider);
 
             Certificate certificate = certificateFactory
                     .generateCertificate(new ByteArrayInputStream(selfSignedCertMD5
@@ -606,8 +606,7 @@
     public void testVerifyMD2() throws Exception {
         Provider[] providers = Security.getProviders("CertificateFactory.X509");
         for (Provider provider : providers) {
-            CertificateFactory certificateFactory = CertificateFactory
-                    .getInstance("X509", provider);
+            CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", provider);
 
             Certificate certificate = certificateFactory
                     .generateCertificate(new ByteArrayInputStream(selfSignedCertMD2
@@ -622,27 +621,21 @@
     }
 
     public void testVerifyMD2_chain() throws Exception {
-        CertificateFactory certificateFactory = CertificateFactory
-                .getInstance("X509");
-
-        CertPath path;
-        CertPathValidator certPathValidator;
-        PKIXParameters params;
-        CertPathValidatorResult res;
+        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
 
         // First check with the trust anchor not included in the chain
-        path = certificateFactory.generateCertPath(getCertList(true, false));
+        CertPath path = certificateFactory.generateCertPath(getCertList(true, false));
 
-        certPathValidator = CertPathValidator.getInstance("PKIX");
-        params = createPKIXParams();
+        CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX");
+        PKIXParameters params = createPKIXParams();
 
-        res = certPathValidator.validate(path, params);
+        CertPathValidatorResult res = certPathValidator.validate(path, params);
         assertTrue("wrong result type",
-                res instanceof PKIXCertPathValidatorResult);
+                   res instanceof PKIXCertPathValidatorResult);
 
         PKIXCertPathValidatorResult r = (PKIXCertPathValidatorResult) res;
-        assertTrue("Wrong trust anchor returned", params.getTrustAnchors()
-                .contains(r.getTrustAnchor()));
+        assertTrue("Wrong trust anchor returned",
+                   params.getTrustAnchors().contains(r.getTrustAnchor()));
 
         // Now check with the trust anchor included in the chain
         path = certificateFactory.generateCertPath(getCertList(true, true));
@@ -650,37 +643,38 @@
         certPathValidator = CertPathValidator.getInstance("PKIX");
         params = createPKIXParams();
 
-        res = certPathValidator.validate(path, params);
-        assertTrue("wrong result type",
-                res instanceof PKIXCertPathValidatorResult);
+        if (StandardNames.IS_RI) {
+            res = certPathValidator.validate(path, params);
+            assertTrue("wrong result type", res instanceof PKIXCertPathValidatorResult);
 
-        r = (PKIXCertPathValidatorResult) res;
-        assertTrue("Wrong trust anchor returned", params.getTrustAnchors()
-                .contains(r.getTrustAnchor()));
+            r = (PKIXCertPathValidatorResult) res;
+            assertTrue("Wrong trust anchor returned",
+                       params.getTrustAnchors().contains(r.getTrustAnchor()));
+        } else {
+            try {
+                certPathValidator.validate(path, params);
+                fail();
+            } catch (CertPathValidatorException expected) {
+            }
+        }
     }
 
     public void testVerifyMD5_chain() throws Exception {
-        CertificateFactory certificateFactory = CertificateFactory
-                .getInstance("X509");
-
-        CertPath path;
-        CertPathValidator certPathValidator;
-        PKIXParameters params;
-        CertPathValidatorResult res;
+        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
 
         // First check with the trust anchor not included in the chain
-        path = certificateFactory.generateCertPath(getCertList(false, false));
+        CertPath path = certificateFactory.generateCertPath(getCertList(false, false));
 
-        certPathValidator = CertPathValidator.getInstance("PKIX");
-        params = createPKIXParams();
+        CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX");
+        PKIXParameters params = createPKIXParams();
 
-        res = certPathValidator.validate(path, params);
+        CertPathValidatorResult res = certPathValidator.validate(path, params);
         assertTrue("wrong result type",
-                res instanceof PKIXCertPathValidatorResult);
+                   res instanceof PKIXCertPathValidatorResult);
 
         PKIXCertPathValidatorResult r = (PKIXCertPathValidatorResult) res;
-        assertTrue("Wrong trust anchor returned", params.getTrustAnchors()
-                .contains(r.getTrustAnchor()));
+        assertTrue("Wrong trust anchor returned",
+                   params.getTrustAnchors().contains(r.getTrustAnchor()));
 
         // Now check with the trust anchor included in the chain
         path = certificateFactory.generateCertPath(getCertList(false, true));
@@ -693,8 +687,8 @@
                 res instanceof PKIXCertPathValidatorResult);
 
         r = (PKIXCertPathValidatorResult) res;
-        assertTrue("Wrong trust anchor returned", params.getTrustAnchors()
-                .contains(r.getTrustAnchor()));
+        assertTrue("Wrong trust anchor returned",
+                   params.getTrustAnchors().contains(r.getTrustAnchor()));
     }
 
     private X509Certificate[] certs= new X509Certificate[3];
@@ -702,8 +696,7 @@
     private List<Certificate> getCertList(boolean useMD2root,
             boolean includeRootInChain) throws Exception {
 
-        CertificateFactory certificateFactory = CertificateFactory
-                .getInstance("X509");
+        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
 
         if (useMD2root) {
             certs[0] = (X509Certificate) certificateFactory
diff --git a/luni/src/test/resources/serialization/tests/security/cert/CertificateTest.golden.ser b/luni/src/test/resources/serialization/tests/security/cert/CertificateTest.golden.ser
index b5bc9e0..f7e2a9a 100644
--- a/luni/src/test/resources/serialization/tests/security/cert/CertificateTest.golden.ser
+++ b/luni/src/test/resources/serialization/tests/security/cert/CertificateTest.golden.ser
Binary files differ
diff --git a/support/src/test/java/libcore/java/security/StandardNames.java b/support/src/test/java/libcore/java/security/StandardNames.java
index 3f37000..9163fbe 100644
--- a/support/src/test/java/libcore/java/security/StandardNames.java
+++ b/support/src/test/java/libcore/java/security/StandardNames.java
@@ -330,11 +330,11 @@
             unprovide("TrustManagerFactory", "PKIX");
             provide("TrustManagerFactory", "X509");
 
-            // different names: ARCFOUR vs ARC4/RC$
+            // different names: ARCFOUR vs ARC4
             unprovide("Cipher", "ARCFOUR");
             provide("Cipher", "ARC4");
             unprovide("KeyGenerator", "ARCFOUR");
-            provide("KeyGenerator", "RC4");
+            provide("KeyGenerator", "ARC4");
 
             // different case names: Blowfish vs BLOWFISH
             unprovide("AlgorithmParameters", "Blowfish");
diff --git a/support/src/test/java/libcore/net/http/HttpResponseCache.java b/support/src/test/java/libcore/net/http/HttpResponseCache.java
new file mode 100644
index 0000000..a551e27
--- /dev/null
+++ b/support/src/test/java/libcore/net/http/HttpResponseCache.java
@@ -0,0 +1,300 @@
+/*
+ * 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 libcore.net.http;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ResponseCache;
+import java.net.SecureCacheResponse;
+import java.net.URI;
+import java.net.URLConnection;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+
+/**
+ * Cache all responses in memory by URI.
+ *
+ * TODO: disk storage, tuning knobs, LRU
+ * TODO: move this class to android.util
+ */
+public final class HttpResponseCache extends ResponseCache {
+    private final Map<URI, Entry> entries = new HashMap<URI, Entry>();
+    private int abortCount;
+    private int successCount;
+    private int hitCount;
+    private int missCount;
+
+    @Override public synchronized CacheResponse get(URI uri, String requestMethod,
+            Map<String, List<String>> requestHeaders) throws IOException {
+        Entry entry = entries.get(uri);
+        if (entry == null) {
+            missCount++;
+            return null;
+        }
+
+        if (!requestMethod.equals(entry.requestMethod)) {
+            return null;
+        }
+
+        // RawHeaders headers = RawHeaders.fromMultimap(entry.responseHeaders);
+
+        hitCount++;
+        return entry.asResponse();
+    }
+
+    @Override public CacheRequest put(URI uri, URLConnection urlConnection)
+            throws IOException {
+        if (!(urlConnection instanceof HttpURLConnection)) {
+            return null;
+        }
+
+        HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
+        String requestMethod = httpConnection.getRequestMethod();
+
+        // Invalidate the cache on POST, PUT and DELETE.
+        if (requestMethod.equals("POST") || requestMethod.equals("PUT")
+                || requestMethod.equals("DELETE")) {
+            entries.remove(uri);
+        }
+
+        /*
+         * 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.
+         */
+        if (!requestMethod.equals("GET")) {
+            return null;
+        }
+
+        // For implementation simplicity, don't  cache responses that have a Vary field.
+        if (httpConnection.getHeaderField("Vary") != null) {
+            return null;
+        }
+
+        return new Entry(uri, httpConnection).asRequest();
+    }
+
+    public synchronized Map<URI, Entry> getContents() {
+        return new HashMap<URI, Entry>(entries);
+    }
+
+    /**
+     * Returns the number of requests that were aborted before they were closed.
+     */
+    public synchronized int getAbortCount() {
+        return abortCount;
+    }
+
+    /**
+     * Returns the number of requests that were closed successfully.
+     */
+    public synchronized int getSuccessCount() {
+        return successCount;
+    }
+
+    /**
+     * Returns the number of responses served by the cache.
+     */
+    public synchronized int getHitCount() {
+        return hitCount;
+    }
+
+    /**
+     * Returns the number of responses that couldn't be served by the cache.
+     */
+    public synchronized int getMissCount() {
+        return missCount;
+    }
+
+    public final class Entry {
+        private final ByteArrayOutputStream body = new ByteArrayOutputStream() {
+            private boolean closed;
+            @Override public void close() throws IOException {
+                synchronized (HttpResponseCache.this) {
+                    if (closed) {
+                        return;
+                    }
+
+                    super.close();
+                    entries.put(uri, Entry.this);
+                    successCount++;
+                    closed = true;
+                }
+            }
+        };
+
+        private final String requestMethod;
+        private final Map<String, List<String>> responseHeaders;
+        private final URI uri;
+        private final CacheResponse cacheResponse;
+
+        private Entry(URI uri, HttpURLConnection connection) {
+            this.uri = uri;
+            this.requestMethod = connection.getRequestMethod();
+            this.responseHeaders = deepCopy(connection.getHeaderFields());
+            this.cacheResponse = connection instanceof HttpsURLConnection
+                    ? new SecureCacheResponseImpl(responseHeaders, body,
+                            (HttpsURLConnection) connection)
+                    : new CacheResponseImpl(responseHeaders, body);
+        }
+
+        public CacheRequest asRequest() {
+            return new CacheRequest() {
+                private boolean aborted;
+                @Override public void abort() {
+                    synchronized (HttpResponseCache.this) {
+                        if (aborted) {
+                            return;
+                        }
+
+                        abortCount++;
+                        aborted = true;
+                    }
+                }
+                @Override public OutputStream getBody() throws IOException {
+                    return body;
+                }
+            };
+        }
+
+        public CacheResponse asResponse() {
+            return cacheResponse;
+        }
+
+        public byte[] getBytes() {
+            return body.toByteArray();
+        }
+    }
+
+    private final class CacheResponseImpl extends CacheResponse {
+        private final Map<String, List<String>> headers;
+        private final ByteArrayOutputStream bytesOut;
+
+        public CacheResponseImpl(Map<String, List<String>> headers,
+                ByteArrayOutputStream bytesOut) {
+            this.headers = headers;
+            this.bytesOut = bytesOut;
+        }
+
+        @Override public Map<String, List<String>> getHeaders() {
+            return deepCopy(headers);
+        }
+
+        @Override public InputStream getBody() {
+            return new ByteArrayInputStream(bytesOut.toByteArray());
+        }
+    }
+
+    private final class SecureCacheResponseImpl extends SecureCacheResponse {
+        private final Map<String, List<String>> headers;
+        private final ByteArrayOutputStream bytesOut;
+        private final String cipherSuite;
+        private final Certificate[] localCertificates;
+        private final List<Certificate> serverCertificates;
+        private final Principal peerPrincipal;
+        private final Principal localPrincipal;
+
+        public SecureCacheResponseImpl(Map<String, List<String>> headers,
+                ByteArrayOutputStream bytesOut,
+                HttpsURLConnection httpsConnection) {
+            this.headers = headers;
+            this.bytesOut = bytesOut;
+
+            /*
+             * Retrieve the fields eagerly to avoid needing a strong
+             * reference to the connection. We do acrobatics for the two
+             * methods that can throw so that the cache response also
+             * throws.
+             */
+            List<Certificate> serverCertificatesNonFinal = null;
+            try {
+                serverCertificatesNonFinal = Arrays.asList(
+                        httpsConnection.getServerCertificates());
+            } catch (SSLPeerUnverifiedException ignored) {
+            }
+            Principal peerPrincipalNonFinal = null;
+            try {
+                peerPrincipalNonFinal = httpsConnection.getPeerPrincipal();
+            } catch (SSLPeerUnverifiedException ignored) {
+            }
+            this.cipherSuite = httpsConnection.getCipherSuite();
+            this.localCertificates = httpsConnection.getLocalCertificates();
+            this.serverCertificates = serverCertificatesNonFinal;
+            this.peerPrincipal = peerPrincipalNonFinal;
+            this.localPrincipal = httpsConnection.getLocalPrincipal();
+        }
+
+        @Override public Map<String, List<String>> getHeaders() {
+            return deepCopy(headers);
+        }
+
+        @Override public InputStream getBody() {
+            return new ByteArrayInputStream(bytesOut.toByteArray());
+        }
+
+        @Override public String getCipherSuite() {
+            return cipherSuite;
+        }
+
+        @Override public List<Certificate> getLocalCertificateChain() {
+            return localCertificates != null
+                    ? Arrays.asList(localCertificates.clone())
+                    : null;
+        }
+
+        @Override public List<Certificate> getServerCertificateChain()
+                throws SSLPeerUnverifiedException {
+            if (serverCertificates == null) {
+                throw new SSLPeerUnverifiedException(null);
+            }
+            return new ArrayList<Certificate>(serverCertificates);
+        }
+
+        @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+            if (peerPrincipal == null) {
+                throw new SSLPeerUnverifiedException(null);
+            }
+            return peerPrincipal;
+        }
+
+        @Override public Principal getLocalPrincipal() {
+            return localPrincipal;
+        }
+    }
+
+    private static Map<String, List<String>> deepCopy(Map<String, List<String>> input) {
+        Map<String, List<String>> result = new LinkedHashMap<String, List<String>>(input);
+        for (Map.Entry<String, List<String>> entry : result.entrySet()) {
+            entry.setValue(new ArrayList<String>(entry.getValue()));
+        }
+        return result;
+    }
+}
diff --git a/support/src/test/java/org/apache/harmony/testframework/serialization/SerializationTest.java b/support/src/test/java/org/apache/harmony/testframework/serialization/SerializationTest.java
index f20f9de..8d0c3a4 100644
--- a/support/src/test/java/org/apache/harmony/testframework/serialization/SerializationTest.java
+++ b/support/src/test/java/org/apache/harmony/testframework/serialization/SerializationTest.java
@@ -38,7 +38,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
-import junit.framework.Assert;
 import junit.framework.TestCase;
 
 /**
@@ -178,20 +177,16 @@
         /**
          * Compares deserialized and reference objects.
          *
-         * @param initial -
-         *            initial object used for creating serialized form
-         * @param deserialized -
-         *            deserialized object
+         * @param initial - initial object used for creating serialized form
+         * @param deserialized - deserialized object
          */
         void assertDeserialized(Serializable initial, Serializable deserialized);
     }
 
     // default comparator for a class that has equals(Object) method
     private final static SerializableAssert DEFAULT_COMPARATOR = new SerializableAssert() {
-        public void assertDeserialized(Serializable initial,
-                Serializable deserialized) {
-
-            Assert.assertEquals(initial, deserialized);
+        public void assertDeserialized(Serializable initial, Serializable deserialized) {
+            assertEquals(initial, deserialized);
         }
     };
 
@@ -199,34 +194,30 @@
      * Comparator for verifying that deserialized object is the same as initial.
      */
     public final static SerializableAssert SAME_COMPARATOR = new SerializableAssert() {
-        public void assertDeserialized(Serializable initial,
-                Serializable deserialized) {
-
-            Assert.assertSame(initial, deserialized);
+        public void assertDeserialized(Serializable initial, Serializable deserialized) {
+            assertSame(initial, deserialized);
         }
     };
 
     /**
-     * Comparator for java.lang.Throwable objects
+     * Comparator for Throwable objects
      */
     public final static SerializableAssert THROWABLE_COMPARATOR = new SerializableAssert() {
         public void assertDeserialized(Serializable initial, Serializable deserialized) {
-
             Throwable initThr = (Throwable) initial;
             Throwable dserThr = (Throwable) deserialized;
 
             // verify class
-            Assert.assertEquals(initThr.getClass(), dserThr.getClass());
+            assertEquals(initThr.getClass(), dserThr.getClass());
 
             // verify message
-            Assert.assertEquals(initThr.getMessage(), dserThr.getMessage());
+            assertEquals(initThr.getMessage(), dserThr.getMessage());
 
             // verify cause
             if (initThr.getCause() == null) {
-                Assert.assertNull(dserThr.getCause());
+                assertNull(dserThr.getCause());
             } else {
-                Assert.assertNotNull(dserThr.getCause());
-
+                assertNotNull(dserThr.getCause());
                 THROWABLE_COMPARATOR.assertDeserialized(initThr.getCause(),
                         dserThr.getCause());
             }
@@ -234,7 +225,7 @@
     };
 
     /**
-     * Comparator for java.security.PermissionCollection objects
+     * Comparator for PermissionCollection objects
      */
     public final static SerializableAssert PERMISSION_COLLECTION_COMPARATOR = new SerializableAssert() {
         public void assertDeserialized(Serializable initial, Serializable deserialized) {
@@ -243,10 +234,10 @@
             PermissionCollection dserPC = (PermissionCollection) deserialized;
 
             // verify class
-            Assert.assertEquals(initPC.getClass(), dserPC.getClass());
+            assertEquals(initPC.getClass(), dserPC.getClass());
 
             // verify 'readOnly' field
-            Assert.assertEquals(initPC.isReadOnly(), dserPC.isReadOnly());
+            assertEquals(initPC.isReadOnly(), dserPC.isReadOnly());
 
             // verify collection of permissions
             Collection<Permission> refCollection = new HashSet<Permission>(
@@ -254,7 +245,7 @@
             Collection<Permission> tstCollection = new HashSet<Permission>(
                     Collections.list(dserPC.elements()));
 
-            Assert.assertEquals(refCollection, tstCollection);
+            assertEquals(refCollection, tstCollection);
         }
     };
 
@@ -262,20 +253,18 @@
      * Returns <code>comparator</code> for provided serializable
      * <code>object</code>.
      *
-     * The <code>comparator</code> is searched in the following order: <br>-
-     * if <code>test</code> implements SerializableAssert interface then it is
+     * The <code>comparator</code> is searched in the following order: <br>
+     * - if <code>test</code> implements SerializableAssert interface then it is
      * selected as </code>comparator</code>.<br>- if passed <code>object</code>
      * has class in its classes hierarchy that overrides <code>equals(Object)</code>
      * method then <code>DEFAULT_COMPARATOR</code> is selected.<br> - the
      * method tries to select one of known comparators basing on <code>object's</code>
      * class,for example, if passed <code>object</code> is instance of
-     * java.lang.Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>-
-     * otherwise RuntimeException is thrown
+     * Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>
+     * - otherwise RuntimeException is thrown
      *
-     * @param test -
-     *            test case
-     * @param object -
-     *            object to be compared
+     * @param test - test case
+     * @param object - object to be compared
      * @return object's comparator
      */
     public static SerializableAssert defineComparator(TestCase test, Object object)
@@ -285,9 +274,7 @@
             return (SerializableAssert) test;
         }
 
-        Method m = object.getClass().getMethod("equals",
-                new Class[] { Object.class });
-
+        Method m = object.getClass().getMethod("equals", new Class[] { Object.class });
         if (m.getDeclaringClass() != Object.class) {
             // one of classes overrides Object.equals(Object) method
             // use default comparator
@@ -296,12 +283,12 @@
 
         // TODO use generics to detect comparator
         // instead of 'instanceof' for the first element
-        if (object instanceof java.lang.Throwable) {
+        if (object instanceof Throwable) {
             return THROWABLE_COMPARATOR;
-        } else if (object instanceof java.security.PermissionCollection) {
+        }
+        if (object instanceof PermissionCollection) {
             return PERMISSION_COLLECTION_COMPARATOR;
         }
-
         throw new RuntimeException("Failed to detect comparator");
     }
 
@@ -311,10 +298,8 @@
      * The method invokes <br>
      * verifyGolden(test, object, defineComparator(test, object));
      *
-     * @param test -
-     *            test case
-     * @param object -
-     *            to be compared
+     * @param test - test case
+     * @param object - to be compared
      */
     public static void verifyGolden(TestCase test, Object object) throws Exception {
         verifyGolden(test, object, defineComparator(test, object));
@@ -328,20 +313,14 @@
      * folder, reads an object from the loaded file and compares it with
      * <code>object</code> using specified <code>comparator</code>.
      *
-     * @param test-
-     *            test case
-     * @param object-
-     *            to be compared
-     * @param comparator -
-     *            for comparing (de)serialized objects
+     * @param test - test case
+     * @param object - to be compared
+     * @param comparator - for comparing (de)serialized objects
      */
     public static void verifyGolden(TestCase test, Object object, SerializableAssert comparator)
             throws Exception {
-
-        Assert.assertNotNull("Null comparator", comparator);
-
+        assertNotNull("Null comparator", comparator);
         Serializable deserialized = getObject(test, ".golden.ser");
-
         comparator.assertDeserialized((Serializable) object, deserialized);
     }
 
@@ -352,14 +331,11 @@
      * The method invokes <br>
      * verifyGolden(test, objects, defineComparator(test, object[0]));
      *
-     * @param test -
-     *            test case
-     * @param objects -
-     *            array of objects to be compared
+     * @param test - test case
+     * @param objects - array of objects to be compared
      */
     public static void verifyGolden(TestCase test, Object[] objects) throws Exception {
-
-        Assert.assertFalse("Empty array", objects.length == 0);
+        assertFalse("Empty array", objects.length == 0);
         verifyGolden(test, objects, defineComparator(test, objects[0]));
     }
 
@@ -374,21 +350,16 @@
      * using specified <code>comparator</code>. (<code>N</code> is index
      * in object's array.)
      *
-     * @param test-
-     *            test case
-     * @param objects -
-     *            array of objects to be compared
-     * @param comparator -
-     *            for comparing (de)serialized objects
+     * @param test - test case
+     * @param objects - array of objects to be compared
+     * @param comparator - for comparing (de)serialized objects
      */
     public static void verifyGolden(TestCase test, Object[] objects, SerializableAssert comparator)
             throws Exception {
-
-        Assert.assertFalse("Empty array", objects.length == 0);
+        assertFalse("Empty array", objects.length == 0);
         for (int i = 0; i < objects.length; i++) {
             Serializable deserialized = getObject(test, ".golden." + i + ".ser");
-            comparator.assertDeserialized((Serializable) objects[i],
-                    deserialized);
+            comparator.assertDeserialized((Serializable) objects[i], deserialized);
         }
     }
 
@@ -398,11 +369,9 @@
      * The method invokes <br>
      * verifySelf(object, defineComparator(null, object));
      *
-     * @param object -
-     *            to be serialized/deserialized
+     * @param object - to be serialized/deserialized
      */
     public static void verifySelf(Object object) throws Exception {
-
         verifySelf(object, defineComparator(null, object));
     }
 
@@ -412,16 +381,11 @@
      * The method serialize/deserialize <code>object</code> and compare it
      * with initial <code>object</code>.
      *
-     * @param object -
-     *            object to be serialized/deserialized
-     * @param comparator -
-     *            for comparing serialized/deserialized object with initial
-     *            object
+     * @param object - object to be serialized/deserialized
+     * @param comparator - for comparing serialized/deserialized object with initial object
      */
     public static void verifySelf(Object object, SerializableAssert comparator) throws Exception {
-
         Serializable initial = (Serializable) object;
-
         comparator.assertDeserialized(initial, copySerializable(initial));
     }
 
@@ -432,12 +396,10 @@
      * The method invokes <br>
      * verifySelf(objects, defineComparator(null, object[0]));
      *
-     * @param objects -
-     *            array of objects to be serialized/deserialized
+     * @param objects - array of objects to be serialized/deserialized
      */
     public static void verifySelf(Object[] objects) throws Exception {
-
-        Assert.assertFalse("Empty array", objects.length == 0);
+        assertFalse("Empty array", objects.length == 0);
         verifySelf(objects, defineComparator(null, objects[0]));
     }
 
@@ -448,35 +410,25 @@
      * The method serialize/deserialize each object in <code>objects</code>
      * array and compare it with initial object.
      *
-     * @param objects -
-     *            array of objects to be serialized/deserialized
-     * @param comparator -
-     *            for comparing serialized/deserialized object with initial
-     *            object
+     * @param objects - array of objects to be serialized/deserialized
+     * @param comparator - for comparing serialized/deserialized object with initial object
      */
     public static void verifySelf(Object[] objects, SerializableAssert comparator)
             throws Exception {
-
-        Assert.assertFalse("Empty array", objects.length == 0);
-        for(Object entry: objects){
+        assertFalse("Empty array", objects.length == 0);
+        for (Object entry: objects){
             verifySelf(entry, comparator);
         }
     }
 
     private static Serializable getObject(TestCase test, String toAppend) throws Exception {
-
         StringBuilder path = new StringBuilder("/serialization");
-
         path.append(File.separatorChar);
         path.append(test.getClass().getName().replace('.', File.separatorChar));
         path.append(toAppend);
 
-        InputStream in = SerializationTest.class
-                .getResourceAsStream(path.toString());
-
-        Assert.assertNotNull("Failed to load serialization resource file: "
-                + path, in);
-
+        InputStream in = SerializationTest.class.getResourceAsStream(path.toString());
+        assertNotNull("Failed to load serialization resource file: " + path, in);
         return getObjectFromStream(in);
     }
 
@@ -486,52 +438,41 @@
      * The folder for created file is: <code>root + test's package name</code>.
      * The file name is: <code>test's name + "golden.ser"</code>
      *
-     * @param root -
-     *            root directory for serialization resource files
-     * @param test -
-     *            test case
-     * @param object -
-     *            object to be serialized
-     * @throws IOException -
-     *             if I/O error
+     * @param root - root directory for serialization resource files
+     * @param test - test case
+     * @param object - to be serialized
+     * @throws IOException - if I/O error
      */
     public static void createGoldenFile(String root, TestCase test, Object object)
             throws IOException {
-
-        String goldenPath = test.getClass().getName().replace('.',
-                File.separatorChar)
-                + ".golden.ser";
-
+        String goldenPath = (test.getClass().getName().replace('.', File.separatorChar)
+                             + ".golden.ser");
         if (root != null) {
             goldenPath = root + File.separatorChar + goldenPath;
         }
 
-
         File goldenFile = new File(goldenPath);
         goldenFile.getParentFile().mkdirs();
+        assertTrue("Could not create " + goldenFile.getParentFile(),
+                   goldenFile.getParentFile().isDirectory());
         goldenFile.createNewFile();
-
         putObjectToStream(object, new FileOutputStream(goldenFile));
 
         // don't forget to remove it from test case after using
-        Assert.fail("Generating golden file.\nGolden file name:"
-                + goldenFile.getAbsolutePath());
+        fail("Generating golden file. Golden file name: " + goldenFile.getAbsolutePath());
     }
 
     /**
      * Copies an object by serializing/deserializing it.
      *
-     * @param initial -
-     *            an object to be copied
+     * @param initial - an object to be copied
      * @return copy of provided object
      */
     public static Serializable copySerializable(Serializable initial)
             throws IOException, ClassNotFoundException {
-
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         putObjectToStream(initial, out);
         ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-
         return getObjectFromStream(in);
     }
 }
diff --git a/support/src/test/java/tests/http/DefaultResponseCache.java b/support/src/test/java/tests/http/DefaultResponseCache.java
deleted file mode 100644
index 912e879..0000000
--- a/support/src/test/java/tests/http/DefaultResponseCache.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * 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 tests.http;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.CacheRequest;
-import java.net.CacheResponse;
-import java.net.ResponseCache;
-import java.net.SecureCacheResponse;
-import java.net.URI;
-import java.net.URLConnection;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLPeerUnverifiedException;
-
-/**
- * Cache all responses in memory by URI.
- */
-public final class DefaultResponseCache extends ResponseCache {
-    private final Map<URI, Entry> entries = new HashMap<URI, Entry>();
-    private int abortCount;
-    private int successCount;
-    private int hitCount;
-    private int missCount;
-
-    @Override public synchronized CacheResponse get(URI uri, String requestMethod,
-            Map<String, List<String>> requestHeaders) throws IOException {
-        // TODO: honor the request headers in the cache key
-        Entry entry = entries.get(uri);
-        if (entry != null) {
-            hitCount++;
-            return entry.asResponse();
-        } else {
-            missCount++;
-            return null;
-        }
-    }
-
-    @Override public CacheRequest put(URI uri, URLConnection urlConnection)
-            throws IOException {
-        // TODO: honor the response headers for cache invalidation
-        return new Entry(uri, urlConnection).asRequest();
-    }
-
-    public synchronized Map<URI, Entry> getContents() {
-        return new HashMap<URI, Entry>(entries);
-    }
-
-    /**
-     * Returns the number of requests that were aborted before they were closed.
-     */
-    public synchronized int getAbortCount() {
-        return abortCount;
-    }
-
-    /**
-     * Returns the number of requests that were closed successfully.
-     */
-    public synchronized int getSuccessCount() {
-        return successCount;
-    }
-
-    /**
-     * Returns the number of responses served by the cache.
-     */
-    public synchronized int getHitCount() {
-        return hitCount;
-    }
-
-    /**
-     * Returns the number of responses that couldn't be served by the cache.
-     */
-    public synchronized int getMissCount() {
-        return missCount;
-    }
-
-    public final class Entry {
-        private final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream() {
-            private boolean closed;
-            @Override public void close() throws IOException {
-                synchronized (DefaultResponseCache.this) {
-                    if (closed) {
-                        return;
-                    }
-
-                    super.close();
-                    entries.put(uri, Entry.this);
-                    successCount++;
-                    closed = true;
-                }
-            }
-        };
-        private final Map<String, List<String>> headers;
-        private final URI uri;
-        private final CacheResponse cacheResponse;
-
-        private Entry(URI uri, URLConnection connection) {
-            this.uri = uri;
-            this.headers = deepCopy(connection.getHeaderFields());
-            this.cacheResponse = connectionToCacheResponse(connection);
-        }
-
-        public CacheRequest asRequest() {
-            return new CacheRequest() {
-                private boolean aborted;
-                @Override public void abort() {
-                    synchronized (DefaultResponseCache.this) {
-                        if (aborted) {
-                            return;
-                        }
-
-                        abortCount++;
-                        aborted = true;
-                    }
-                }
-                @Override public OutputStream getBody() throws IOException {
-                    return bytesOut;
-                }
-            };
-        }
-
-        private CacheResponse connectionToCacheResponse(URLConnection connection) {
-            if (!(connection instanceof HttpsURLConnection)) {
-                return new CacheResponse() {
-                    @Override public InputStream getBody() {
-                        return new ByteArrayInputStream(getBytes());
-                    }
-                    @Override public Map<String, List<String>> getHeaders() {
-                        return deepCopy(headers);
-                    }
-                };
-            }
-
-            HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
-
-            /*
-             * Retrieve the fields eagerly to avoid needing a strong reference
-             * to the connection. We do acrobatics for the two methods that can
-             * throw so that the cache response also throws.
-             */
-            List<Certificate> serverCertificatesNonFinal = null;
-            try {
-                serverCertificatesNonFinal = Arrays.asList(httpsConnection.getServerCertificates());
-            } catch (SSLPeerUnverifiedException ignored) {
-            }
-            Principal peerPrincipalNonFinal = null;
-            try {
-                peerPrincipalNonFinal = httpsConnection.getPeerPrincipal();
-            } catch (SSLPeerUnverifiedException ignored) {
-            }
-            final String cipherSuite = httpsConnection.getCipherSuite();
-            final Certificate[] localCertificates = httpsConnection.getLocalCertificates();
-            final List<Certificate> serverCertificates = serverCertificatesNonFinal;
-            final Principal peerPrincipal = peerPrincipalNonFinal;
-            final Principal localPrincipal = httpsConnection.getLocalPrincipal();
-
-            return new SecureCacheResponse() {
-                @Override public InputStream getBody() {
-                    return new ByteArrayInputStream(getBytes());
-                }
-                @Override public Map<String, List<String>> getHeaders() {
-                    return deepCopy(headers);
-                }
-                @Override public String getCipherSuite() {
-                    return cipherSuite;
-                }
-                @Override public List<Certificate> getLocalCertificateChain() {
-                    return localCertificates != null
-                            ? Arrays.asList(localCertificates.clone())
-                            : null;
-                }
-                @Override public List<Certificate> getServerCertificateChain()
-                        throws SSLPeerUnverifiedException {
-                    if (serverCertificates == null) {
-                        throw new SSLPeerUnverifiedException(null);
-                    }
-                    return new ArrayList<Certificate>(serverCertificates);
-                }
-                @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
-                    if (peerPrincipal == null) {
-                        throw new SSLPeerUnverifiedException(null);
-                    }
-                    return peerPrincipal;
-                }
-                @Override public Principal getLocalPrincipal() {
-                    return localPrincipal;
-                }
-            };
-        }
-
-        public CacheResponse asResponse() {
-            return cacheResponse;
-        }
-
-        public byte[] getBytes() {
-            return bytesOut.toByteArray();
-        }
-    }
-
-    private static Map<String, List<String>> deepCopy(Map<String, List<String>> input) {
-        Map<String, List<String>> result = new LinkedHashMap<String, List<String>>(input);
-        for (Map.Entry<String, List<String>> entry : result.entrySet()) {
-            entry.setValue(new ArrayList<String>(entry.getValue()));
-        }
-        return result;
-    }
-}
diff --git a/support/src/test/java/tests/http/MockResponse.java b/support/src/test/java/tests/http/MockResponse.java
index 314bfc3..d3aaa9d 100644
--- a/support/src/test/java/tests/http/MockResponse.java
+++ b/support/src/test/java/tests/http/MockResponse.java
@@ -26,7 +26,8 @@
 /**
  * A scripted response to be replayed by the mock web server.
  */
-public class MockResponse {
+public class MockResponse implements Cloneable {
+    private static final String RFC_1123_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
     private static final String EMPTY_BODY_HEADER = "Content-Length: 0";
     private static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked";
     private static final byte[] EMPTY_BODY = new byte[0];
@@ -40,6 +41,16 @@
         headers.add(EMPTY_BODY_HEADER);
     }
 
+    @Override public MockResponse clone() {
+        try {
+            MockResponse result = (MockResponse) super.clone();
+            result.headers = new ArrayList<String>(result.headers);
+            return result;
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
+    }
+
     /**
      * Returns the HTTP response line, such as "HTTP/1.1 200 OK".
      */
diff --git a/support/src/test/java/tests/http/MockWebServer.java b/support/src/test/java/tests/http/MockWebServer.java
index 2083117..2d8215c 100644
--- a/support/src/test/java/tests/http/MockWebServer.java
+++ b/support/src/test/java/tests/http/MockWebServer.java
@@ -22,6 +22,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.HttpURLConnection;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
@@ -37,7 +38,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -138,7 +138,7 @@
     }
 
     public void enqueue(MockResponse response) {
-        responseQueue.add(response);
+        responseQueue.add(response.clone());
     }
 
     /**
@@ -155,15 +155,26 @@
     }
 
     /**
-     * Starts the server, serves all enqueued requests, and shuts the server
-     * down.
+     * Equivalent to {@code play(0)}.
      */
     public void play() throws IOException {
+        play(0);
+    }
+
+    /**
+     * Starts the server, serves all enqueued requests, and shuts the server
+     * down.
+     *
+     * @param port the port to listen to, or 0 for any available port.
+     *     Automated tests should always use port 0 to avoid flakiness when a
+     *     specific port is unavailable.
+     */
+    public void play(int port) throws IOException {
         executor = Executors.newCachedThreadPool();
-        serverSocket = new ServerSocket(0);
+        serverSocket = new ServerSocket(port);
         serverSocket.setReuseAddress(true);
 
-        port = serverSocket.getLocalPort();
+        this.port = serverSocket.getLocalPort();
         executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
             public void run() {
                 try {
@@ -347,13 +358,15 @@
             }
         }
 
-        if (request.startsWith("GET ") || request.startsWith("CONNECT ")) {
+        if (request.startsWith("OPTIONS ") || request.startsWith("GET ")
+                || request.startsWith("HEAD ") || request.startsWith("DELETE ")
+                || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) {
             if (hasBody) {
-                throw new IllegalArgumentException("GET requests should not have a body!");
+                throw new IllegalArgumentException("Request must not have a body: " + request);
             }
-        } else if (request.startsWith("POST ")) {
+        } else if (request.startsWith("POST ") || request.startsWith("PUT ")) {
             if (!hasBody) {
-                throw new IllegalArgumentException("POST requests must have a body!");
+                throw new IllegalArgumentException("Request must have a body: " + request);
             }
         } else {
             throw new UnsupportedOperationException("Unexpected method: " + request);
@@ -371,6 +384,13 @@
             throw new IllegalStateException("Unexpected request: " + request);
         }
 
+        // to permit interactive/browser testing, ignore requests for favicons
+        if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) {
+            System.out.println("served " + request.getRequestLine());
+            return new MockResponse()
+                        .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
+        }
+
         if (singleResponse) {
             return responseQueue.peek();
         } else {
diff --git a/support/src/test/java/tests/security/AlgorithmParametersTest.java b/support/src/test/java/tests/security/AlgorithmParametersTest.java
index 67255fc..e93f216 100644
--- a/support/src/test/java/tests/security/AlgorithmParametersTest.java
+++ b/support/src/test/java/tests/security/AlgorithmParametersTest.java
@@ -21,7 +21,7 @@
 import java.security.spec.InvalidParameterSpecException;
 import junit.framework.TestCase;
 
-public class AlgorithmParametersTest extends TestCase {
+public abstract class AlgorithmParametersTest extends TestCase {
 
     private final String algorithmName;
     private final TestHelper<AlgorithmParameters> helper;